├── .gitignore ├── .gitmodules ├── 1_simple_arithmetic ├── generate_challenge_input.js ├── generate_circuit_input.js ├── sample_challenge_circuit.circom └── sample_circuit.circom ├── 2_verify_eddsa ├── generate_challenge_input.js ├── generate_circuit_input.js ├── sample_challenge_circuit.circom └── sample_circuit.circom ├── 3_verify_merkle ├── MiMCMerkle.js ├── generate_challenge_leaf_existence_input.js ├── generate_get_merkle_root_input.js ├── generate_leaf_existence_input.js ├── sample_challenge_leaf_existence.circom ├── sample_get_merkle_root.circom └── sample_leaf_existence.circom ├── 4_single_tx ├── MiMCMerkle.js ├── generate_circuit_input.js ├── get_merkle_root.circom ├── leaf_existence.circom ├── sample_circuit.circom ├── stringifybigint.js └── verify_eddsamimc.circom ├── 5_comparator ├── bad_force_equal_if_enabled.circom ├── circuit.circom ├── force_equal_if_enabled.circom └── iszero.circom ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | witness.json 3 | verification_key.json 4 | proving_key.json 5 | input.json 6 | proof.json 7 | public.json 8 | circuit.json 9 | package-lock.json 10 | debug.js -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "circomlib"] 2 | path = circomlib 3 | url = https://github.com/iden3/circomlib 4 | -------------------------------------------------------------------------------- /1_simple_arithmetic/generate_challenge_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | var a = [2,2,2,2]; 4 | var b = [4,4,4,4]; 5 | var c = [6,6,6,6]; 6 | var d = [24,24,24,24]; 7 | 8 | const inputs = { 9 | "a": a, 10 | "b": b, 11 | "c": c, 12 | "d": d 13 | } 14 | 15 | fs.writeFileSync( 16 | "./input.json", 17 | JSON.stringify(inputs), 18 | "utf-8" 19 | ); -------------------------------------------------------------------------------- /1_simple_arithmetic/generate_circuit_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | var a = 2; 4 | var b = 4; 5 | var c = 6; 6 | var d = 24; 7 | 8 | const inputs = { 9 | "a": a, 10 | "b": b, 11 | "c": c, 12 | "d": d 13 | } 14 | 15 | fs.writeFileSync( 16 | "./input.json", 17 | JSON.stringify(inputs), 18 | "utf-8" 19 | ); -------------------------------------------------------------------------------- /1_simple_arithmetic/sample_challenge_circuit.circom: -------------------------------------------------------------------------------- 1 | template SimpleChecks(k) { 2 | signal private input a[k]; 3 | signal private input b[k]; 4 | signal input c[k]; 5 | signal private input d[k]; 6 | signal output out; 7 | 8 | var sum = 0; 9 | for (var i = 0; i < k; i++){ 10 | // force a + b = c 11 | a[i] + b[i] === c[i]; 12 | 13 | // force b * c = d 14 | b[i] * c[i] === d[i]; 15 | 16 | // add up c and d arrays 17 | sum = sum + c[i] + d[i]; 18 | } 19 | // output sum of c and d arrays 20 | out <== sum; 21 | } 22 | 23 | component main = SimpleChecks(4); -------------------------------------------------------------------------------- /1_simple_arithmetic/sample_circuit.circom: -------------------------------------------------------------------------------- 1 | template SimpleChecks() { 2 | signal private input a; 3 | signal private input b; 4 | signal input c; 5 | signal private input d; 6 | signal output out; 7 | 8 | // force a + b = c 9 | a + b === c; 10 | 11 | // force b * c = d 12 | b * c === d; 13 | 14 | // output c + d 15 | out <== c + d; 16 | } 17 | 18 | component main = SimpleChecks(); -------------------------------------------------------------------------------- /2_verify_eddsa/generate_challenge_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const eddsa = require("../circomlib/src/eddsa.js"); 3 | const mimcjs = require("../circomlib/src/mimc7.js"); 4 | 5 | const preimage = [123,456,789]; 6 | const M = mimcjs.multiHash(preimage); 7 | const prvKey = Buffer.from('1'.toString().padStart(64,'0'), "hex"); 8 | const pubKey = eddsa.prv2pub(prvKey); 9 | 10 | const signature = eddsa.signMiMC(prvKey, M); 11 | 12 | const inputs = { 13 | "from_x": pubKey[0].toString(), 14 | "from_y": pubKey[1].toString(), 15 | "R8x": signature['R8'][0].toString(), 16 | "R8y": signature['R8'][1].toString(), 17 | "S": signature['S'].toString(), 18 | "preimage": preimage 19 | } 20 | 21 | fs.writeFileSync( 22 | "./input.json", 23 | JSON.stringify(inputs), 24 | "utf-8" 25 | ); -------------------------------------------------------------------------------- /2_verify_eddsa/generate_circuit_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const eddsa = require("../circomlib/src/eddsa.js"); 3 | const mimcjs = require("../circomlib/src/mimc7.js"); 4 | 5 | const preimage = [123,456,789]; 6 | const M = mimcjs.multiHash(preimage); 7 | const prvKey = Buffer.from('1'.toString().padStart(64,'0'), "hex"); 8 | const pubKey = eddsa.prv2pub(prvKey); 9 | 10 | const signature = eddsa.signMiMC(prvKey, M); 11 | 12 | const inputs = { 13 | "from_x": pubKey[0].toString(), 14 | "from_y": pubKey[1].toString(), 15 | "R8x": signature['R8'][0].toString(), 16 | "R8y": signature['R8'][1].toString(), 17 | "S": signature['S'].toString(), 18 | "M": M.toString() 19 | } 20 | 21 | fs.writeFileSync( 22 | "./input.json", 23 | JSON.stringify(inputs), 24 | "utf-8" 25 | ); 26 | -------------------------------------------------------------------------------- /2_verify_eddsa/sample_challenge_circuit.circom: -------------------------------------------------------------------------------- 1 | include "../circomlib/circuits/eddsamimc.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | template VerifyEdDSAMiMC(k) { 5 | 6 | // k is length of preimage 7 | 8 | signal input from_x; 9 | signal input from_y; 10 | signal input R8x; 11 | signal input R8y; 12 | signal input S; 13 | signal private input preimage[k]; 14 | 15 | component M = MultiMiMC7(k,91); 16 | for (var i = 0; i < k; i++){ 17 | M.in[i] <== preimage[i]; 18 | } 19 | 20 | component verifier = EdDSAMiMCVerifier(); 21 | verifier.enabled <== 1; 22 | verifier.Ax <== from_x; 23 | verifier.Ay <== from_y; 24 | verifier.R8x <== R8x; 25 | verifier.R8y <== R8y; 26 | verifier.S <== S; 27 | verifier.M <== M.out; 28 | } 29 | 30 | component main = VerifyEdDSAMiMC(3); -------------------------------------------------------------------------------- /2_verify_eddsa/sample_circuit.circom: -------------------------------------------------------------------------------- 1 | include "../circomlib/circuits/eddsamimc.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | template VerifyEdDSAMiMC() { 5 | 6 | // k is length of preimage 7 | 8 | signal input from_x; 9 | signal input from_y; 10 | signal input R8x; 11 | signal input R8y; 12 | signal input S; 13 | signal input M; 14 | 15 | component verifier = EdDSAMiMCVerifier(); 16 | verifier.enabled <== 1; 17 | verifier.Ax <== from_x; 18 | verifier.Ay <== from_y; 19 | verifier.R8x <== R8x; 20 | verifier.R8y <== R8y; 21 | verifier.S <== S; 22 | verifier.M <== M; 23 | } 24 | 25 | component main = VerifyEdDSAMiMC(); -------------------------------------------------------------------------------- /3_verify_merkle/MiMCMerkle.js: -------------------------------------------------------------------------------- 1 | const mimcjs = require("../circomlib/src/mimc7.js"); 2 | const bigInt = require("snarkjs").bigInt; 3 | 4 | 5 | module.exports = { 6 | 7 | // cache empty tree values 8 | getZeroCache: function(zeroLeafHash, depth){ 9 | var zeroCache = new Array(depth) 10 | zeroCache[0] = zeroLeafHash 11 | for (var i = 1; i < depth; i++){ 12 | zeroCache[i] = mimcjs.multiHash([zeroCache[i-1],zeroCache[i-1]]) 13 | } 14 | return zeroCache 15 | }, 16 | 17 | getProof: function(leafIdx, tree, leaves){ 18 | depth = tree.length; 19 | proofIdx = module.exports.proofIdx(leafIdx, depth); 20 | proof = new Array(depth); 21 | proof[0] = leaves[proofIdx[0]] 22 | for (i = 1; i < depth; i++){ 23 | proof[i] = tree[depth - i][proofIdx[i]] 24 | } 25 | return proof; 26 | }, 27 | 28 | getProofEmpty: function(height, zeroCache){ 29 | const depth = zeroCache.length 30 | if (height < depth){ 31 | return zeroCache.slice(height, depth + 1) 32 | } else { 33 | return [] 34 | } 35 | }, 36 | 37 | verifyProof: function(leaf, idx, proof, root){ 38 | computed_root = module.exports.rootFromLeafAndPath(leaf, idx, proof) 39 | return (root == computed_root) 40 | }, 41 | 42 | rootFromLeafAndPath: function(leaf, idx, merkle_path){ 43 | if (merkle_path.length > 0){ 44 | const depth = merkle_path.length 45 | const merkle_path_pos = module.exports.idxToBinaryPos(idx, depth) 46 | var root = new Array(depth); 47 | left = bigInt(leaf) - bigInt(merkle_path_pos[0])*(bigInt(leaf) - bigInt(merkle_path[0])); 48 | right = bigInt(merkle_path[0]) - bigInt(merkle_path_pos[0])*(bigInt(merkle_path[0]) - bigInt(leaf)); 49 | root[0] = mimcjs.multiHash([left, right]); 50 | var i; 51 | for (i = 1; i < depth; i++) { 52 | left = root[i-1] - bigInt(merkle_path_pos[i])*(root[i-1] - bigInt(merkle_path[i])); 53 | right = bigInt(merkle_path[i]) - bigInt(merkle_path_pos[i])*(bigInt(merkle_path[i]) - root[i-1]); 54 | root[i] = mimcjs.multiHash([left, right]); 55 | } 56 | 57 | return root[depth - 1]; 58 | } else { 59 | return leaf 60 | } 61 | 62 | }, 63 | 64 | // fill a leaf array with zero leaves until it is a power of 2 65 | padLeafArray: function(leafArray, zeroLeaf, fillerLength){ 66 | if (Array.isArray(leafArray)){ 67 | var arrayClone = leafArray.slice(0) 68 | const nearestPowerOfTwo = Math.ceil(module.exports.getBase2Log(leafArray.length)) 69 | const diff = fillerLength || 2**nearestPowerOfTwo - leafArray.length 70 | for (var i = 0; i < diff; i++){ 71 | arrayClone.push(zeroLeaf) 72 | } 73 | return arrayClone 74 | } else { 75 | console.log("please enter pubKeys as an array") 76 | } 77 | }, 78 | 79 | 80 | // fill a leaf hash array with zero leaf hashes until it is a power of 2 81 | padLeafHashArray: function(leafHashArray, zeroLeafHash, fillerLength){ 82 | if (Array.isArray(leafHashArray)){ 83 | var arrayClone = leafHashArray.slice(0) 84 | const nearestPowerOfTwo = Math.ceil(module.exports.getBase2Log(leafHashArray.length)) 85 | const diff = fillerLength || 2**nearestPowerOfTwo - leafHashArray.length 86 | for (var i = 0; i < diff; i++){ 87 | arrayClone.push(zeroLeafHash) 88 | } 89 | return arrayClone 90 | } else { 91 | console.log("please enter pubKeys as an array") 92 | } 93 | }, 94 | 95 | treeFromLeafArray: function(leafArray){ 96 | depth = module.exports.getBase2Log(leafArray.length); 97 | tree = Array(depth); 98 | 99 | tree[depth - 1] = module.exports.pairwiseHash(leafArray) 100 | 101 | for (j = depth - 2; j >= 0; j--){ 102 | tree[j] = module.exports.pairwiseHash(tree[j+1]) 103 | } 104 | 105 | // return treeRoot[depth-1] 106 | return tree 107 | }, 108 | 109 | rootFromLeafArray: function(leafArray){ 110 | return module.exports.treeFromLeafArray(leafArray)[0][0] 111 | }, 112 | 113 | pairwiseHash: function(array){ 114 | if (array.length % 2 == 0){ 115 | arrayHash = [] 116 | for (i = 0; i < array.length; i = i + 2){ 117 | arrayHash.push(mimcjs.multiHash( 118 | [array[i].toString(),array[i+1].toString()] 119 | )) 120 | } 121 | return arrayHash 122 | } else { 123 | console.log('array must have even number of elements') 124 | } 125 | }, 126 | 127 | generateMerklePosArray: function(depth){ 128 | merklePosArray = []; 129 | for (i = 0; i < 2**depth; i++){ 130 | binPos = module.exports.idxToBinaryPos(i, depth) 131 | merklePosArray.push(binPos) 132 | } 133 | return merklePosArray; 134 | }, 135 | 136 | generateMerkleProofArray: function(txTree, txLeafHashes){ 137 | txProofs = new Array(txLeafHashes.length) 138 | for (jj = 0; jj < txLeafHashes.length; jj++){ 139 | txProofs[jj] = module.exports.getProof(jj, txTree, txLeafHashes) 140 | } 141 | return txProofs; 142 | }, 143 | 144 | /////////////////////////////////////////////////////////////////////// 145 | // HELPER FUNCTIONS 146 | /////////////////////////////////////////////////////////////////////// 147 | 148 | getBase2Log: function(y){ 149 | return Math.log(y) / Math.log(2); 150 | }, 151 | 152 | binaryPosToIdx: function(binaryPos){ 153 | var idx = 0; 154 | for (i = 0; i < binaryPos.length; i++){ 155 | idx = idx + binaryPos[i]*(2**i) 156 | } 157 | return idx; 158 | }, 159 | 160 | idxToBinaryPos: function(idx, binLength){ 161 | 162 | binString = idx.toString(2); 163 | binPos = Array(binLength).fill(0) 164 | for (j = 0; j < binString.length; j++){ 165 | binPos[j] = Number(binString.charAt(binString.length - j - 1)); 166 | } 167 | return binPos; 168 | }, 169 | 170 | proofIdx: function(leafIdx, treeDepth){ 171 | proofIdxArray = new Array(treeDepth); 172 | proofPos = module.exports.idxToBinaryPos(leafIdx, treeDepth); 173 | // console.log('proofPos', proofPos) 174 | 175 | if (leafIdx % 2 == 0){ 176 | proofIdxArray[0] = leafIdx + 1; 177 | } else { 178 | proofIdxArray[0] = leafIdx - 1; 179 | } 180 | 181 | for (i = 1; i < treeDepth; i++){ 182 | if (proofPos[i] == 1){ 183 | proofIdxArray[i] = Math.floor(proofIdxArray[i - 1] / 2) - 1; 184 | } else { 185 | proofIdxArray[i] = Math.floor(proofIdxArray[i - 1] / 2) + 1; 186 | } 187 | } 188 | 189 | return(proofIdxArray) 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /3_verify_merkle/generate_challenge_leaf_existence_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const mimcjs = require("../circomlib/src/mimc7.js"); 3 | const mimcMerkle = require('./MiMCMerkle.js') 4 | 5 | const leaf1 = mimcjs.multiHash([1,2,3]) 6 | const leaf2 = mimcjs.multiHash([4,5,6]) 7 | const leaf3 = mimcjs.multiHash([7,8,9]) 8 | const leaf4 = mimcjs.multiHash([9,8,7]) 9 | const leafArray = [leaf1,leaf2,leaf3,leaf4] 10 | 11 | const tree = mimcMerkle.treeFromLeafArray(leafArray) 12 | const root = tree[0][0]; 13 | const leaf1Proof = mimcMerkle.getProof(0, tree, leafArray) 14 | const leaf1Pos = [1,1] 15 | 16 | const inputs = { 17 | "preimage": [1,2,3], 18 | "root": root.toString(), 19 | "paths2_root": [leaf1Proof[0].toString(),leaf1Proof[1].toString()], 20 | "paths2_root_pos": leaf1Pos 21 | } 22 | 23 | fs.writeFileSync( 24 | "./input.json", 25 | JSON.stringify(inputs), 26 | "utf-8" 27 | ); 28 | -------------------------------------------------------------------------------- /3_verify_merkle/generate_get_merkle_root_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const mimcjs = require("../circomlib/src/mimc7.js"); 3 | const mimcMerkle = require('./MiMCMerkle.js') 4 | 5 | const leaf1 = mimcjs.multiHash([1,2,3]) 6 | const leaf2 = mimcjs.multiHash([4,5,6]) 7 | const leaf3 = mimcjs.multiHash([7,8,9]) 8 | const leaf4 = mimcjs.multiHash([9,8,7]) 9 | const leafArray = [leaf1,leaf2,leaf3,leaf4] 10 | 11 | const tree = mimcMerkle.treeFromLeafArray(leafArray) 12 | const root = tree[0][0]; 13 | const leaf1Proof = mimcMerkle.getProof(0, tree, leafArray) 14 | const leaf1Pos = mimcMerkle.idxToBinaryPos(0, 2) 15 | 16 | const inputs = { 17 | "leaf": leaf1.toString(), 18 | "paths2_root": [leaf1Proof[0].toString(),leaf1Proof[1].toString()], 19 | "paths2_root_pos": leaf1Pos 20 | } 21 | 22 | fs.writeFileSync( 23 | "./input.json", 24 | JSON.stringify(inputs), 25 | "utf-8" 26 | ); -------------------------------------------------------------------------------- /3_verify_merkle/generate_leaf_existence_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const mimcjs = require("../circomlib/src/mimc7.js"); 3 | const mimcMerkle = require('./MiMCMerkle.js') 4 | 5 | const leaf1 = mimcjs.multiHash([1,2,3]) 6 | const leaf2 = mimcjs.multiHash([4,5,6]) 7 | const leaf3 = mimcjs.multiHash([7,8,9]) 8 | const leaf4 = mimcjs.multiHash([9,8,7]) 9 | const leafArray = [leaf1,leaf2,leaf3,leaf4] 10 | 11 | const tree = mimcMerkle.treeFromLeafArray(leafArray) 12 | const root = tree[0][0]; 13 | const leaf1Proof = mimcMerkle.getProof(0, tree, leafArray) 14 | const leaf1Pos = [1,1] 15 | 16 | const inputs = { 17 | "leaf": leaf1.toString(), 18 | "root": root.toString(), 19 | "paths2_root": [leaf1Proof[0].toString(),leaf1Proof[1].toString()], 20 | "paths2_root_pos": leaf1Pos 21 | } 22 | 23 | fs.writeFileSync( 24 | "./input.json", 25 | JSON.stringify(inputs), 26 | "utf-8" 27 | ); 28 | -------------------------------------------------------------------------------- /3_verify_merkle/sample_challenge_leaf_existence.circom: -------------------------------------------------------------------------------- 1 | include "./sample_get_merkle_root.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | // checks for existence of leaf in tree of depth k 5 | 6 | template LeafExistence(k, l){ 7 | // k is depth of tree 8 | // l is length of preimage of leaf 9 | 10 | signal private input preimage[l]; 11 | signal input root; 12 | signal input paths2_root_pos[k]; 13 | signal input paths2_root[k]; 14 | 15 | component leaf = MultiMiMC7(l,91); 16 | for (var i = 0; i < l; i++){ 17 | leaf.in[i] <== preimage[i]; 18 | } 19 | 20 | component computed_root = GetMerkleRoot(k); 21 | computed_root.leaf <== leaf.out; 22 | 23 | for (var w = 0; w < k; w++){ 24 | computed_root.paths2_root[w] <== paths2_root[w]; 25 | computed_root.paths2_root_pos[w] <== paths2_root_pos[w]; 26 | } 27 | 28 | // equality constraint: input tx root === computed tx root 29 | root === computed_root.out; 30 | 31 | } 32 | 33 | component main = LeafExistence(2, 3); -------------------------------------------------------------------------------- /3_verify_merkle/sample_get_merkle_root.circom: -------------------------------------------------------------------------------- 1 | include "../circomlib/circuits/mimc.circom"; 2 | 3 | template GetMerkleRoot(k){ 4 | // k is depth of tree 5 | 6 | signal input leaf; 7 | signal input paths2_root[k]; 8 | signal input paths2_root_pos[k]; 9 | 10 | signal output out; 11 | 12 | // hash of first two entries in tx Merkle proof 13 | component merkle_root[k]; 14 | merkle_root[0] = MultiMiMC7(2,91); 15 | merkle_root[0].in[0] <== paths2_root[0] - paths2_root_pos[0]* (paths2_root[0] - leaf); 16 | merkle_root[0].in[1] <== leaf - paths2_root_pos[0]* (leaf - paths2_root[0]); 17 | 18 | // hash of all other entries in tx Merkle proof 19 | for (var v = 1; v < k; v++){ 20 | merkle_root[v] = MultiMiMC7(2,91); 21 | merkle_root[v].in[0] <== paths2_root[v] - paths2_root_pos[v]* (paths2_root[v] - merkle_root[v-1].out); 22 | merkle_root[v].in[1] <== merkle_root[v-1].out - paths2_root_pos[v]* (merkle_root[v-1].out - paths2_root[v]); 23 | 24 | } 25 | 26 | // output computed Merkle root 27 | out <== merkle_root[k-1].out; 28 | 29 | } 30 | 31 | // component main = GetMerkleRoot(2) -------------------------------------------------------------------------------- /3_verify_merkle/sample_leaf_existence.circom: -------------------------------------------------------------------------------- 1 | include "./sample_get_merkle_root.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | // checks for existence of leaf in tree of depth k 5 | 6 | template LeafExistence(k){ 7 | // k is depth of tree 8 | 9 | signal input leaf; 10 | signal input root; 11 | signal input paths2_root_pos[k]; 12 | signal input paths2_root[k]; 13 | 14 | component computed_root = GetMerkleRoot(k); 15 | computed_root.leaf <== leaf; 16 | 17 | for (var w = 0; w < k; w++){ 18 | computed_root.paths2_root[w] <== paths2_root[w]; 19 | computed_root.paths2_root_pos[w] <== paths2_root_pos[w]; 20 | } 21 | 22 | // equality constraint: input tx root === computed tx root 23 | root === computed_root.out; 24 | 25 | } 26 | 27 | component main = LeafExistence(2); 28 | -------------------------------------------------------------------------------- /4_single_tx/MiMCMerkle.js: -------------------------------------------------------------------------------- 1 | const mimcjs = require("../circomlib/src/mimc7.js"); 2 | const bigInt = require("snarkjs").bigInt; 3 | 4 | 5 | module.exports = { 6 | 7 | // cache empty tree values 8 | getZeroCache: function(zeroLeafHash, depth){ 9 | var zeroCache = new Array(depth) 10 | zeroCache[0] = zeroLeafHash 11 | for (var i = 1; i < depth; i++){ 12 | zeroCache[i] = mimcjs.multiHash([zeroCache[i-1],zeroCache[i-1]]) 13 | } 14 | return zeroCache 15 | }, 16 | 17 | getProof: function(leafIdx, tree, leaves){ 18 | depth = tree.length; 19 | proofIdx = module.exports.proofIdx(leafIdx, depth); 20 | proof = new Array(depth); 21 | proof[0] = leaves[proofIdx[0]] 22 | for (i = 1; i < depth; i++){ 23 | proof[i] = tree[depth - i][proofIdx[i]] 24 | } 25 | return proof; 26 | }, 27 | 28 | getProofEmpty: function(height, zeroCache){ 29 | const depth = zeroCache.length 30 | if (height < depth){ 31 | return zeroCache.slice(height, depth + 1) 32 | } else { 33 | return [] 34 | } 35 | }, 36 | 37 | verifyProof: function(leaf, idx, proof, root){ 38 | computed_root = module.exports.rootFromLeafAndPath(leaf, idx, proof) 39 | return (root == computed_root) 40 | }, 41 | 42 | rootFromLeafAndPath: function(leaf, idx, merkle_path){ 43 | if (merkle_path.length > 0){ 44 | const depth = merkle_path.length 45 | const merkle_path_pos = module.exports.idxToBinaryPos(idx, depth) 46 | var root = new Array(depth); 47 | left = bigInt(leaf) - bigInt(merkle_path_pos[0])*(bigInt(leaf) - bigInt(merkle_path[0])); 48 | right = bigInt(merkle_path[0]) - bigInt(merkle_path_pos[0])*(bigInt(merkle_path[0]) - bigInt(leaf)); 49 | root[0] = mimcjs.multiHash([left, right]); 50 | var i; 51 | for (i = 1; i < depth; i++) { 52 | left = root[i-1] - bigInt(merkle_path_pos[i])*(root[i-1] - bigInt(merkle_path[i])); 53 | right = bigInt(merkle_path[i]) - bigInt(merkle_path_pos[i])*(bigInt(merkle_path[i]) - root[i-1]); 54 | root[i] = mimcjs.multiHash([left, right]); 55 | } 56 | 57 | return root[depth - 1]; 58 | } else { 59 | return leaf 60 | } 61 | 62 | }, 63 | 64 | // fill a leaf array with zero leaves until it is a power of 2 65 | padLeafArray: function(leafArray, zeroLeaf, fillerLength){ 66 | if (Array.isArray(leafArray)){ 67 | var arrayClone = leafArray.slice(0) 68 | const nearestPowerOfTwo = Math.ceil(module.exports.getBase2Log(leafArray.length)) 69 | const diff = fillerLength || 2**nearestPowerOfTwo - leafArray.length 70 | for (var i = 0; i < diff; i++){ 71 | arrayClone.push(zeroLeaf) 72 | } 73 | return arrayClone 74 | } else { 75 | console.log("please enter pubKeys as an array") 76 | } 77 | }, 78 | 79 | 80 | // fill a leaf hash array with zero leaf hashes until it is a power of 2 81 | padLeafHashArray: function(leafHashArray, zeroLeafHash, fillerLength){ 82 | if (Array.isArray(leafHashArray)){ 83 | var arrayClone = leafHashArray.slice(0) 84 | const nearestPowerOfTwo = Math.ceil(module.exports.getBase2Log(leafHashArray.length)) 85 | const diff = fillerLength || 2**nearestPowerOfTwo - leafHashArray.length 86 | for (var i = 0; i < diff; i++){ 87 | arrayClone.push(zeroLeafHash) 88 | } 89 | return arrayClone 90 | } else { 91 | console.log("please enter pubKeys as an array") 92 | } 93 | }, 94 | 95 | treeFromLeafArray: function(leafArray){ 96 | depth = module.exports.getBase2Log(leafArray.length); 97 | tree = Array(depth); 98 | 99 | tree[depth - 1] = module.exports.pairwiseHash(leafArray) 100 | 101 | for (j = depth - 2; j >= 0; j--){ 102 | tree[j] = module.exports.pairwiseHash(tree[j+1]) 103 | } 104 | 105 | // return treeRoot[depth-1] 106 | return tree 107 | }, 108 | 109 | rootFromLeafArray: function(leafArray){ 110 | return module.exports.treeFromLeafArray(leafArray)[0][0] 111 | }, 112 | 113 | pairwiseHash: function(array){ 114 | if (array.length % 2 == 0){ 115 | arrayHash = [] 116 | for (i = 0; i < array.length; i = i + 2){ 117 | arrayHash.push(mimcjs.multiHash( 118 | [array[i].toString(),array[i+1].toString()] 119 | )) 120 | } 121 | return arrayHash 122 | } else { 123 | console.log('array must have even number of elements') 124 | } 125 | }, 126 | 127 | generateMerklePosArray: function(depth){ 128 | merklePosArray = []; 129 | for (i = 0; i < 2**depth; i++){ 130 | binPos = module.exports.idxToBinaryPos(i, depth) 131 | merklePosArray.push(binPos) 132 | } 133 | return merklePosArray; 134 | }, 135 | 136 | generateMerkleProofArray: function(txTree, txLeafHashes){ 137 | txProofs = new Array(txLeafHashes.length) 138 | for (jj = 0; jj < txLeafHashes.length; jj++){ 139 | txProofs[jj] = module.exports.getProof(jj, txTree, txLeafHashes) 140 | } 141 | return txProofs; 142 | }, 143 | 144 | /////////////////////////////////////////////////////////////////////// 145 | // HELPER FUNCTIONS 146 | /////////////////////////////////////////////////////////////////////// 147 | 148 | getBase2Log: function(y){ 149 | return Math.log(y) / Math.log(2); 150 | }, 151 | 152 | binaryPosToIdx: function(binaryPos){ 153 | var idx = 0; 154 | for (i = 0; i < binaryPos.length; i++){ 155 | idx = idx + binaryPos[i]*(2**i) 156 | } 157 | return idx; 158 | }, 159 | 160 | idxToBinaryPos: function(idx, binLength){ 161 | 162 | binString = idx.toString(2); 163 | binPos = Array(binLength).fill(0) 164 | for (j = 0; j < binString.length; j++){ 165 | binPos[j] = Number(binString.charAt(binString.length - j - 1)); 166 | } 167 | return binPos; 168 | }, 169 | 170 | proofIdx: function(leafIdx, treeDepth){ 171 | proofIdxArray = new Array(treeDepth); 172 | proofPos = module.exports.idxToBinaryPos(leafIdx, treeDepth); 173 | // console.log('proofPos', proofPos) 174 | 175 | if (leafIdx % 2 == 0){ 176 | proofIdxArray[0] = leafIdx + 1; 177 | } else { 178 | proofIdxArray[0] = leafIdx - 1; 179 | } 180 | 181 | for (i = 1; i < treeDepth; i++){ 182 | if (proofPos[i] == 1){ 183 | proofIdxArray[i] = Math.floor(proofIdxArray[i - 1] / 2) - 1; 184 | } else { 185 | proofIdxArray[i] = Math.floor(proofIdxArray[i - 1] / 2) + 1; 186 | } 187 | } 188 | 189 | return(proofIdxArray) 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /4_single_tx/generate_circuit_input.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const eddsa = require("../circomlib/src/eddsa.js"); 3 | const mimcjs = require("../circomlib/src/mimc7.js"); 4 | 5 | const alicePrvKey = Buffer.from('1'.toString().padStart(64,'0'), "hex"); 6 | const alicePubKey = eddsa.prv2pub(alicePrvKey); 7 | const bobPrvKey = Buffer.from('2'.toString().padStart(64,'0'), "hex"); 8 | const bobPubKey = eddsa.prv2pub(bobPrvKey); 9 | 10 | // accounts 11 | const Alice = { 12 | pubkey: alicePubKey, 13 | balance: 500 14 | } 15 | const aliceHash = mimcjs.multiHash( 16 | [Alice.pubkey[0], Alice.pubkey[1], Alice.balance] 17 | ); 18 | 19 | const Bob = { 20 | pubkey: bobPubKey, 21 | balance: 0 22 | } 23 | const bobHash = mimcjs.multiHash( 24 | [Bob.pubkey[0], Bob.pubkey[1], Bob.balance] 25 | ); 26 | 27 | const accounts_root = mimcjs.multiHash([aliceHash, bobHash]) 28 | 29 | // transaction 30 | const tx = { 31 | from: Alice.pubkey, 32 | to: Bob.pubkey, 33 | amount: 500 34 | } 35 | 36 | // Alice sign tx 37 | const txHash = mimcjs.multiHash( 38 | [tx.from[0], tx.from[1], tx.to[0], tx.to[1], tx.amount] 39 | ); 40 | const signature = eddsa.signMiMC(alicePrvKey, txHash) 41 | 42 | // update Alice account 43 | const newAlice = { 44 | pubkey: alicePubKey, 45 | balance: 0 46 | } 47 | const newAliceHash = mimcjs.multiHash( 48 | [newAlice.pubkey[0], newAlice.pubkey[1], newAlice.balance] 49 | ); 50 | 51 | // update intermediate root 52 | const intermediate_root = mimcjs.multiHash([newAliceHash, bobHash]) 53 | 54 | // update Bob account 55 | const newBob = { 56 | pubkey: bobPubKey, 57 | balance: 500 58 | } 59 | const newBobHash = mimcjs.multiHash( 60 | [newBob.pubkey[0], newBob.pubkey[1], newBob.balance] 61 | ); 62 | 63 | // update final root 64 | const final_root = mimcjs.multiHash([newAliceHash, newBobHash]) 65 | 66 | 67 | const inputs = { 68 | "accounts_root": accounts_root.toString(), 69 | "intermediate_root": intermediate_root.toString(), 70 | "accounts_pubkeys": [ 71 | [Alice.pubkey[0].toString(), Alice.pubkey[1].toString()], 72 | [Bob.pubkey[0].toString(), Bob.pubkey[1].toString()] 73 | ], 74 | "accounts_balances": [Alice.balance, Bob.balance], 75 | "sender_pubkey": [Alice.pubkey[0].toString(), Alice.pubkey[1].toString()], 76 | "sender_balance": Alice.balance, 77 | "receiver_pubkey": [Bob.pubkey[0].toString(), Bob.pubkey[1].toString()], 78 | "receiver_balance": Bob.balance, 79 | "amount": tx.amount, 80 | "signature_R8x": signature['R8'][0].toString(), 81 | "signature_R8y": signature['R8'][1].toString(), 82 | "signature_S": signature['S'].toString(), 83 | "sender_proof": [bobHash.toString()], 84 | "sender_proof_pos": [1], 85 | "receiver_proof": [newAliceHash.toString()], 86 | "receiver_proof_pos": [0] 87 | } 88 | 89 | fs.writeFileSync( 90 | "./input.json", 91 | JSON.stringify(inputs), 92 | "utf-8" 93 | ); -------------------------------------------------------------------------------- /4_single_tx/get_merkle_root.circom: -------------------------------------------------------------------------------- 1 | include "../circomlib/circuits/mimc.circom"; 2 | 3 | template GetMerkleRoot(k){ 4 | // k is depth of tree 5 | 6 | signal input leaf; 7 | signal input paths2_root[k]; 8 | signal input paths2_root_pos[k]; 9 | 10 | signal output out; 11 | 12 | // hash of first two entries in tx Merkle proof 13 | component merkle_root[k]; 14 | merkle_root[0] = MultiMiMC7(2,91); 15 | merkle_root[0].in[0] <== paths2_root[0] - paths2_root_pos[0]* (paths2_root[0] - leaf); 16 | merkle_root[0].in[1] <== leaf - paths2_root_pos[0]* (leaf - paths2_root[0]); 17 | 18 | // hash of all other entries in tx Merkle proof 19 | for (var v = 1; v < k; v++){ 20 | merkle_root[v] = MultiMiMC7(2,91); 21 | merkle_root[v].in[0] <== paths2_root[v] - paths2_root_pos[v]* (paths2_root[v] - merkle_root[v-1].out); 22 | merkle_root[v].in[1] <== merkle_root[v-1].out - paths2_root_pos[v]* (merkle_root[v-1].out - paths2_root[v]); 23 | 24 | } 25 | 26 | // output computed Merkle root 27 | out <== merkle_root[k-1].out; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /4_single_tx/leaf_existence.circom: -------------------------------------------------------------------------------- 1 | include "./get_merkle_root.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | // checks for existence of leaf in tree of depth k 5 | 6 | template LeafExistence(k, l){ 7 | // k is depth of tree 8 | // l is length of preimage of leaf 9 | 10 | signal private input preimage[l]; 11 | signal input root; 12 | signal input paths2_root_pos[k]; 13 | signal input paths2_root[k]; 14 | 15 | component leaf = MultiMiMC7(l,91); 16 | for (var i = 0; i < l; i++){ 17 | leaf.in[i] <== preimage[i]; 18 | } 19 | 20 | component computed_root = GetMerkleRoot(k); 21 | computed_root.leaf <== leaf.out; 22 | 23 | for (var w = 0; w < k; w++){ 24 | computed_root.paths2_root[w] <== paths2_root[w]; 25 | computed_root.paths2_root_pos[w] <== paths2_root_pos[w]; 26 | } 27 | 28 | // equality constraint: input tx root === computed tx root 29 | root === computed_root.out; 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /4_single_tx/sample_circuit.circom: -------------------------------------------------------------------------------- 1 | include "./leaf_existence.circom"; 2 | include "./verify_eddsamimc.circom"; 3 | include "./get_merkle_root.circom"; 4 | include "../circomlib/circuits/mimc.circom"; 5 | 6 | template ProcessTx(k){ 7 | // k is depth of accounts tree 8 | 9 | // accounts tree info 10 | signal input accounts_root; 11 | signal private input intermediate_root; 12 | signal private input accounts_pubkeys[2**k, 2]; 13 | signal private input accounts_balances[2**k]; 14 | 15 | // transactions info 16 | signal private input sender_pubkey[2]; 17 | signal private input sender_balance; 18 | signal private input receiver_pubkey[2]; 19 | signal private input receiver_balance; 20 | signal private input amount; 21 | signal private input signature_R8x; 22 | signal private input signature_R8y; 23 | signal private input signature_S; 24 | signal private input sender_proof[k]; 25 | signal private input sender_proof_pos[k]; 26 | signal private input receiver_proof[k]; 27 | signal private input receiver_proof_pos[k]; 28 | 29 | // output 30 | signal output new_accounts_root; 31 | 32 | // verify sender account exists in accounts_root 33 | component senderExistence = LeafExistence(k, 3); 34 | senderExistence.preimage[0] <== sender_pubkey[0]; 35 | senderExistence.preimage[1] <== sender_pubkey[1]; 36 | senderExistence.preimage[2] <== sender_balance; 37 | senderExistence.root <== accounts_root; 38 | for (var i = 0; i < k; i++){ 39 | senderExistence.paths2_root_pos[i] <== sender_proof_pos[i]; 40 | senderExistence.paths2_root[i] <== sender_proof[i]; 41 | } 42 | 43 | // check that transaction was signed by sender 44 | component signatureCheck = VerifyEdDSAMiMC(5); 45 | signatureCheck.from_x <== sender_pubkey[0]; 46 | signatureCheck.from_y <== sender_pubkey[1]; 47 | signatureCheck.R8x <== signature_R8x; 48 | signatureCheck.R8y <== signature_R8y; 49 | signatureCheck.S <== signature_S; 50 | signatureCheck.preimage[0] <== sender_pubkey[0]; 51 | signatureCheck.preimage[1] <== sender_pubkey[1]; 52 | signatureCheck.preimage[2] <== receiver_pubkey[0]; 53 | signatureCheck.preimage[3] <== receiver_pubkey[1]; 54 | signatureCheck.preimage[4] <== amount; 55 | 56 | // debit sender account and hash new sender leaf 57 | component newSenderLeaf = MultiMiMC7(3,91){ 58 | newSenderLeaf.in[0] <== sender_pubkey[0]; 59 | newSenderLeaf.in[1] <== sender_pubkey[1]; 60 | newSenderLeaf.in[2] <== sender_balance - amount; 61 | } 62 | 63 | // update accounts_root 64 | component computed_intermediate_root = GetMerkleRoot(k); 65 | computed_intermediate_root.leaf <== newSenderLeaf.out; 66 | for (var i = 0; i < k; i++){ 67 | computed_intermediate_root.paths2_root_pos[i] <== sender_proof_pos[i]; 68 | computed_intermediate_root.paths2_root[i] <== sender_proof[i]; 69 | } 70 | 71 | // check that computed_intermediate_root.out === intermediate_root 72 | computed_intermediate_root.out === intermediate_root; 73 | 74 | // verify receiver account exists in intermediate_root 75 | component receiverExistence = LeafExistence(k, 3); 76 | receiverExistence.preimage[0] <== receiver_pubkey[0]; 77 | receiverExistence.preimage[1] <== receiver_pubkey[1]; 78 | receiverExistence.preimage[2] <== receiver_balance; 79 | receiverExistence.root <== intermediate_root; 80 | for (var i = 0; i < k; i++){ 81 | receiverExistence.paths2_root_pos[i] <== receiver_proof_pos[i]; 82 | receiverExistence.paths2_root[i] <== receiver_proof[i]; 83 | } 84 | 85 | // credit receiver account and hash new receiver leaf 86 | component newReceiverLeaf = MultiMiMC7(3,91){ 87 | newReceiverLeaf.in[0] <== receiver_pubkey[0]; 88 | newReceiverLeaf.in[1] <== receiver_pubkey[1]; 89 | newReceiverLeaf.in[2] <== receiver_balance + amount; 90 | } 91 | 92 | // update accounts_root 93 | component computed_final_root = GetMerkleRoot(k); 94 | computed_final_root.leaf <== newReceiverLeaf.out; 95 | for (var i = 0; i < k; i++){ 96 | computed_final_root.paths2_root_pos[i] <== receiver_proof_pos[i]; 97 | computed_final_root.paths2_root[i] <== receiver_proof[i]; 98 | } 99 | 100 | // output final accounts_root 101 | new_accounts_root <== computed_final_root.out; 102 | } 103 | 104 | component main = ProcessTx(1); -------------------------------------------------------------------------------- /4_single_tx/stringifybigint.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 0kims association. 3 | 4 | This file is part of snarkjs. 5 | 6 | snarkjs is a free software: you can redistribute it and/or 7 | modify it under the terms of the GNU General Public License as published by the 8 | Free Software Foundation, either version 3 of the License, or (at your option) 9 | any later version. 10 | 11 | snarkjs is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | more details. 15 | 16 | You should have received a copy of the GNU General Public License along with 17 | snarkjs. If not, see . 18 | */ 19 | 20 | const bigInt = require("snarkjs").bigInt; 21 | 22 | module.exports.stringifyBigInts = stringifyBigInts; 23 | module.exports.unstringifyBigInts = unstringifyBigInts; 24 | 25 | function stringifyBigInts(o) { 26 | if ((typeof(o) == "bigint") || (o instanceof bigInt)) { 27 | return o.toString(10); 28 | } else if (Array.isArray(o)) { 29 | return o.map(stringifyBigInts); 30 | } else if (typeof o == "object") { 31 | const res = {}; 32 | for (let k in o) { 33 | res[k] = stringifyBigInts(o[k]); 34 | } 35 | return res; 36 | } else { 37 | return o; 38 | } 39 | } 40 | 41 | function unstringifyBigInts(o) { 42 | if ((typeof(o) == "string") && (/^[0-9]+$/.test(o) )) { 43 | return bigInt(o); 44 | } else if (Array.isArray(o)) { 45 | return o.map(unstringifyBigInts); 46 | } else if (typeof o == "object") { 47 | const res = {}; 48 | for (let k in o) { 49 | res[k] = unstringifyBigInts(o[k]); 50 | } 51 | return res; 52 | } else { 53 | return o; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /4_single_tx/verify_eddsamimc.circom: -------------------------------------------------------------------------------- 1 | include "../circomlib/circuits/eddsamimc.circom"; 2 | include "../circomlib/circuits/mimc.circom"; 3 | 4 | template VerifyEdDSAMiMC(k) { 5 | signal input from_x; 6 | signal input from_y; 7 | signal input R8x; 8 | signal input R8y; 9 | signal input S; 10 | signal private input preimage[k]; 11 | 12 | component M = MultiMiMC7(k,91); 13 | for (var i = 0; i < k; i++){ 14 | M.in[i] <== preimage[i]; 15 | } 16 | 17 | component verifier = EdDSAMiMCVerifier(); 18 | verifier.enabled <== 1; 19 | verifier.Ax <== from_x; 20 | verifier.Ay <== from_y; 21 | verifier.R8x <== R8x; 22 | verifier.R8y <== R8y; 23 | verifier.S <== S; 24 | verifier.M <== M.out; 25 | } -------------------------------------------------------------------------------- /5_comparator/bad_force_equal_if_enabled.circom: -------------------------------------------------------------------------------- 1 | 2 | template BadForceEqualIfEnabled() { 3 | signal input enabled; 4 | signal input in[2]; 5 | 6 | if (enabled) { 7 | in[1] === in[0] 8 | } 9 | } 10 | 11 | component main = BadForceEqualIfEnabled() 12 | -------------------------------------------------------------------------------- /5_comparator/circuit.circom: -------------------------------------------------------------------------------- 1 | 2 | template BadForceEqualIfEnabled() { 3 | signal input enabled; 4 | signal input in[2]; 5 | 6 | if (enabled) { 7 | in[1] === in[0] 8 | } 9 | } 10 | 11 | component main = BadForceEqualIfEnabled() 12 | -------------------------------------------------------------------------------- /5_comparator/force_equal_if_enabled.circom: -------------------------------------------------------------------------------- 1 | include "iszero.circom"; 2 | 3 | template ForceEqualIfEnabled() { 4 | signal input enabled; 5 | signal input in[2]; 6 | 7 | component isz = IsZero(); 8 | 9 | in[1] - in[0] ==> isz.in; 10 | 11 | (1 - isz.out)*enabled === 0; 12 | } 13 | 14 | component main = ForceEqualIfEnabled() 15 | -------------------------------------------------------------------------------- /5_comparator/iszero.circom: -------------------------------------------------------------------------------- 1 | template IsZero() { 2 | signal input in; 3 | signal output out; 4 | 5 | signal inv; 6 | 7 | inv <-- in!=0 ? 1/in : 0; 8 | 9 | out <== -in*inv +1; 10 | in*out === 0; 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zk Rollup Tutorial 2 | 3 | Head over to : https://keen-noyce-c29dfa.netlify.com/#0 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollupnc_tutorial", 3 | "version": "1.0.0", 4 | "description": "This is a [circom](https://github.com/iden3/circom) and [snarkjs](https://github.com/iden3/snarkjs) / [websnark](https://github.com/iden3/websnark) tutorial, using [RollupNC](https://github.com/barryWhiteHat/RollupNC) as an example. It takes you through how to build RollupNC, circuit by circuit, with generated inputs to test the circuits out.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/therealyingtong/RollupNC_tutorial.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/therealyingtong/RollupNC_tutorial/issues" 17 | }, 18 | "homepage": "https://github.com/therealyingtong/RollupNC_tutorial#readme", 19 | "dependencies": { 20 | "circom": "0.0.28", 21 | "markdown-toc": "^1.2.0", 22 | "snarkjs": "^0.1.13" 23 | } 24 | } 25 | --------------------------------------------------------------------------------