diff --git a/.solhint.json b/.solhint.json index 35f4806..7487b00 100644 --- a/.solhint.json +++ b/.solhint.json @@ -10,6 +10,7 @@ "expression-indent": false, "max-line-length": false, "two-lines-top-level-separator": false, - "separate-by-one-line-in-contract": false + "separate-by-one-line-in-contract": false, + "function-max-lines": false } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 49dba94..db7232a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,14 +4,6 @@ project(ethsnarks) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) -if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # Common compilation flags and warning configuration - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wfatal-errors -Wno-unused-variable") - if("${MULTICORE}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") - endif() - # Default optimizations flags (to override, use -DOPT_FLAGS=...) -endif() set( @@ -60,6 +52,12 @@ option( OFF ) +option( + USE_MIXED_ADDITION + "Convert each element of the key pair to affine coordinates" + OFF +) + option( BINARY_OUTPUT "Use binary output for serialisation" @@ -67,27 +65,22 @@ option( ) option( - PERFORMANCE - "Enable link-time and aggressive optimizations" - OFF + MONTGOMERY_OUTPUT + "Serialize Fp elements as their Montgomery representations (faster but not human-readable)" + ON ) + option( WITH_PROCPS "Use procps for memory profiling" OFF ) -option( - VERBOSE - "Print internal messages" - OFF -) - option( DEBUG "Enable debugging mode" - ON + OFF ) option( @@ -103,19 +96,12 @@ if(${CURVE} STREQUAL "BN128") add_definitions(-DBN_SUPPORT_SNARK=1) endif() -if("${VERBOSE}") - add_definitions(-DVERBOSE=1) -endif() - if("${MULTICORE}") add_definitions(-DMULTICORE=1) endif() -if("${DEBUG}") +if("${DEBUG}" OR "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") add_definitions(-DDEBUG=1) - add_compile_options(-g) -else() - add_compile_options(-O3) endif() @@ -154,6 +140,15 @@ else() ) endif() +if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # Common compilation flags and warning configuration + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wfatal-errors -Wno-unused-variable") + if("${MULTICORE}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") + endif() + # Default optimizations flags (to override, use -DOPT_FLAGS=...) +endif() + find_path(GMP_INCLUDE_DIR NAMES gmp.h) find_library(GMP_LIBRARY gmp) @@ -205,6 +200,4 @@ add_library( target_link_libraries(ff GMP::gmp gmpxx ${PROCPS_LIBRARIES}) -#add_subdirectory(depends) add_subdirectory(src) - diff --git a/Makefile b/Makefile index 876b6ac..c452c65 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,16 @@ bin/miximus_genKeys: build/Makefile build/src/libmiximus.$(DLL_EXT): build/Makefile make -C build +cmake-debug: build + cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. + +cmake-release: build + cd build && cmake -DCMAKE_BUILD_TYPE=Release .. + +release: cmake-release all + +debug: cmake-debug all + build/Makefile: build CMakeLists.txt cd build && cmake .. @@ -163,6 +173,9 @@ $(GANACHE): node_modules truffle-test: $(TRUFFLE) $(NPM) run test +truffle-migrate: $(TRUFFLE) + $(TRUFFLE) migrate + truffle-compile: $(TRUFFLE) $(TRUFFLE) compile diff --git a/contracts/MerkleTree.sol b/contracts/MerkleTree.sol index e64c480..586b584 100644 --- a/contracts/MerkleTree.sol +++ b/contracts/MerkleTree.sol @@ -63,7 +63,7 @@ library MerkleTree function Insert(Data storage self, uint256 leaf) - internal returns (uint256) + internal returns (uint256, uint256) { require( leaf != 0 ); @@ -83,7 +83,48 @@ library MerkleTree self.cur = offset + 1; - return new_root; + return (new_root, offset); + } + + + /** + * Returns calculated merkle root + */ + function VerifyPath(uint256 leaf, uint256[29] in_path, bool[29] address_bits) + internal pure returns (uint256) + { + uint256[10] memory C; + LongsightL.ConstantsL12p5(C); + + uint256[29] memory IVs; + FillLevelIVs(IVs); + + uint256 item = leaf; + + for (uint depth = 0; depth < TREE_DEPTH; depth++) + { + if (address_bits[depth]) { + item = HashImpl(in_path[depth], item, C, IVs[depth]); + } else { + item = HashImpl(item, in_path[depth], C, IVs[depth]); + } + } + + return item; + } + + + function VerifyPath(Data storage self, uint256 leaf, uint256[29] in_path, bool[29] address_bits) + internal view returns (bool) + { + return VerifyPath(leaf, in_path, address_bits) == GetRoot(self); + } + + + function GetLeaf(Data storage self, uint depth, uint offset) + internal view returns (uint256) + { + return GetUniqueLeaf(depth, offset, self.leaves[depth][offset]); } @@ -98,12 +139,10 @@ library MerkleTree { address_bits[depth] = index % 2 == 0 ? false : true; - if (index%2 == 0) - { - proof_path[depth] = GetUniqueLeaf(depth, index, self.leaves[depth][index + 1]); - } else - { - proof_path[depth] = GetUniqueLeaf(depth, index, self.leaves[depth][index - 1]); + if (index%2 == 0) { + proof_path[depth] = GetLeaf(self, depth, index + 1); + } else { + proof_path[depth] = GetLeaf(self, depth, index - 1); } index = uint(index / 2); @@ -161,13 +200,6 @@ library MerkleTree return self.leaves[TREE_DEPTH][0]; } - - - function GetLeaf(Data storage self, uint depth, uint offset) - internal view returns (uint256) - { - return self.leaves[depth][offset]; - } function GetRoot (Data storage self) diff --git a/contracts/Miximus.sol b/contracts/Miximus.sol index 28425e9..9dfc445 100644 --- a/contracts/Miximus.sol +++ b/contracts/Miximus.sol @@ -18,11 +18,11 @@ */ pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; import "./Verifier.sol"; import "./SnarkUtils.sol"; import "./MerkleTree.sol"; +import "./LongsightL.sol"; contract Miximus @@ -35,8 +35,19 @@ contract Miximus MerkleTree.Data internal tree; + + function GetRoot() + public view returns (uint256) + { + return tree.GetRoot(); + } + + + /** + * Returns leaf offset + */ function Deposit(uint256 leaf) - public payable returns (uint256) + public payable returns (uint256 new_root, uint256 new_offset) { require( msg.value == AMOUNT ); @@ -44,30 +55,69 @@ contract Miximus } - function Withdraw( - uint256 in_root, - uint256 in_nullifier, - Verifier.Proof in_proof - ) - public + function MakeLeafHash(uint256 spend_preimage, uint256 nullifier) + public pure returns (uint256) { - require( false == nullifiers[in_nullifier] ); + uint256[10] memory round_constants; + LongsightL.ConstantsL12p5(round_constants); + + uint256 spend_hash = LongsightL.LongsightL12p5_MP([spend_preimage, nullifier], 0, round_constants); + + return LongsightL.LongsightL12p5_MP([nullifier, spend_hash], 0, round_constants); + } - uint256[] memory snark_input = new uint256[](3); - snark_input[0] = SnarkUtils.ReverseBits(in_root); + function GetPath(uint256 leaf) + public view returns (uint256[29] out_path, bool[29] out_addr) + { + return tree.GetProof(leaf); + } - snark_input[1] = SnarkUtils.ReverseBits(in_nullifier); - snark_input[2] = SnarkUtils.ReverseBits(uint256(sha256( + function GetExtHash() + public view returns (uint256) + { + return uint256(sha256( abi.encodePacked( address(this), msg.sender - )))) % Verifier.ScalarField(); + ))) % Verifier.ScalarField(); + } + + + function IsSpent(uint256 nullifier) + public view returns (bool) + { + return nullifiers[nullifier]; + } + + + function VerifyProof( uint256 in_root, uint256 in_nullifier, uint256 in_exthash, uint256[8] proof ) + public view returns (bool) + { + uint256[] memory snark_input = new uint256[](3); + snark_input[0] = in_root; + snark_input[1] = in_nullifier; + snark_input[2] = in_exthash; + + uint256[14] memory vk; + uint256[] memory vk_gammaABC; + (vk, vk_gammaABC) = GetVerifyingKey(); + + return Verifier.Verify( vk, vk_gammaABC, proof, snark_input ); + } - Verifier.VerifyingKey memory vk = GetVerifyingKey(); - bool is_valid = Verifier.Verify( vk, in_proof, snark_input ); + function Withdraw( + uint256 in_root, + uint256 in_nullifier, + uint256[8] proof + ) + public + { + require( false == nullifiers[in_nullifier] ); + + bool is_valid = VerifyProof(in_root, in_nullifier, GetExtHash(), proof); require( is_valid ); @@ -76,7 +126,7 @@ contract Miximus msg.sender.transfer(AMOUNT); } - function GetVerifyingKey () - internal pure returns (Verifier.VerifyingKey memory); + function GetVerifyingKey () + public view returns (uint256[14] out_vk, uint256[] out_gammaABC); } diff --git a/contracts/Pairing.sol b/contracts/Pairing.sol index d40bbe6..68f52a9 100644 --- a/contracts/Pairing.sol +++ b/contracts/Pairing.sol @@ -39,7 +39,9 @@ library Pairing { } /// @return the sum of two points of G1 - function pointAdd(G1Point p1, G1Point p2) internal returns (G1Point r) { + function pointAdd(G1Point p1, G1Point p2) + internal view returns (G1Point r) + { uint[4] memory input; input[0] = p1.X; input[1] = p1.Y; @@ -47,25 +49,23 @@ library Pairing { input[3] = p2.Y; bool success; assembly { - success := call(sub(gas, 2000), 6, 0, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success case 0 { invalid } + success := staticcall(sub(gas, 2000), 6, input, 0xc0, r, 0x60) } require(success); } /// @return the product of a point on G1 and a scalar, i.e. /// p == p.mul(1) and p.add(p) == p.mul(2) for all points p. - function pointMul(G1Point p, uint s) internal returns (G1Point r) { + function pointMul(G1Point p, uint s) + internal view returns (G1Point r) + { uint[3] memory input; input[0] = p.X; input[1] = p.Y; input[2] = s; bool success; assembly { - success := call(sub(gas, 2000), 7, 0, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success case 0 { invalid } + success := staticcall(sub(gas, 2000), 7, input, 0x80, r, 0x60) } require (success); } @@ -74,7 +74,9 @@ library Pairing { /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should /// return true. - function pairing(G1Point[] p1, G2Point[] p2) internal returns (bool) { + function pairing(G1Point[] p1, G2Point[] p2) + internal view returns (bool) + { require(p1.length == p2.length); uint elements = p1.length; uint inputSize = elements * 6; @@ -91,16 +93,16 @@ library Pairing { uint[1] memory out; bool success; assembly { - success := call(sub(gas, 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work - switch success case 0 { invalid } + success := staticcall(sub(gas, 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) } require(success); return out[0] != 0; } /// Convenience method for a pairing check for two pairs. - function pairingProd2(G1Point a1, G2Point a2, G1Point b1, G2Point b2) internal returns (bool) { + function pairingProd2(G1Point a1, G2Point a2, G1Point b1, G2Point b2) + internal view returns (bool) + { G1Point[] memory p1 = new G1Point[](2); G2Point[] memory p2 = new G2Point[](2); p1[0] = a1; @@ -114,7 +116,9 @@ library Pairing { G1Point a1, G2Point a2, G1Point b1, G2Point b2, G1Point c1, G2Point c2 - ) internal returns (bool) { + ) + internal view returns (bool) + { G1Point[] memory p1 = new G1Point[](3); G2Point[] memory p2 = new G2Point[](3); p1[0] = a1; @@ -125,13 +129,16 @@ library Pairing { p2[2] = c2; return pairing(p1, p2); } + /// Convenience method for a pairing check for four pairs. function pairingProd4( G1Point a1, G2Point a2, G1Point b1, G2Point b2, G1Point c1, G2Point c2, G1Point d1, G2Point d2 - ) internal returns (bool) { + ) + internal view returns (bool) + { G1Point[] memory p1 = new G1Point[](4); G2Point[] memory p2 = new G2Point[](4); p1[0] = a1; diff --git a/contracts/TestableMiximus.sol b/contracts/TestableMiximus.sol new file mode 100644 index 0000000..6ae2321 --- /dev/null +++ b/contracts/TestableMiximus.sol @@ -0,0 +1,36 @@ +pragma solidity 0.4.24; + +import "./Miximus.sol"; +import "./Verifier.sol"; + + +// Please note, it saves a lot of gas to use the `vk2sol` +// utility to generate Solidity code, hard-coding the +// verifying key avoids the cost of loading from storage. + +contract TestableMiximus is Miximus +{ + uint256[14] m_vk; + uint256[] m_gammaABC; + + constructor( uint256[14] in_vk, uint256[] in_gammaABC ) + public + { + m_vk = in_vk; + m_gammaABC = in_gammaABC; + } + + + function TestVerify ( uint256[14] in_vk, uint256[] vk_gammaABC, uint256[8] in_proof, uint256[] proof_inputs ) + public view returns (bool) + { + return Verifier.Verify(in_vk, vk_gammaABC, in_proof, proof_inputs); + } + + + function GetVerifyingKey () + public view returns (uint256[14] out_vk, uint256[] out_gammaABC) + { + return (m_vk, m_gammaABC); + } +} diff --git a/contracts/Verifier.sol b/contracts/Verifier.sol index 1ff03d3..b762444 100644 --- a/contracts/Verifier.sol +++ b/contracts/Verifier.sol @@ -37,14 +37,129 @@ library Verifier uint256[] input; } + + function NegateY( uint256 Y ) + internal pure returns (uint256) + { + uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + return q - (Y % q); + } + + + /* + * This implements the Solidity equivalent of the following Python code: + + from py_ecc.bn128 import * + + data = # ... arguments to function [in_vk, vk_gammaABC, in_proof, proof_inputs] + + vk = [int(_, 16) for _ in data[0]] + ic = [FQ(int(_, 16)) for _ in data[1]] + proof = [int(_, 16) for _ in data[2]] + inputs = [int(_, 16) for _ in data[3]] + + it = iter(ic) + ic = [(_, next(it)) for _ in it] + vk_alpha = [FQ(_) for _ in vk[:2]] + vk_beta = (FQ2(vk[2:4][::-1]), FQ2(vk[4:6][::-1])) + vk_gamma = (FQ2(vk[6:8][::-1]), FQ2(vk[8:10][::-1])) + vk_delta = (FQ2(vk[10:12][::-1]), FQ2(vk[12:14][::-1])) + + assert is_on_curve(vk_alpha, b) + assert is_on_curve(vk_beta, b2) + assert is_on_curve(vk_gamma, b2) + assert is_on_curve(vk_delta, b2) + + proof_A = [FQ(_) for _ in proof[:2]] + proof_B = (FQ2(proof[2:4][::-1]), FQ2(proof[4:-2][::-1])) + proof_C = [FQ(_) for _ in proof[-2:]] + + assert is_on_curve(proof_A, b) + assert is_on_curve(proof_B, b2) + assert is_on_curve(proof_C, b) + + vk_x = ic[0] + for i, s in enumerate(inputs): + vk_x = add(vk_x, multiply(ic[i + 1], s)) + + check_1 = pairing(proof_B, proof_A) + check_2 = pairing(vk_beta, neg(vk_alpha)) + check_3 = pairing(vk_gamma, neg(vk_x)) + check_4 = pairing(vk_delta, neg(proof_C)) + + ok = check_1 * check_2 * check_3 * check_4 + assert ok == FQ12.one() + */ + function Verify ( uint256[14] in_vk, uint256[] vk_gammaABC, uint256[8] in_proof, uint256[] proof_inputs ) + internal view returns (bool) + { + require( ((vk_gammaABC.length / 2) - 1) == proof_inputs.length ); + + // Compute the linear combination vk_x + uint256[3] memory mul_input; + uint256[4] memory add_input; + bool success; + uint m = 2; + + // First two fields are used as the sum + add_input[0] = vk_gammaABC[0]; + add_input[1] = vk_gammaABC[1]; + + // Performs a sum of gammaABC[0] + sum[ gammaABC[i+1]^proof_inputs[i] ] + for (uint i = 0; i < proof_inputs.length; i++) { + mul_input[0] = vk_gammaABC[m++]; + mul_input[1] = vk_gammaABC[m++]; + mul_input[2] = proof_inputs[i]; + + assembly { + // ECMUL, output to last 2 elements of `add_input` + success := staticcall(sub(gas, 2000), 7, mul_input, 0x80, add(add_input, 0x40), 0x60) + } + require( success ); + + assembly { + // ECADD + success := staticcall(sub(gas, 2000), 6, add_input, 0xc0, add_input, 0x60) + } + require( success ); + } + + uint[24] memory input = [ + // (proof.A, proof.B) + in_proof[0], in_proof[1], // proof.A (G1) + in_proof[2], in_proof[3], in_proof[4], in_proof[5], // proof.B (G2) + + // (-vk.alpha, vk.beta) + in_vk[0], NegateY(in_vk[1]), // -vk.alpha (G1) + in_vk[2], in_vk[3], in_vk[4], in_vk[5], // vk.beta (G2) + + // (-vk_x, vk.gamma) + add_input[0], NegateY(add_input[1]), // -vk_x (G1) + in_vk[6], in_vk[7], in_vk[8], in_vk[9], // vk.gamma (G2) + + // (-proof.C, vk.delta) + in_proof[6], NegateY(in_proof[7]), // -proof.C (G1) + in_vk[10], in_vk[11], in_vk[12], in_vk[13] // vk.delta (G2) + ]; + + uint[1] memory out; + assembly { + success := staticcall(sub(gas, 2000), 8, input, 768, out, 0x20) + } + require(success); + return out[0] != 0; + } + + function Verify (VerifyingKey memory vk, ProofWithInput memory pwi) - internal returns (bool) + internal view returns (bool) { return Verify(vk, pwi.proof, pwi.input); } + function Verify (VerifyingKey memory vk, Proof memory proof, uint256[] memory input) - internal returns (bool) + internal view returns (bool) { require(input.length + 1 == vk.gammaABC.length); diff --git a/ethsnarks/field.py b/ethsnarks/field.py index 54b1abb..8c934a4 100644 --- a/ethsnarks/field.py +++ b/ethsnarks/field.py @@ -24,38 +24,25 @@ # import sys -import math from random import randint from collections import defaultdict from .numbertheory import square_root_mod_prime # python3 compatibility -if sys.version_info.major == 2: - int_types = (int, long) # noqa: F821 -else: +if sys.version_info.major > 2: int_types = (int,) + long = int +else: + int_types = (int, long) # noqa: F821 SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617 -def powmod(a, b, n): - """Modulo exponentiation""" - c = 0 - f = 1 - k = int(math.log(b, 2)) - while k >= 0: - c *= 2 - f = (f*f)%n - if b & (1 << k): - c += 1 - f = (f*a) % n - k -= 1 - return f - # Extended euclidean algorithm to find modular inverses for # integers +# equivalent to pow(a, n-2, n) def inv(a, n): if a == 0: return 0 @@ -146,14 +133,14 @@ def __sub__(self, other): def inv(self): self._count('inv') - return FQ(inv(self.n, self.m), self.m) + return FQ(pow(self.n, self.m - 2, self.m), self.m) def sqrt(self): return FQ(square_root_mod_prime(self.n, self.m), self.m) def exp(self, e): e = self._other_n(e) - return FQ(powmod(self.n, e, self.m), self.m) + return FQ(pow(self.n, e, self.m), self.m) def __div__(self, other): on = self._other_n(other) diff --git a/ethsnarks/longsight.py b/ethsnarks/longsight.py index e4e3eb4..80986ba 100644 --- a/ethsnarks/longsight.py +++ b/ethsnarks/longsight.py @@ -29,7 +29,7 @@ from hashlib import sha256 -from .field import SNARK_SCALAR_FIELD, powmod +from .field import SNARK_SCALAR_FIELD def random_element(): @@ -123,7 +123,7 @@ def LongsightL(x, k, C, R, e, p): for i, C_i in enumerate([0] + C): t = (x_i + k + C_i) % p - x_i = powmod(t, e, p) + x_i = pow(t, e, p) y = (x_i + k) % p diff --git a/ethsnarks/merkletree.py b/ethsnarks/merkletree.py index daf6a6c..2610f65 100644 --- a/ethsnarks/merkletree.py +++ b/ethsnarks/merkletree.py @@ -38,6 +38,7 @@ def hash_pair(self, depth, left, right): return LongsightL12p5_MP([left, right], IV) def unique(self, depth, index): + assert depth < self._tree_depth item = int(depth).to_bytes(2, 'big') + int(index).to_bytes(30, 'big') hasher = hashlib.sha256() hasher.update(item) diff --git a/ethsnarks/numbertheory.py b/ethsnarks/numbertheory.py index 2b8884d..2b52990 100644 --- a/ethsnarks/numbertheory.py +++ b/ethsnarks/numbertheory.py @@ -15,10 +15,11 @@ import math from functools import reduce -if sys.version_info.major == 2: - integer_types = (int, long) # noqa: F821 -else: +if sys.version_info.major > 2: integer_types = (int,) + long = int +else: + integer_types = (int, long) # noqa: F821 class Error( Exception ): diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js new file mode 100644 index 0000000..3b4812c --- /dev/null +++ b/migrations/2_deploy_contracts.js @@ -0,0 +1,42 @@ +const Verifier = artifacts.require('Verifier.sol'); +const TestableMiximus = artifacts.require('TestableMiximus.sol'); + + + +let list_flatten = (l) => { + return [].concat.apply([], l); +}; + + +let vk_to_flat = (vk) => { + return [ + list_flatten([ + vk.alpha[0], vk.alpha[1], + list_flatten(vk.beta), + list_flatten(vk.gamma), + list_flatten(vk.delta), + ]), + list_flatten(vk.gammaABC) + ]; +}; + + +async function doDeploy( deployer, network ) +{ + await deployer.deploy(Verifier); + await deployer.link(Verifier, TestableMiximus); + + var vk = require('../zksnark_element/miximus.vk.json'); + let [vk_flat, vk_flat_IC] = vk_to_flat(vk); + await deployer.deploy(TestableMiximus, + vk_flat, + vk_flat_IC + ); +} + + +module.exports = function (deployer, network) { + deployer.then(async () => { + await doDeploy(deployer, network); + }); +}; diff --git a/package.json b/package.json index b72634f..446a60f 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,16 @@ "author": "HarryR@noreply.users.gihub.com", "license": "GPL-3.0+", "dependencies": { - "solc": "^0.4.24", - "truffle": "^4.1.13" + "solc": "^0.4.25", + "truffle": "^4.1.14" }, "devDependencies": { - "ganache-cli": "^6.1.3", - "solidity-coverage": "^0.5.5", - "solhint": "^1.1.10" + "ganache-cli": "^6.1.8", + "solidity-coverage": "^0.5.11", + "solhint": "^1.4.0", + "ffi": "^2.2.0", + "ref": "^1.3.5", + "ref-array": "^1.2.0" }, "scripts": { "compile": "truffle compile", diff --git a/src/mod/miximus.cpp b/src/mod/miximus.cpp index 9ad6193..86975d2 100644 --- a/src/mod/miximus.cpp +++ b/src/mod/miximus.cpp @@ -44,14 +44,15 @@ class mod_miximus : public GadgetT typedef LongsightL12p5_MP_gadget HashT; const size_t tree_depth = MIXIMUS_TREE_DEPTH; - // public constants - const VariableArrayT m_IVs; // public inputs const VariableT root_var; const VariableT nullifier_var; const VariableT external_hash_var; + // public constants + const VariableArrayT m_IVs; + // constant inputs const VariableT spend_hash_IV; const VariableT leaf_hash_IV; @@ -74,13 +75,13 @@ class mod_miximus : public GadgetT ) : GadgetT(in_pb, annotation_prefix), - m_IVs(merkle_tree_IVs(in_pb)), - // public inputs root_var(make_variable(in_pb, FMT(annotation_prefix, ".root_var"))), nullifier_var(make_variable(in_pb, FMT(annotation_prefix, ".nullifier_var"))), external_hash_var(make_variable(in_pb, FMT(annotation_prefix, ".external_hash_var"))), + m_IVs(merkle_tree_IVs(in_pb)), + // constant inputs spend_hash_IV(make_variable(in_pb, FMT(annotation_prefix, ".spend_hash_IV"))), leaf_hash_IV(make_variable(in_pb, FMT(annotation_prefix, ".leaf_hash_IV"))), @@ -96,6 +97,8 @@ class mod_miximus : public GadgetT m_authenticator(in_pb, tree_depth, address_bits, m_IVs, leaf_hash.result(), root_var, path_var, FMT(annotation_prefix, ".authenticator")) { in_pb.set_input_sizes( 3 ); + + // TODO: verify that inputs are expected publics } void generate_r1cs_constraints() diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 418c59f..7cbb827 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,12 +1,13 @@ +add_executable(benchmark_mpz_mul benchmark_mpz_mul.cpp) +target_link_libraries(benchmark_mpz_mul ff) +add_executable(benchmark_pairing benchmark_pairing.cpp) +target_link_libraries(benchmark_pairing ff) add_executable(test_vk_raw2json test_vk_raw2json.cpp) target_link_libraries(test_vk_raw2json ethsnarks_common) -add_executable(test_shootout test_shootout.cpp) -target_link_libraries(test_shootout ff) - add_executable(test_load_proofkey test_load_proofkey.cpp) target_link_libraries(test_load_proofkey ethsnarks_common) diff --git a/src/test/benchmark_mpz_mul.cpp b/src/test/benchmark_mpz_mul.cpp new file mode 100644 index 0000000..22adc26 --- /dev/null +++ b/src/test/benchmark_mpz_mul.cpp @@ -0,0 +1,36 @@ +#include "ethsnarks.hpp" + +using namespace libsnark; +using namespace libff; + +int main( ) +{ + bigint number_a; + bigint number_b; + + number_a.randomize(); + number_b.randomize(); + + mpz_t result; + mpz_init(result); + + mpz_t number_a_mpz; + mpz_init(number_a_mpz); + number_a.to_mpz(number_a_mpz); + + mpz_t number_b_mpz; + mpz_init(number_b_mpz); + number_b.to_mpz(number_b_mpz); + + enter_block("Multiplying with MPZ, 1 million times"); + + for( int i = 0; i < 100000000; i++ ) { + mpz_mul(result, number_a_mpz, number_b_mpz); + } + + leave_block("Multiplying with MPZ, 1 million times"); + + mpz_clear(number_a_mpz); + mpz_clear(number_b_mpz); + mpz_clear(result); +} \ No newline at end of file diff --git a/src/test/test_shootout.cpp b/src/test/benchmark_pairing.cpp similarity index 100% rename from src/test/test_shootout.cpp rename to src/test/benchmark_pairing.cpp diff --git a/test/TestMerkleTree.sol b/test/TestMerkleTree.sol index 797dd79..92038c7 100644 --- a/test/TestMerkleTree.sol +++ b/test/TestMerkleTree.sol @@ -11,18 +11,60 @@ contract TestMerkleTree function testUniqueLeafs () public { - Assert.equal(MerkleTree.GetUniqueLeaf(20, 20, 0), 6738165491478210350639451800403024427867073896603076888955948358229240057870, "Unique leaf mismatch!"); + Assert.equal(MerkleTree.GetUniqueLeaf(20, 20, 0), 6738165491478210350639451800403024427867073896603076888955948358229240057870, "Unique leaf mismatch 20 20!"); + + Assert.equal(MerkleTree.GetUniqueLeaf(2, 2, 0), 21534879888322772601810176771999178940739467644392123609236489175629034941722, "Unique leaf mismatch 2 2!"); + + Assert.equal(MerkleTree.GetUniqueLeaf(0, 0, 0), 2544023609834722662089612003212769975105508295482723304413974529614913939747, "Unique leaf mismatch 2 2!"); } MerkleTree.Data tree1; + function testUniqueLeaf () + public + { + // .... + } + function testTreeInsert () public { - tree1.Insert(3703141493535563179657531719960160174296085208671919316200479060314459804651); + uint256 leaf_0 = 3703141493535563179657531719960160174296085208671919316200479060314459804651; + tree1.Insert(leaf_0); + + uint256 leaf_1 = 134551314051432487569247388144051420116740427803855572138106146683954151557; + uint256 leaf_1_root; + uint256 leaf_1_offset; + + (leaf_1_root, leaf_1_offset) = tree1.Insert(leaf_1); + + // Verify root after insertion + uint256 new_root = tree1.GetRoot(); + Assert.equal(new_root, leaf_1_root, "Root must match after insert"); + Assert.equal(new_root, 12880293998234311228895747943713504338160238149993004139365982527556885579681, "Root mismatch!"); + + // Verify leaf offset 0 + Assert.equal(tree1.GetLeaf(0, 0), 3703141493535563179657531719960160174296085208671919316200479060314459804651, "Leaf mismatch 0 0"); + Assert.equal(tree1.GetLeaf(1, 0), 13981856331482487152452149678096232821987624395720231314895268163963385035507, "Leaf mismatch 1 0"); + Assert.equal(tree1.GetLeaf(2, 0), 4008582766686307301250960594183893449903725811265984514032955228389672705119, "Leaf mismatch 2 0"); + + // Verify leaf offset 1 + Assert.equal(tree1.GetLeaf(1, 1), 17296471688945713021042054900108821045192859417413320566181654591511652308323, "Leaf mismatch 1 1"); + Assert.equal(tree1.GetLeaf(2, 1), 4832852105446597958495745596582249246190817345027389430471458078394903639834, "Leaf mismatch 2 1"); + Assert.equal(tree1.GetLeaf(13, 1), 14116139569958633576637617144876714429777518811711593939929091541932333542283, "Leaf mismatch 13 1"); + Assert.equal(tree1.GetLeaf(22, 1), 16077039334695461958102978289003547153551663194787878097275872631374489043531, "Leaf mismatch 22 1"); + + // Validate the proof + uint256[29] memory path_0; + bool[29] memory bits_0; + (path_0, bits_0) = tree1.GetProof(0); + Assert.equal(tree1.VerifyPath(leaf_0, path_0, bits_0), true, "Own path didn't validate!"); + + // Validate individual items from the path + Assert.equal(path_0[0], 134551314051432487569247388144051420116740427803855572138106146683954151557, "Path 0 not match"); - tree1.Insert(134551314051432487569247388144051420116740427803855572138106146683954151557); + Assert.equal(path_0[1], 17296471688945713021042054900108821045192859417413320566181654591511652308323, "Path 1 not match"); - Assert.equal(tree1.GetRoot(), 12880293998234311228895747943713504338160238149993004139365982527556885579681, "Root mismatch!"); + Assert.equal(path_0[28], 12153512749608688970226014453322261962108142410782369348825826565095395587383, "Path 28 not match"); } } diff --git a/test/TestMiximus.js b/test/TestMiximus.js new file mode 100644 index 0000000..812356b --- /dev/null +++ b/test/TestMiximus.js @@ -0,0 +1,174 @@ +const TestableMiximus = artifacts.require("TestableMiximus"); + +const crypto = require("crypto"); + +const fs = require("fs"); +const ffi = require("ffi"); +const ref = require("ref"); +const ArrayType = require("ref-array"); +const BigNumber = require("bignumber.js"); + +var StringArray = ArrayType(ref.types.CString); + +var libmiximus = ffi.Library("build/src/libmiximus", { + // Retrieve depth of tree + "miximus_tree_depth": [ + "size_t", [] + ], + + // Create a proof for the parameters + "miximus_prove": [ + "string", [ + "string", // pk_file + "string", // in_root + "string", // in_nullifier + "string", // in_exthash + "string", // in_spend_preimage + "string", // in_address + StringArray, // in_path + ] + ], + + // Verify a proof + "miximus_verify": [ + "bool", [ + "string", // vk_json + "string", // proof_json + ] + ] +}); + + + +let list_flatten = (l) => { + return [].concat.apply([], l); +}; + + +let vk_to_flat = (vk) => { + return [ + list_flatten([ + vk.alpha[0], vk.alpha[1], + list_flatten(vk.beta), + list_flatten(vk.gamma), + list_flatten(vk.delta), + ]), + list_flatten(vk.gammaABC) + ]; +}; + + +let proof_to_flat = (proof) => { + return list_flatten([ + proof.A, + list_flatten(proof.B), + proof.C + ]); +}; + + +contract("TestableMiximus", () => { + describe("Deposit", () => { + it("deposits then withdraws", async () => { + let obj = await TestableMiximus.deployed(); + + // Parameters for deposit + let spend_preimage = new BigNumber(crypto.randomBytes(30).toString("hex"), 16); + let nullifier = new BigNumber(crypto.randomBytes(30).toString("hex"), 16); + let leaf_hash = await obj.MakeLeafHash.call(spend_preimage, nullifier); + + + // Perform deposit + let new_root_and_offset = await obj.Deposit.call(leaf_hash, {value: 1000000000000000000}); + await obj.Deposit.sendTransaction([leaf_hash], {value: 1000000000000000000}); + + + // TODO: verify amount has been transferred + + + // Build parameters for proving + let tmp = await obj.GetPath.call(new_root_and_offset[1]); + let proof_address = tmp[1].map((_) => _ ? "1" : "0").join(""); + let proof_path = []; + for( var i = 0; i < proof_address.length; i++ ) { + proof_path.push( tmp[0][i].toString(10) ); + } + let proof_root = await obj.GetRoot.call(); + proof_root = new_root_and_offset[0]; + let proof_exthash = await obj.GetExtHash.call(); + + + // Run prover to generate proof + let args = [ + "zksnark_element/miximus.pk.raw", + proof_root.toString(10), + nullifier.toString(10), + proof_exthash.toString(10), + spend_preimage.toString(10), + proof_address, + proof_path + ]; + let proof_json = libmiximus.miximus_prove(...args); + assert.notStrictEqual(proof_json, null); + let proof = JSON.parse(proof_json); + + + // Ensure proof inputs match ours + assert.strictEqual("0x" + proof_root.toString(16), proof.input[0]); + assert.strictEqual("0x" + nullifier.toString(16), proof.input[1]); + assert.strictEqual("0x" + proof_exthash.toString(16), proof.input[2]); + + + // Re-verify proof using native library + let vk_json = fs.readFileSync("zksnark_element/miximus.vk.json"); + let proof_valid_native = libmiximus.miximus_verify(vk_json, proof_json); + assert.strictEqual(proof_valid_native, true); + let vk = JSON.parse(vk_json); + + + // Verify VK and Proof together + let [vk_flat, vk_flat_IC] = vk_to_flat(vk); + let test_verify_args = [ + vk_flat, // (alpha, beta, gamma, delta) + vk_flat_IC, // gammaABC[] + proof_to_flat(proof), // A B C + [ + proof.input[0], + proof.input[1], + proof.input[2] + ] + ]; + let test_verify_result = await obj.TestVerify(...test_verify_args); + assert.strictEqual(test_verify_result, true); + + + // Verify whether or not our proof would be valid + let proof_valid = await obj.VerifyProof.call( + proof.input[0], + proof.input[1], + proof.input[2], + proof_to_flat(proof)); + assert.strictEqual(proof_valid, true); + + + // Verify nullifier doesn't exist + let is_spent_b4_withdraw = await obj.IsSpent(nullifier.toString(10)); + assert.strictEqual(is_spent_b4_withdraw, false); + + + // Then perform the withdraw + await obj.Withdraw( + proof_root.toString(10), + nullifier.toString(10), + proof_to_flat(proof)); + + + // Verify nullifier exists + let is_spent = await obj.IsSpent(nullifier.toString(10)); + assert.strictEqual(is_spent, true); + + + // TODO: verify balance has been increased + }); + }); +}); diff --git a/test/test_merkle.py b/test/test_merkle.py index 08cd919..f71f001 100644 --- a/test/test_merkle.py +++ b/test/test_merkle.py @@ -56,9 +56,27 @@ def test_known_2pow28(self): self.assertEqual(tree.root, 12880293998234311228895747943713504338160238149993004139365982527556885579681) + proof_a = tree.proof(0) + self.assertTrue(proof_a.verify(tree.root)) + + proof_b = tree.proof(1) + self.assertTrue(proof_b.verify(tree.root)) + + self.assertEqual(tree.leaf(0, 0), 3703141493535563179657531719960160174296085208671919316200479060314459804651) + self.assertEqual(tree.leaf(1, 0), 13981856331482487152452149678096232821987624395720231314895268163963385035507) + self.assertEqual(tree.leaf(2, 0), 4008582766686307301250960594183893449903725811265984514032955228389672705119) + + self.assertEqual(tree.leaf(1, 1), 17296471688945713021042054900108821045192859417413320566181654591511652308323) + self.assertEqual(tree.leaf(2, 1), 4832852105446597958495745596582249246190817345027389430471458078394903639834) + self.assertEqual(tree.leaf(13, 1), 14116139569958633576637617144876714429777518811711593939929091541932333542283) + self.assertEqual(tree.leaf(22, 1), 16077039334695461958102978289003547153551663194787878097275872631374489043531) + + def test_uniques(self): hasher = MerkleHasherLongsight(29) self.assertEqual(hasher.unique(20, 20), 6738165491478210350639451800403024427867073896603076888955948358229240057870) + self.assertEqual(hasher.unique(2, 2), 21534879888322772601810176771999178940739467644392123609236489175629034941722) + self.assertEqual(hasher.unique(0, 0), 2544023609834722662089612003212769975105508295482723304413974529614913939747) if __name__ == "__main__": unittest.main()