├── .gitattributes ├── .gitignore ├── .npmignore ├── .soliumignore ├── .soliumrc.json ├── LICENSE ├── README.md ├── circuits ├── impls │ ├── zk_transaction_1_1.circom │ ├── zk_transaction_1_2.circom │ ├── zk_transaction_1_3.circom │ ├── zk_transaction_1_4.circom │ ├── zk_transaction_2_1.circom │ ├── zk_transaction_2_2.circom │ ├── zk_transaction_2_3.circom │ ├── zk_transaction_2_4.circom │ ├── zk_transaction_3_1.circom │ ├── zk_transaction_3_2.circom │ ├── zk_transaction_3_3.circom │ ├── zk_transaction_3_4.circom │ ├── zk_transaction_4_1.circom │ ├── zk_transaction_4_2.circom │ ├── zk_transaction_4_3.circom │ └── zk_transaction_4_4.circom ├── lib │ ├── atomic_swap_mpc.circom │ ├── erc20_sum.circom │ ├── inclusion_proof.circom │ ├── non_fungible.circom │ ├── note_hash.circom │ ├── nullifier.circom │ ├── ownership_proof.circom │ ├── utils.circom │ └── zk_transaction.circom └── tester │ ├── erc20_sum.test.circom │ ├── inclusion_proof.test.circom │ ├── non_fungible.test.circom │ ├── note_hash.test.circom │ ├── nullifier.test.circom │ ├── ownership_proof.test.circom │ ├── zk_transaction_1_2.test.circom │ ├── zk_transaction_3_1.test.circom │ └── zk_transaction_3_3.test.circom ├── contracts ├── Layer2Controller.sol ├── SetupWizard.sol ├── ZkOptimisticRollUp.sol ├── controllers │ ├── Challengeable.sol │ ├── Coordinatable.sol │ ├── Migratable.sol │ ├── RollUpable.sol │ ├── UserInteractable.sol │ └── challenges │ │ ├── DepositChallenge.sol │ │ ├── HeaderChallenge.sol │ │ ├── MigrationChallenge.sol │ │ ├── RollUpChallenge.sol │ │ └── TxChallenge.sol ├── interfaces │ ├── ICoordinatable.sol │ ├── IDepositChallenge.sol │ ├── IHeaderChallenge.sol │ ├── IMigratable.sol │ ├── IMigrationChallenge.sol │ ├── IRollUpChallenge.sol │ ├── IRollUpable.sol │ ├── ISetupWizard.sol │ ├── ITxChallenge.sol │ └── IUserInteractable.sol ├── libraries │ ├── Asset.sol │ ├── Deserializer.sol │ ├── Hash.sol │ ├── Pairing.sol │ ├── SNARKs.sol │ └── Types.sol ├── storage │ ├── Configurated.sol │ └── Layer2.sol └── utils │ ├── IERC20.sol │ ├── IERC721.sol │ ├── Migrations.sol │ └── test_helpers │ └── DeserializerHelper.sol ├── data ├── tree1 │ ├── 000004.sst │ ├── 000007.sst │ ├── 000010.sst │ ├── CURRENT │ ├── IDENTITY │ ├── LOCK │ ├── MANIFEST-000011 │ ├── OPTIONS-000011 │ └── OPTIONS-000014 └── txs │ ├── zk_tx_1.tx │ ├── zk_tx_2_1.tx │ ├── zk_tx_2_2.tx │ ├── zk_tx_3.tx │ └── zk_tx_4.tx ├── migrations ├── 10_deposit_challenge.js ├── 11_migration_challenge.js ├── 12_migratable.js ├── 13_zk_optimistic_rollup.js ├── 14_setup_wizard.js ├── 1_initial_migration.js ├── 2_poseidon.js ├── 3_mimc.js ├── 4_erc20.js ├── 5_ui.js ├── 6_rollup.js ├── 7_rollup_challenge.js ├── 8_header_challenge.js └── 9_tx_challenge.js ├── package.json ├── script ├── compile_circuits.sh ├── compile_circuits_for_test.sh ├── snark_setup.sh ├── snark_setup_for_test.sh └── testEnvSetUp.js ├── src ├── cli.ts ├── coordinator.ts ├── eddsa.ts ├── field.ts ├── index.ts ├── jubjub.ts ├── synchronizer.ts ├── tokens.ts ├── transaction.ts ├── tree.ts ├── types │ ├── jest │ │ └── index.d.ts │ ├── mocha │ │ └── index.d.ts │ └── truffle-contracts │ │ ├── index.d.ts │ │ └── merge.d.ts ├── utils.ts ├── utxo.ts ├── wallet.ts ├── zk_transaction.ts └── zk_wizard.ts ├── test ├── dataset │ └── dataset.ts ├── solidity │ ├── Deserializer.soltest.ts │ └── Lib.soltest.ts └── ts │ ├── coordinator.test.ts │ ├── transaction.test.ts │ ├── utxo.test.ts │ └── zk_wizard.test.ts ├── truffle-config.js ├── tsconfig.json ├── utils ├── TestERC20.sol ├── erc20Generator.js ├── mimcGenerator.js └── poseidonGenerator.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | src -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "indentation": ["error", 4], 7 | "linebreak-style": ["error", "unix"], 8 | "security/no-inline-assembly": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] zk-optimistic-rollup 2 | 3 | [see github.com/zkopru-network/zkopru](https://github.com/zkopru-network/zkopru) 4 | -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_1_1.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 1, 1); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_1_2.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 1, 2); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_1_3.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 1, 3); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_1_4.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 1, 4); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_2_1.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 2, 1); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_2_2.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 2, 2); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_2_3.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 2, 3); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_2_4.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 2, 4); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_3_1.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 1); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_3_2.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 2); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_3_3.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 3); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_3_4.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 4); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_4_1.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 4, 1); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_4_2.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 4, 2); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_4_3.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 4, 3); -------------------------------------------------------------------------------- /circuits/impls/zk_transaction_4_4.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 4, 4); -------------------------------------------------------------------------------- /circuits/lib/atomic_swap_mpc.circom: -------------------------------------------------------------------------------- 1 | include "./utils.circom"; 2 | include "../../node_modules/circomlib/circuits/comparators.circom"; 3 | include "../../node_modules/circomlib/circuits/escalarmul.circom"; 4 | 5 | template AtomicSwapMPC() { 6 | signal input my_mpc_salt; 7 | signal input order[3]; 8 | signal input giving_token_type; 9 | signal input giving_token_addr; 10 | signal input giving_note_salt; 11 | signal input counterpart_pk[2]; 12 | signal input counterpart_computation[2]; /// counterpart_computation = g^(counterpart_mpc_salt * receiving_token_type * receiving_token_addr * receiving_note_salt * my_pk) 13 | signal output out[2]; 14 | 15 | /// type 0: no-swap / 1: ETH / 2: ERC20 / 3: ERC721 16 | component correct_type = LessThan(3); 17 | correct_type.in[0] <== giving_token_type; 18 | correct_type.in[1] <== 4; 19 | correct_type.out === 1; 20 | 21 | /// Order data or token addr can include some zero values. 22 | /// If then, multiply (JUBJUB prime - 1) instead of zero. 23 | component filter = ZeroToJubjubPrime(4); 24 | filter.in[0] <== order[0]; 25 | filter.in[1] <== order[1]; 26 | filter.in[2] <== order[2]; 27 | filter.in[3] <== giving_token_addr; 28 | 29 | /// Calculate scalar multiplication of the input values and the counterpart's public salt 30 | var BASE8 = [ 31 | 5299619240641551281634865583518297030282874472190772894086521144482721001553, 32 | 16950150798460657717958625567821834550301663161624707787222815936182638968203 33 | ]; 34 | component mpc = EscalarMul(9, BASE8); 35 | mpc.inp[0] <== counterpart_computation[0]; 36 | mpc.inp[1] <== counterpart_computation[1]; 37 | mpc.in[0] <== my_mpc_salt; 38 | mpc.in[1] <== filter.out[0] /// order[0]; 39 | mpc.in[2] <== filter.out[1] /// order[1]; 40 | mpc.in[3] <== filter.out[2] /// order[2]; 41 | mpc.in[4] <== giving_token_type; 42 | mpc.in[5] <== filter.out[3] /// giving_token_addr; 43 | mpc.in[6] <== giving_note_salt; 44 | mpc.in[7] <== counterpart_pk[0]; 45 | mpc.in[8] <== counterpart_pk[1]; 46 | 47 | // Return outputs 48 | mpc.out[0] ==> out[0]; 49 | mpc.out[1] ==> out[1]; 50 | } 51 | -------------------------------------------------------------------------------- /circuits/lib/erc20_sum.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/babyjub.circom"; 2 | include "./utils.circom"; 3 | 4 | template ERC20Sum(n) { 5 | signal input addr; 6 | signal input note_addr[n]; 7 | signal input note_amount[n]; 8 | signal output out; 9 | 10 | component sum[n]; 11 | signal intermediates[n+1]; 12 | intermediates[0] <== 0; 13 | for(var i = 0; i < n; i++) { 14 | sum[i] = IfElseThen(1); 15 | sum[i].obj1[0] <== addr; 16 | sum[i].obj2[0] <== note_addr[i]; 17 | sum[i].if_v <== intermediates[i] + note_amount[i]; 18 | sum[i].else_v <== intermediates[i]; 19 | sum[i].out ==> intermediates[i+1]; 20 | } 21 | out <== intermediates[n]; 22 | } -------------------------------------------------------------------------------- /circuits/lib/inclusion_proof.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/poseidon.circom"; 2 | include "../../node_modules/circomlib/circuits/bitify.circom"; 3 | include "../../node_modules/circomlib/circuits/mux1.circom"; 4 | 5 | template BranchNode() { 6 | signal input left; 7 | signal input right; 8 | signal output parent; 9 | 10 | component hasher = Poseidon(2, 3, 8, 57); // Constant 11 | hasher.inputs[0] <== left; 12 | hasher.inputs[1] <== right; 13 | 14 | parent <== hasher.out; 15 | } 16 | 17 | template InclusionProof(depth) { 18 | // Signal definitions 19 | /** Public inputs */ 20 | signal input root; 21 | /** Private inputs */ 22 | signal private input leaf; 23 | signal private input path; 24 | signal private input siblings[depth]; 25 | 26 | component path_bits = Num2Bits(depth); 27 | path_bits.in <== path; 28 | 29 | // Constraint definition 30 | signal nodes[depth + 1]; 31 | component branch_nodes[depth]; 32 | nodes[0] <== leaf; 33 | component left[depth]; 34 | component right[depth]; 35 | for (var level = 0; level < depth; level++) { 36 | branch_nodes[level] = BranchNode(); 37 | // If the bitified path_bits is 0, the branch node has a left sibling 38 | left[level] = Mux1(); 39 | left[level].c[0] <== nodes[level]; 40 | left[level].c[1] <== siblings[level]; 41 | left[level].s <== path_bits.out[level]; 42 | right[level] = Mux1(); 43 | right[level].c[0] <== siblings[level]; 44 | right[level].c[1] <== nodes[level]; 45 | right[level].s <== path_bits.out[level]; 46 | 47 | branch_nodes[level].left <== left[level].out 48 | branch_nodes[level].right <== right[level].out 49 | nodes[level+1] <== branch_nodes[level].parent; 50 | } 51 | nodes[depth] === root; 52 | } -------------------------------------------------------------------------------- /circuits/lib/non_fungible.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/babyjub.circom"; 2 | include "../../node_modules/circomlib/circuits/comparators.circom"; 3 | include "./utils.circom"; 4 | 5 | template CountSameNFT(n) { 6 | signal input addr; 7 | signal input nft; 8 | signal input comp_addr[n]; 9 | signal input comp_nft[n]; 10 | signal output out; 11 | 12 | component counter[n]; 13 | component nft_exist[n]; 14 | signal intermediates[n+1]; 15 | intermediates[0] <== 0; 16 | for(var i = 0; i < n; i++) { 17 | nft_exist[i] = IsZero(); 18 | nft_exist[i].in <== comp_nft[i]; 19 | 20 | counter[i] = IfElseThen(3); 21 | counter[i].obj1[0] <== addr; 22 | counter[i].obj2[0] <== comp_addr[i]; 23 | counter[i].obj1[1] <== nft; 24 | counter[i].obj2[1] <== comp_nft[i]; 25 | counter[i].obj1[2] <== nft_exist[i].out; // only count the non-zero nfts 26 | counter[i].obj2[2] <== 0; 27 | counter[i].if_v <== intermediates[i] + 1; 28 | counter[i].else_v <== intermediates[i]; 29 | counter[i].out ==> intermediates[i+1]; 30 | } 31 | 32 | out <== intermediates[n]; 33 | } 34 | 35 | template NonFungible(n_i, n_o) { 36 | signal input prev_token_addr[n_i]; 37 | signal input prev_token_nft[n_i]; 38 | signal input post_token_addr[n_o]; 39 | signal input post_token_nft[n_o]; 40 | 41 | component token_count_1[n_i]; 42 | component expected_count_1[n_i]; 43 | for(var i = 0; i < n_i; i++) { 44 | expected_count_1[i] = IfElseThen(1); 45 | expected_count_1[i].obj1[0] <== prev_token_nft[i]; 46 | expected_count_1[i].obj2[0] <== 0; 47 | expected_count_1[i].if_v <== 0; 48 | expected_count_1[i].else_v <== 1; 49 | 50 | token_count_1[i] = CountSameNFT(n_o); 51 | token_count_1[i].addr <== prev_token_addr[i]; 52 | token_count_1[i].nft <== prev_token_nft[i]; 53 | for(var j = 0; j < n_o; j++) { 54 | token_count_1[i].comp_addr[j] <== post_token_addr[j]; 55 | token_count_1[i].comp_nft[j] <== post_token_nft[j]; 56 | } 57 | token_count_1[i].out === expected_count_1[i].out; 58 | } 59 | 60 | component token_count_2[n_o]; 61 | component expected_count_2[n_o]; 62 | for(var i = 0; i < n_o; i++) { 63 | expected_count_2[i] = IfElseThen(1); 64 | expected_count_2[i].obj1[0] <== post_token_nft[i]; 65 | expected_count_2[i].obj2[0] <== 0; 66 | expected_count_2[i].if_v <== 0; 67 | expected_count_2[i].else_v <== 1; 68 | token_count_2[i] = CountSameNFT(n_i); 69 | token_count_2[i].addr <== post_token_addr[i]; 70 | token_count_2[i].nft <== post_token_nft[i]; 71 | for(var j = 0; j < n_i; j++) { 72 | token_count_2[i].comp_addr[j] <== prev_token_addr[j]; 73 | token_count_2[i].comp_nft[j] <== prev_token_nft[j]; 74 | } 75 | token_count_2[i].out === expected_count_2[i].out; 76 | } 77 | } -------------------------------------------------------------------------------- /circuits/lib/note_hash.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/poseidon.circom"; 2 | 3 | template NoteHash() { 4 | signal input eth; 5 | signal input pubkey_x; 6 | signal input pubkey_y; 7 | signal input salt; 8 | signal input token_addr; 9 | signal input erc20; 10 | signal input nft; 11 | signal output out; 12 | 13 | component intermediate_hash = Poseidon(4, 6, 8, 57); 14 | intermediate_hash.inputs[0] <== eth; 15 | intermediate_hash.inputs[1] <== pubkey_x; 16 | intermediate_hash.inputs[2] <== pubkey_y; 17 | intermediate_hash.inputs[3] <== salt; 18 | component final_result = Poseidon(4, 6, 8, 57); 19 | final_result.inputs[0] <== intermediate_hash.out; 20 | final_result.inputs[1] <== token_addr; 21 | final_result.inputs[2] <== erc20; 22 | final_result.inputs[3] <== nft; 23 | final_result.out ==> out; 24 | } -------------------------------------------------------------------------------- /circuits/lib/nullifier.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/poseidon.circom"; 2 | 3 | template Nullifier() { 4 | signal input note_hash; 5 | signal input note_salt; 6 | signal output out; 7 | 8 | component hash = Poseidon(2, 6, 8, 57); // Constant 9 | hash.inputs[0] <== note_hash; 10 | hash.inputs[1] <== note_salt; 11 | hash.out ==> out; 12 | } 13 | -------------------------------------------------------------------------------- /circuits/lib/ownership_proof.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/eddsaposeidon.circom"; 2 | 3 | template OwnershipProof() { 4 | // Signal definitions 5 | /** Private inputs */ 6 | signal private input note; 7 | signal private input pub_key[2]; 8 | signal private input sig[3]; 9 | component eddsa = EdDSAPoseidonVerifier() 10 | eddsa.enabled <== 1; 11 | eddsa.M <== note; 12 | eddsa.Ax <== pub_key[0]sig[0]; 13 | eddsa.Ay <== pub_key[1]sig[1]; 14 | eddsa.R8x <== sig[0]; 15 | eddsa.R8y <== sig[1]; 16 | eddsa.S <== sig[2]; 17 | } 18 | -------------------------------------------------------------------------------- /circuits/lib/utils.circom: -------------------------------------------------------------------------------- 1 | include "../../node_modules/circomlib/circuits/bitify.circom"; 2 | include "../../node_modules/circomlib/circuits/mux1.circom"; 3 | include "../../node_modules/circomlib/circuits/comparators.circom"; 4 | 5 | template NFTtoBits(size) { 6 | signal input nft; 7 | signal output out[size]; 8 | component is_zero = IsZero(); 9 | is_zero.in <== nft; 10 | component mux = Mux1(); 11 | mux.s <== is_zero.out; 12 | mux.c[0] <== nft; 13 | mux.c[1] <== 1; /// means skipping the multiplication 14 | component bits = Num2Bits(size); 15 | bits.in <== mux.out; 16 | for (var i = 0; i < size; i++) { 17 | out[i] <== bits.out[i]; 18 | } 19 | } 20 | 21 | 22 | template IfElseThen(n) { 23 | signal input obj1[n]; 24 | signal input obj2[n]; 25 | signal input if_v; 26 | signal input else_v; 27 | signal output out; 28 | component comparators[n]; 29 | signal result[n + 1]; 30 | result[0] <== 1; 31 | for(var i = 0; i < n; i++) { 32 | comparators[i] = IsEqual(); 33 | comparators[i].in[0] <== obj1[i]; 34 | comparators[i].in[1] <== obj2[i]; 35 | result[i + 1] <== result[i] * comparators[i].out; 36 | } 37 | component mux = Mux1(); 38 | mux.c[1] <== if_v; 39 | mux.c[0] <== else_v; 40 | mux.s <== result[n]; 41 | out <== mux.out; 42 | } 43 | 44 | template ZeroToJubjubPrime(n) { 45 | signal input in[n]; 46 | signal output out[n]; 47 | 48 | component filter[n]; 49 | component in_range[n]; 50 | var prime_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 51 | 52 | for(var i = 0; i < n; i++) { 53 | in_range[i] = LessThan(254); 54 | in_range[i].in[0] <== in[i]; 55 | in_range[i].in[1] <== prime_field - 1; 56 | in_range[i].out === 1; 57 | 58 | filter[i] = IfElseThen(1); 59 | filter[i].obj1[0] <== in[i]; 60 | filter[i].obj2[0] <== 0; 61 | filter[i].if_v <== prime_field - 1; 62 | filter[i].else_v <== in[i]; 63 | out[i] <== filter[i].out; 64 | } 65 | } -------------------------------------------------------------------------------- /circuits/tester/erc20_sum.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/erc20_sum.circom"; 2 | 3 | component main = ERC20Sum(3); 4 | -------------------------------------------------------------------------------- /circuits/tester/inclusion_proof.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/inclusion_proof.circom"; 2 | 3 | component main = InclusionProof(31); 4 | -------------------------------------------------------------------------------- /circuits/tester/non_fungible.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/non_fungible.circom"; 2 | 3 | component main = NonFungible(3, 4); 4 | -------------------------------------------------------------------------------- /circuits/tester/note_hash.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/note_hash.circom"; 2 | 3 | component main = NoteHash(); 4 | -------------------------------------------------------------------------------- /circuits/tester/nullifier.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/nullifier.circom"; 2 | 3 | component main = Nullifier(); 4 | -------------------------------------------------------------------------------- /circuits/tester/ownership_proof.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/ownership_proof.circom"; 2 | 3 | component main = OwnershipProof(); 4 | -------------------------------------------------------------------------------- /circuits/tester/zk_transaction_1_2.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 1, 2); 4 | -------------------------------------------------------------------------------- /circuits/tester/zk_transaction_3_1.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 1); 4 | -------------------------------------------------------------------------------- /circuits/tester/zk_transaction_3_3.test.circom: -------------------------------------------------------------------------------- 1 | include "../lib/zk_transaction.circom"; 2 | 3 | component main = ZkTransaction(31, 3, 3); 4 | -------------------------------------------------------------------------------- /contracts/Layer2Controller.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Coordinatable } from "./controllers/Coordinatable.sol"; 4 | import { SNARKsVerifier } from "./libraries/SNARKs.sol"; 5 | import { Pairing } from "./libraries/Pairing.sol"; 6 | import { IUserInteractable } from "./interfaces/IUserInteractable.sol"; 7 | import { IRollUpable } from "./interfaces/IRollUpable.sol"; 8 | import { IMigratable } from "./interfaces/IMigratable.sol"; 9 | import { IDepositChallenge } from "./interfaces/IDepositChallenge.sol"; 10 | import { IHeaderChallenge } from "./interfaces/IHeaderChallenge.sol"; 11 | import { IMigrationChallenge } from "./interfaces/IMigrationChallenge.sol"; 12 | import { IRollUpChallenge } from "./interfaces/IRollUpChallenge.sol"; 13 | import { ITxChallenge } from "./interfaces/ITxChallenge.sol"; 14 | 15 | 16 | contract Layer2Controller is Coordinatable { 17 | /** Addresses where to execute the given function call */ 18 | mapping(bytes4=>address) public proxied; 19 | 20 | /** 21 | * @notice This proxies supports the following interfaces 22 | * - ICoordinatable.sol 23 | * - IUserInteractable.sol 24 | * - IRollUpable.sol 25 | * - IChallengeable.sol 26 | * - IMigratable.sol 27 | */ 28 | fallback () external payable { 29 | bytes4 sig = abi.decode(msg.data[:4], (bytes4)); 30 | address addr = proxied[sig]; 31 | assembly { 32 | let freememstart := mload(0x40) 33 | calldatacopy(freememstart, 0, calldatasize()) 34 | let success := delegatecall(not(0), addr, freememstart, calldatasize(), freememstart, 32) 35 | switch success 36 | case 0 { revert(freememstart, 32) } 37 | default { return(freememstart, 32) } 38 | } 39 | } 40 | 41 | /** 42 | * @dev See Coordinatable.sol's register() function 43 | */ 44 | receive() external payable { 45 | Coordinatable.register(); 46 | } 47 | 48 | function _connectUserInteractable(address addr) internal { 49 | _connect(addr, IUserInteractable(0).deposit.selector); 50 | _connect(addr, IUserInteractable(0).withdraw.selector); 51 | _connect(addr, IUserInteractable(0).withdrawUsingSignature.selector); 52 | } 53 | 54 | function _connectRollUpable(address addr) internal { 55 | _connect(addr, IRollUpable(0).newProofOfUTXORollUp.selector); 56 | _connect(addr, IRollUpable(0).newProofOfNullifierRollUp.selector); 57 | _connect(addr, IRollUpable(0).newProofOfWithdrawalRollUp.selector); 58 | _connect(addr, IRollUpable(0).updateProofOfUTXORollUp.selector); 59 | _connect(addr, IRollUpable(0).updateProofOfNullifierRollUp.selector); 60 | _connect(addr, IRollUpable(0).updateProofOfWithdrawalRollUp.selector); 61 | } 62 | 63 | function _connectChallengeable( 64 | address depositChallenge, 65 | address headerChallenge, 66 | address migrationChallenge, 67 | address rollUpChallenge, 68 | address txChallenge 69 | ) internal virtual { 70 | _connect(depositChallenge, IDepositChallenge(0).challengeMassDeposit.selector); 71 | _connect(headerChallenge, IHeaderChallenge(0).challengeDepositRoot.selector); 72 | _connect(headerChallenge, IHeaderChallenge(0).challengeTxRoot.selector); 73 | _connect(headerChallenge, IHeaderChallenge(0).challengeMigrationRoot.selector); 74 | _connect(headerChallenge, IHeaderChallenge(0).challengeTotalFee.selector); 75 | _connect(migrationChallenge, IMigrationChallenge(0).challengeMassMigrationToMassDeposit.selector); 76 | _connect(migrationChallenge, IMigrationChallenge(0).challengeERC20Migration.selector); 77 | _connect(migrationChallenge, IMigrationChallenge(0).challengeERC721Migration.selector); 78 | _connect(rollUpChallenge, IRollUpChallenge(0).challengeUTXORollUp.selector); 79 | _connect(rollUpChallenge, IRollUpChallenge(0).challengeNullifierRollUp.selector); 80 | _connect(rollUpChallenge, IRollUpChallenge(0).challengeWithdrawalRollUp.selector); 81 | _connect(txChallenge, ITxChallenge(0).challengeInclusion.selector); 82 | _connect(txChallenge, ITxChallenge(0).challengeTransaction.selector); 83 | _connect(txChallenge, ITxChallenge(0).challengeUsedNullifier.selector); 84 | _connect(txChallenge, ITxChallenge(0).challengeDuplicatedNullifier.selector); 85 | _connect(txChallenge, ITxChallenge(0).isValidRef.selector); 86 | } 87 | 88 | function _connectMigratable(address addr) internal virtual { 89 | _connect(addr, IMigratable(0).migrateTo.selector); 90 | } 91 | 92 | function _connect(address to, bytes4 sig) internal { 93 | proxied[sig] = to; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /contracts/SetupWizard.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { ISetupWizard } from "./interfaces/ISetupWizard.sol"; 4 | import { Layer2 } from "./storage/Layer2.sol"; 5 | import { Layer2Controller } from "./Layer2Controller.sol"; 6 | import { SNARKsVerifier } from "./libraries/SNARKs.sol"; 7 | import { Types } from "./libraries/Types.sol"; 8 | import { Pairing } from "./libraries/Pairing.sol"; 9 | 10 | 11 | contract SetupWizard is Layer2Controller { 12 | address setupWizard; 13 | 14 | constructor(address _setupWizard) public { 15 | setupWizard = _setupWizard; 16 | } 17 | 18 | modifier onlySetupWizard { 19 | require(msg.sender == setupWizard, "Not authorized"); 20 | _; 21 | } 22 | 23 | function registerVk( 24 | uint8 numOfInputs, 25 | uint8 numOfOutputs, 26 | uint[2] memory alfa1, 27 | uint[2][2] memory beta2, 28 | uint[2][2] memory gamma2, 29 | uint[2][2] memory delta2, 30 | uint[2][] memory ic 31 | ) public onlySetupWizard { 32 | bytes32 txSig = Types.getSNARKsSignature(numOfInputs, numOfOutputs); 33 | SNARKsVerifier.VerifyingKey storage vk = Layer2.vks[txSig]; 34 | vk.alfa1 = Pairing.G1Point(alfa1[0], alfa1[1]); 35 | vk.beta2 = Pairing.G2Point(beta2[0], beta2[1]); 36 | vk.gamma2 = Pairing.G2Point(gamma2[0], gamma2[1]); 37 | vk.delta2 = Pairing.G2Point(delta2[0], delta2[1]); 38 | for (uint i = 0; i < ic.length; i++) { 39 | vk.ic.push(Pairing.G1Point(ic[i][0], ic[i][1])); 40 | } 41 | } 42 | 43 | function makeUserInteractable(address addr) public onlySetupWizard{ 44 | Layer2Controller._connectUserInteractable(addr); 45 | } 46 | 47 | function makeRollUpable(address addr) public onlySetupWizard{ 48 | Layer2Controller._connectRollUpable(addr); 49 | } 50 | 51 | function makeChallengeable( 52 | address depositChallenge, 53 | address headerChallenge, 54 | address migrationChallenge, 55 | address rollUpChallenge, 56 | address txChallenge 57 | ) public onlySetupWizard { 58 | Layer2Controller._connectChallengeable( 59 | depositChallenge, 60 | headerChallenge, 61 | migrationChallenge, 62 | rollUpChallenge, 63 | txChallenge 64 | ); 65 | } 66 | 67 | function makeMigratable(address addr) public onlySetupWizard { 68 | Layer2Controller._connectMigratable(addr); 69 | } 70 | 71 | function allowMigrants(address[] memory migrants) public onlySetupWizard { 72 | for (uint i = 0; i < migrants.length; i++) { 73 | Layer2.allowedMigrants[migrants[i]] = true; 74 | } 75 | } 76 | 77 | function init(bytes32 genesis) internal { 78 | Layer2.chain.latest = genesis; 79 | Layer2.chain.withdrawables.push(); /// withdrawables[0]: daily snapshot 80 | Layer2.chain.withdrawables.push(); /// withdrawables[0]: initial withdrawable tree 81 | } 82 | 83 | function completeSetup() public onlySetupWizard { 84 | delete setupWizard; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/ZkOptimisticRollUp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { SetupWizard } from "./SetupWizard.sol"; 4 | 5 | contract ZkOptimisticRollUp is SetupWizard { 6 | constructor(address _setupWizard) SetupWizard(_setupWizard) public { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contracts/controllers/Challengeable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../storage/Layer2.sol"; 4 | import { 5 | Challenge, 6 | Proposer, 7 | Proposal 8 | } from "../libraries/Types.sol"; 9 | 10 | contract Challengeable is Layer2 { 11 | /// Duplicated codes: solidity does not allow linear inheritance 12 | function _checkChallengeCondition(Proposal storage proposal) internal view { 13 | /// Check the optimistic roll up is in the challenge period 14 | require(proposal.challengeDue > block.number, "Out of challenge period"); 15 | /// Check it is already slashed 16 | require(!proposal.slashed, "Already slashed"); 17 | /// Check the optimistic rollup exists 18 | require(proposal.headerHash != bytes32(0), "Does not exist"); 19 | } 20 | 21 | function _forfeitAndReward(address proposerAddr, address challenger) internal { 22 | Proposer storage proposer = Layer2.chain.proposers[proposerAddr]; 23 | /// Reward 24 | uint challengeReward = proposer.stake * 2 / 3; 25 | payable(challenger).transfer(challengeReward); 26 | /// Forfeit 27 | proposer.stake = 0; 28 | proposer.reward = 0; 29 | /// Delete proposer 30 | delete Layer2.chain.proposers[proposerAddr]; 31 | } 32 | 33 | function _execute(Challenge memory result) internal { 34 | require(result.slash, result.message); 35 | 36 | Proposal storage proposal = Layer2.chain.proposals[result.proposalId]; 37 | /// Check basic challenge conditions 38 | _checkChallengeCondition(proposal); 39 | /// Since the challenge satisfies the given conditions, slash the optimistic rollup proposer 40 | proposal.slashed = true; /// Record it as slashed; 41 | _forfeitAndReward(result.proposer, msg.sender); 42 | /// TODO log message 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/controllers/Coordinatable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../storage/Layer2.sol"; 4 | import { 5 | Proposer, 6 | Blockchain, 7 | Block, 8 | Proposal, 9 | Finalization, 10 | MassDeposit, 11 | Withdrawable, 12 | Types 13 | } from "../libraries/Types.sol"; 14 | import { Deserializer } from "../libraries/Deserializer.sol"; 15 | 16 | 17 | contract Coordinatable is Layer2 { 18 | using Types for *; 19 | 20 | event NewProposal(bytes32 submissionId); 21 | event Finalized(bytes32 submissionId); 22 | event MassDepositCommit(uint id, bytes32 merged, uint256 fee); 23 | 24 | function register() public payable { 25 | require(msg.value >= MINIMUM_STAKE, "Should stake more than minimum amount of ETH"); 26 | Proposer storage proposer = Layer2.chain.proposers[msg.sender]; 27 | proposer.stake += msg.value; 28 | } 29 | 30 | function deregister() public { 31 | address payable proposerAddr = msg.sender; 32 | Proposer storage proposer = Layer2.chain.proposers[proposerAddr]; 33 | require(proposer.exitAllowance <= block.number, "Still in the challenge period"); 34 | /// Withdraw stake 35 | proposerAddr.transfer(proposer.stake); 36 | /// Withdraw reward 37 | payable(proposerAddr).transfer(proposer.reward); 38 | /// Delete proposer 39 | delete Layer2.chain.proposers[proposerAddr]; 40 | } 41 | 42 | function propose(bytes memory) public { 43 | Block memory _block = Deserializer.blockFromCalldataAt(0); 44 | /// The message sender address should be same with the proposer address 45 | require(_block.header.proposer == msg.sender, "Coordinator account is different with the message sender"); 46 | Proposer storage proposer = Layer2.chain.proposers[msg.sender]; 47 | /// Check permission 48 | require(isProposable(msg.sender), "Not allowed to propose"); 49 | /// Duplicated proposal is not allowed 50 | require(Layer2.chain.proposals[_block.submissionId].headerHash == bytes32(0), "Already submitted"); 51 | /** LEGACY 52 | /// Do not exceed maximum challenging cost 53 | require(_block.maxChallengeCost() < CHALLENGE_LIMIT, "Its challenge cost exceeds the limit"); 54 | */ 55 | /// Save opru proposal 56 | bytes32 currentBlockHash = _block.header.hash(); 57 | Layer2.chain.proposals[_block.submissionId] = Proposal( 58 | currentBlockHash, 59 | block.number + CHALLENGE_PERIOD, 60 | false 61 | ); 62 | /// Record l2 chain 63 | Layer2.chain.parentOf[currentBlockHash] = _block.header.parentBlock; 64 | /// Record reference for the inclusion proofs 65 | Layer2.chain.utxoRootOf[currentBlockHash] = _block.header.nextUTXORoot; 66 | /// Update exit allowance period 67 | proposer.exitAllowance = block.number + CHALLENGE_PERIOD; 68 | /// Freeze the latest mass deposit for the next block proposer 69 | Layer2.chain.committedDeposits[Layer2.chain.stagedDeposits.hash()] += 1; 70 | emit MassDepositCommit( 71 | Layer2.chain.massDepositId, 72 | Layer2.chain.stagedDeposits.merged, 73 | Layer2.chain.stagedDeposits.fee 74 | ); 75 | delete Layer2.chain.stagedDeposits; 76 | delete Layer2.chain.stagedSize; 77 | Layer2.chain.massDepositId++; 78 | emit NewProposal(_block.submissionId); 79 | } 80 | 81 | function finalize(bytes memory) public { 82 | Finalization memory finalization = Deserializer.finalizationFromCalldataAt(0); 83 | Proposal storage proposal = Layer2.chain.proposals[finalization.submissionId]; 84 | /// Check requirements 85 | require(finalization.massDeposits.root() == finalization.header.depositRoot, "Submitted different deposit root"); 86 | require(finalization.massMigrations.root() == finalization.header.migrationRoot, "Submitted different deposit root"); 87 | require(finalization.header.hash() == proposal.headerHash, "Invalid header data"); 88 | require(!proposal.slashed, "Slashed roll up can't be finalized"); 89 | require(finalization.header.parentBlock == Layer2.chain.latest, "The latest block should be its parent"); 90 | 91 | uint totalFee = finalization.header.fee; 92 | /// Execute deposits and collect fees 93 | for (uint i = 0; i < finalization.massDeposits.length; i++) { 94 | MassDeposit memory deposit = finalization.massDeposits[i]; 95 | require(chain.committedDeposits[deposit.hash()] > 0, "MassDeposit does not exist."); 96 | totalFee += deposit.fee; 97 | chain.committedDeposits[deposit.hash()] -= 1; 98 | } 99 | 100 | /// Update withdrawable every finalization 101 | require(Layer2.chain.withdrawables.length >= 2, "not initialized blockchain"); 102 | Withdrawable storage latest = Layer2.chain.withdrawables[Layer2.chain.withdrawables.length - 1]; 103 | require(latest.root == finalization.header.prevWithdrawalRoot, "Different withdrawal tree"); 104 | require(latest.index == finalization.header.prevWithdrawalIndex, "Different withdrawal tree"); 105 | if (finalization.header.prevWithdrawalIndex > finalization.header.nextWithdrawalIndex) { 106 | /// Fully filled. Start a new withdrawal tree 107 | Layer2.chain.withdrawables.push(); 108 | } 109 | Withdrawable storage target = Layer2.chain.withdrawables[Layer2.chain.withdrawables.length - 1]; 110 | target.root = finalization.header.nextWithdrawalRoot; 111 | target.index = finalization.header.nextWithdrawalIndex; 112 | 113 | /// Update the daily snapshot of withdrawable tree to prevent race conditions 114 | if (Layer2.chain.snapshotTimestamp + 1 days < now) { 115 | Layer2.chain.snapshotTimestamp = now; 116 | Layer2.chain.withdrawables[0].root = target.root; 117 | Layer2.chain.withdrawables[0].index = target.index; 118 | } 119 | 120 | /// Record mass migrations and collect fees. 121 | /// A MassMigration becomes a MassDeposit for the migration destination. 122 | for (uint i = 0; i < finalization.massMigrations.length; i++) { 123 | bytes32 migrationId = keccak256( 124 | abi.encodePacked( 125 | finalization.submissionId, 126 | finalization.massMigrations[i].hash() 127 | ) 128 | ); 129 | require(!Layer2.chain.migrations[migrationId], "Same id exists. Migrate it first"); 130 | Layer2.chain.migrations[migrationId] = true; 131 | } 132 | 133 | /// Give fee to the proposer 134 | Proposer storage proposer = Layer2.chain.proposers[finalization.header.proposer]; 135 | proposer.reward += totalFee; 136 | 137 | /// Update the chain 138 | Layer2.chain.latest = proposal.headerHash; 139 | emit Finalized(finalization.submissionId); 140 | } 141 | 142 | function withdrawReward(uint amount) public { 143 | address payable proposerAddr = msg.sender; 144 | Proposer storage proposer = Layer2.chain.proposers[proposerAddr]; 145 | require(proposer.reward >= amount, "You can't withdraw more than you have"); 146 | payable(proposerAddr).transfer(amount); 147 | proposer.reward -= amount; 148 | } 149 | 150 | function isProposable(address proposerAddr) public view returns (bool) { 151 | Proposer memory proposer = Layer2.chain.proposers[proposerAddr]; 152 | /// You can add more consensus logic here 153 | if (proposer.stake <= MINIMUM_STAKE) { 154 | return false; 155 | } else { 156 | return true; 157 | } 158 | } 159 | } 160 | 161 | /// TODO - If the gas usage exceeds the challenge limit, the proposer will get slashed 162 | /// TODO - instant withdrawal 163 | /// TODO - guarantee of tx including 164 | /// Some thoughts - There exists a possibility of racing condition to get the slash reward 165 | -------------------------------------------------------------------------------- /contracts/controllers/Migratable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../storage/Layer2.sol"; 4 | import { IERC20 } from "../utils/IERC20.sol"; 5 | import { IERC721 } from "../utils/IERC721.sol"; 6 | import { MassDeposit, MassMigration, Types } from "../libraries/Types.sol"; 7 | import { Deserializer } from "../libraries/Deserializer.sol"; 8 | 9 | 10 | contract Migratable is Layer2 { 11 | using Types for *; 12 | 13 | event NewMassMigration(bytes32 submissionId, address network, bytes32 merged, uint fee); 14 | 15 | function migrateTo( 16 | bytes32 submissionId, 17 | bytes calldata 18 | ) external { 19 | MassMigration memory migration = Deserializer.massMigrationFromCalldataAt(1); 20 | address to = migration.destination; 21 | bytes32 migrationId = keccak256(abi.encodePacked(submissionId, migration.hash())); 22 | require(chain.migrations[migrationId], "MassMigration does not exist"); 23 | try Migratable(to).acceptMigration( 24 | migrationId, 25 | migration.migratingLeaves.merged, 26 | migration.migratingLeaves.fee 27 | ) { 28 | // TODO: Handle out of gas due to the push pattern => ex: slash proposer using submissionId? 29 | // send ETH first 30 | payable(to).transfer(migration.totalETH); 31 | // send ERC20 32 | for(uint i = 0; i < migration.erc20.length; i++) { 33 | IERC20(migration.erc20[i].addr).transfer(to, migration.erc20[i].amount); 34 | } 35 | // send ERC721 36 | for(uint i = 0; i < migration.erc721.length; i++) { 37 | for(uint j = 0; j < migration.erc721[i].nfts.length; j++) { 38 | IERC721(migration.erc721[i].addr).transferFrom( 39 | address(this), 40 | to, 41 | migration.erc721[i].nfts[j] 42 | ); 43 | } 44 | } 45 | /// Delete mass migration 46 | delete chain.migrations[migrationId]; 47 | } catch { 48 | revert("Dest contract denied migration"); 49 | } 50 | } 51 | 52 | function acceptMigration(bytes32 submissionId, bytes32 merged, uint fee) external virtual { 53 | require(Layer2.allowedMigrants[msg.sender], "Not an allowed departure"); 54 | Layer2.chain.committedDeposits[MassDeposit(merged,fee).hash()] += 1; 55 | emit NewMassMigration(submissionId, msg.sender, merged, fee); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/controllers/RollUpable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../storage/Layer2.sol"; 4 | import { OPRU, SplitRollUp } from "../../node_modules/merkle-tree-rollup/contracts/library/Types.sol"; 5 | import { RollUpLib } from "../../node_modules/merkle-tree-rollup/contracts/library/RollUpLib.sol"; 6 | import { SubTreeRollUpLib } from "../../node_modules/merkle-tree-rollup/contracts/library/SubTreeRollUpLib.sol"; 7 | import { SMT256 } from "../../node_modules/smt-rollup/contracts/SMT.sol"; 8 | import { Hash } from "../libraries/Hash.sol"; 9 | 10 | 11 | contract RollUpable is Layer2 { 12 | // using RollUpLib for *; 13 | using SubTreeRollUpLib for *; 14 | using SMT256 for SMT256.OPRU; 15 | 16 | enum RollUpType { UTXO, Nullifier, Withdrawal} 17 | 18 | event NewProofOfRollUp(RollUpType rollUpType, uint id); 19 | 20 | modifier requirePermission(RollUpType rollUpType, uint id) { 21 | require( 22 | Layer2.proof.permittedTo[uint8(rollUpType)][id] == msg.sender, 23 | "Not permitted to update this roll up" 24 | ); 25 | _; 26 | } 27 | 28 | /** Roll up interaction functions */ 29 | function newProofOfUTXORollUp( 30 | uint startingRoot, 31 | uint startingIndex, 32 | uint[] calldata initialSiblings 33 | ) external { 34 | SplitRollUp storage rollUp = Layer2.proof.ofUTXORollUp.push(); 35 | rollUp.initWithSiblings( 36 | Hash.poseidon(), 37 | startingRoot, 38 | startingIndex, 39 | SUB_TREE_DEPTH, 40 | initialSiblings 41 | ); 42 | uint id = Layer2.proof.ofUTXORollUp.length - 1; 43 | Layer2.proof.permittedTo[uint8(RollUpType.UTXO)][id] = msg.sender; 44 | emit NewProofOfRollUp(RollUpType.UTXO, id); 45 | } 46 | 47 | function newProofOfNullifierRollUp(bytes32 prevRoot) external { 48 | SMT256.OPRU storage rollUp = Layer2.proof.ofNullifierRollUp.push(); 49 | rollUp.prev = prevRoot; 50 | rollUp.next = prevRoot; 51 | rollUp.mergedLeaves = bytes32(0); 52 | uint id = Layer2.proof.ofNullifierRollUp.length - 1; 53 | Layer2.proof.permittedTo[uint8(RollUpType.Nullifier)][id] = msg.sender; 54 | emit NewProofOfRollUp(RollUpType.Nullifier, id); 55 | } 56 | 57 | function newProofOfWithdrawalRollUp( 58 | uint startingRoot, 59 | uint startingIndex 60 | ) external { 61 | SplitRollUp storage rollUp = Layer2.proof.ofWithdrawalRollUp.push(); 62 | rollUp.init(startingRoot, startingIndex); 63 | uint id = Layer2.proof.ofWithdrawalRollUp.length - 1; 64 | Layer2.proof.permittedTo[uint8(RollUpType.Withdrawal)][id] = msg.sender; 65 | emit NewProofOfRollUp(RollUpType.Withdrawal, id); 66 | } 67 | 68 | function updateProofOfUTXORollUp( 69 | uint id, 70 | uint[] calldata leaves 71 | ) 72 | external 73 | requirePermission(RollUpType.Withdrawal, id) 74 | { 75 | SplitRollUp storage rollUp = Layer2.proof.ofUTXORollUp[id]; 76 | rollUp.update( 77 | Hash.poseidon(), 78 | SUB_TREE_DEPTH, 79 | leaves 80 | ); 81 | } 82 | 83 | function updateProofOfNullifierRollUp( 84 | uint id, 85 | bytes32[] calldata leaves, 86 | bytes32[256][] calldata siblings 87 | ) 88 | external 89 | requirePermission(RollUpType.Nullifier, id) 90 | { 91 | SMT256.OPRU storage rollUp = Layer2.proof.ofNullifierRollUp[id]; 92 | rollUp.update(leaves, siblings); 93 | } 94 | 95 | function updateProofOfWithdrawalRollUp( 96 | uint id, 97 | uint[] calldata initialSiblings, 98 | uint[] calldata leaves 99 | ) 100 | external 101 | requirePermission(RollUpType.Withdrawal, id) 102 | { 103 | SplitRollUp storage rollUp = Layer2.proof.ofWithdrawalRollUp[id]; 104 | rollUp.update( 105 | Hash.keccak(), 106 | SUB_TREE_DEPTH, 107 | initialSiblings, 108 | leaves 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contracts/controllers/UserInteractable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../storage/Layer2.sol"; 4 | import { IERC20 } from "../utils/IERC20.sol"; 5 | import { IERC721 } from "../utils/IERC721.sol"; 6 | import { Hash, Poseidon, MiMC } from "../libraries/Hash.sol"; 7 | import { RollUpLib } from "../../node_modules/merkle-tree-rollup/contracts/library/RollUpLib.sol"; 8 | import { Withdrawable, Blockchain, Types } from "../libraries/Types.sol"; 9 | 10 | contract UserInteractable is Layer2 { 11 | uint public constant SNARK_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 12 | uint public constant RANGE_LIMIT = SNARK_FIELD >> 32; 13 | using RollUpLib for *; 14 | 15 | event Deposit(uint indexed queuedAt, uint note, uint fee); 16 | 17 | function deposit( 18 | uint eth, 19 | uint salt, 20 | address token, 21 | uint amount, 22 | uint nft, 23 | uint[2] memory pubKey, 24 | uint fee 25 | ) public payable { 26 | _deposit(eth, salt, token, amount, nft, pubKey, fee); 27 | } 28 | 29 | function withdraw( 30 | uint eth, 31 | address token, 32 | uint amount, 33 | uint nft, 34 | uint fee, 35 | uint rootIndex, 36 | uint leafIndex, 37 | uint[] memory siblings 38 | ) public { 39 | _withdraw(msg.sender, eth, token, amount, nft, fee, rootIndex, leafIndex, siblings); 40 | } 41 | 42 | function withdrawUsingSignature( 43 | address to, 44 | uint eth, 45 | address token, 46 | uint amount, 47 | uint nft, 48 | uint fee, 49 | uint rootIndex, 50 | uint leafIndex, 51 | uint[] memory siblings, 52 | uint8 v, 53 | bytes32 r, 54 | bytes32 s 55 | ) public { 56 | require( 57 | _verifyWithdrawalSignature(to, eth, token, amount, nft, fee, v, r, s), 58 | "Invalid signature" 59 | ); 60 | _withdraw(to, eth, token, amount, nft, fee, rootIndex, leafIndex, siblings); 61 | } 62 | 63 | function _verifyWithdrawalSignature( 64 | address to, 65 | uint256 eth, 66 | address token, 67 | uint256 amount, 68 | uint256 nft, 69 | uint256 fee, 70 | uint8 v, 71 | bytes32 r, 72 | bytes32 s 73 | ) internal pure returns (bool) { 74 | bytes32 leaf = keccak256(abi.encodePacked(to, eth, token, amount, nft, fee)); 75 | bytes32 prefixedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", leaf)); 76 | address signer = ecrecover(prefixedHash, v, r, s); 77 | return signer == to; 78 | } 79 | 80 | function _deposit( 81 | uint eth, 82 | uint salt, 83 | address token, 84 | uint amount, 85 | uint nft, 86 | uint[2] memory pubKey, 87 | uint fee 88 | ) internal { 89 | require(msg.value < RANGE_LIMIT, "Too big value can cause the overflow inside the SNARK"); 90 | require(amount < RANGE_LIMIT, "Too big value can cause the overflow inside the SNARK"); 91 | require(nft < SNARK_FIELD, "Does not support too big nubmer of nft id"); 92 | require(amount * nft == 0, "Only one of ERC20 or ERC721 exists"); 93 | require(eth + fee == msg.value, "Inexact amount of eth"); 94 | require(Layer2.chain.stagedSize < 1024, "Should wait until it is committed"); 95 | 96 | ///TODO: require(fee >= specified fee); 97 | /// Validate the note is same with the hash result 98 | uint[] memory inputs = new uint[](7); 99 | inputs[0] = eth; 100 | inputs[1] = pubKey[0]; 101 | inputs[2] = pubKey[1]; 102 | inputs[3] = salt; 103 | inputs[4] = uint(token); 104 | inputs[5] = amount; 105 | inputs[6] = nft; 106 | uint note = Poseidon.poseidon(inputs); 107 | /// Receive token 108 | if(amount != 0) { 109 | try IERC20(token).transferFrom(msg.sender, address(this), amount) { 110 | } catch { 111 | revert("Transfer ERC20 failed"); 112 | } 113 | } else { 114 | try IERC721(token).transferFrom(msg.sender, address(this), nft) { 115 | } catch { 116 | revert("Transfer NFT failed"); 117 | } 118 | } 119 | /// Update the mass deposit 120 | Layer2.chain.stagedDeposits.merged = keccak256(abi.encodePacked(Layer2.chain.stagedDeposits.merged, note)); 121 | Layer2.chain.stagedDeposits.fee += fee; 122 | Layer2.chain.stagedSize += 1; 123 | /// Emit event. Coordinator should subscribe this event. 124 | emit Deposit(Layer2.chain.massDepositId, note, fee); 125 | } 126 | 127 | function _withdraw( 128 | address to, 129 | uint eth, 130 | address token, 131 | uint256 amount, 132 | uint256 nft, 133 | uint256 fee, 134 | uint rootIndex, 135 | uint noteIndex, 136 | uint[] memory siblings 137 | ) internal { 138 | require(nft*amount == 0, "Only ERC20 or ERC721"); 139 | bytes32 note = keccak256(abi.encodePacked(to, eth, token, amount, nft, fee)); 140 | /// inclusion proof 141 | Withdrawable memory withdrawable = chain.withdrawables[rootIndex]; 142 | bool inclusion = Hash.keccak().merkleProof( 143 | uint(withdrawable.root), 144 | uint(note), 145 | noteIndex, 146 | siblings 147 | ); 148 | require(inclusion, "The given withdrawal note does not exist"); 149 | /// Withdraw ETH & get fee 150 | if(eth!=0) { 151 | if(to == msg.sender) { 152 | payable(to).transfer(eth + fee); 153 | } else { 154 | payable(to).transfer(eth); 155 | payable(msg.sender).transfer(fee); 156 | } 157 | } 158 | /// Withdrawn token 159 | if(amount!=0) { 160 | IERC20(token).transfer(to, amount); 161 | } else { 162 | IERC721(token).transferFrom(address(this), to, nft); 163 | } 164 | /// Mark as withdrawn 165 | require(!Layer2.chain.withdrawn[note], "Already withdrawn"); 166 | Layer2.chain.withdrawn[note] = true; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /contracts/controllers/challenges/DepositChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../../storage/Layer2.sol"; 4 | import { Challengeable } from "../Challengeable.sol"; 5 | import { SNARKsVerifier } from "../../libraries/SNARKs.sol"; 6 | import { SMT256 } from "../../../node_modules/smt-rollup/contracts/SMT.sol"; 7 | import { 8 | Block, 9 | MassDeposit, 10 | Challenge, 11 | Types 12 | } from "../../libraries/Types.sol"; 13 | import { Deserializer } from "../../libraries/Deserializer.sol"; 14 | 15 | contract DepositChallenge is Challengeable { 16 | using Types for MassDeposit; 17 | using SMT256 for SMT256.OPRU; 18 | using SNARKsVerifier for SNARKsVerifier.VerifyingKey; 19 | 20 | function challengeMassDeposit( 21 | uint index, 22 | bytes calldata 23 | ) external { 24 | Block memory _block = Deserializer.blockFromCalldataAt(1); 25 | Challenge memory result = _challengeMassDeposit(_block, index); 26 | _execute(result); 27 | } 28 | 29 | function _challengeMassDeposit( 30 | Block memory _block, 31 | uint _index 32 | ) 33 | internal 34 | view 35 | returns (Challenge memory) 36 | { 37 | MassDeposit memory massDeposit = _block.body.massDeposits[_index]; 38 | if(chain.committedDeposits[massDeposit.hash()] > 0) { 39 | /// This mass deposit does not exist 40 | return Challenge( 41 | true, 42 | _block.submissionId, 43 | _block.header.proposer, 44 | "This deposit queue is not committed" 45 | ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/controllers/challenges/HeaderChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../../storage/Layer2.sol"; 4 | import { Challengeable } from "../Challengeable.sol"; 5 | import { 6 | Block, 7 | Transaction, 8 | MassDeposit, 9 | MassMigration, 10 | Challenge, 11 | Types 12 | } from "../../libraries/Types.sol"; 13 | import { Deserializer } from "../../libraries/Deserializer.sol"; 14 | 15 | contract HeaderChallenge is Challengeable { 16 | using Types for MassDeposit[]; 17 | using Types for MassMigration[]; 18 | using Types for Transaction[]; 19 | 20 | function challengeDepositRoot(bytes calldata) external { 21 | Block memory _block = Deserializer.blockFromCalldataAt(0); 22 | Challenge memory result = _challengeResultOfDepositRoot(_block); 23 | _execute(result); 24 | } 25 | 26 | function challengTxRoot(bytes calldata) external { 27 | Block memory _block = Deserializer.blockFromCalldataAt(0); 28 | Challenge memory result = _challengeResultOfTxRoot(_block); 29 | _execute(result); 30 | } 31 | 32 | function challengeMigrationRoot(bytes calldata) external { 33 | Block memory _block = Deserializer.blockFromCalldataAt(0); 34 | Challenge memory result = _challengeResultOfMigrationRoot(_block); 35 | _execute(result); 36 | } 37 | 38 | function challengeTotalFee(bytes calldata) external { 39 | Block memory _block = Deserializer.blockFromCalldataAt(0); 40 | Challenge memory result = _challengeResultOfTotalFee(_block); 41 | _execute(result); 42 | } 43 | 44 | function _challengeResultOfDepositRoot( 45 | Block memory _block 46 | ) 47 | internal 48 | pure 49 | returns (Challenge memory) 50 | { 51 | return Challenge( 52 | _block.header.depositRoot != _block.body.massDeposits.root(), 53 | _block.submissionId, 54 | _block.header.proposer, 55 | "Deposit root validation" 56 | ); 57 | } 58 | 59 | function _challengeResultOfTxRoot( 60 | Block memory _block 61 | ) 62 | internal 63 | pure 64 | returns (Challenge memory) 65 | { 66 | return Challenge( 67 | _block.header.txRoot != _block.body.txs.root(), 68 | _block.submissionId, 69 | _block.header.proposer, 70 | "Transaction root validation" 71 | ); 72 | } 73 | 74 | function _challengeResultOfMigrationRoot( 75 | Block memory _block 76 | ) 77 | internal 78 | pure 79 | returns (Challenge memory) 80 | { 81 | return Challenge( 82 | _block.header.migrationRoot != _block.body.massMigrations.root(), 83 | _block.submissionId, 84 | _block.header.proposer, 85 | "Transaction root validation" 86 | ); 87 | } 88 | 89 | 90 | function _challengeResultOfTotalFee( 91 | Block memory _block 92 | ) 93 | internal 94 | pure 95 | returns (Challenge memory) 96 | { 97 | uint totalFee = 0; 98 | for (uint i = 0; i < _block.body.massDeposits.length; i ++) { 99 | totalFee += _block.body.massDeposits[i].fee; 100 | } 101 | for (uint i = 0; i < _block.body.txs.length; i ++) { 102 | totalFee += _block.body.txs[i].fee; 103 | } 104 | /// FYI, fee in the massMigration is for the destination contract 105 | return Challenge( 106 | totalFee != _block.header.fee, 107 | _block.submissionId, 108 | _block.header.proposer, 109 | "Total fee validation" 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /contracts/controllers/challenges/MigrationChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../../storage/Layer2.sol"; 4 | import { Challengeable } from "../Challengeable.sol"; 5 | import { SNARKsVerifier } from "../../libraries/SNARKs.sol"; 6 | import { SMT256 } from "../../../node_modules/smt-rollup/contracts/SMT.sol"; 7 | import { 8 | Block, 9 | Challenge, 10 | Transaction, 11 | Outflow, 12 | MassDeposit, 13 | MassMigration, 14 | ERC20Migration, 15 | ERC721Migration, 16 | OutflowType, 17 | Types 18 | } from "../../libraries/Types.sol"; 19 | import { Deserializer } from "../../libraries/Deserializer.sol"; 20 | 21 | contract MigrationChallenge is Challengeable { 22 | using SMT256 for SMT256.OPRU; 23 | using SNARKsVerifier for SNARKsVerifier.VerifyingKey; 24 | using Types for Outflow; 25 | 26 | function challengeMassMigrationToMassDeposit( 27 | address destination, 28 | bytes calldata 29 | ) external { 30 | Block memory _block = Deserializer.blockFromCalldataAt(1); 31 | Challenge memory result = _challengeResultOfMassMigrationToMassDeposit(_block, destination); 32 | _execute(result); 33 | } 34 | 35 | function challengeERC20Migration( 36 | address destination, 37 | address erc20, 38 | bytes calldata 39 | ) external { 40 | Block memory _block = Deserializer.blockFromCalldataAt(2); 41 | Challenge memory result = _challengeResultOfERC20Migration(_block, destination, erc20); 42 | _execute(result); 43 | } 44 | 45 | function challengeERC721Migration( 46 | address destination, 47 | address erc721, 48 | uint tokenId, 49 | bytes calldata 50 | ) external { 51 | Block memory _block = Deserializer.blockFromCalldataAt(2); 52 | Challenge memory result = _challengeResultOfERC721Migration( 53 | _block, 54 | destination, 55 | erc721, 56 | tokenId 57 | ); 58 | _execute(result); 59 | } 60 | 61 | function _challengeResultOfMassMigrationToMassDeposit( 62 | Block memory _block, 63 | address destination 64 | ) 65 | internal 66 | view 67 | returns (Challenge memory) 68 | { 69 | MassMigration memory submitted; 70 | for(uint i = 0; i < _block.body.massMigrations.length; i++) { 71 | if(destination == _block.body.massMigrations[i].destination) { 72 | if(submitted.destination != address(0)) { 73 | return Challenge( 74 | true, 75 | _block.submissionId, 76 | _block.header.proposer, 77 | "Duplicated MassMigration destination" 78 | ); 79 | } 80 | submitted = _block.body.massMigrations[i]; 81 | } 82 | } 83 | uint totalETH; 84 | MassDeposit memory migratingLeaves; 85 | for(uint i = 0; i < _block.body.txs.length; i++) { 86 | Transaction memory transaction = _block.body.txs[i]; 87 | for(uint j = 0; j < transaction.outflow.length; j++) { 88 | Outflow memory outflow = transaction.outflow[j]; 89 | if(outflow.outflowType == uint8(OutflowType.Migration) && outflow.publicData.to == destination) { 90 | totalETH += outflow.publicData.eth; 91 | migratingLeaves.fee += outflow.publicData.fee; 92 | migratingLeaves.merged = keccak256(abi.encodePacked(migratingLeaves.merged, outflow.note)); 93 | } 94 | } 95 | } 96 | bool validityOfMassDeposit; 97 | if( 98 | totalETH == submitted.totalETH && 99 | migratingLeaves.merged == submitted.migratingLeaves.merged && 100 | migratingLeaves.fee == submitted.migratingLeaves.fee 101 | ) { 102 | validityOfMassDeposit = true; 103 | } else { 104 | validityOfMassDeposit = false; 105 | } 106 | return Challenge( 107 | !validityOfMassDeposit, 108 | _block.submissionId, 109 | _block.header.proposer, 110 | "Computed mass deposit is different with the submitted" 111 | ); 112 | } 113 | 114 | function _challengeResultOfERC20Migration( 115 | Block memory _block, 116 | address destination, 117 | address erc20 118 | ) 119 | internal 120 | view 121 | returns (Challenge memory) 122 | { 123 | ERC20Migration memory submitted; 124 | for(uint i = 0; i < _block.body.massMigrations.length; i++) { 125 | MassMigration memory massMigration = _block.body.massMigrations[i]; 126 | if(destination == massMigration.destination) { 127 | for(uint j = 0; j < massMigration.erc20.length; j++) { 128 | ERC20Migration memory erc20Migration = massMigration.erc20[j]; 129 | if(erc20Migration.addr == erc20) { 130 | if(erc20Migration.addr != address(0)) { 131 | /// There exist more than 2 of erc20 migration against the address. 132 | return Challenge( 133 | true, 134 | _block.submissionId, 135 | _block.header.proposer, 136 | "Duplicated ERC20 migration dests exist" 137 | ); 138 | } 139 | submitted = erc20Migration; 140 | } 141 | } 142 | } 143 | } 144 | 145 | uint erc20Amount; 146 | for(uint i = 0; i < _block.body.txs.length; i++) { 147 | Transaction memory transaction = _block.body.txs[i]; 148 | for(uint j = 0; j < transaction.outflow.length; j++) { 149 | Outflow memory outflow = transaction.outflow[j]; 150 | if( 151 | outflow.outflowType == uint8(OutflowType.Migration) && 152 | outflow.publicData.to == destination && 153 | outflow.publicData.token == erc20 154 | ) { 155 | erc20Amount += outflow.publicData.amount; 156 | } 157 | } 158 | } 159 | return Challenge( 160 | erc20Amount == submitted.amount, 161 | _block.submissionId, 162 | _block.header.proposer, 163 | "Migrating amount of token is invalid" 164 | ); 165 | } 166 | 167 | function _challengeResultOfERC721Migration( 168 | Block memory _block, 169 | address destination, 170 | address erc721, 171 | uint tokenId 172 | ) 173 | internal 174 | view 175 | returns (Challenge memory) 176 | { 177 | ERC721Migration memory submitted; 178 | for(uint i = 0; i < _block.body.massMigrations.length; i++) { 179 | MassMigration memory massMigration = _block.body.massMigrations[i]; 180 | if(destination == massMigration.destination) { 181 | for(uint j = 0; j < massMigration.erc721.length; j++) { 182 | ERC721Migration memory erc721Migration = massMigration.erc721[j]; 183 | if(erc721Migration.addr == erc721) { 184 | if(erc721Migration.addr != address(0)) { 185 | /// There exist more than 2 of erc721 migration against the address. 186 | return Challenge( 187 | true, 188 | _block.submissionId, 189 | _block.header.proposer, 190 | "Duplicated ERC721 migration dests exist" 191 | ); 192 | } 193 | submitted = erc721Migration; 194 | } 195 | } 196 | } 197 | } 198 | uint submittedNftCount = 0; 199 | for (uint i = 0; i < submitted.nfts.length; i++) { 200 | if(tokenId == submitted.nfts[i]) { 201 | submittedNftCount ++; 202 | } 203 | } 204 | if(submittedNftCount > 1) { 205 | return Challenge( 206 | true, 207 | _block.submissionId, 208 | _block.header.proposer, 209 | "It destroys the non-fungibility" 210 | ); 211 | } 212 | 213 | uint computedNftCount; 214 | for(uint i = 0; i < _block.body.txs.length; i++) { 215 | Transaction memory transaction = _block.body.txs[i]; 216 | for(uint j = 0; j < transaction.outflow.length; j++) { 217 | Outflow memory outflow = transaction.outflow[j]; 218 | if( 219 | outflow.outflowType == uint8(OutflowType.Migration) && 220 | outflow.publicData.to == destination && 221 | outflow.publicData.token == erc721 && 222 | outflow.publicData.nft == tokenId 223 | ) { 224 | computedNftCount += 1; 225 | } 226 | } 227 | } 228 | return Challenge( 229 | submittedNftCount == computedNftCount, 230 | _block.submissionId, 231 | _block.header.proposer, 232 | "Invalid nft migration" 233 | ); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /contracts/controllers/challenges/RollUpChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Layer2 } from "../../storage/Layer2.sol"; 4 | import { Challengeable } from "../Challengeable.sol"; 5 | import { SplitRollUp } from "../../../node_modules/merkle-tree-rollup/contracts/library/Types.sol"; 6 | import { SubTreeRollUpLib } from "../../../node_modules/merkle-tree-rollup/contracts/library/SubTreeRollUpLib.sol"; 7 | import { RollUpLib } from "../../../node_modules/merkle-tree-rollup/contracts/library/RollUpLib.sol"; 8 | import { SMT256 } from "../../../node_modules/smt-rollup/contracts/SMT.sol"; 9 | import { 10 | Block, 11 | Challenge, 12 | Transaction, 13 | Outflow, 14 | MassDeposit, 15 | OutflowType, 16 | Types 17 | } from "../../libraries/Types.sol"; 18 | import { Deserializer } from "../../libraries/Deserializer.sol"; 19 | 20 | contract RollUpChallenge is Challengeable { 21 | using SubTreeRollUpLib for SplitRollUp; 22 | using SMT256 for SMT256.OPRU; 23 | using Types for Outflow; 24 | 25 | function challengeUTXORollUp( 26 | uint utxoRollUpId, 27 | uint[] calldata _deposits, 28 | uint numOfUTXOs, 29 | bytes calldata 30 | ) external { 31 | Block memory _block = Deserializer.blockFromCalldataAt(3); 32 | Challenge memory result = _challengeResultOfUTXORollUp(_block, utxoRollUpId, numOfUTXOs, _deposits); 33 | _execute(result); 34 | } 35 | 36 | function challengeNullifierRollUp( 37 | uint nullifierRollUpId, 38 | uint numOfNullifiers, 39 | bytes calldata 40 | ) external { 41 | Block memory _block = Deserializer.blockFromCalldataAt(2); 42 | Challenge memory result = _challengeResultOfNullifierRollUp( 43 | _block, 44 | nullifierRollUpId, 45 | numOfNullifiers 46 | ); 47 | _execute(result); 48 | } 49 | 50 | function challengeWithdrawalRollUp( 51 | uint withdrawalRollUpId, 52 | uint numOfWithdrawals, 53 | bytes calldata 54 | ) external { 55 | Block memory _block = Deserializer.blockFromCalldataAt(2); 56 | Challenge memory result = _challengeResultOfWithdrawalRollUp(_block, withdrawalRollUpId, numOfWithdrawals); 57 | _execute(result); 58 | } 59 | 60 | /** Computes challenge here */ 61 | function _challengeResultOfUTXORollUp( 62 | Block memory _block, 63 | uint _utxoRollUpId, 64 | uint _utxoNum, 65 | uint[] memory _deposits 66 | ) 67 | internal 68 | view 69 | returns (Challenge memory) 70 | { 71 | /// Check submitted _deposits are equal to the leaves in the MassDeposits 72 | uint depositIndex = 0; 73 | for(uint i = 0; i < _block.body.massDeposits.length; i++) { 74 | MassDeposit memory massDeposit = _block.body.massDeposits[i]; 75 | bytes32 merged = bytes32(0); 76 | bytes32 target = massDeposit.merged; 77 | while(merged != target) { 78 | /// merge _deposits until it matches with the submitted mass deposit's merged leaves. 79 | merged = keccak256(abi.encodePacked(merged, _deposits[depositIndex])); 80 | depositIndex++; 81 | } 82 | } 83 | require(depositIndex == _deposits.length, "Submitted _deposits are different with the MassDeposits"); 84 | 85 | /// Assign a new array 86 | uint[] memory outputs = new uint[](_utxoNum); 87 | uint index = 0; 88 | /// Append _deposits first 89 | for (uint i = 0; i < _deposits.length; i++) { 90 | outputs[index++] = _deposits[i]; 91 | } 92 | /// Append UTXOs from transactions 93 | for (uint i = 0; i < _block.body.txs.length; i++) { 94 | Transaction memory transaction = _block.body.txs[i]; 95 | for(uint j = 0; j < transaction.outflow.length; j++) { 96 | if(transaction.outflow[j].isUTXO()) { 97 | outputs[index++] = transaction.outflow[j].note; 98 | } 99 | } 100 | } 101 | require(_utxoNum == index, "Submitted invalid num of utxo num"); 102 | 103 | /// Start a new tree if there's no room to add the new outputs 104 | uint startingIndex; 105 | uint startingRoot; 106 | if (_block.header.prevUTXOIndex + _utxoNum < POOL_SIZE) { 107 | /// it uses the latest tree 108 | startingIndex = _block.header.prevUTXOIndex; 109 | startingRoot = _block.header.prevUTXORoot; 110 | } else { 111 | /// start a new tree 112 | startingIndex = 0; 113 | startingRoot = 0; 114 | } 115 | /// Submitted invalid next output index 116 | if (_block.header.nextUTXOIndex != (startingIndex + _utxoNum)) { 117 | return Challenge( 118 | true, 119 | _block.submissionId, 120 | _block.header.proposer, 121 | "UTXO tree flushed" 122 | ); 123 | } 124 | 125 | /// Check validity of the roll up using the storage based Poseidon sub-tree roll up 126 | SplitRollUp memory rollUpProof = Layer2.proof.ofUTXORollUp[_utxoRollUpId]; 127 | bool isValidRollUp = rollUpProof.verify( 128 | SubTreeRollUpLib.newSubTreeOPRU( 129 | uint(startingRoot), 130 | startingIndex, 131 | uint(_block.header.nextUTXORoot), 132 | SUB_TREE_DEPTH, 133 | outputs 134 | ) 135 | ); 136 | 137 | return Challenge( 138 | !isValidRollUp, 139 | _block.submissionId, 140 | _block.header.proposer, 141 | "UTXO roll up" 142 | ); 143 | } 144 | 145 | /// Possibility to cost a lot of failure gases because of the 'already slashed' _blocks 146 | function _challengeResultOfNullifierRollUp( 147 | Block memory _block, 148 | uint nullifierRollUpId, 149 | uint numOfNullifiers 150 | ) 151 | internal 152 | view 153 | returns (Challenge memory) 154 | { 155 | /// Assign a new array 156 | bytes32[] memory nullifiers = new bytes32[](numOfNullifiers); 157 | /// Get outputs to append 158 | uint index = 0; 159 | for (uint i = 0; i < _block.body.txs.length; i++) { 160 | Transaction memory transaction = _block.body.txs[i]; 161 | for (uint j = 0; j < transaction.inflow.length; j++) { 162 | nullifiers[index++] = transaction.inflow[j].nullifier; 163 | } 164 | } 165 | require(index == numOfNullifiers, "Invalid length of the nullifiers"); 166 | 167 | /// Get rolled up root 168 | SMT256.OPRU memory proof = Layer2.proof.ofNullifierRollUp[nullifierRollUpId]; 169 | bool isValidRollUp = proof.verify( 170 | _block.header.prevNullifierRoot, 171 | _block.header.nextNullifierRoot, 172 | RollUpLib.merge(bytes32(0), nullifiers) 173 | ); 174 | 175 | return Challenge( 176 | !isValidRollUp, 177 | _block.submissionId, 178 | _block.header.proposer, 179 | "Nullifier roll up" 180 | ); 181 | } 182 | 183 | function _challengeResultOfWithdrawalRollUp( 184 | Block memory _block, 185 | uint withdrawalRollUpId, 186 | uint numOfWithdrawals 187 | ) 188 | internal 189 | view 190 | returns (Challenge memory) 191 | { 192 | /// Assign a new array 193 | bytes32[] memory withdrawals = new bytes32[](numOfWithdrawals); 194 | /// Append Withdrawal notes from transactions 195 | uint index = 0; 196 | for (uint i = 0; i < _block.body.txs.length; i++) { 197 | Transaction memory transaction = _block.body.txs[i]; 198 | for(uint j = 0; j < transaction.outflow.length; j++) { 199 | if(transaction.outflow[j].outflowType == uint8(OutflowType.Withdrawal)) { 200 | withdrawals[index++] = transaction.outflow[j].withdrawalNote(); 201 | } 202 | } 203 | } 204 | require(numOfWithdrawals == index, "Submitted invalid num of utxo num"); 205 | /// Start a new tree if there's no room to add the new withdrawals 206 | uint startingIndex; 207 | bytes32 startingRoot; 208 | if (_block.header.prevWithdrawalIndex + numOfWithdrawals < POOL_SIZE) { 209 | /// it uses the latest tree 210 | startingIndex = _block.header.prevWithdrawalIndex; 211 | startingRoot = _block.header.prevWithdrawalRoot; 212 | } else { 213 | /// start a new tree 214 | startingIndex = 0; 215 | startingRoot = 0; 216 | } 217 | /// Submitted invalid index of the next withdrawal tree 218 | if (_block.header.nextWithdrawalIndex != (startingIndex + numOfWithdrawals)) { 219 | return Challenge( 220 | true, 221 | _block.submissionId, 222 | _block.header.proposer, 223 | "Withdrawal tree flushed" 224 | ); 225 | } 226 | 227 | /// Check validity of the roll up using the storage based Keccak sub-tree roll up 228 | SplitRollUp memory proof = Layer2.proof.ofWithdrawalRollUp[withdrawalRollUpId]; 229 | uint[] memory uintLeaves; 230 | assembly { 231 | uintLeaves := withdrawals 232 | } 233 | bool isValidRollUp = proof.verify( 234 | SubTreeRollUpLib.newSubTreeOPRU( 235 | uint(startingRoot), 236 | startingIndex, 237 | uint(_block.header.nextWithdrawalRoot), 238 | SUB_TREE_DEPTH, 239 | uintLeaves 240 | ) 241 | ); 242 | 243 | return Challenge( 244 | !isValidRollUp, 245 | _block.submissionId, 246 | _block.header.proposer, 247 | "Withdrawal roll up" 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /contracts/interfaces/ICoordinatable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface ICoordinatable { 4 | /** 5 | * @notice Coordinator calls this function for the proof of stake. 6 | * Coordinator should pay more than MINIMUM_STAKE. See 'Configurated.sol' 7 | * 8 | */ 9 | function register() external payable; 10 | 11 | /** 12 | * @notice Coordinator can withdraw deposited stakes after the challenge period. 13 | */ 14 | function deregister() external; 15 | 16 | /** 17 | * @dev Coordinator proposes a new block using this function. propose() will freeze 18 | * the current mass deposit for the next block proposer, and will go through 19 | * CHALLENGE_PERIOD. 20 | * @param submission Serialized newly minted block data 21 | */ 22 | function propose(bytes calldata submission) external; 23 | 24 | /** 25 | * @dev Coordinator can finalize a submitted block if it isn't slashed during the 26 | * challenge period. It updates the aggregated fee and withdrawal root. 27 | * @param submission Serialized newly minted block data 28 | */ 29 | function finalize(bytes calldata submission) external; 30 | 31 | /** 32 | * @dev Coordinators can withdraw aggregated transaction fees. 33 | * @param amount Amount to withdraw. 34 | */ 35 | function withdrawReward(uint amount) external; 36 | 37 | /** 38 | * @dev You can override this function to implement your own consensus logic. 39 | * @param proposerAddr Coordinator address to check the allowance of block proposing. 40 | */ 41 | function isProposable(address proposerAddr) external view returns (bool); 42 | } -------------------------------------------------------------------------------- /contracts/interfaces/IDepositChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IDepositChallenge { 4 | function challengeMassDeposit(uint index, bytes calldata) external; 5 | } -------------------------------------------------------------------------------- /contracts/interfaces/IHeaderChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IHeaderChallenge { 4 | /** 5 | * @dev Challenge when the submitted header's deposit root is invalid. 6 | * @param submission The proposal data which is exactly same with the submitted. 7 | */ 8 | function challengeDepositRoot(bytes calldata submission) external; 9 | 10 | /** 11 | * @dev Challenge when the submitted header's transfer root is invalid. 12 | * The transfer root in the header should be the merkle root of the transfer 13 | * tx hash values. 14 | * @param submission The proposal data which is exactly same with the submitted. 15 | */ 16 | function challengeTxRoot(bytes calldata submission) external; 17 | 18 | /** 19 | * @dev Challenge when the submitted header's migration root is invalid. 20 | * The migration root in the header should be the merkle root of the migration 21 | * tx hash values. 22 | * @param submission The proposal data which is exactly same with the submitted. 23 | */ 24 | function challengeMigrationRoot(bytes calldata submission) external; 25 | 26 | /** 27 | * @dev Challenge when the submitted header's total fee is not same with 28 | * the sum of the fees in every transactions in the block. 29 | * @param submission The proposal data which is exactly same with the submitted. 30 | */ 31 | function challengeTotalFee(bytes calldata submission) external; 32 | } -------------------------------------------------------------------------------- /contracts/interfaces/IMigratable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IMigratable { 4 | /** 5 | * @dev You can do the mass migration using this function. To execute 6 | * this function, the destination contract should inherits the 7 | * "Migratable" contract and have registered this current contract's 8 | * address as an allowed migrant. 9 | * @param migrationId Index of a MassMigration to execute. 10 | * @param to Address of the destination contract. 11 | */ 12 | function migrateTo(uint migrationId, address to) external; 13 | } -------------------------------------------------------------------------------- /contracts/interfaces/IMigrationChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IMigrationChallenge { 4 | /** 5 | * @param destination Address of another layer 2 contract 6 | * @param submission The proposal data which is exactly same with the submitted. 7 | */ 8 | function challengeMassMigrationToMassDeposit( 9 | address destination, 10 | bytes calldata submission 11 | ) external; 12 | 13 | 14 | function challengeERC20Migration( 15 | address destination, 16 | address erc20, 17 | bytes calldata submission 18 | ) external; 19 | 20 | 21 | function challengeERC721Migration( 22 | address destination, 23 | address erc721, 24 | uint tokenId, 25 | bytes calldata submission 26 | ) external; 27 | } -------------------------------------------------------------------------------- /contracts/interfaces/IRollUpChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IRollUpChallenge { 4 | /** 5 | * @dev Challenge when the submitted block's utxo tree transition is invalid. 6 | * @param proofId Id of your utxo roll up proof. See 'RollUpable.sol'. 7 | * @param deposits Submit all deposit leaves to be merged. 8 | * @param numOfUTXO Number of new UTXOs to help the computation. 9 | * @param submission The proposal data which is exactly same with the submitted. 10 | */ 11 | function challengeUTXORollUp(uint proofId, uint[] calldata deposits, uint numOfUTXO, bytes calldata submission) external; 12 | 13 | /** 14 | * @dev Challenge when the submitted block's nullifier tree transition is invalid. 15 | * @param proofId Id of your nullifier roll up proof. See 'RollUpable.sol'. 16 | * @param numOfNullifiers Number of used nullifiers to help the computation. 17 | * @param submission The proposal data which is exactly same with the submitted. 18 | */ 19 | function challengeNullifierRollUp(uint proofId, uint numOfNullifiers, bytes calldata submission) external; 20 | 21 | /** 22 | * @dev Challenge when the submitted block's withdrawal tree transition is invalid. 23 | * @param proofId Id of your withdrawal roll up proof. See 'RollUpable.sol'. 24 | * @param numOfWithdrawals Number of new withdrawal notes to help the computation. 25 | * @param submission The proposal data which is exactly same with the submitted. 26 | */ 27 | function challengeWithdrawalRollUp(uint proofId, uint numOfWithdrawals, bytes calldata submission) external; 28 | } -------------------------------------------------------------------------------- /contracts/interfaces/IRollUpable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IRollUpable { 4 | /** 5 | * @dev Challenger starts to generate a proof for the UTXO tree transition 6 | */ 7 | function newProofOfUTXORollUp(uint startingRoot, uint startingIndex, uint[] calldata initialSiblings) external; 8 | 9 | /** 10 | * @dev Challenger starts to generate a proof for the nullifier tree transition 11 | */ 12 | function newProofOfNullifierRollUp(bytes32 prevRoot) external; 13 | 14 | /** 15 | * @dev Challenger starts to generate a proof for the withdrawal tree transition 16 | */ 17 | function newProofOfWithdrawalRollUp(uint startingRoot, uint startingIndex) external; 18 | 19 | /** 20 | * @dev Challenger appends items to the utxo tree and record the intermediate result on the storage. 21 | * This Poseidon sub tree roll up costs around 5.8 million gas to append 32 items. 22 | */ 23 | function updateProofOfUTXORollUp(uint id, uint[] calldata leaves) external; 24 | 25 | /** 26 | * @dev Challenger appends items to the nullifier tree and record the intermediate result on the storage. 27 | */ 28 | function updateProofOfNullifierRollUp(uint id, bytes32[] calldata leaves, bytes32[256][] calldata siblings) external; 29 | 30 | /** 31 | * @dev Challenger appends items to the withdrawal tree and record the intermediate result on the storage 32 | * This keccak sub tree roll up costs around 300k gas to append 32 items. 33 | */ 34 | function updateProofOfWithdrawalRollUp(uint id, uint[] calldata initialSiblings, uint[] calldata leaves) external; 35 | } 36 | -------------------------------------------------------------------------------- /contracts/interfaces/ISetupWizard.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface ISetupWizard { 4 | /** 5 | * @dev This configures a zk SNARKs verification key to support the given transaction type 6 | * @param txType TxType.Transfer / TxType.Withdrawal / TxType.Migration 7 | * @param numOfInputs Number of inflow UTXOs 8 | * @param numOfOutputs Number of outflow UTXOs 9 | */ 10 | function registerVk( 11 | uint8 txType, 12 | uint8 numOfInputs, 13 | uint8 numOfOutputs, 14 | uint[2] calldata alfa1, 15 | uint[2][2] calldata beta2, 16 | uint[2][2] calldata gamma2, 17 | uint[2][2] calldata delta2, 18 | uint[2][] calldata ic 19 | ) external; 20 | 21 | /** 22 | * @dev It connects this proxy contract to the UserInteractable controller. 23 | */ 24 | function makeUserInteractable(address addr) external; 25 | 26 | /** 27 | * @dev It connects this proxy contract to the RollUpable controller. 28 | */ 29 | function makeRollUpable(address addr) external; 30 | 31 | /** 32 | * @dev It connects this proxy contract to the Challengeable controllers. 33 | */ 34 | function makeChallengeable( 35 | address depositChallenge, 36 | address headerChallenge, 37 | address migrationChallenge, 38 | address rollUpChallenge, 39 | address txChallenge 40 | ) external; 41 | 42 | /** 43 | * @dev It connects this proxy contract to the Migratable controller. 44 | */ 45 | function makeMigratable(address addr) external; 46 | 47 | /** 48 | * @dev Migration process: 49 | 1. On the destination contract, execute allowMigrants() to configure the allowed migrants. 50 | The departure contract should be in the allowed list. 51 | 2. On the departure contract, execute migrateTo(). See "IMigratable.sol" 52 | * @param migrants List of contracts' address to allow migrations. 53 | */ 54 | function allowMigrants(address[] calldata migrants) external; 55 | 56 | /** 57 | * @dev If you once execute this, every configuration freezes and does not change forever. 58 | */ 59 | function completeSetup() external; 60 | } 61 | -------------------------------------------------------------------------------- /contracts/interfaces/ITxChallenge.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface ITxChallenge { 4 | /** 5 | * @dev Challenge when any of the used nullifier's inclusion reference is invalid. 6 | * @param txType Type of the transaction. 7 | * @param txIndex Index of the transaction in the tx list of the block body. 8 | * @param inflowIndex Index of the inflow note in the tx. 9 | * @param submission The proposal data which is exactly same with the submitted. 10 | */ 11 | function challengeInclusion(uint8 txType, uint txIndex, uint inflowIndex, bytes calldata submission) external; 12 | 13 | /** 14 | * @dev Challenge when any submitted transaction has an invalid SNARKs proof 15 | * @param txIndex Index of the transaction in the tx list of the block body. 16 | * @param submission The proposal data which is exactly same with the submitted. 17 | */ 18 | function challengeTransaction(uint txIndex, bytes calldata submission) external; 19 | 20 | /** 21 | * @dev Challenge when the block is trying to use an already used nullifier. 22 | * @param txIndex Index of the transaction in the tx list of the block body. 23 | * @param inflowIndex Index of the inflow note in the tx. 24 | * @param sibling The sibling data of the nullifier. 25 | * @param submission The proposal data which is exactly same with the submitted. 26 | */ 27 | function challengeUsedNullifier(uint txIndex, uint inflowIndex, bytes32[256] calldata sibling, bytes calldata submission) external; 28 | 29 | /** 30 | * @dev Challenge when a nullifier used twice in a same block. 31 | * @param nullifier Double included nullifier. 32 | * @param submission The proposal data which is exactly same with the submitted. 33 | */ 34 | function challengeDuplicatedNullifier(bytes32 nullifier, bytes calldata submission) external; 35 | 36 | /** 37 | * @notice It checks the validity of an inclusion refernce for a nullifier. 38 | * @dev Each nullifier should be paired with an inclusion reference which is a root of 39 | * utxo tree. For the inclusion reference, You can use finalized roots or recent 40 | * blocks' utxo roots. When you use recent blocks' utxo roots, recent REF_DEPTH 41 | * of utxo roots are available. It costs maximum 1800*REF_DEPTH gas to validate 42 | * an inclusion reference during the TX challenge process. 43 | * @param l2BlockHash Layer2 block's hash value where to start searching for. 44 | * @param ref Utxo root which includes the nullifier's origin utxo. 45 | */ 46 | function isValidRef(bytes32 l2BlockHash, uint256 ref) external view returns (bool); 47 | } -------------------------------------------------------------------------------- /contracts/interfaces/IUserInteractable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IUserInteractable { 4 | /** 5 | * @notice Users can use zkopru network by submitting a new homomorphically hiden note. 6 | * @param note Should be same with the poseidon hash of (amount, fee, pubKey) 7 | * @param amount Amount to deposit 8 | * @param fee Amount of fee to give to the coordinator 9 | * @param pubKey EdDSA public key to use in the zkopru network 10 | */ 11 | function deposit( 12 | uint note, 13 | uint amount, 14 | uint fee, 15 | uint[2] calldata pubKey 16 | ) external payable; 17 | 18 | /** 19 | * @notice Users can withdraw a note when your withdrawal tx is finalized 20 | * @param amount Amount to withdraw out. 21 | * @param proofHash Hash value of the SNARKs proof of your withdrawal transaction. 22 | * @param rootIndex Withdrawer should submit inclusion proof. Submit which withdrawal root to use. 23 | * withdrawables[0]: daily snapshot of withdrawable tree 24 | * withdrawables[latest]: the latest withdrawal tree 25 | * withdrawables[1~latest-1]: finalized tree 26 | * @param leafIndex The index of your withdrawal note's leaf in the given tree. 27 | * @param siblings Inclusion proof data 28 | */ 29 | function withdraw( 30 | uint amount, 31 | bytes32 proofHash, 32 | uint rootIndex, 33 | uint leafIndex, 34 | uint[] calldata siblings 35 | ) external; 36 | 37 | /** 38 | * @notice Others can execute the withdrawal instead of the recipient account using ECDSA. 39 | * @param amount Amount to withdraw out. 40 | * @param to Address of the ECDSA signer 41 | * @param proofHash Hash value of the SNARKs proof of your withdrawal transaction. 42 | * @param rootIndex Withdrawer should submit inclusion proof. Submit which withdrawal root to use. 43 | * withdrawables[0]: daily snapshot of withdrawable tree 44 | * withdrawables[latest]: the latest withdrawal tree 45 | * withdrawables[1~latest-1]: finalized tree 46 | * @param leafIndex The index of your withdrawal note's leaf in the given tree. 47 | * @param siblings Inclusion proof data 48 | * @param v ECDSA signature v 49 | * @param r ECDSA signature r 50 | * @param s ECDSA signature s 51 | */ 52 | function withdrawUsingSignature( 53 | uint amount, 54 | address to, 55 | bytes32 proofHash, 56 | uint rootIndex, 57 | uint leafIndex, 58 | uint[] calldata siblings, 59 | uint8 v, 60 | bytes32 r, 61 | bytes32 s 62 | ) external; 63 | } -------------------------------------------------------------------------------- /contracts/libraries/Asset.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { MassDeposit, Withdrawable, Types } from "../libraries/Types.sol"; 4 | interface IERC20 { 5 | function transfer(address recipient, uint256 amount) external returns (bool); 6 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 7 | } 8 | 9 | interface IERC721 { 10 | function transferFrom(address from, address to, uint256 tokenId) external; 11 | } 12 | 13 | struct Asset { 14 | address erc20; 15 | address wallet; 16 | } 17 | 18 | library AssetHandler { 19 | /** 20 | * @dev It moves assets from layer 1 to the layer 2 anchor contract. 21 | */ 22 | function depositFrom(Asset memory self, address from, uint amount) internal returns (bool) { 23 | if (self.erc20 == address(0)) { 24 | /// Asset is Ether 25 | require(amount == msg.value, "Does not receive correct amount"); 26 | require(from == msg.sender, "Different sender"); 27 | } else { 28 | /// Asset is ERC20 29 | IERC20(self.erc20).transferFrom(from, self.wallet, amount); 30 | } 31 | return true; 32 | } 33 | 34 | /** 35 | * @dev It withdraw assets back to the layer 1 from the layer 2 anchor contract. 36 | */ 37 | function withdrawTo(Asset memory self, address to, uint amount) internal { 38 | if (self.erc20 == address(0)) { 39 | /// Asset is Ether 40 | payable(to).transfer(amount); 41 | } else { 42 | /// Asset is ERC20 43 | IERC20(self.erc20).transfer(to, amount); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/libraries/Pairing.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | 4 | // 5 | // Copyright 2017 Christian Reitwiessner 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | // 10 | // 2019 OKIMS 11 | // ported to solidity 0.5 12 | // fixed linter warnings 13 | // added requiere error messages 14 | // 15 | library Pairing { 16 | struct G1Point { 17 | uint X; 18 | uint Y; 19 | } 20 | // Encoding of field elements is: X[0] * z + X[1] 21 | struct G2Point { 22 | uint[2] X; 23 | uint[2] Y; 24 | } 25 | /// @return the generator of G1 26 | function P1() internal pure returns (G1Point memory) { 27 | return G1Point(1, 2); 28 | } 29 | /// @return the generator of G2 30 | function P2() internal pure returns (G2Point memory) { 31 | // Original code point 32 | return G2Point( 33 | [ 34 | 11559732032986387107991004021392285783925812861821192530917403151452391805634, 35 | 10857046999023057135944570762232829481370756359578518086990519993285655852781 36 | ], 37 | [ 38 | 4082367875863433681332203403145435568316851327593401208105741076214120093531, 39 | 8495653923123431417604973247489272438418190587263600148770280649306958101930 40 | ] 41 | ); 42 | 43 | /* 44 | // Changed by Jordi point 45 | return G2Point( 46 | [10857046999023057135944570762232829481370756359578518086990519993285655852781, 47 | 11559732032986387107991004021392285783925812861821192530917403151452391805634], 48 | [8495653923123431417604973247489272438418190587263600148770280649306958101930, 49 | 4082367875863433681332203403145435568316851327593401208105741076214120093531] 50 | ); 51 | */ 52 | } 53 | /// @return the negation of p, i.e. p.addition(p.negate()) should be zero. 54 | function negate(G1Point memory p) internal pure returns (G1Point memory) { 55 | // The prime q in the base field F_q for G1 56 | uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 57 | if (p.X == 0 && p.Y == 0) 58 | return G1Point(0, 0); 59 | return G1Point(p.X, q - (p.Y % q)); 60 | } 61 | /// @return r the sum of two points of G1 62 | function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { 63 | uint[4] memory input; 64 | input[0] = p1.X; 65 | input[1] = p1.Y; 66 | input[2] = p2.X; 67 | input[3] = p2.Y; 68 | bool success; 69 | // solium-disable-next-line security/no-inline-assembly 70 | assembly { 71 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 72 | // Use "invalid" to make gas estimation work 73 | switch success case 0 { invalid() } 74 | } 75 | require(success,"pairing-add-failed"); 76 | } 77 | /// @return r the product of a point on G1 and a scalar, i.e. 78 | /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. 79 | function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) { 80 | uint[3] memory input; 81 | input[0] = p.X; 82 | input[1] = p.Y; 83 | input[2] = s; 84 | bool success; 85 | // solium-disable-next-line security/no-inline-assembly 86 | assembly { 87 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 88 | // Use "invalid" to make gas estimation work 89 | switch success case 0 { invalid() } 90 | } 91 | require (success,"pairing-mul-failed"); 92 | } 93 | /// @return the result of computing the pairing check 94 | /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 95 | /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should 96 | /// return true. 97 | function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { 98 | require(p1.length == p2.length,"pairing-lengths-failed"); 99 | uint elements = p1.length; 100 | uint inputSize = elements * 6; 101 | uint[] memory input = new uint[](inputSize); 102 | for (uint i = 0; i < elements; i++) 103 | { 104 | input[i * 6 + 0] = p1[i].X; 105 | input[i * 6 + 1] = p1[i].Y; 106 | input[i * 6 + 2] = p2[i].X[0]; 107 | input[i * 6 + 3] = p2[i].X[1]; 108 | input[i * 6 + 4] = p2[i].Y[0]; 109 | input[i * 6 + 5] = p2[i].Y[1]; 110 | } 111 | uint[1] memory out; 112 | bool success; 113 | // solium-disable-next-line security/no-inline-assembly 114 | assembly { 115 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 116 | // Use "invalid" to make gas estimation work 117 | switch success case 0 { invalid() } 118 | } 119 | require(success,"pairing-opcode-failed"); 120 | return out[0] != 0; 121 | } 122 | /// Convenience method for a pairing check for two pairs. 123 | function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) { 124 | G1Point[] memory p1 = new G1Point[](2); 125 | G2Point[] memory p2 = new G2Point[](2); 126 | p1[0] = a1; 127 | p1[1] = b1; 128 | p2[0] = a2; 129 | p2[1] = b2; 130 | return pairing(p1, p2); 131 | } 132 | /// Convenience method for a pairing check for three pairs. 133 | function pairingProd3( 134 | G1Point memory a1, G2Point memory a2, 135 | G1Point memory b1, G2Point memory b2, 136 | G1Point memory c1, G2Point memory c2 137 | ) internal view returns (bool) { 138 | G1Point[] memory p1 = new G1Point[](3); 139 | G2Point[] memory p2 = new G2Point[](3); 140 | p1[0] = a1; 141 | p1[1] = b1; 142 | p1[2] = c1; 143 | p2[0] = a2; 144 | p2[1] = b2; 145 | p2[2] = c2; 146 | return pairing(p1, p2); 147 | } 148 | /// Convenience method for a pairing check for four pairs. 149 | function pairingProd4( 150 | G1Point memory a1, G2Point memory a2, 151 | G1Point memory b1, G2Point memory b2, 152 | G1Point memory c1, G2Point memory c2, 153 | G1Point memory d1, G2Point memory d2 154 | ) internal view returns (bool) { 155 | G1Point[] memory p1 = new G1Point[](4); 156 | G2Point[] memory p2 = new G2Point[](4); 157 | p1[0] = a1; 158 | p1[1] = b1; 159 | p1[2] = c1; 160 | p1[3] = d1; 161 | p2[0] = a2; 162 | p2[1] = b2; 163 | p2[2] = c2; 164 | p2[3] = d2; 165 | return pairing(p1, p2); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /contracts/libraries/SNARKs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Pairing } from "./Pairing.sol"; 4 | import { Proof } from "./Types.sol"; 5 | 6 | library SNARKsVerifier { 7 | using Pairing for *; 8 | struct VerifyingKey { 9 | Pairing.G1Point alfa1; 10 | Pairing.G2Point beta2; 11 | Pairing.G2Point gamma2; 12 | Pairing.G2Point delta2; 13 | Pairing.G1Point[] ic; 14 | } 15 | 16 | function zkSNARKs(VerifyingKey memory vk, uint[] memory input, Proof memory proof) internal view returns (bool) { 17 | uint256 SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 18 | require(input.length + 1 == vk.ic.length,"verifier-bad-input"); 19 | // Compute the linear combination vkX 20 | Pairing.G1Point memory vkX = Pairing.G1Point(0, 0); 21 | for (uint i = 0; i < input.length; i++) { 22 | require(input[i] < SNARK_SCALAR_FIELD,"verifier-gte-snark-scalar-field"); 23 | vkX = Pairing.addition(vkX, Pairing.scalar_mul(vk.ic[i + 1], input[i])); 24 | } 25 | vkX = Pairing.addition(vkX, vk.ic[0]); 26 | if ( 27 | !Pairing.pairingProd4( 28 | Pairing.negate(proof.a), proof.b, 29 | vk.alfa1, vk.beta2, 30 | vkX, vk.gamma2, 31 | proof.c, vk.delta2 32 | ) 33 | ) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/storage/Configurated.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | 4 | contract Configurated { 5 | /** 6 | * Constants to manage this layer2 system. 7 | * Rationales: https://github.com/wilsonbeam/zk-optimistic-rollup/wiki 8 | */ 9 | uint constant public CHALLENGE_PERIOD = 7 days; 10 | uint constant public CHALLENGE_LIMIT = 8000000; 11 | uint constant public MINIMUM_STAKE = 32 ether; 12 | uint constant public REF_DEPTH = 128; 13 | uint constant public POOL_SIZE = (1 << 31); 14 | uint constant public SUB_TREE_DEPTH = 5; // 32 items at once 15 | uint constant public SUB_TREE_SIZE = 1 << SUB_TREE_DEPTH; 16 | } 17 | -------------------------------------------------------------------------------- /contracts/storage/Layer2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import "../libraries/Types.sol"; 4 | import { Pairing } from "../libraries/Pairing.sol"; 5 | import { SNARKsVerifier } from "../libraries/SNARKs.sol"; 6 | import { Configurated } from "./Configurated.sol"; 7 | import { OPRU, SplitRollUp } from "../../node_modules/merkle-tree-rollup/contracts/library/Types.sol"; 8 | import { SMT256 } from "../../node_modules/smt-rollup/contracts/SMT.sol"; 9 | 10 | struct RollUpProofs { 11 | SplitRollUp[] ofUTXORollUp; 12 | SMT256.OPRU[] ofNullifierRollUp; 13 | SplitRollUp[] ofWithdrawalRollUp; 14 | mapping(uint8=>mapping(uint=>address)) permittedTo; 15 | } 16 | 17 | contract Layer2 is Configurated { 18 | /** State of the layer2 blockchain is maintained by the optimistic roll up */ 19 | Blockchain chain; 20 | 21 | /** SNARKs verifying keys assigned by the setup wizard for each tx type */ 22 | mapping(bytes32=>SNARKsVerifier.VerifyingKey) vks; 23 | 24 | /** Addresses allowed to migrate from. Setup wizard manages the list */ 25 | mapping(address=>bool) allowedMigrants; 26 | 27 | /** Roll up proofs for challenge */ 28 | RollUpProofs proof; 29 | } 30 | -------------------------------------------------------------------------------- /contracts/utils/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IERC20 { 4 | function transfer(address recipient, uint256 amount) external returns (bool); 5 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/utils/IERC721.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | interface IERC721 { 4 | function transferFrom(address from, address to, uint256 tokenId) external; 5 | } -------------------------------------------------------------------------------- /contracts/utils/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | /** 4 | * Truffle migration contract 5 | */ 6 | contract Migrations { 7 | address public owner; 8 | uint public last_completed_migration; 9 | constructor() public { 10 | owner = msg.sender; 11 | } 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | 20 | function upgrade(address new_address) public restricted { 21 | Migrations upgraded = Migrations(new_address); 22 | upgraded.setCompleted(last_completed_migration); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/utils/test_helpers/DeserializerHelper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.6.0; 2 | 3 | import { Deserializer } from "../../libraries/Deserializer.sol"; 4 | import { Block, Inflow, Outflow, PublicData, Proof } from "../../libraries/Types.sol"; 5 | 6 | contract DeserializationTester { 7 | function getProposer(bytes calldata) external pure returns (address) { 8 | Block memory _block = Deserializer.blockFromCalldataAt(0); 9 | return _block.header.proposer; 10 | } 11 | 12 | function getParentBlock(bytes calldata) external pure returns (bytes32) { 13 | Block memory _block = Deserializer.blockFromCalldataAt(0); 14 | return _block.header.parentBlock; 15 | } 16 | 17 | function getUTXORollUp(bytes calldata) 18 | external 19 | pure 20 | returns ( 21 | uint256 prevRoot, 22 | uint256 prevIndex, 23 | uint256 nextRoot, 24 | uint256 nextIndex 25 | ) { 26 | Block memory _block = Deserializer.blockFromCalldataAt(0); 27 | prevRoot = _block.header.prevUTXORoot; 28 | prevIndex = _block.header.prevUTXOIndex; 29 | nextRoot = _block.header.nextUTXORoot; 30 | nextIndex = _block.header.nextUTXOIndex; 31 | } 32 | 33 | function getNullifierRollUp(bytes calldata) 34 | external 35 | pure 36 | returns ( 37 | bytes32 prevRoot, 38 | bytes32 nextRoot 39 | ) { 40 | Block memory _block = Deserializer.blockFromCalldataAt(0); 41 | prevRoot = _block.header.prevNullifierRoot; 42 | nextRoot = _block.header.nextNullifierRoot; 43 | } 44 | 45 | function getWithdrawalRollUp(bytes calldata) 46 | external 47 | pure 48 | returns ( 49 | bytes32 prevRoot, 50 | uint256 prevIndex, 51 | bytes32 nextRoot, 52 | uint256 nextIndex 53 | ) { 54 | Block memory _block = Deserializer.blockFromCalldataAt(0); 55 | prevRoot = _block.header.prevWithdrawalRoot; 56 | prevIndex = _block.header.prevWithdrawalIndex; 57 | nextRoot = _block.header.nextWithdrawalRoot; 58 | nextIndex = _block.header.nextWithdrawalIndex; 59 | } 60 | 61 | function getTxRoot(bytes calldata) external pure returns (bytes32) { 62 | Block memory _block = Deserializer.blockFromCalldataAt(0); 63 | return _block.header.txRoot; 64 | } 65 | 66 | function getMassDepositRoot(bytes calldata) external pure returns (bytes32) { 67 | Block memory _block = Deserializer.blockFromCalldataAt(0); 68 | return _block.header.depositRoot; 69 | } 70 | 71 | function getMassMigrationRoot(bytes calldata) external pure returns (bytes32) { 72 | Block memory _block = Deserializer.blockFromCalldataAt(0); 73 | return _block.header.migrationRoot; 74 | } 75 | 76 | function getTxInflow( 77 | uint txIndex, 78 | uint inflowIndex, 79 | bytes calldata 80 | ) external pure returns ( 81 | uint256 inclusionRoot, 82 | bytes32 nullifier 83 | ) { 84 | Block memory _block = Deserializer.blockFromCalldataAt(1); 85 | Inflow memory inflow = _block.body.txs[txIndex].inflow[inflowIndex]; 86 | inclusionRoot = inflow.inclusionRoot; 87 | nullifier = inflow.nullifier; 88 | } 89 | 90 | function getTxOutflow( 91 | uint txIndex, 92 | uint outflowIndex, 93 | bytes calldata 94 | ) external pure returns ( 95 | uint256 note, 96 | address to, 97 | uint256 eth, 98 | address token, 99 | uint256 amount, 100 | uint256 nft, 101 | uint256 fee 102 | ) { 103 | Block memory _block = Deserializer.blockFromCalldataAt(1); 104 | Outflow memory outflow = _block.body.txs[txIndex].outflow[outflowIndex]; 105 | note = outflow.note; 106 | to = outflow.publicData.to; 107 | eth = outflow.publicData.eth; 108 | token = outflow.publicData.token; 109 | amount = outflow.publicData.amount; 110 | nft = outflow.publicData.nft; 111 | fee = outflow.publicData.fee; 112 | } 113 | 114 | function getTxSwap(uint txIndex) external pure returns (uint256) { 115 | Block memory _block = Deserializer.blockFromCalldataAt(1); 116 | return _block.body.txs[txIndex].swap; 117 | } 118 | 119 | function getProof(uint txIndex) external pure returns (uint256[8] memory proof) { 120 | Block memory _block = Deserializer.blockFromCalldataAt(1); 121 | Proof memory _proof = _block.body.txs[txIndex].proof; 122 | proof = [_proof.a.X, _proof.a.Y, _proof.b.X[0], _proof.b.X[1], _proof.b.Y[0], _proof.b.Y[1], _proof.c.X, _proof.c.Y]; 123 | } 124 | 125 | function getTxFee(uint txIndex) external pure returns (uint256) { 126 | Block memory _block = Deserializer.blockFromCalldataAt(1); 127 | return _block.body.txs[txIndex].fee; 128 | } 129 | } -------------------------------------------------------------------------------- /data/tree1/000004.sst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/tree1/000004.sst -------------------------------------------------------------------------------- /data/tree1/000007.sst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/tree1/000007.sst -------------------------------------------------------------------------------- /data/tree1/000010.sst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/tree1/000010.sst -------------------------------------------------------------------------------- /data/tree1/CURRENT: -------------------------------------------------------------------------------- 1 | MANIFEST-000011 2 | -------------------------------------------------------------------------------- /data/tree1/IDENTITY: -------------------------------------------------------------------------------- 1 | dac77d43-e4a1-43ff-939f-e0d1246fa280 2 | -------------------------------------------------------------------------------- /data/tree1/LOCK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/tree1/LOCK -------------------------------------------------------------------------------- /data/tree1/MANIFEST-000011: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/tree1/MANIFEST-000011 -------------------------------------------------------------------------------- /data/tree1/OPTIONS-000011: -------------------------------------------------------------------------------- 1 | # This is a RocksDB option file. 2 | # 3 | # For detailed file format spec, please refer to the example file 4 | # in examples/rocksdb_option_file_example.ini 5 | # 6 | 7 | [Version] 8 | rocksdb_version=5.3.0 9 | options_file_version=1.1 10 | 11 | [DBOptions] 12 | avoid_flush_during_recovery=false 13 | max_file_opening_threads=16 14 | use_direct_writes=false 15 | access_hint_on_compaction_start=NORMAL 16 | create_if_missing=true 17 | allow_fallocate=true 18 | paranoid_checks=false 19 | delayed_write_rate=16777216 20 | create_missing_column_families=false 21 | use_direct_reads=false 22 | WAL_size_limit_MB=0 23 | max_log_file_size=2097152 24 | is_fd_close_on_exec=true 25 | random_access_max_buffer_size=1048576 26 | delete_obsolete_files_period_micros=21600000000 27 | enable_thread_tracking=false 28 | error_if_exists=false 29 | new_table_reader_for_compaction_inputs=false 30 | skip_log_error_on_recovery=false 31 | avoid_flush_during_shutdown=false 32 | skip_stats_update_on_db_open=false 33 | compaction_readahead_size=0 34 | max_background_flushes=1 35 | allow_2pc=false 36 | writable_file_max_buffer_size=1048576 37 | allow_mmap_reads=false 38 | allow_mmap_writes=false 39 | use_adaptive_mutex=false 40 | use_fsync=false 41 | db_log_dir= 42 | base_background_compactions=1 43 | max_open_files=1000 44 | table_cache_numshardbits=6 45 | db_write_buffer_size=0 46 | allow_concurrent_memtable_write=true 47 | recycle_log_file_num=0 48 | log_file_time_to_roll=0 49 | manifest_preallocation_size=4194304 50 | max_background_compactions=1 51 | enable_write_thread_adaptive_yield=true 52 | wal_dir=build/1.tree 53 | WAL_ttl_seconds=0 54 | max_subcompactions=1 55 | dump_malloc_stats=false 56 | bytes_per_sync=0 57 | max_manifest_file_size=18446744073709551615 58 | wal_bytes_per_sync=0 59 | wal_recovery_mode=kPointInTimeRecovery 60 | keep_log_file_num=1000 61 | max_total_wal_size=0 62 | stats_dump_period_sec=600 63 | fail_if_options_file_error=false 64 | write_thread_slow_yield_usec=3 65 | write_thread_max_yield_usec=100 66 | advise_random_on_open=true 67 | info_log_level=HEADER_LEVEL 68 | 69 | 70 | [CFOptions "default"] 71 | compaction_pri=kByCompensatedSize 72 | compaction_filter_factory=nullptr 73 | memtable_factory=SkipListFactory 74 | bottommost_compression=kDisableCompressionOption 75 | compression=kSnappyCompression 76 | max_sequential_skip_in_iterations=8 77 | max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 78 | compression_per_level= 79 | max_bytes_for_level_multiplier=10.000000 80 | max_bytes_for_level_base=268435456 81 | table_factory=BlockBasedTable 82 | max_successive_merges=0 83 | arena_block_size=524288 84 | merge_operator=nullptr 85 | target_file_size_multiplier=1 86 | num_levels=7 87 | min_write_buffer_number_to_merge=1 88 | prefix_extractor=nullptr 89 | bloom_locality=0 90 | max_write_buffer_number=2 91 | level0_stop_writes_trigger=36 92 | level0_slowdown_writes_trigger=20 93 | level0_file_num_compaction_trigger=4 94 | write_buffer_size=4194304 95 | memtable_huge_page_size=0 96 | max_compaction_bytes=1677721600 97 | hard_pending_compaction_bytes_limit=274877906944 98 | target_file_size_base=67108864 99 | soft_pending_compaction_bytes_limit=68719476736 100 | comparator=leveldb.BytewiseComparator 101 | memtable_insert_with_hint_prefix_extractor=nullptr 102 | force_consistency_checks=false 103 | max_write_buffer_number_to_maintain=0 104 | paranoid_file_checks=false 105 | optimize_filters_for_hits=false 106 | level_compaction_dynamic_level_bytes=false 107 | purge_redundant_kvs_while_flush=true 108 | inplace_update_support=false 109 | compaction_style=kCompactionStyleLevel 110 | compaction_filter=nullptr 111 | disable_auto_compactions=false 112 | inplace_update_num_locks=10000 113 | memtable_prefix_bloom_size_ratio=0.000000 114 | report_bg_io_stats=false 115 | 116 | [TableOptions/BlockBasedTable "default"] 117 | read_amp_bytes_per_bit=8589934592 118 | format_version=2 119 | whole_key_filtering=true 120 | filter_policy=rocksdb.BuiltinBloomFilter 121 | verify_compression=false 122 | block_size_deviation=10 123 | block_size=4096 124 | partition_filters=false 125 | checksum=kCRC32c 126 | hash_index_allow_collision=true 127 | index_block_restart_interval=1 128 | block_restart_interval=16 129 | no_block_cache=false 130 | pin_l0_filter_and_index_blocks_in_cache=false 131 | cache_index_and_filter_blocks_with_high_priority=false 132 | metadata_block_size=4096 133 | cache_index_and_filter_blocks=false 134 | index_type=kBinarySearch 135 | flush_block_policy_factory=FlushBlockBySizePolicyFactory 136 | 137 | -------------------------------------------------------------------------------- /data/tree1/OPTIONS-000014: -------------------------------------------------------------------------------- 1 | # This is a RocksDB option file. 2 | # 3 | # For detailed file format spec, please refer to the example file 4 | # in examples/rocksdb_option_file_example.ini 5 | # 6 | 7 | [Version] 8 | rocksdb_version=5.3.0 9 | options_file_version=1.1 10 | 11 | [DBOptions] 12 | avoid_flush_during_recovery=false 13 | max_file_opening_threads=16 14 | use_direct_writes=false 15 | access_hint_on_compaction_start=NORMAL 16 | create_if_missing=true 17 | allow_fallocate=true 18 | paranoid_checks=false 19 | delayed_write_rate=16777216 20 | create_missing_column_families=false 21 | use_direct_reads=false 22 | WAL_size_limit_MB=0 23 | max_log_file_size=2097152 24 | is_fd_close_on_exec=true 25 | random_access_max_buffer_size=1048576 26 | delete_obsolete_files_period_micros=21600000000 27 | enable_thread_tracking=false 28 | error_if_exists=false 29 | new_table_reader_for_compaction_inputs=false 30 | skip_log_error_on_recovery=false 31 | avoid_flush_during_shutdown=false 32 | skip_stats_update_on_db_open=false 33 | compaction_readahead_size=0 34 | max_background_flushes=1 35 | allow_2pc=false 36 | writable_file_max_buffer_size=1048576 37 | allow_mmap_reads=false 38 | allow_mmap_writes=false 39 | use_adaptive_mutex=false 40 | use_fsync=false 41 | db_log_dir= 42 | base_background_compactions=1 43 | max_open_files=1000 44 | table_cache_numshardbits=6 45 | db_write_buffer_size=0 46 | allow_concurrent_memtable_write=true 47 | recycle_log_file_num=0 48 | log_file_time_to_roll=0 49 | manifest_preallocation_size=4194304 50 | max_background_compactions=1 51 | enable_write_thread_adaptive_yield=true 52 | wal_dir=data/tree1 53 | WAL_ttl_seconds=0 54 | max_subcompactions=1 55 | dump_malloc_stats=false 56 | bytes_per_sync=0 57 | max_manifest_file_size=18446744073709551615 58 | wal_bytes_per_sync=0 59 | wal_recovery_mode=kPointInTimeRecovery 60 | keep_log_file_num=1000 61 | max_total_wal_size=0 62 | stats_dump_period_sec=600 63 | fail_if_options_file_error=false 64 | write_thread_slow_yield_usec=3 65 | write_thread_max_yield_usec=100 66 | advise_random_on_open=true 67 | info_log_level=HEADER_LEVEL 68 | 69 | 70 | [CFOptions "default"] 71 | compaction_pri=kByCompensatedSize 72 | compaction_filter_factory=nullptr 73 | memtable_factory=SkipListFactory 74 | bottommost_compression=kDisableCompressionOption 75 | compression=kSnappyCompression 76 | max_sequential_skip_in_iterations=8 77 | max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 78 | compression_per_level= 79 | max_bytes_for_level_multiplier=10.000000 80 | max_bytes_for_level_base=268435456 81 | table_factory=BlockBasedTable 82 | max_successive_merges=0 83 | arena_block_size=524288 84 | merge_operator=nullptr 85 | target_file_size_multiplier=1 86 | num_levels=7 87 | min_write_buffer_number_to_merge=1 88 | prefix_extractor=nullptr 89 | bloom_locality=0 90 | max_write_buffer_number=2 91 | level0_stop_writes_trigger=36 92 | level0_slowdown_writes_trigger=20 93 | level0_file_num_compaction_trigger=4 94 | write_buffer_size=4194304 95 | memtable_huge_page_size=0 96 | max_compaction_bytes=1677721600 97 | hard_pending_compaction_bytes_limit=274877906944 98 | target_file_size_base=67108864 99 | soft_pending_compaction_bytes_limit=68719476736 100 | comparator=leveldb.BytewiseComparator 101 | memtable_insert_with_hint_prefix_extractor=nullptr 102 | force_consistency_checks=false 103 | max_write_buffer_number_to_maintain=0 104 | paranoid_file_checks=false 105 | optimize_filters_for_hits=false 106 | level_compaction_dynamic_level_bytes=false 107 | purge_redundant_kvs_while_flush=true 108 | inplace_update_support=false 109 | compaction_style=kCompactionStyleLevel 110 | compaction_filter=nullptr 111 | disable_auto_compactions=false 112 | inplace_update_num_locks=10000 113 | memtable_prefix_bloom_size_ratio=0.000000 114 | report_bg_io_stats=false 115 | 116 | [TableOptions/BlockBasedTable "default"] 117 | read_amp_bytes_per_bit=8589934592 118 | format_version=2 119 | whole_key_filtering=true 120 | filter_policy=rocksdb.BuiltinBloomFilter 121 | verify_compression=false 122 | block_size_deviation=10 123 | block_size=4096 124 | partition_filters=false 125 | checksum=kCRC32c 126 | hash_index_allow_collision=true 127 | index_block_restart_interval=1 128 | block_restart_interval=16 129 | no_block_cache=false 130 | pin_l0_filter_and_index_blocks_in_cache=false 131 | cache_index_and_filter_blocks_with_high_priority=false 132 | metadata_block_size=4096 133 | cache_index_and_filter_blocks=false 134 | index_type=kBinarySearch 135 | flush_block_policy_factory=FlushBlockBySizePolicyFactory 136 | 137 | -------------------------------------------------------------------------------- /data/txs/zk_tx_1.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/txs/zk_tx_1.tx -------------------------------------------------------------------------------- /data/txs/zk_tx_2_1.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/txs/zk_tx_2_1.tx -------------------------------------------------------------------------------- /data/txs/zk_tx_2_2.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/txs/zk_tx_2_2.tx -------------------------------------------------------------------------------- /data/txs/zk_tx_3.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/txs/zk_tx_3.tx -------------------------------------------------------------------------------- /data/txs/zk_tx_4.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/data/txs/zk_tx_4.tx -------------------------------------------------------------------------------- /migrations/10_deposit_challenge.js: -------------------------------------------------------------------------------- 1 | const DepositChallenge = artifacts.require('DepositChallenge'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(DepositChallenge); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/11_migration_challenge.js: -------------------------------------------------------------------------------- 1 | const MigrationChallenge = artifacts.require('MigrationChallenge'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(MigrationChallenge); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/12_migratable.js: -------------------------------------------------------------------------------- 1 | const Migratable = artifacts.require('Migratable'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migratable); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/13_zk_optimistic_rollup.js: -------------------------------------------------------------------------------- 1 | const Poseidon = artifacts.require('Poseidon'); 2 | const ZkOPRU = artifacts.require('ZkOptimisticRollUp'); 3 | 4 | module.exports = function(deployer, _, accounts) { 5 | deployer.link(Poseidon, ZkOPRU); 6 | deployer.deploy(ZkOPRU, accounts[0]); 7 | }; 8 | -------------------------------------------------------------------------------- /migrations/14_setup_wizard.js: -------------------------------------------------------------------------------- 1 | const Poseidon = artifacts.require('Poseidon'); 2 | const TestERC20 = artifacts.require('TestERC20'); 3 | const UserInteractable = artifacts.require('UserInteractable'); 4 | const RollUpable = artifacts.require('RollUpable'); 5 | const RollUpChallenge = artifacts.require('RollUpChallenge'); 6 | const DepositChallenge = artifacts.require('DepositChallenge'); 7 | const HeaderChallenge = artifacts.require('HeaderChallenge'); 8 | const TxChallenge = artifacts.require('TxChallenge'); 9 | const MigrationChallenge = artifacts.require('MigrationChallenge'); 10 | const Migratable = artifacts.require('Migratable'); 11 | const ZkOPRU = artifacts.require('ZkOptimisticRollUp'); 12 | const ISetupWizard = artifacts.require('ISetupWizard'); 13 | 14 | let instances = {}; 15 | 16 | module.exports = function(deployer, _, accounts) { 17 | deployer 18 | .then(function() { 19 | return TestERC20.deployed(); 20 | }) 21 | .then(function(erc20) { 22 | instances.erc20 = erc20; 23 | return UserInteractable.deployed(); 24 | }) 25 | .then(function(ui) { 26 | instances.ui = ui; 27 | return RollUpable.deployed(); 28 | }) 29 | .then(function(rollUp) { 30 | instances.rollup = rollUp; 31 | return RollUpChallenge.deployed(); 32 | }) 33 | .then(function(rollUpChallenge) { 34 | instances.rollUpChallenge = rollUpChallenge; 35 | return HeaderChallenge.deployed(); 36 | }) 37 | .then(function(headerChallenge) { 38 | instances.headerChallenge = headerChallenge; 39 | return TxChallenge.deployed(); 40 | }) 41 | .then(function(txChallenge) { 42 | instances.txChallenge = txChallenge; 43 | return DepositChallenge.deployed(); 44 | }) 45 | .then(function(depositChallenge) { 46 | instances.depositChallenge = depositChallenge; 47 | return MigrationChallenge.deployed(); 48 | }) 49 | .then(function(migrationChallenge) { 50 | instances.migrationChallenge = migrationChallenge; 51 | return Migratable.deployed(); 52 | }) 53 | .then(function(migratable) { 54 | instances.migratable = migratable; 55 | return ZkOPRU.deployed(); 56 | }) 57 | .then(function(coordinatable) { 58 | return ISetupWizard.at(coordinatable.address); 59 | }) 60 | .then(async function(wizard) { 61 | // Setup proxy 62 | await wizard.makeUserInteractable(instances.ui.address); 63 | await wizard.makeRollUpable(instances.rollup.address); 64 | await wizard.makeChallengeable( 65 | instances.depositChallenge.address, 66 | instances.headerChallenge.address, 67 | instances.migrationChallenge.address, 68 | instances.rollUpChallenge.address, 69 | instances.txChallenge.address 70 | ); 71 | await wizard.makeMigratable(instances.migratable.address); 72 | // Setup zkSNARKs 73 | // await wizard.registerVk(...) 74 | // Setup migrations 75 | // await wizard.allowMigrants(...) 76 | // Complete setup 77 | await wizard.completeSetup(); 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_poseidon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | const path = require('path'); 22 | 23 | const poseidonGenContract = require('circomlib/src/poseidon_gencontract.js'); 24 | const Artifactor = require('truffle-artifactor'); 25 | 26 | const SEED = 'poseidon'; 27 | 28 | module.exports = function(deployer) { 29 | return deployer.then(async () => { 30 | const contractsDir = path.join(__dirname, '..', 'build/contracts'); 31 | let artifactor = new Artifactor(contractsDir); 32 | let poseidonContractName = 'Poseidon'; 33 | await artifactor 34 | .save({ 35 | contractName: poseidonContractName, 36 | abi: poseidonGenContract.abi, 37 | unlinked_binary: poseidonGenContract.createCode(6, 8, 57, SEED) 38 | }) 39 | .then(async () => { 40 | const Poseidon = artifacts.require(poseidonContractName); 41 | await deployer.deploy(Poseidon); 42 | }); 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /migrations/3_mimc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | const path = require('path'); 22 | 23 | const mimcGenContract = require('circomlib/src/mimcsponge_gencontract.js'); 24 | const Artifactor = require('truffle-artifactor'); 25 | 26 | const SEED = 'mimcsponge'; 27 | 28 | module.exports = function(deployer) { 29 | return deployer.then(async () => { 30 | const contractsDir = path.join(__dirname, '..', 'build/contracts'); 31 | let artifactor = new Artifactor(contractsDir); 32 | let mimcContractName = 'MiMC'; 33 | await artifactor 34 | .save({ 35 | contractName: mimcContractName, 36 | abi: mimcGenContract.abi, 37 | unlinked_binary: mimcGenContract.createCode(SEED, 220) 38 | }) 39 | .then(async () => { 40 | const MiMC = artifacts.require(mimcContractName); 41 | await deployer.deploy(MiMC); 42 | }); 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /migrations/4_erc20.js: -------------------------------------------------------------------------------- 1 | const TestERC20 = artifacts.require('TestERC20'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(TestERC20, web3.utils.toWei('1000000000')); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/5_ui.js: -------------------------------------------------------------------------------- 1 | const Poseidon = artifacts.require('Poseidon'); 2 | const MiMC = artifacts.require('MiMC'); 3 | const UserInteractable = artifacts.require('UserInteractable'); 4 | 5 | module.exports = function(deployer) { 6 | deployer.link(Poseidon, UserInteractable); 7 | deployer.link(MiMC, UserInteractable); 8 | deployer.deploy(UserInteractable); 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/6_rollup.js: -------------------------------------------------------------------------------- 1 | const Poseidon = artifacts.require('Poseidon'); 2 | const RollUpable = artifacts.require('RollUpable'); 3 | 4 | module.exports = function(deployer) { 5 | deployer.link(Poseidon, RollUpable); 6 | deployer.deploy(RollUpable); 7 | }; 8 | -------------------------------------------------------------------------------- /migrations/7_rollup_challenge.js: -------------------------------------------------------------------------------- 1 | const RollUpChallenge = artifacts.require('RollUpChallenge'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(RollUpChallenge); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/8_header_challenge.js: -------------------------------------------------------------------------------- 1 | const HeaderChallenge = artifacts.require('HeaderChallenge'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(HeaderChallenge); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/9_tx_challenge.js: -------------------------------------------------------------------------------- 1 | const TxChallenge = artifacts.require('TxChallenge'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(TxChallenge); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zk-optimistic-rollup", 3 | "version": "0.0.1", 4 | "description": "Private token pool with optimistic rollup for zero knowledge transfer", 5 | "main": "dist/src/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "compile": "./node_modules/.bin/truffle compile", 9 | "circuit": "./script/compile_circuits.sh", 10 | "setup": "./script/snark_setup.sh", 11 | "test-setup": "./script/compile_circuits_for_test.sh && ./script/snark_setup_for_test.sh", 12 | "verifier": "./node_modules/.bin/snarkjs generateverifier --vk build/verification_key.json -v contracts/ZkTransferVerifier.sol", 13 | "test": "yarn compile && yarn testSolidity && yarn testTS", 14 | "test-sol": "./node_modules/.bin/truffle test", 15 | "test-ts": "yarn ts && mocha -r ts-node/register test/**/*.test.ts --timeout 0 -s 0", 16 | "ts": "./node_modules/.bin/tsc", 17 | "prettier": "./node_modules/.bin/prettier --single-quote --write --print-width 160 ./**/*.ts", 18 | "coverage": "./node_modules/.bin/solidity-coverage", 19 | "web3TS": "./node_modules/.bin/truffle compile && typechain --target web3-v1 './build/**/*.json' --outDir src/contracts", 20 | "truffleTS": "./node_modules/.bin/truffle compile && typechain --target truffle './build/**/*.json' --outDir src/types/truffle-contracts" 21 | }, 22 | "dependencies": { 23 | "chacha20": "^0.1.4", 24 | "circom": "^0.0.34", 25 | "circomlib": "^0.0.21", 26 | "level-rocksdb": "^4.0.0", 27 | "semaphore-merkle-tree": "^1.0.12", 28 | "snarkjs": "^0.1.20", 29 | "web3": "^1.2.6", 30 | "web3-utils": "^1.2.6" 31 | }, 32 | "devDependencies": { 33 | "@truffle/hdwallet-provider": "^1.0.33", 34 | "@types/chai": "^4.2.4", 35 | "@types/elliptic": "^6.4.12", 36 | "@types/fs-extra": "^8.0.1", 37 | "@types/jest": "25.1.0", 38 | "@types/levelup": "^3.1.1", 39 | "@types/mocha": "^5.2.7", 40 | "@types/rocksdb": "^3.0.0", 41 | "@types/web3-provider-engine": "^14.0.0", 42 | "bignumber.js": "^9.0.0", 43 | "bip39": "^3.0.2", 44 | "chai": "^4.2.0", 45 | "chai-as-promised": "^7.1.1", 46 | "chai-bn": "^0.2.0", 47 | "eth-gas-reporter": "^0.2.12", 48 | "ethlint": "^1.2.5", 49 | "husky": "^3.0.9", 50 | "libsemaphore": "^1.0.14", 51 | "lint-staged": "^9.4.2", 52 | "merkle-tree-rollup": "^1.1.4", 53 | "mocha": "^6.2.2", 54 | "prettier": "^1.19.1", 55 | "smt-rollup": "github:wilsonbeam/smt-rollup", 56 | "solc": "0.5.15", 57 | "solium": "^1.2.5", 58 | "truffle": "^5.1.10", 59 | "truffle-artifactor": "^4.0.30", 60 | "truffle-typings": "1.0.4", 61 | "ts-generator": "^0.0.8", 62 | "ts-node": "^8.7.0", 63 | "ts-postgres": "^1.1.3", 64 | "typechain": "^1.0.5", 65 | "typechain-target-truffle": "^1.0.2", 66 | "typescript": "^3.7.2", 67 | "wasmbuilder": "^0.0.8", 68 | "websnark": "^0.0.5" 69 | }, 70 | "lint-staged": { 71 | "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ 72 | "prettier --single-quote --write --print-width 160", 73 | "git add" 74 | ], 75 | ".contracts/**/*.{sol}": [ 76 | "solium -d contracts/ --fix", 77 | "git add" 78 | ] 79 | }, 80 | "husky": { 81 | "hooks": { 82 | "pre-commit": "lint-staged && npm run test" 83 | } 84 | }, 85 | "repository": { 86 | "type": "git", 87 | "url": "git+https://github.com/wilsonbeam/zk-optimistic-rollup.git" 88 | }, 89 | "keywords": [ 90 | "optimistic", 91 | "rollup", 92 | "zk", 93 | "transfer", 94 | "private", 95 | "pool" 96 | ], 97 | "author": "Wilson Beam ", 98 | "license": "GPL-3.0-or-later", 99 | "bugs": { 100 | "url": "https://github.com/wilsonbeam/zk-optimistic-rollup/issues" 101 | }, 102 | "homepage": "https://github.com/wilsonbeam/zk-optimistic-rollup#readme" 103 | } 104 | -------------------------------------------------------------------------------- /script/compile_circuits.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname "$0") 4 | ARTIFACTS="build/circuits" 5 | MAX_JOB=8 6 | cd $BASEDIR/.. 7 | mkdir -p $ARTIFACTS 8 | 9 | i=0 10 | for circuit in "circuits/impls"/*.circom; 11 | do 12 | i=$(($i+1)) 13 | echo "Compiling $circuit" 14 | filename="$ARTIFACTS/$(basename "$circuit" ".circom").json" 15 | NODE_OPTIONS="--max-old-space-size=4096" ./node_modules/.bin/circom "$circuit" -o $filename & 16 | if (( $i % $MAX_JOB == 0 )); then wait; fi 17 | done 18 | wait; -------------------------------------------------------------------------------- /script/compile_circuits_for_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname "$0") 4 | ARTIFACTS="build/circuits.test/" 5 | MAX_JOB=8 6 | cd $BASEDIR/.. 7 | mkdir -p $ARTIFACTS 8 | 9 | i=0 10 | for circuit in "circuits/tester"/*.circom; 11 | do 12 | i=$(($i+1)) 13 | echo "Compiling $circuit" 14 | filename="$ARTIFACTS/$(basename "$circuit" ".circom").json" 15 | NODE_OPTIONS="--max-old-space-size=4096" ./node_modules/.bin/circom "$circuit" -o $filename & 16 | if (( $i % $MAX_JOB == 0 )); then wait; fi 17 | done 18 | wait; -------------------------------------------------------------------------------- /script/snark_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname "$0") 4 | VK_ARTIFACTS="build/vks" 5 | PK_ARTIFACTS="build/pks" 6 | MAX_JOB=4 7 | cd $BASEDIR/.. 8 | mkdir -p $VK_ARTIFACTS 9 | mkdir -p $PK_ARTIFACTS 10 | 11 | i=0 12 | for circuit in "build/circuits"/*.json; 13 | do 14 | i=$(($i+1)) 15 | echo "Running setup $circuit" 16 | circuit_name="$(basename "$circuit" ".json")" 17 | NODE_OPTIONS="--max-old-space-size=4096" ./node_modules/.bin/snarkjs setup -c "$circuit" -o --pk "$PK_ARTIFACTS/$circuit_name.pk.json" --vk "$VK_ARTIFACTS/$circuit_name.vk.json" --protocol groth & 18 | if (( $i % $MAX_JOB == 0 )); then wait; fi 19 | done 20 | wait -------------------------------------------------------------------------------- /script/snark_setup_for_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname "$0") 4 | CIRCUIT_PATH="build/circuits.test" 5 | VK_ARTIFACTS="build/vks.test" 6 | PK_ARTIFACTS="build/pks.test" 7 | MAX_JOB=4 8 | cd $BASEDIR/.. 9 | mkdir -p $VK_ARTIFACTS 10 | mkdir -p $PK_ARTIFACTS 11 | 12 | i=0 13 | for circuit in "$CIRCUIT_PATH"/*.json; 14 | do 15 | i=$(($i+1)) 16 | echo "Running setup $circuit" 17 | circuit_name="$(basename "$circuit" ".json")" 18 | NODE_OPTIONS="--max-old-space-size=4096" ./node_modules/.bin/snarkjs setup -c "$circuit" -o --pk "$PK_ARTIFACTS/$circuit_name.pk.json" --vk "$VK_ARTIFACTS/$circuit_name.vk.json" --protocol groth & 19 | if (( $i % $MAX_JOB == 0 )); then wait; fi 20 | done 21 | wait -------------------------------------------------------------------------------- /script/testEnvSetUp.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const snark = require('snarkjs'); 4 | 5 | const circuitDir = path.resolve(path.dirname(__filename), '../circuits/impls'); 6 | const circuits = fs.readdirSync(circuitDir); 7 | const circuitDefs = {}; 8 | for (let circuit of circuits) { 9 | let circuitPath = path.resolve(circuitDir, circuit); 10 | circuitDefs[circuit] = JSON.parse(fs.readFileSync(circuitPath)); 11 | } 12 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import repl from 'repl'; 2 | 3 | repl.start('Zk Wizard > '); 4 | -------------------------------------------------------------------------------- /src/coordinator.ts: -------------------------------------------------------------------------------- 1 | import { Hex } from 'web3-utils'; 2 | import { ZkTransaction } from './zk_transaction'; 3 | import { root } from './utils'; 4 | import * as snarkjs from 'snarkjs'; 5 | 6 | export interface Block { 7 | header: Header; 8 | body: Body; 9 | } 10 | 11 | export interface Header { 12 | proposer: Hex; 13 | parentBlock: Hex; 14 | metadata: Hex; 15 | fee: Hex; 16 | /** UTXO roll up */ 17 | prevUTXORoot: Hex; 18 | prevUTXOIndex: Hex; 19 | nextUTXORoot: Hex; 20 | nextUTXOIndex: Hex; 21 | /** Nullifier roll up */ 22 | prevNullifierRoot: Hex; 23 | nextNullifierRoot: Hex; 24 | prevWithdrawalRoot: Hex; 25 | prevWithdrawalIndex: Hex; 26 | /** Withdrawal roll up */ 27 | nextWithdrawalRoot: Hex; 28 | nextWithdrawalIndex: Hex; 29 | /** Transactions */ 30 | txRoot: Hex; 31 | depositRoot: Hex; 32 | migrationRoot: Hex; 33 | } 34 | 35 | export interface Body { 36 | txs: ZkTransaction[]; 37 | deposits: MassDeposit[]; 38 | migrations: MassMigration[]; 39 | } 40 | 41 | export interface MassDeposit { 42 | merged: Hex; 43 | fee: Hex; 44 | } 45 | 46 | export interface MassMigration { 47 | destination: Hex; 48 | totalETH: Hex; 49 | migratingLeaves: MassDeposit; 50 | erc20: ERC20Migration[]; 51 | erc721: ERC721Migration[]; 52 | } 53 | 54 | export interface ERC20Migration { 55 | addr: Hex; 56 | amount: Hex; 57 | } 58 | 59 | export interface ERC721Migration { 60 | addr: Hex; 61 | nfts: Hex[]; 62 | } 63 | 64 | export interface AggregatedZkTx { 65 | zkTx: ZkTransaction; 66 | includedIn: Hex; // block hash 67 | } 68 | 69 | const PENDING = 'pending'; 70 | 71 | export class TxMemPool { 72 | blockTxMap: { 73 | [includedIn: string]: Hex[]; 74 | }; 75 | txs: { 76 | [txHash: string]: ZkTransaction; 77 | }; 78 | verifyingKeys: { [key: string]: {} }; 79 | 80 | constructor() { 81 | this.blockTxMap = { 82 | [PENDING]: [] 83 | }; 84 | this.txs = {}; 85 | this.verifyingKeys = {}; 86 | } 87 | 88 | addVerifier({ n_i, n_o, vk }: { n_i: number; n_o: number; vk: any }) { 89 | let key = `${n_i}-${n_o}`; 90 | this.verifyingKeys[key] = snarkjs.unstringifyBigInts(vk); 91 | } 92 | 93 | pendingNum(): number { 94 | return this.blockTxMap[PENDING].length; 95 | } 96 | 97 | addToTxPool(zkTx: ZkTransaction) { 98 | let txHash = zkTx.hash(); 99 | if (!this.verifyTx(zkTx)) { 100 | throw Error('SNARK is invalid'); 101 | } 102 | this.addToBlock({ blockHash: PENDING, txHash }); 103 | this.txs[txHash] = zkTx; 104 | } 105 | 106 | pickPendingTxs(maxBytes: number): ZkTransaction[] { 107 | let available = maxBytes; 108 | this.sortPendingTxs(); 109 | let candidates = this.blockTxMap[PENDING]; 110 | let picked: ZkTransaction[] = []; 111 | while (available > 0 && candidates.length > 0) { 112 | let tx = this.txs[candidates.pop()]; 113 | let size = tx.size(); 114 | if (available >= size) { 115 | available -= size; 116 | picked.push(tx); 117 | } 118 | } 119 | let txHashes = picked.map(tx => tx.hash()); 120 | let txRoot = root(txHashes); 121 | this.blockTxMap[txRoot] = txHashes; 122 | return picked; 123 | } 124 | 125 | updateTxsAsIncludedInBlock({ txRoot, blockHash }: { txRoot: string; blockHash: string }) { 126 | this.blockTxMap[blockHash] = this.blockTxMap[txRoot]; 127 | delete this.blockTxMap[txRoot]; 128 | } 129 | 130 | verifyTx(zkTx: ZkTransaction): boolean { 131 | let key = `${zkTx.inflow.length}-${zkTx.outflow.length}`; 132 | let isValid = snarkjs.groth.isValid(this.verifyingKeys[key], zkTx.circomProof(), zkTx.signals()); 133 | return isValid; 134 | } 135 | 136 | revertTxs(txRoot: string) { 137 | this.blockTxMap[txRoot].forEach(hash => this.blockTxMap[PENDING].push(hash)); 138 | this.sortPendingTxs(); 139 | } 140 | 141 | private sortPendingTxs() { 142 | this.blockTxMap[PENDING].sort((a, b) => (this.txs[a].fee.greaterThan(this.txs[b].fee) ? 1 : -1)); 143 | } 144 | 145 | private addToBlock({ blockHash, txHash }: { blockHash: string; txHash: Hex }) { 146 | let txHashes: Hex[] = this.blockTxMap[blockHash]; 147 | // let txs: ZkTransaction[] = this.txs[blockHash]; 148 | if (!txHashes) { 149 | txHashes = []; 150 | this.blockTxMap[blockHash] = txHashes; 151 | } 152 | let alreadyExist = txHashes.reduce((exist, val) => { 153 | if (exist) return true; 154 | else { 155 | return val === txHash; 156 | } 157 | }, false); 158 | if (!alreadyExist) { 159 | txHashes.push(txHash); 160 | } else { 161 | throw Error('Already exists'); 162 | } 163 | } 164 | 165 | async blockReverted(blockHash: string) { 166 | try { 167 | // this.db.create 168 | } finally { 169 | } 170 | } 171 | } 172 | 173 | export class Coordinator {} 174 | -------------------------------------------------------------------------------- /src/eddsa.ts: -------------------------------------------------------------------------------- 1 | import { BabyJubjub } from './jubjub'; 2 | import { Field } from './field'; 3 | import * as circomlib from 'circomlib'; 4 | 5 | export interface EdDSA { 6 | R8: BabyJubjub.Point; 7 | S: Field; 8 | } 9 | 10 | export function sign({ msg, privKey }: { msg: Field; privKey: string }): EdDSA { 11 | let signature = circomlib.eddsa.signPoseidon(privKey, msg.val); 12 | return { 13 | R8: BabyJubjub.Point.from(signature.R8[0], signature.R8[1]), 14 | S: Field.from(signature.S) 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/field.ts: -------------------------------------------------------------------------------- 1 | import * as snarkjs from 'snarkjs'; 2 | import { Hex, toBN, padLeft } from 'web3-utils'; 3 | 4 | export class Field { 5 | val: snarkjs.bigInt; 6 | 7 | constructor(val: BigInt) { 8 | this.val = snarkjs.bigInt(val); 9 | if (this.val >= snarkjs.bn128.r) { 10 | throw Error('Exceeds SNARK field: ' + this.val.toString()); 11 | } 12 | } 13 | 14 | static zero = Field.from(0); 15 | 16 | static from(x: any): Field { 17 | if (x === undefined) return new Field(BigInt(0)); 18 | else return new Field(x); 19 | } 20 | 21 | static fromBuffer(buff: Buffer): Field { 22 | return Field.from(toBN('0x' + buff.toString('hex'))); 23 | } 24 | 25 | static leInt2Buff(n: BigInt, len: number): Buffer { 26 | return snarkjs.bigInt.leInt2Buff(n, len); 27 | } 28 | 29 | static leBuff2int(buff: Buffer): Field { 30 | return Field.from(snarkjs.bigInt.leBuff2int(buff)); 31 | } 32 | 33 | toBuffer(n?: number): Buffer { 34 | if (!this.val.shr(n * 8).isZero()) throw Error('Not enough buffer size'); 35 | let hex = n ? padLeft(this.val.toString(16), 2 * n) : this.val.toString(16); 36 | return Buffer.from(hex, 'hex'); 37 | } 38 | 39 | shr(n: number): Field { 40 | return Field.from(this.val.shr(n)); 41 | } 42 | 43 | isZero(): boolean { 44 | return this.val.isZero(); 45 | } 46 | 47 | toString(): string { 48 | return this.val.toString(); 49 | } 50 | 51 | toHex(): Hex { 52 | return '0x' + this.val.toString(16); 53 | } 54 | 55 | equal(n: Field): boolean { 56 | if (n.val) return this.val === n.val; 57 | else return this.val === Field.from(n).val; 58 | } 59 | 60 | add(n: Field): Field { 61 | let newVal = this.val + n.val; 62 | if (newVal < this.val) { 63 | throw Error('Field overflow'); 64 | } 65 | return Field.from(newVal); 66 | } 67 | 68 | sub(n: Field): Field { 69 | let newVal = this.val - n.val; 70 | if (newVal > this.val) { 71 | throw Error('Field underflow'); 72 | } 73 | return Field.from(newVal); 74 | } 75 | 76 | greaterThan(n: Field): boolean { 77 | if (!n) return true; 78 | return this.val > n.val; 79 | } 80 | 81 | gte(n: Field): boolean { 82 | return this.val >= n.val; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { BabyJubjub } from './jubjub'; 2 | export { UTXO } from './utxo'; 3 | -------------------------------------------------------------------------------- /src/jubjub.ts: -------------------------------------------------------------------------------- 1 | import { Field } from './field'; 2 | 3 | import * as snarkjs from 'snarkjs'; 4 | import * as circomlib from 'circomlib'; 5 | import createBlakeHash from 'blake-hash'; 6 | 7 | export namespace BabyJubjub { 8 | export class Point { 9 | x: Field; 10 | y: Field; 11 | constructor(x: Field, y: Field) { 12 | this.x = x; 13 | this.y = y; 14 | if (!circomlib.babyJub.inCurve([this.x.val, this.y.val])) { 15 | throw new Error('Given point is not on the Babyjubjub curve'); 16 | } 17 | } 18 | 19 | static zero = Point.from(0, 1); 20 | 21 | static from(x: any, y: any) { 22 | return new Point(Field.from(x), Field.from(y)); 23 | } 24 | 25 | static decode(packed: Buffer): Point { 26 | let point = circomlib.babyJub.unpackPoint(packed); 27 | return Point.from(point[0], point[1]); 28 | } 29 | 30 | static generate(n: Field): Point { 31 | return BASE8.mul(n); 32 | } 33 | 34 | static fromPrivKey(key: string | Buffer): Point { 35 | let result = circomlib.eddsa.prv2pub(key); 36 | return Point.from(result[0], result[1]); 37 | } 38 | 39 | static getMultiplier(key: string): Field { 40 | const sBuff = circomlib.eddsa.pruneBuffer( 41 | createBlakeHash('blake512') 42 | .update(key) 43 | .digest() 44 | .slice(0, 32) 45 | ); 46 | return Field.from(snarkjs.bigInt.leBuff2int(sBuff).shr(3)); 47 | } 48 | 49 | static isOnJubjub(x: Field, y: Field) { 50 | return circomlib.babyJub.inCurve([x.val, y.val]); 51 | } 52 | 53 | encode(): Buffer { 54 | return circomlib.babyJub.packPoint([this.x.val, this.y.val]); 55 | } 56 | 57 | add(p: Point): Point { 58 | let result = circomlib.babyJub.addPoint([this.x.val, this.y.val], [p.x.val, p.y.val]); 59 | return Point.from(result[0], result[1]); 60 | } 61 | 62 | mul(n: Field): Point { 63 | let result = circomlib.babyJub.mulPointEscalar([this.x.val, this.y.val], n); 64 | return Point.from(result[0], result[1]); 65 | } 66 | } 67 | 68 | export namespace EdDSA { 69 | export interface Signature { 70 | R8: Point; 71 | S: Field; 72 | } 73 | 74 | export function sign(privKey: string, msg: Field): Signature { 75 | let result = circomlib.eddsa.signPoseidon(privKey, msg); 76 | return { 77 | R8: Point.from(result.R8[0], result.R8[1]), 78 | S: result.S 79 | }; 80 | } 81 | 82 | export function verify(msg: Field, sig: Signature, pubKey: Point): boolean { 83 | let result = circomlib.eddsa.verifyPoseidon(msg, { R8: [sig.R8.x, sig.R8.y], S: sig.S }, [pubKey.x.val, pubKey.y.val]); 84 | return result; 85 | } 86 | } 87 | 88 | export const GENERATOR: Point = Point.from(circomlib.babyJub.Generator[0], circomlib.babyJub.Generator[1]); 89 | export const BASE8: Point = Point.from(circomlib.babyJub.Base8[0], circomlib.babyJub.Base8[1]); 90 | export const ORDER: bigint = circomlib.babyJub.order; 91 | export const SUB_ORDER: bigint = circomlib.babyJub.subOrder; 92 | export const PRIME: bigint = circomlib.babyJub.p; 93 | export const A: bigint = circomlib.babyJub.A; 94 | export const D: bigint = circomlib.babyJub.D; 95 | } 96 | -------------------------------------------------------------------------------- /src/synchronizer.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import { provider } from 'web3-core'; 3 | import RocksDB from 'rocksdb'; 4 | 5 | export class FullSynchronizer { 6 | private web3: Web3; 7 | private db: RocksDB; 8 | private isSyncing: boolean; 9 | 10 | constructor(provider: provider, path: string) { 11 | this.web3 = new Web3(provider); 12 | this.db = new RocksDB(path); 13 | this.isSyncing = false; 14 | } 15 | 16 | public startSync(): void {} 17 | 18 | private getRecentBlocks() {} 19 | } 20 | -------------------------------------------------------------------------------- /src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { hexToNumber, padLeft, toChecksumAddress } from 'web3-utils'; 2 | import { Field } from './field'; 3 | 4 | export namespace TokenAddress { 5 | export const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'; 6 | export const CRYPTO_KITTIES = '0x06012c8cf97bead5deae237070f9587f8e7a266d'; 7 | } 8 | 9 | const tokenIdMap: Object = {}; 10 | tokenIdMap['0x0'] = 0; 11 | tokenIdMap[TokenAddress.DAI] = 1; 12 | tokenIdMap[TokenAddress.CRYPTO_KITTIES] = 2; 13 | 14 | export function getTokenId(addr: Field): number { 15 | let hexAddr = padLeft('0x' + addr.val.toString(16), 40); 16 | let checkSumAddress = toChecksumAddress(hexAddr); 17 | let id = tokenIdMap[checkSumAddress]; 18 | if (id === undefined) { 19 | id = 0; 20 | } 21 | if (id >= 256) throw Error('Only support maximum 255 number of tokens'); 22 | return id; 23 | } 24 | 25 | export function getTokenAddress(id: number): Field { 26 | if (id >= 256) throw Error('Only support maximum 255 number of tokens'); 27 | let key = id; 28 | if (typeof id === 'string') { 29 | key = hexToNumber(id); 30 | } 31 | for (let obj in tokenIdMap) { 32 | if (tokenIdMap[obj] === key) return Field.from(obj); 33 | } 34 | return null; 35 | } 36 | -------------------------------------------------------------------------------- /src/tree.ts: -------------------------------------------------------------------------------- 1 | import RocksDB from 'level-rocksdb'; 2 | import { hashers, tree, storage } from 'semaphore-merkle-tree'; 3 | import { Field } from './field'; 4 | import { Hex, soliditySha3, toBN } from 'web3-utils'; 5 | 6 | export interface MerkleProof { 7 | root: Field; 8 | index: Field; 9 | utxo: Field; 10 | siblings: Field[]; 11 | } 12 | 13 | class RocksDBStorage { 14 | db: RocksDB; 15 | constructor(path: string) { 16 | this.db = RocksDB(path); 17 | } 18 | 19 | async open() { 20 | await this.db.open(); 21 | } 22 | 23 | async get(key): Promise { 24 | return await this.db.get(key.toString()); 25 | } 26 | 27 | async get_or_element(key, element): Promise { 28 | let res; 29 | try { 30 | res = await this.db.get(key.toString()); 31 | } catch { 32 | res = element; 33 | } 34 | return res; 35 | } 36 | 37 | async put(key, value) { 38 | await this.db.put(key, value); 39 | } 40 | 41 | async del(key) { 42 | await this.db.del(key); 43 | } 44 | 45 | async put_batch(key_values) { 46 | await this.db.batch( 47 | key_values.map(item => { 48 | return { type: 'put', ...item }; 49 | }) 50 | ); 51 | } 52 | 53 | async close() { 54 | await this.db.close(); 55 | } 56 | } 57 | 58 | export class MerkleTree { 59 | tree: tree.MerkleTree; 60 | storage: RocksDBStorage; 61 | prefix: string; 62 | maxSize: bigint; 63 | 64 | constructor(prefix: string, storage: storage.IStorage, hasher: hashers.IHasher, depth: number, defaultValue: Hex) { 65 | this.prefix = prefix; 66 | this.storage = storage; 67 | this.tree = new tree.MerkleTree(prefix, storage, hasher, depth, defaultValue); 68 | this.maxSize = BigInt(1) << BigInt(depth); 69 | } 70 | 71 | async append(utxo: Hex) { 72 | let size = await this.size(); 73 | if (size >= this.maxSize) { 74 | throw Error('Tree is fully filled.'); 75 | } 76 | let index = size + BigInt(1); 77 | await this.tree.update(index.toString(), utxo); 78 | let root = await this.tree.root(); 79 | await this.recordSize(root, index); 80 | } 81 | 82 | async size(): Promise { 83 | let root = await this.tree.root(); 84 | let size; 85 | try { 86 | size = await this.storage.get(this.sizeKey(root)); 87 | } catch { 88 | size = 0; 89 | } finally { 90 | return BigInt(size); 91 | } 92 | } 93 | 94 | async rollBack(root: Hex) { 95 | this.tree.rollback_to_root(root); 96 | } 97 | 98 | async include(utxo: Hex): Promise { 99 | let itemIndex = parseInt(await this.tree.element_index(utxo)); 100 | return itemIndex !== -1; 101 | } 102 | 103 | async merkleProof(utxo: Hex): Promise { 104 | let itemIndex = parseInt(await this.tree.element_index(utxo)); 105 | if (itemIndex === -1) return null; 106 | else { 107 | let path = await this.tree.path(itemIndex); 108 | return { 109 | root: Field.from(path.root), 110 | index: Field.from(itemIndex), 111 | utxo: Field.from(utxo), 112 | siblings: path.path_elements.map(sib => Field.from(sib)) 113 | }; 114 | } 115 | } 116 | 117 | private async recordSize(root: Hex, size: bigint): Promise { 118 | this.storage.put(this.sizeKey(root), size); 119 | } 120 | 121 | private sizeKey(root: Hex): string { 122 | return `${this.prefix}-${root}-size`; 123 | } 124 | } 125 | 126 | export const keccakHasher = { 127 | hash: (_, left, right) => { 128 | return toBN(soliditySha3(left, right)); 129 | } 130 | }; 131 | 132 | export class Grove { 133 | utxoTrees: MerkleTree[]; 134 | withdrawalTrees: MerkleTree[]; 135 | nullifierTree: MerkleTree; 136 | depth: number; 137 | prefix: string; 138 | storage: RocksDBStorage; 139 | 140 | constructor(prefix: string, path: string, depth: number) { 141 | this.prefix = prefix; 142 | this.utxoTrees = []; 143 | this.withdrawalTrees = []; 144 | this.storage = new RocksDBStorage(path); 145 | this.depth = depth; 146 | } 147 | 148 | async init() { 149 | // Retrieve UTXO trees 150 | let numOfUTXOTrees: number; 151 | try { 152 | numOfUTXOTrees = parseInt(await this.storage.get(this.prefixForNumOfUtxoTrees())); 153 | } catch { 154 | numOfUTXOTrees = 0; 155 | } finally { 156 | for (let i = 0; i < numOfUTXOTrees; i++) { 157 | this.utxoTrees[i] = Grove.utxoTree(this.prefixForUtxoTree(i), this.depth, this.storage); 158 | } 159 | } 160 | // Retrieve Withdrawal trees 161 | let numOfWithdrawalTrees: number; 162 | try { 163 | numOfWithdrawalTrees = parseInt(await this.storage.get(this.prefixForNumOfWithdrawalTrees)); 164 | } catch { 165 | numOfWithdrawalTrees = 0; 166 | } finally { 167 | for (let i = 0; i < numOfWithdrawalTrees; i++) { 168 | this.withdrawalTrees[i] = Grove.withdrawalTree(this.prefixForWithdrawalTree(i), this.depth, this.storage); 169 | } 170 | } 171 | // Retrieve the nullifier tree 172 | this.nullifierTree = Grove.nullifierTree(this.prefixForNullifierTree(), this.storage); 173 | } 174 | 175 | latestUTXOTree(): MerkleTree { 176 | return this.utxoTrees[this.utxoTrees.length - 1]; 177 | } 178 | 179 | latestWithdrawalTree(): MerkleTree { 180 | return this.withdrawalTrees[this.withdrawalTrees.length - 1]; 181 | } 182 | 183 | async appendUTXO(utxo: Hex): Promise { 184 | let tree = this.latestUTXOTree(); 185 | if (tree && (await tree.size()) < tree.maxSize) { 186 | await tree.append(utxo); 187 | } else { 188 | let newTreeIndex = this.utxoTrees.length; 189 | let newTree = Grove.utxoTree(this.prefixForUtxoTree(newTreeIndex), this.depth, this.storage); 190 | this.utxoTrees.push(newTree); 191 | await this.recordTrees(); 192 | await newTree.append(utxo); 193 | } 194 | } 195 | 196 | async appendWithdrawal(withdrawal: Hex): Promise { 197 | let tree = this.latestWithdrawalTree(); 198 | if ((await tree.size()) < tree.maxSize) { 199 | await tree.append(withdrawal); 200 | } else { 201 | let newTreeIndex = this.withdrawalTrees.length; 202 | let newTree = Grove.withdrawalTree(this.prefixForWithdrawalTree(newTreeIndex), this.depth, this.storage); 203 | this.withdrawalTrees.push(newTree); 204 | await this.recordTrees(); 205 | await newTree.append(withdrawal); 206 | } 207 | } 208 | 209 | async close() { 210 | await this.recordTrees(); 211 | await this.storage.close(); 212 | } 213 | 214 | async utxoMerkleProof(utxo: Hex): Promise { 215 | let index = -1; 216 | let tree: MerkleTree; 217 | let proof; 218 | for (let i = this.utxoTrees.length; i--; ) { 219 | tree = this.utxoTrees[i]; 220 | proof = await tree.merkleProof(utxo); 221 | if (proof) break; 222 | } 223 | if (!proof) throw Error('Failed to find utxo'); 224 | return proof; 225 | } 226 | 227 | async withdrawalMerkleProof(withdrawal: Hex): Promise { 228 | let index = -1; 229 | let tree: MerkleTree; 230 | let proof; 231 | for (let i = this.withdrawalTrees.length; i--; ) { 232 | tree = this.withdrawalTrees[i]; 233 | proof = await tree.merkleProof(withdrawal); 234 | if (proof) break; 235 | } 236 | if (!proof) throw Error('Failed to find utxo'); 237 | return proof; 238 | } 239 | 240 | private async recordTrees() { 241 | await this.storage.put_batch([ 242 | { 243 | key: this.prefixForNumOfUtxoTrees(), 244 | value: this.utxoTrees.length 245 | }, 246 | { 247 | key: this.prefixForNumOfWithdrawalTrees(), 248 | value: this.withdrawalTrees.length 249 | } 250 | ]); 251 | } 252 | 253 | private prefixForNumOfUtxoTrees(): string { 254 | return `${this.prefix}-utxo-num`; 255 | } 256 | 257 | private prefixForNumOfWithdrawalTrees(): string { 258 | return `${this.prefix}-withdrawal-num`; 259 | } 260 | 261 | private prefixForUtxoTree(groveIndex: number): string { 262 | return `${this.prefix}-utxo-${groveIndex}`; 263 | } 264 | 265 | private prefixForWithdrawalTree(groveIndex: number): string { 266 | return `${this.prefix}-withdrawal-${groveIndex}`; 267 | } 268 | 269 | private prefixForNullifierTree(): string { 270 | return `${this.prefix}-nullifier`; 271 | } 272 | 273 | static utxoTree(prefix: string, depth: number, storage: RocksDBStorage): tree.MerkleTree { 274 | let hasher = new hashers.PoseidonHasher(); 275 | let defaultValue = '0'; 276 | return new MerkleTree(prefix, storage, hasher, depth, defaultValue); 277 | } 278 | 279 | static withdrawalTree(prefix: string, depth: number, storage: RocksDBStorage): tree.MerkleTree { 280 | let hasher = keccakHasher; 281 | let defaultValue = '0'; 282 | return new MerkleTree(prefix, storage, hasher, depth, defaultValue); 283 | } 284 | 285 | static nullifierTree(prefix: string, storage: RocksDBStorage): tree.MerkleTree { 286 | let hasher = keccakHasher; 287 | let defaultValue = '0'; 288 | let depth = 255; 289 | return new MerkleTree(prefix, storage, hasher, depth, defaultValue); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/types/jest/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanseob/zk-optimistic-rollup/027d54bae574fe4503879055683172fabd202923/src/types/jest/index.d.ts -------------------------------------------------------------------------------- /src/types/truffle-contracts/merge.d.ts: -------------------------------------------------------------------------------- 1 | /* Generated by ts-generator ver. 0.0.8 */ 2 | /* tslint:disable */ 3 | 4 | /// 5 | 6 | import * as TruffleContracts from '.'; 7 | 8 | declare global { 9 | namespace Truffle { 10 | interface Artifacts { 11 | require(name: 'Challengeable'): TruffleContracts.ChallengeableContract; 12 | require(name: 'Configurated'): TruffleContracts.ConfiguratedContract; 13 | require(name: 'Coordinatable'): TruffleContracts.CoordinatableContract; 14 | require(name: 'DepositChallenge'): TruffleContracts.DepositChallengeContract; 15 | require(name: 'DeserializationTester'): TruffleContracts.DeserializationTesterContract; 16 | require(name: 'HeaderChallenge'): TruffleContracts.HeaderChallengeContract; 17 | require(name: 'ICoordinatable'): TruffleContracts.ICoordinatableContract; 18 | require(name: 'IDepositChallenge'): TruffleContracts.IDepositChallengeContract; 19 | require(name: 'IERC20'): TruffleContracts.IERC20Contract; 20 | require(name: 'IERC721'): TruffleContracts.IERC721Contract; 21 | require(name: 'IHeaderChallenge'): TruffleContracts.IHeaderChallengeContract; 22 | require(name: 'IMigratable'): TruffleContracts.IMigratableContract; 23 | require(name: 'IMigrationChallenge'): TruffleContracts.IMigrationChallengeContract; 24 | require(name: 'IRollUpable'): TruffleContracts.IRollUpableContract; 25 | require(name: 'IRollUpChallenge'): TruffleContracts.IRollUpChallengeContract; 26 | require(name: 'ISetupWizard'): TruffleContracts.ISetupWizardContract; 27 | require(name: 'ITxChallenge'): TruffleContracts.ITxChallengeContract; 28 | require(name: 'IUserInteractable'): TruffleContracts.IUserInteractableContract; 29 | require(name: 'Layer2'): TruffleContracts.Layer2Contract; 30 | require(name: 'Layer2Controller'): TruffleContracts.Layer2ControllerContract; 31 | require(name: 'Migratable'): TruffleContracts.MigratableContract; 32 | require(name: 'MigrationChallenge'): TruffleContracts.MigrationChallengeContract; 33 | require(name: 'Migrations'): TruffleContracts.MigrationsContract; 34 | require(name: 'MiMC'): TruffleContracts.MiMCContract; 35 | require(name: 'Poseidon'): TruffleContracts.PoseidonContract; 36 | require(name: 'RollUpable'): TruffleContracts.RollUpableContract; 37 | require(name: 'RollUpChallenge'): TruffleContracts.RollUpChallengeContract; 38 | require(name: 'SetupWizard'): TruffleContracts.SetupWizardContract; 39 | require(name: 'SMT256'): TruffleContracts.SMT256Contract; 40 | require(name: 'TestERC20'): TruffleContracts.TestERC20Contract; 41 | require(name: 'TxChallenge'): TruffleContracts.TxChallengeContract; 42 | require(name: 'UserInteractable'): TruffleContracts.UserInteractableContract; 43 | require(name: 'ZkOptimisticRollUp'): TruffleContracts.ZkOptimisticRollUpContract; 44 | require(name: 'MiMC'): TruffleContracts.MiMCContract; 45 | require(name: 'Poseidon'): TruffleContracts.PoseidonContract; 46 | require(name: 'TestERC20'): TruffleContracts.TestERC20Contract; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Hex } from 'web3-utils'; 2 | import { soliditySha3, padLeft } from 'web3-utils'; 3 | 4 | export function root(hashes: Hex[]): Hex { 5 | if (hashes.length === 0) { 6 | return padLeft(0, 64); 7 | } else if (hashes.length === 1) { 8 | return hashes[0]; 9 | } 10 | let parents: string[] = []; 11 | let numOfParentNodes = Math.ceil(hashes.length / 2); 12 | let hasEmptyLeaf = hashes.length % 2 === 1; 13 | for (let i = 0; i < numOfParentNodes; i++) { 14 | if (hasEmptyLeaf && i == numOfParentNodes - 1) { 15 | parents[i] = soliditySha3(hashes[i * 2]); 16 | } else { 17 | parents[i] = soliditySha3(hashes[i * 2], hashes[i * 2 + 1]); 18 | } 19 | } 20 | return root(parents); 21 | } 22 | 23 | export class Queue { 24 | buffer: Buffer; 25 | cursor: number; 26 | constructor(buffer: Buffer) { 27 | this.buffer = buffer; 28 | this.cursor = 0; 29 | } 30 | dequeue(n: number): Buffer { 31 | let dequeued = this.buffer.slice(this.cursor, this.cursor + n); 32 | this.cursor += n; 33 | return dequeued; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utxo.ts: -------------------------------------------------------------------------------- 1 | import { Hex, randomHex } from 'web3-utils'; 2 | 3 | import * as circomlib from 'circomlib'; 4 | import { BabyJubjub } from './jubjub'; 5 | import { Field } from './field'; 6 | import * as chacha20 from 'chacha20'; 7 | import { getTokenId, getTokenAddress } from './tokens'; 8 | import { Outflow, PublicData } from './transaction'; 9 | 10 | const poseidonHash = circomlib.poseidon.createHash(6, 8, 57, 'poseidon'); 11 | 12 | export class UTXO { 13 | eth: Field; 14 | pubKey: BabyJubjub.Point; 15 | salt: Field; 16 | tokenAddr: Field; 17 | erc20Amount: Field; 18 | nft: Field; 19 | publicData?: { 20 | isWithdrawal: boolean; 21 | to: Field; 22 | fee: Field; 23 | }; 24 | 25 | constructor(eth: Field, salt: Field, tokenAddr: Field, erc20Amount: Field, nft: Field, pubKey: BabyJubjub.Point, migrationTo?: Field) { 26 | this.eth = eth; 27 | this.pubKey = pubKey; 28 | this.salt = salt; 29 | this.tokenAddr = tokenAddr; 30 | this.erc20Amount = erc20Amount; 31 | this.nft = nft; 32 | } 33 | 34 | static newEtherNote({ eth, pubKey, salt }: { eth: Hex | Field; pubKey: BabyJubjub.Point; salt?: Hex | Field }): UTXO { 35 | salt = salt ? Field.from(salt) : Field.from(randomHex(16)); 36 | return new UTXO(Field.from(eth), salt, Field.from(0), Field.from(0), Field.from(0), pubKey); 37 | } 38 | 39 | static newERC20Note({ 40 | eth, 41 | tokenAddr, 42 | erc20Amount, 43 | pubKey, 44 | salt 45 | }: { 46 | eth: Hex | Field; 47 | tokenAddr: Hex | Field; 48 | erc20Amount: Hex | Field; 49 | pubKey: BabyJubjub.Point; 50 | salt?: Hex | Field; 51 | }): UTXO { 52 | salt = salt ? Field.from(salt) : Field.from(randomHex(16)); 53 | return new UTXO(Field.from(eth), salt, Field.from(tokenAddr), Field.from(erc20Amount), Field.from(0), pubKey); 54 | } 55 | 56 | static newNFTNote({ 57 | eth, 58 | tokenAddr, 59 | nft, 60 | pubKey, 61 | salt 62 | }: { 63 | eth: Hex | Field; 64 | tokenAddr: Hex | Field; 65 | nft: Hex | Field; 66 | pubKey: BabyJubjub.Point; 67 | salt?: Hex | Field; 68 | }): UTXO { 69 | salt = salt ? Field.from(salt) : Field.from(randomHex(16)); 70 | return new UTXO(Field.from(eth), salt, Field.from(tokenAddr), Field.from(0), Field.from(nft), pubKey); 71 | } 72 | 73 | markAsWithdrawal({ to, fee }: { to: Hex | Field; fee: Hex | Field }) { 74 | this.publicData = { isWithdrawal: true, to: Field.from(to), fee: Field.from(fee) }; 75 | } 76 | 77 | markAsMigration({ to, fee }: { to: Hex | Field; fee: Hex | Field }) { 78 | this.publicData = { isWithdrawal: false, to: Field.from(to), fee: Field.from(fee) }; 79 | } 80 | 81 | markAsInternalUtxo() { 82 | this.publicData = undefined; 83 | } 84 | 85 | hash(): Field { 86 | let firstHash = Field.from(poseidonHash([this.eth.val, this.pubKey.x.val, this.pubKey.y, this.salt.val])); 87 | let resultHash = Field.from(poseidonHash([firstHash, this.tokenAddr.val, this.erc20Amount.val, this.nft.val])); 88 | return resultHash; 89 | } 90 | 91 | nullifier(): Field { 92 | return Field.from(poseidonHash([this.hash(), this.salt.val])); 93 | } 94 | 95 | encrypt(): Buffer { 96 | let ephemeralSecretKey: Field = Field.from(randomHex(16)); 97 | let sharedKey: Buffer = this.pubKey.mul(ephemeralSecretKey).encode(); 98 | let tokenId = getTokenId(this.tokenAddr); 99 | let value = this.eth ? this.eth : this.erc20Amount ? this.erc20Amount : this.nft; 100 | let secret = [this.salt.toBuffer(16), Field.from(tokenId).toBuffer(1), value.toBuffer(32)]; 101 | let ciphertext = chacha20.encrypt(sharedKey, 0, Buffer.concat(secret)); 102 | let encryptedMemo = Buffer.concat([BabyJubjub.Point.generate(ephemeralSecretKey).encode(), ciphertext]); 103 | // 32bytes ephemeral pub key + 16 bytes salt + 1 byte token id + 32 bytes value = 81 bytes 104 | return encryptedMemo; 105 | } 106 | 107 | toOutflow(): Outflow { 108 | let data: PublicData; 109 | if (this.publicData) { 110 | data = { 111 | to: this.publicData.to, 112 | eth: this.eth, 113 | tokenAddr: this.tokenAddr, 114 | erc20Amount: this.erc20Amount, 115 | nft: this.nft, 116 | fee: this.publicData.fee 117 | }; 118 | } 119 | let outflow = { 120 | note: this.hash(), 121 | outflowType: this.outflowType(), 122 | data 123 | }; 124 | return outflow; 125 | } 126 | 127 | toJSON(): string { 128 | return JSON.stringify({ 129 | eth: this.eth, 130 | salt: this.salt, 131 | token: this.tokenAddr, 132 | amount: this.erc20Amount, 133 | nft: this.nft.toHex(), 134 | pubKey: { 135 | x: this.pubKey.x, 136 | y: this.pubKey.y 137 | } 138 | }); 139 | } 140 | 141 | outflowType(): Field { 142 | if (this.publicData) { 143 | if (this.publicData.isWithdrawal) { 144 | return Field.from(1); // Withdrawal 145 | } else { 146 | return Field.from(2); // Migration 147 | } 148 | } else { 149 | return Field.from(0); // UTXO 150 | } 151 | } 152 | 153 | static fromJSON(data: string): UTXO { 154 | let obj = JSON.parse(data); 155 | return new UTXO(obj.eth, obj.salt, obj.token, obj.amount, obj.nft, new BabyJubjub.Point(obj.pubKey.x, obj.pubKey.y)); 156 | } 157 | 158 | static decrypt({ utxoHash, memo, privKey }: { utxoHash: Field; memo: Buffer; privKey: string }): UTXO { 159 | let multiplier = BabyJubjub.Point.getMultiplier(privKey); 160 | let ephemeralPubKey = BabyJubjub.Point.decode(memo.subarray(0, 32)); 161 | let sharedKey = ephemeralPubKey.mul(multiplier).encode(); 162 | let data = memo.subarray(32, 81); 163 | let decrypted = chacha20.decrypt(sharedKey, 0, data); // prints "testing" 164 | 165 | let salt = Field.fromBuffer(decrypted.subarray(0, 16)); 166 | let tokenAddress = getTokenAddress(decrypted.subarray(16, 17)[0]); 167 | let value = Field.fromBuffer(decrypted.subarray(17, 49)); 168 | 169 | let myPubKey: BabyJubjub.Point = BabyJubjub.Point.fromPrivKey(privKey); 170 | if (tokenAddress.isZero()) { 171 | let etherNote = UTXO.newEtherNote({ eth: value, pubKey: myPubKey, salt }); 172 | if (utxoHash.equal(etherNote.hash())) { 173 | return etherNote; 174 | } 175 | } else { 176 | let erc20Note = UTXO.newERC20Note({ eth: Field.from(0), tokenAddr: tokenAddress, erc20Amount: value, pubKey: myPubKey, salt }); 177 | if (utxoHash.equal(erc20Note.hash())) { 178 | return erc20Note; 179 | } 180 | let nftNote = UTXO.newNFTNote({ eth: Field.from(0), tokenAddr: tokenAddress, nft: value, pubKey: myPubKey, salt }); 181 | if (utxoHash.equal(nftNote.hash())) { 182 | return nftNote; 183 | } 184 | } 185 | return null; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/wallet.ts: -------------------------------------------------------------------------------- 1 | import 'bip39'; 2 | import Web3 from 'web3'; 3 | 4 | class Wallet { 5 | web3: Web3; 6 | } 7 | -------------------------------------------------------------------------------- /src/zk_transaction.ts: -------------------------------------------------------------------------------- 1 | import { Field } from './field'; 2 | import { Hex, soliditySha3 } from 'web3-utils'; 3 | import { Queue } from './utils'; 4 | import { Outflow, Inflow, PublicData } from './transaction'; 5 | import * as snarkjs from 'snarkjs'; 6 | 7 | export interface SNARK { 8 | pi_a: Field[]; 9 | pi_b: Field[][]; 10 | pi_c: Field[]; 11 | } 12 | 13 | export class ZkTransaction { 14 | inflow: Inflow[]; 15 | outflow: Outflow[]; 16 | fee: Field; 17 | proof?: SNARK; 18 | swap?: Field; 19 | memo?: Buffer; 20 | _hash?: Hex; 21 | _size?: number; 22 | 23 | constructor({ inflow, outflow, fee, proof, swap, memo }: { inflow: Inflow[]; outflow: Outflow[]; fee: Field; proof?: SNARK; swap?: Field; memo?: Buffer }) { 24 | this.inflow = inflow; 25 | this.outflow = outflow; 26 | this.fee = fee; 27 | this.proof = proof; 28 | this.swap = swap; 29 | this.memo = memo; 30 | } 31 | 32 | encode(): Buffer { 33 | if (!this.proof) throw Error('SNARK does not exist'); 34 | return Buffer.concat([ 35 | Uint8Array.from([this.inflow.length]), 36 | ...this.inflow.map(inflow => Buffer.concat([inflow.root.toBuffer(32), inflow.nullifier.toBuffer(32)])), 37 | Uint8Array.from([this.outflow.length]), 38 | ...this.outflow.map(outflow => 39 | Buffer.concat([ 40 | outflow.note.toBuffer(32), 41 | outflow.outflowType.toBuffer(1), 42 | outflow.data 43 | ? Buffer.concat([ 44 | outflow.data.to.toBuffer(20), 45 | outflow.data.eth.toBuffer(32), 46 | outflow.data.tokenAddr.toBuffer(20), 47 | outflow.data.erc20Amount.toBuffer(32), 48 | outflow.data.nft.toBuffer(32), 49 | outflow.data.fee.toBuffer(32) 50 | ]) 51 | : Buffer.from([]) 52 | ]) 53 | ), 54 | this.fee.toBuffer(32), 55 | this.proof.pi_a[0].toBuffer(32), 56 | this.proof.pi_a[1].toBuffer(32), 57 | this.proof.pi_b[0][0].toBuffer(32), 58 | this.proof.pi_b[0][1].toBuffer(32), 59 | this.proof.pi_b[1][0].toBuffer(32), 60 | this.proof.pi_b[1][1].toBuffer(32), 61 | this.proof.pi_c[0].toBuffer(32), 62 | this.proof.pi_c[1].toBuffer(32), 63 | Uint8Array.from([this.swap ? (1 << 0) + (this.memo ? 1 << 1 : 0 << 1) : (0 << 0) + (this.memo ? 1 << 1 : 0 << 1)]), // b'11' => tx has swap & memo, b'00' => no swap & no memo 64 | this.swap ? this.swap.toBuffer(32) : Buffer.from([]), 65 | this.memo ? this.memo.slice(0, 81) : Buffer.from([]) 66 | ]); 67 | } 68 | 69 | hash(): Hex { 70 | if (!this._hash) { 71 | let encodePacked = Buffer.concat([ 72 | ...this.inflow.map(inflow => { 73 | return Buffer.concat([inflow.root.toBuffer(32), inflow.nullifier.toBuffer(32)]); 74 | }), 75 | ...this.outflow.map(outflow => { 76 | return Buffer.concat([ 77 | outflow.note.toBuffer(32), 78 | outflow.data ? outflow.data.to.toBuffer(20) : Buffer.from([]), 79 | outflow.data ? outflow.data.eth.toBuffer(32) : Buffer.from([]), 80 | outflow.data ? outflow.data.tokenAddr.toBuffer(20) : Buffer.from([]), 81 | outflow.data ? outflow.data.erc20Amount.toBuffer(32) : Buffer.from([]), 82 | outflow.data ? outflow.data.nft.toBuffer(32) : Buffer.from([]), 83 | outflow.data ? outflow.data.fee.toBuffer(32) : Buffer.from([]) 84 | ]); 85 | }), 86 | this.swap ? this.swap.toBuffer(32) : Buffer.alloc(32), 87 | this.proof.pi_a[0].toBuffer(32), 88 | this.proof.pi_a[1].toBuffer(32), 89 | this.proof.pi_b[0][0].toBuffer(32), 90 | this.proof.pi_b[0][1].toBuffer(32), 91 | this.proof.pi_b[1][0].toBuffer(32), 92 | this.proof.pi_b[1][1].toBuffer(32), 93 | this.proof.pi_c[0].toBuffer(32), 94 | this.proof.pi_c[0].toBuffer(32), 95 | this.fee.toBuffer(32) 96 | ]); 97 | this._hash = soliditySha3(encodePacked.toString('hex')); 98 | } 99 | return this._hash; 100 | } 101 | size(): number { 102 | if (!this._size) { 103 | this._size = this.encode().length; 104 | } 105 | return this._size; 106 | } 107 | 108 | signals(): bigint[] { 109 | let signals = [ 110 | this.fee.val, 111 | this.swap ? this.swap.val : Field.zero.val, 112 | ...this.inflow.map(inflow => inflow.root.val), 113 | ...this.inflow.map(inflow => inflow.nullifier.val), 114 | ...this.outflow.map(outflow => outflow.note.val), 115 | ...this.outflow.map(outflow => outflow.outflowType.val), 116 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.to.val : Field.zero.val)), 117 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.eth.val : Field.zero.val)), 118 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.tokenAddr.val : Field.zero.val)), 119 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.erc20Amount.val : Field.zero.val)), 120 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.nft.val : Field.zero.val)), 121 | ...this.outflow.map(outflow => (outflow.data ? outflow.data.fee.val : Field.zero.val)) 122 | ]; 123 | return signals; 124 | } 125 | 126 | static decode(buff: Buffer): ZkTransaction { 127 | let zkTx: ZkTransaction = Object.create(ZkTransaction.prototype); 128 | let queue = new Queue(buff); 129 | // Inflow 130 | let n_i = queue.dequeue(1)[0]; 131 | zkTx.inflow = []; 132 | for (let i = 0; i < n_i; i++) { 133 | zkTx.inflow.push({ 134 | root: Field.fromBuffer(queue.dequeue(32)), 135 | nullifier: Field.fromBuffer(queue.dequeue(32)) 136 | }); 137 | } 138 | // Outflow 139 | let n_o = queue.dequeue(1)[0]; 140 | zkTx.outflow = []; 141 | for (let i = 0; i < n_o; i++) { 142 | let note = Field.fromBuffer(queue.dequeue(32)); 143 | let outflowType = Field.from(queue.dequeue(1)[0]); 144 | let data: PublicData = undefined; 145 | if (!outflowType.isZero()) { 146 | data = { 147 | to: Field.fromBuffer(queue.dequeue(20)), 148 | eth: Field.fromBuffer(queue.dequeue(32)), 149 | tokenAddr: Field.fromBuffer(queue.dequeue(20)), 150 | erc20Amount: Field.fromBuffer(queue.dequeue(32)), 151 | nft: Field.fromBuffer(queue.dequeue(32)), 152 | fee: Field.fromBuffer(queue.dequeue(32)) 153 | }; 154 | } 155 | zkTx.outflow.push({ 156 | note, 157 | outflowType, 158 | data 159 | }); 160 | } 161 | // Fee 162 | zkTx.fee = Field.fromBuffer(queue.dequeue(32)); 163 | // SNARK 164 | zkTx.proof = { 165 | pi_a: [Field.fromBuffer(queue.dequeue(32)), Field.fromBuffer(queue.dequeue(32))], 166 | pi_b: [ 167 | [Field.fromBuffer(queue.dequeue(32)), Field.fromBuffer(queue.dequeue(32))], 168 | [Field.fromBuffer(queue.dequeue(32)), Field.fromBuffer(queue.dequeue(32))] 169 | ], 170 | pi_c: [Field.fromBuffer(queue.dequeue(32)), Field.fromBuffer(queue.dequeue(32))] 171 | }; 172 | // Swap 173 | let swapAndMemo = queue.dequeue(1)[0]; 174 | if (swapAndMemo & (1 << 0)) { 175 | zkTx.swap = Field.fromBuffer(queue.dequeue(32)); 176 | } 177 | // Memo 178 | if (swapAndMemo & (1 << 1)) { 179 | zkTx.memo = queue.dequeue(81); 180 | } 181 | zkTx._size = buff.length; 182 | return zkTx; 183 | } 184 | 185 | circomProof(): { pi_a: bigint[]; pi_b: bigint[][]; pi_c: bigint[]; protocol: string } { 186 | return { 187 | pi_a: [...this.proof.pi_a.map(f => f.val), snarkjs.bigInt(1)], 188 | pi_b: [...this.proof.pi_b.map(arr => arr.map(f => f.val)), [snarkjs.bigInt(1), snarkjs.bigInt(0)]], 189 | pi_c: [...this.proof.pi_c.map(f => f.val), snarkjs.bigInt(1)], 190 | protocol: 'groth' 191 | }; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/zk_wizard.ts: -------------------------------------------------------------------------------- 1 | import { Field } from './field'; 2 | import { BabyJubjub } from './jubjub'; 3 | import { Grove, MerkleProof } from './tree'; 4 | import { EdDSA, sign } from './eddsa'; 5 | import * as snarkjs from 'snarkjs'; 6 | import { Transaction } from './transaction'; 7 | import { ZkTransaction } from './zk_transaction'; 8 | 9 | export class ZkWizard { 10 | circuits: { [key: string]: snarkjs.Circuit }; 11 | provingKeys: { [key: string]: {} }; 12 | grove: Grove; 13 | privKey: string; 14 | pubKey: BabyJubjub.Point; 15 | 16 | constructor({ grove, privKey }: { grove: Grove; privKey: string }) { 17 | this.grove = grove; 18 | this.privKey = privKey; 19 | this.circuits = {}; 20 | this.provingKeys = {}; 21 | this.pubKey = BabyJubjub.Point.fromPrivKey(privKey); 22 | } 23 | 24 | addCircuit({ n_i, n_o, circuitDef, provingKey }: { n_i: number; n_o: number; circuitDef: {}; provingKey: {} }) { 25 | this.circuits[this.circuitKey({ n_i, n_o })] = new snarkjs.Circuit(circuitDef); 26 | this.provingKeys[this.circuitKey({ n_i, n_o })] = provingKey; 27 | } 28 | 29 | private circuitKey({ n_i, n_o }: { n_i: number; n_o: number }): string { 30 | return `${n_i}-${n_o}`; 31 | } 32 | 33 | /** 34 | * @param toMemo n-th outflow will be encrypted 35 | */ 36 | async shield({ tx, toMemo }: { tx: Transaction; toMemo?: number }): Promise { 37 | return new Promise((resolve, reject) => { 38 | let merkleProof: { [hash: string]: MerkleProof } = {}; 39 | let eddsa: { [hash: string]: EdDSA } = {}; 40 | let _this = this; 41 | 42 | function isDataPrepared(): boolean { 43 | return Object.keys(merkleProof).length === tx.inflow.length && Object.keys(eddsa).length === tx.inflow.length; 44 | } 45 | 46 | function genSNARK() { 47 | if (!isDataPrepared()) return; 48 | let circuit = _this.circuits[_this.circuitKey({ n_i: tx.inflow.length, n_o: tx.outflow.length })]; 49 | let provingKey = _this.provingKeys[_this.circuitKey({ n_i: tx.inflow.length, n_o: tx.outflow.length })]; 50 | if (circuit === undefined || provingKey === undefined) { 51 | reject(`Does not support transactions for ${tx.inflow.length} inputs and ${tx.outflow.length} outputs`); 52 | } 53 | let input = {}; 54 | // inflow data 55 | tx.inflow.forEach((utxo, i) => { 56 | // private signals 57 | input[`spending_note[0][${i}]`] = utxo.eth; 58 | input[`spending_note[1][${i}]`] = utxo.pubKey.x; 59 | input[`spending_note[2][${i}]`] = utxo.pubKey.y; 60 | input[`spending_note[3][${i}]`] = utxo.salt; 61 | input[`spending_note[4][${i}]`] = utxo.tokenAddr; 62 | input[`spending_note[5][${i}]`] = utxo.erc20Amount; 63 | input[`spending_note[6][${i}]`] = utxo.nft; 64 | input[`signatures[0][${i}]`] = eddsa[i].R8.x; 65 | input[`signatures[1][${i}]`] = eddsa[i].R8.y; 66 | input[`signatures[2][${i}]`] = eddsa[i].S; 67 | input[`note_index[${i}]`] = merkleProof[i].index; 68 | for (let j = 0; j < _this.grove.depth; j++) { 69 | input[`siblings[${j}][${i}]`] = merkleProof[i].siblings[j]; 70 | } 71 | // public signals 72 | input[`inclusion_references[${i}]`] = merkleProof[i].root; 73 | input[`nullifiers[${i}]`] = utxo.nullifier(); 74 | }); 75 | // outflow data 76 | tx.outflow.forEach((utxo, i) => { 77 | // private signals 78 | input[`new_note[0][${i}]`] = utxo.eth; 79 | input[`new_note[1][${i}]`] = utxo.pubKey.x; 80 | input[`new_note[2][${i}]`] = utxo.pubKey.y; 81 | input[`new_note[3][${i}]`] = utxo.salt; 82 | input[`new_note[4][${i}]`] = utxo.tokenAddr; 83 | input[`new_note[5][${i}]`] = utxo.erc20Amount; 84 | input[`new_note[6][${i}]`] = utxo.nft; 85 | // public signals 86 | input[`new_note_hash[${i}]`] = utxo.hash(); 87 | input[`typeof_new_note[${i}]`] = utxo.outflowType(); 88 | input[`public_data[0][${i}]`] = utxo.publicData ? utxo.publicData.to : 0; 89 | input[`public_data[1][${i}]`] = utxo.publicData ? utxo.eth : 0; 90 | input[`public_data[2][${i}]`] = utxo.publicData ? utxo.tokenAddr : 0; 91 | input[`public_data[3][${i}]`] = utxo.publicData ? utxo.erc20Amount : 0; 92 | input[`public_data[4][${i}]`] = utxo.publicData ? utxo.nft : 0; 93 | input[`public_data[5][${i}]`] = utxo.publicData ? utxo.publicData.fee : 0; 94 | }); 95 | input[`swap`] = tx.swap ? tx.swap : 0; 96 | input[`fee`] = tx.fee; 97 | Object.keys(input).forEach(key => { 98 | input[key] = input[key].toString(); 99 | }); 100 | let witness = circuit.calculateWitness(input); 101 | let { proof, publicSignals } = snarkjs.groth.genProof(snarkjs.unstringifyBigInts(provingKey), witness); 102 | //TODO handle genProof exception 103 | let zkTx: ZkTransaction = new ZkTransaction({ 104 | inflow: tx.inflow.map((utxo, index) => { 105 | return { 106 | nullifier: utxo.nullifier(), 107 | root: merkleProof[index].root 108 | }; 109 | }), 110 | outflow: tx.outflow.map(utxo => utxo.toOutflow()), 111 | fee: tx.fee, 112 | proof: { 113 | pi_a: proof.pi_a.map(val => Field.from(val)), 114 | pi_b: proof.pi_b.map(arr => arr.map(val => Field.from(val))), 115 | pi_c: proof.pi_c.map(val => Field.from(val)) 116 | }, 117 | swap: tx.swap, 118 | memo: toMemo ? tx.outflow[toMemo].encrypt() : undefined 119 | }); 120 | resolve(zkTx); 121 | } 122 | 123 | function addMerkleProof({ index, proof }: { index: number; proof: MerkleProof }) { 124 | merkleProof[index] = proof; 125 | genSNARK(); 126 | } 127 | tx.inflow.forEach((utxo, index) => { 128 | eddsa[index] = sign({ msg: utxo.hash(), privKey: _this.privKey }); 129 | _this.grove 130 | .utxoMerkleProof(utxo.hash().toHex()) 131 | .then(proof => addMerkleProof({ index, proof })) 132 | .catch(reject); 133 | }); 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /test/dataset/dataset.ts: -------------------------------------------------------------------------------- 1 | import { Field } from '../../src/field'; 2 | import { Grove } from '../../src/tree'; 3 | import { UTXO } from '../../src/utxo'; 4 | import { ZkTransaction } from '../../src/zk_transaction'; 5 | import { BabyJubjub } from '../../src/jubjub'; 6 | import { TokenAddress } from '../../src/tokens'; 7 | import { Transaction } from '../../src/transaction'; 8 | import { ZkWizard } from '../../src/zk_wizard'; 9 | import fs from 'fs-extra'; 10 | 11 | const alicePrivKey: string = "I am Alice's private key"; 12 | const alicePubKey: BabyJubjub.Point = BabyJubjub.Point.fromPrivKey(alicePrivKey); 13 | const bobPrivKey: string = "I am Bob's private key"; 14 | const bobPubKey: BabyJubjub.Point = BabyJubjub.Point.fromPrivKey(bobPrivKey); 15 | 16 | const utxo1_in_1: UTXO = UTXO.newEtherNote({ eth: 3333, pubKey: alicePubKey, salt: 11 }); 17 | const utxo1_out_1: UTXO = UTXO.newEtherNote({ eth: 2221, pubKey: bobPubKey, salt: 12 }); 18 | const utxo1_out_2: UTXO = UTXO.newEtherNote({ eth: 1111, pubKey: alicePubKey, salt: 13 }); 19 | 20 | const utxo2_1_in_1: UTXO = UTXO.newERC20Note({ eth: 22222333333, tokenAddr: TokenAddress.DAI, erc20Amount: 8888, pubKey: alicePubKey, salt: 14 }); 21 | const utxo2_1_out_1: UTXO = UTXO.newERC20Note({ eth: 22222333332, tokenAddr: TokenAddress.DAI, erc20Amount: 5555, pubKey: alicePubKey, salt: 15 }); 22 | const utxo2_1_out_2: UTXO = UTXO.newERC20Note({ eth: 0, tokenAddr: TokenAddress.DAI, erc20Amount: 3333, pubKey: bobPubKey, salt: 16 }); 23 | 24 | const KITTY_1 = '0x0078917891789178917891789178917891789178917891789178917891789178'; 25 | const KITTY_2 = '0x0022222222222222222222222222222222222222222222222222222222222222'; 26 | 27 | /** Ganache pre-defined addresses */ 28 | const USER_A = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'; 29 | const CONTRACT_B = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'; 30 | 31 | const utxo2_2_in_1: UTXO = UTXO.newNFTNote({ eth: 7777777777, tokenAddr: TokenAddress.CRYPTO_KITTIES, nft: KITTY_1, pubKey: bobPubKey, salt: 17 }); 32 | const utxo2_2_out_1: UTXO = UTXO.newEtherNote({ eth: 7777777776, pubKey: bobPubKey, salt: 18 }); 33 | const utxo2_2_out_2: UTXO = UTXO.newNFTNote({ eth: 0, tokenAddr: TokenAddress.CRYPTO_KITTIES, nft: KITTY_1, pubKey: alicePubKey, salt: 19 }); 34 | 35 | const utxo3_in_1: UTXO = UTXO.newEtherNote({ eth: 111111111111111, pubKey: alicePubKey, salt: 21 }); 36 | const utxo3_in_2: UTXO = UTXO.newEtherNote({ eth: 222222222222222, pubKey: alicePubKey, salt: 22 }); 37 | const utxo3_in_3: UTXO = UTXO.newEtherNote({ eth: 333333333333333, pubKey: alicePubKey, salt: 23 }); 38 | const utxo3_out_1: UTXO = UTXO.newEtherNote({ eth: 666666666666664, pubKey: alicePubKey, salt: 24 }); 39 | utxo3_out_1.markAsWithdrawal({ to: Field.from(USER_A), fee: Field.from(1) }); 40 | 41 | const utxo4_in_1: UTXO = UTXO.newEtherNote({ eth: 8888888888888, pubKey: alicePubKey, salt: 25 }); 42 | const utxo4_in_2: UTXO = UTXO.newERC20Note({ eth: 0, tokenAddr: TokenAddress.DAI, erc20Amount: 5555, pubKey: alicePubKey, salt: 26 }); 43 | const utxo4_in_3: UTXO = UTXO.newNFTNote({ eth: 0, tokenAddr: TokenAddress.CRYPTO_KITTIES, nft: KITTY_2, pubKey: alicePubKey, salt: 27 }); 44 | const utxo4_out_1: UTXO = UTXO.newEtherNote({ eth: 8888888888884, pubKey: alicePubKey, salt: 28 }); // fee for tx & fee for withdrawal for each utxos 45 | const utxo4_out_2: UTXO = UTXO.newERC20Note({ eth: 0, tokenAddr: TokenAddress.DAI, erc20Amount: 5555, pubKey: alicePubKey, salt: 29 }); 46 | const utxo4_out_3: UTXO = UTXO.newNFTNote({ eth: 0, tokenAddr: TokenAddress.CRYPTO_KITTIES, nft: KITTY_2, pubKey: alicePubKey, salt: 30 }); 47 | utxo4_out_1.markAsMigration({ to: Field.from(CONTRACT_B), fee: Field.from(1) }); 48 | utxo4_out_2.markAsMigration({ to: Field.from(CONTRACT_B), fee: Field.from(1) }); 49 | utxo4_out_3.markAsMigration({ to: Field.from(CONTRACT_B), fee: Field.from(1) }); 50 | 51 | const tx_1: Transaction = { 52 | inflow: [utxo1_in_1], 53 | outflow: [utxo1_out_1, utxo1_out_2], 54 | fee: Field.from(1) 55 | }; 56 | 57 | const tx_2_1: Transaction = { 58 | inflow: [utxo2_1_in_1], 59 | outflow: [utxo2_1_out_1, utxo2_1_out_2], 60 | swap: utxo2_2_out_2.hash(), 61 | fee: Field.from(1) 62 | }; 63 | 64 | const tx_2_2: Transaction = { 65 | inflow: [utxo2_2_in_1], 66 | outflow: [utxo2_2_out_1, utxo2_2_out_2], 67 | swap: utxo2_1_out_2.hash(), 68 | fee: Field.from(1) 69 | }; 70 | 71 | const tx_3: Transaction = { 72 | inflow: [utxo3_in_1, utxo3_in_2, utxo3_in_3], 73 | outflow: [utxo3_out_1], 74 | fee: Field.from(1) 75 | }; 76 | 77 | const tx_4: Transaction = { 78 | inflow: [utxo4_in_1, utxo4_in_2, utxo4_in_3], 79 | outflow: [utxo4_out_1, utxo4_out_2, utxo4_out_3], 80 | fee: Field.from(1) 81 | }; 82 | 83 | export const keys = { 84 | alicePrivKey, 85 | alicePubKey, 86 | bobPrivKey, 87 | bobPubKey 88 | }; 89 | 90 | export const address = { 91 | USER_A, 92 | CONTRACT_B, 93 | CRYPTO_KITTIES: TokenAddress.CRYPTO_KITTIES, 94 | DAI: TokenAddress.DAI 95 | }; 96 | 97 | export const nfts = { 98 | KITTY_1, 99 | KITTY_2 100 | }; 101 | 102 | export const utxos = { 103 | utxo1_in_1, 104 | utxo1_out_1, 105 | utxo1_out_2, 106 | utxo2_1_in_1, 107 | utxo2_1_out_1, 108 | utxo2_2_in_1, 109 | utxo2_2_out_1, 110 | utxo2_2_out_2, 111 | utxo3_in_1, 112 | utxo3_in_2, 113 | utxo3_in_3, 114 | utxo3_out_1, 115 | utxo4_in_1, 116 | utxo4_in_2, 117 | utxo4_in_3, 118 | utxo4_out_1, 119 | utxo4_out_2, 120 | utxo4_out_3 121 | }; 122 | 123 | export const txs = { 124 | tx_1, 125 | tx_2_1, 126 | tx_2_2, 127 | tx_3, 128 | tx_4 129 | }; 130 | 131 | export interface TestSet { 132 | utxoGrove: Grove; 133 | zkTxs: ZkTransaction[]; 134 | closeDB: () => Promise; 135 | } 136 | 137 | export async function loadGrove(): Promise<{ grove: Grove; close: () => Promise }> { 138 | let treePath = 'build/tree1'; 139 | // reset data 140 | fs.removeSync(treePath); 141 | let grove = new Grove('zkopru', treePath, 31); 142 | await grove.init(); 143 | let latestTree = grove.latestUTXOTree(); 144 | let size = latestTree ? await latestTree.size() : BigInt(0); 145 | if (size == BigInt(0)) { 146 | await grove.appendUTXO(utxo1_in_1.hash().toHex()); 147 | await grove.appendUTXO(utxo2_1_in_1.hash().toHex()); 148 | await grove.appendUTXO(utxo2_2_in_1.hash().toHex()); 149 | await grove.appendUTXO(utxo3_in_1.hash().toHex()); 150 | await grove.appendUTXO(utxo3_in_2.hash().toHex()); 151 | await grove.appendUTXO(utxo3_in_3.hash().toHex()); 152 | await grove.appendUTXO(utxo4_in_1.hash().toHex()); 153 | await grove.appendUTXO(utxo4_in_2.hash().toHex()); 154 | await grove.appendUTXO(utxo4_in_3.hash().toHex()); 155 | } 156 | let close = async () => { 157 | await grove.close(); 158 | }; 159 | return { grove, close }; 160 | } 161 | 162 | export function loadCircuits(): { 163 | circuit_1_2: any; 164 | circuit_1_2_pk: any; 165 | circuit_3_1: any; 166 | circuit_3_1_pk: any; 167 | circuit_3_3: any; 168 | circuit_3_3_pk: any; 169 | } { 170 | let circuit_1_2 = JSON.parse(fs.readFileSync('build/circuits.test/zk_transaction_1_2.test.json', 'utf8')); 171 | let circuit_3_1 = JSON.parse(fs.readFileSync('build/circuits.test/zk_transaction_3_1.test.json', 'utf8')); 172 | let circuit_3_3 = JSON.parse(fs.readFileSync('build/circuits.test/zk_transaction_3_3.test.json', 'utf8')); 173 | let circuit_1_2_pk = JSON.parse(fs.readFileSync('build/pks.test/zk_transaction_1_2.test.pk.json', 'utf8')); 174 | let circuit_3_1_pk = JSON.parse(fs.readFileSync('build/pks.test/zk_transaction_3_1.test.pk.json', 'utf8')); 175 | let circuit_3_3_pk = JSON.parse(fs.readFileSync('build/pks.test/zk_transaction_3_3.test.pk.json', 'utf8')); 176 | return { 177 | circuit_1_2, 178 | circuit_1_2_pk, 179 | circuit_3_1, 180 | circuit_3_1_pk, 181 | circuit_3_3, 182 | circuit_3_3_pk 183 | }; 184 | } 185 | 186 | export function loadPrebuiltZkTxs(): ZkTransaction[] { 187 | let prebuiltTxs = ['data/txs/zk_tx_1.tx', 'data/txs/zk_tx_2_1.tx', 'data/txs/zk_tx_2_2.tx', 'data/txs/zk_tx_3.tx', 'data/txs/zk_tx_4.tx']; 188 | return prebuiltTxs.map(path => ZkTransaction.decode(fs.readFileSync(path))); 189 | } 190 | 191 | export async function buildAndSaveZkTxs(): Promise { 192 | let { grove, close } = await loadGrove(); 193 | let aliceZkWizard = new ZkWizard({ 194 | grove, 195 | privKey: alicePrivKey 196 | }); 197 | let bobZkWizard = new ZkWizard({ 198 | grove, 199 | privKey: keys.bobPrivKey 200 | }); 201 | let { circuit_1_2, circuit_1_2_pk, circuit_3_1, circuit_3_1_pk, circuit_3_3, circuit_3_3_pk } = loadCircuits(); 202 | aliceZkWizard.addCircuit({ n_i: 1, n_o: 2, circuitDef: circuit_1_2, provingKey: circuit_1_2_pk }); 203 | aliceZkWizard.addCircuit({ n_i: 3, n_o: 1, circuitDef: circuit_3_1, provingKey: circuit_3_1_pk }); 204 | aliceZkWizard.addCircuit({ n_i: 3, n_o: 3, circuitDef: circuit_3_3, provingKey: circuit_3_3_pk }); 205 | bobZkWizard.addCircuit({ n_i: 1, n_o: 2, circuitDef: circuit_1_2, provingKey: circuit_1_2_pk }); 206 | let zk_tx_1 = await aliceZkWizard.shield({ tx: tx_1 }); 207 | let zk_tx_2_1 = await aliceZkWizard.shield({ tx: tx_2_1 }); 208 | let zk_tx_2_2 = await bobZkWizard.shield({ tx: tx_2_2 }); 209 | let zk_tx_3 = await aliceZkWizard.shield({ tx: tx_3 }); 210 | let zk_tx_4 = await aliceZkWizard.shield({ tx: tx_4 }); 211 | fs.writeFileSync('data/txs/zk_tx_1.tx', zk_tx_1.encode()); 212 | fs.writeFileSync('data/txs/zk_tx_2_1.tx', zk_tx_2_1.encode()); 213 | fs.writeFileSync('data/txs/zk_tx_2_2.tx', zk_tx_2_2.encode()); 214 | fs.writeFileSync('data/txs/zk_tx_3.tx', zk_tx_3.encode()); 215 | fs.writeFileSync('data/txs/zk_tx_4.tx', zk_tx_4.encode()); 216 | await close(); 217 | return; 218 | } 219 | -------------------------------------------------------------------------------- /test/solidity/Deserializer.soltest.ts: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | 3 | // import { Deserializer } from '../src/types/truffle-contracts'; 4 | -------------------------------------------------------------------------------- /test/solidity/Lib.soltest.ts: -------------------------------------------------------------------------------- 1 | // import chai from 'chai'; 2 | // import { soliditySha3 } from 'web3-utils'; 3 | // import fs from 'fs-extra'; 4 | 5 | // const expect = chai.expect; 6 | const Layer2 = artifacts.require('ZkOptimisticRollUp'); 7 | 8 | const data = { 9 | metadata: { 10 | address: '0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', 11 | prev: { 12 | output: '0x1cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 13 | nullifier: '0x2cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 14 | withdrawal: '0x3cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 15 | }, 16 | next: { 17 | output: '0x4cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 18 | nullifier: '0x5cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 19 | withdrawal: '0x6cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 20 | }, 21 | numberOfTxs: '0x0002', 22 | numberOfDeposits: '0x0002', 23 | totalFee: '0x000000000000000000000000000000000000000000000000000000000000000f' 24 | }, 25 | deposits: ['0x4cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', '0x4cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde'], 26 | transactions: [ 27 | { 28 | numOfInputs: '0x02', 29 | numOfOutputs: '0x02', 30 | txType: '0x00', 31 | fee: '0x1111111111111111111111111111111111111111111111111111111111111111', 32 | inclusionRefs: [ 33 | '0x7cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 34 | '0x8cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 35 | ], 36 | nullifiers: ['0x9cdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', '0x10debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde'], 37 | outputs: ['0x11debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', '0x12debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde'], 38 | proofs: [ 39 | '0x131ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 40 | '0x132ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 41 | '0x133ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 42 | '0x134ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 43 | '0x135ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 44 | '0x136ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 45 | '0x137ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 46 | '0x138ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 47 | ] 48 | }, 49 | { 50 | numOfInputs: '0x02', 51 | numOfOutputs: '0x02', 52 | txType: '0x00', 53 | fee: '0x12', 54 | inclusionRefs: [ 55 | '0x14aebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 56 | '0x14bebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 57 | ], 58 | nullifiers: ['0x14cebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', '0x14debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde'], 59 | outputs: ['0x15debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', '0x15debcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde'], 60 | proofs: [ 61 | '0x161ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 62 | '0x162ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 63 | '0x163ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 64 | '0x164ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 65 | '0x165ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 66 | '0x166ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 67 | '0x167ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde', 68 | '0x168ebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde' 69 | ] 70 | } 71 | ] 72 | }; 73 | const combine = arr => { 74 | let result = '0x'; 75 | for (let i = 0; i < arr.length; i++) { 76 | result = result.concat(arr[i].slice(2)); 77 | } 78 | return result; 79 | }; 80 | const serialized = combine([ 81 | data.metadata.address, 82 | data.metadata.prev.output, 83 | data.metadata.prev.nullifier, 84 | data.metadata.prev.withdrawal, 85 | data.metadata.next.output, 86 | data.metadata.next.nullifier, 87 | data.metadata.next.withdrawal, 88 | data.metadata.numberOfTxs, 89 | data.metadata.numberOfDeposits, 90 | data.metadata.totalFee, 91 | ...data.deposits, 92 | ...data.transactions.map(tx => 93 | combine([tx.numOfInputs, tx.numOfOutputs, tx.txType, tx.fee, ...tx.inclusionRefs, ...tx.nullifiers, ...tx.outputs, ...tx.proofs]) 94 | ) 95 | ]); 96 | 97 | contract('Simple tests', async accounts => { 98 | before(async () => { 99 | let layer2 = await Layer2.new(); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/ts/coordinator.test.ts: -------------------------------------------------------------------------------- 1 | import { loadPrebuiltZkTxs, buildAndSaveZkTxs } from '../dataset/dataset'; 2 | import { ZkTransaction } from '../../src/zk_transaction'; 3 | import { TxMemPool } from '../../src/coordinator'; 4 | import { expect } from 'chai'; 5 | import fs from 'fs'; 6 | 7 | context('Coordinator test', () => { 8 | let zkTxs: ZkTransaction[]; 9 | let txPool: TxMemPool = new TxMemPool(); 10 | before('Initialize data set for test', async () => { 11 | // await buildAndSaveZkTxs(); 12 | zkTxs = loadPrebuiltZkTxs(); 13 | }); 14 | context('TxMemPool', () => { 15 | before(() => { 16 | let circuit_1_2_vk = JSON.parse(fs.readFileSync('build/vks.test/zk_transaction_1_2.test.vk.json', 'utf8')); 17 | let circuit_3_1_vk = JSON.parse(fs.readFileSync('build/vks.test/zk_transaction_3_1.test.vk.json', 'utf8')); 18 | let circuit_3_3_vk = JSON.parse(fs.readFileSync('build/vks.test/zk_transaction_3_3.test.vk.json', 'utf8')); 19 | txPool.addVerifier({ n_i: 1, n_o: 2, vk: circuit_1_2_vk }); 20 | txPool.addVerifier({ n_i: 3, n_o: 1, vk: circuit_3_1_vk }); 21 | txPool.addVerifier({ n_i: 3, n_o: 3, vk: circuit_3_3_vk }); 22 | }); 23 | describe('addToTxPool()', () => { 24 | it('should add txs to the "pending" tx mem pool', async () => { 25 | txPool.addToTxPool(zkTxs[0]); 26 | txPool.addToTxPool(zkTxs[1]); 27 | txPool.addToTxPool(zkTxs[2]); 28 | txPool.addToTxPool(zkTxs[3]); 29 | txPool.addToTxPool(zkTxs[4]); 30 | expect(txPool.pendingNum()).to.eq(5); 31 | }); 32 | }); 33 | let candidates: ZkTransaction[]; 34 | describe('pickPendingTxs()', () => { 35 | it('should pick transactions less than the given limit size', () => { 36 | candidates = txPool.pickPendingTxs(2048); 37 | expect(candidates.reduce((size, zkTx) => size + zkTx.size(), 0)).to.be.lessThan(2048); 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/ts/transaction.test.ts: -------------------------------------------------------------------------------- 1 | import { TxBuilder } from '../../src/transaction'; 2 | import { keys, utxos as u, address, nfts } from '../dataset/dataset'; 3 | import { Field } from '../../src/field'; 4 | import { expect } from 'chai'; 5 | import { BabyJubjub } from '../../src/jubjub'; 6 | 7 | context('transaction.ts:', () => { 8 | context('class TxBuilder', () => { 9 | describe('spendable()', () => { 10 | it('should return exact amount of spendable eth', async () => { 11 | let txBuilder = TxBuilder.from(keys.alicePubKey); 12 | const FEE = 1; 13 | let spendable = txBuilder 14 | .fee(FEE) 15 | .spend(u.utxo3_in_1) 16 | .spend(u.utxo3_in_2) 17 | .spend(u.utxo3_in_3) 18 | .spendable(); 19 | let expectedETH = u.utxo3_in_1.eth 20 | .add(u.utxo3_in_2.eth) 21 | .add(u.utxo3_in_3.eth) 22 | .sub(Field.from(FEE)); 23 | expect(spendable.eth.equal(expectedETH)).to.eq(true); 24 | }); 25 | it('should return exact amount of spendable erc20', async () => { 26 | let txBuilder = TxBuilder.from(keys.alicePubKey); 27 | const FEE = 1; 28 | let spendable = txBuilder 29 | .fee(FEE) 30 | .spend(u.utxo2_1_in_1) 31 | .spendable(); 32 | expect(spendable.erc20[u.utxo2_1_in_1.tokenAddr.toHex()].equal(u.utxo2_1_in_1.erc20Amount)).to.eq(true); 33 | }); 34 | it('should return exact spendable nft ids', async () => { 35 | let txBuilder = TxBuilder.from(keys.bobPubKey); 36 | const FEE = 1; 37 | let spendable = txBuilder 38 | .fee(FEE) 39 | .spend(u.utxo2_2_in_1) 40 | .spendable(); 41 | expect(spendable.erc721[u.utxo2_2_in_1.tokenAddr.toHex()].find(nft => nft.equal(u.utxo2_2_in_1.nft))).not.to.eq(undefined); 42 | }); 43 | }); 44 | describe('build()', () => { 45 | it('should return Tx object', async () => { 46 | let tx = TxBuilder.from(keys.alicePubKey) 47 | .fee(1) 48 | .spend(u.utxo1_in_1) 49 | .sendEther({ 50 | eth: 2222, 51 | to: keys.bobPubKey 52 | }) 53 | .build(); 54 | expect(tx).exist; 55 | }); 56 | it('should return Tx object for atomic swap', async () => { 57 | let tx = TxBuilder.from(keys.alicePubKey) 58 | .fee(1) 59 | .spend(u.utxo2_1_in_1) 60 | .sendERC20({ 61 | tokenAddr: address.DAI, 62 | erc20Amount: 3333, 63 | to: keys.bobPubKey 64 | }) 65 | .swapForNFT({ 66 | tokenAddr: address.CRYPTO_KITTIES, 67 | nft: nfts.KITTY_1 68 | }) 69 | .build(); 70 | expect(tx).exist; 71 | }); 72 | it('should return Tx object for withdrawal', async () => { 73 | let tx = TxBuilder.from(keys.alicePubKey) 74 | .fee(1) 75 | .spend(u.utxo3_in_1) 76 | .spend(u.utxo3_in_2) 77 | .spend(u.utxo3_in_3) 78 | .sendEther({ 79 | eth: 555555555555555, 80 | to: BabyJubjub.Point.zero, 81 | withdrawal: { 82 | to: address.USER_A, 83 | fee: 1 84 | } 85 | }) 86 | .build(); 87 | expect(tx).exist; 88 | }); 89 | it('should return Tx object for migration', async () => { 90 | let tx = TxBuilder.from(keys.alicePubKey) 91 | .fee(1) 92 | .spend(u.utxo4_in_1) 93 | .spend(u.utxo4_in_2) 94 | .spend(u.utxo4_in_3) 95 | .sendEther({ 96 | eth: 8888888888884, 97 | to: keys.alicePubKey, 98 | migration: { 99 | to: address.CONTRACT_B, 100 | fee: 1 101 | } 102 | }) 103 | .sendERC20({ 104 | tokenAddr: address.DAI, 105 | erc20Amount: 5555, 106 | to: keys.alicePubKey, 107 | migration: { 108 | to: address.CONTRACT_B, 109 | fee: 1 110 | } 111 | }) 112 | .sendNFT({ 113 | tokenAddr: address.CRYPTO_KITTIES, 114 | nft: nfts.KITTY_2, 115 | to: keys.alicePubKey, 116 | migration: { 117 | to: address.CONTRACT_B, 118 | fee: 1 119 | } 120 | }) 121 | .build(); 122 | expect(tx).exist; 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/ts/zk_wizard.test.ts: -------------------------------------------------------------------------------- 1 | import { loadGrove, loadCircuits, txs, loadPrebuiltZkTxs } from '../dataset/dataset'; 2 | import { keys } from '../dataset/dataset'; 3 | import { expect } from 'chai'; 4 | import { ZkWizard } from '../../src/zk_wizard'; 5 | import { Grove } from '../../src/tree'; 6 | import { ZkTransaction } from '../../src/zk_transaction'; 7 | import fs from 'fs'; 8 | 9 | context('zk SNARK', () => { 10 | let preset: { 11 | grove: Grove; 12 | close: () => Promise; 13 | }; 14 | let aliceZkWizard: ZkWizard; 15 | let bobZkWizard: ZkWizard; 16 | let zkTxs: ZkTransaction[]; 17 | before('Prepare data and circuits', async () => { 18 | preset = await loadGrove(); 19 | aliceZkWizard = new ZkWizard({ 20 | grove: preset.grove, 21 | privKey: keys.alicePrivKey 22 | }); 23 | let { circuit_1_2, circuit_1_2_pk, circuit_3_1, circuit_3_1_pk, circuit_3_3, circuit_3_3_pk } = loadCircuits(); 24 | aliceZkWizard.addCircuit({ 25 | n_i: 1, 26 | n_o: 2, 27 | circuitDef: circuit_1_2, 28 | provingKey: circuit_1_2_pk 29 | }); 30 | aliceZkWizard.addCircuit({ 31 | n_i: 3, 32 | n_o: 1, 33 | circuitDef: circuit_3_1, 34 | provingKey: circuit_3_1_pk 35 | }); 36 | aliceZkWizard.addCircuit({ 37 | n_i: 3, 38 | n_o: 3, 39 | circuitDef: circuit_3_3, 40 | provingKey: circuit_3_3_pk 41 | }); 42 | bobZkWizard = new ZkWizard({ 43 | grove: preset.grove, 44 | privKey: keys.bobPrivKey 45 | }); 46 | bobZkWizard.addCircuit({ 47 | n_i: 1, 48 | n_o: 2, 49 | circuitDef: circuit_1_2, 50 | provingKey: circuit_1_2_pk 51 | }); 52 | zkTxs = []; 53 | }); 54 | context('class ZkWizard @ zk_wizard.js', () => { 55 | describe.skip('shield()', () => { 56 | it('should create a zk transaction from a normal transaction', async () => { 57 | let zk_tx_1 = await aliceZkWizard.shield({ tx: txs.tx_1, toMemo: 0 }); 58 | zkTxs.push(zk_tx_1); 59 | fs.writeFileSync('data/txs/zk_tx_1.tx', zk_tx_1.encode()); 60 | }); 61 | it('should create a zk transaction from a normal transaction', async () => { 62 | let zk_tx_2_1 = await aliceZkWizard.shield({ tx: txs.tx_2_1, toMemo: 1 }); 63 | zkTxs.push(zk_tx_2_1); 64 | fs.writeFileSync('data/txs/zk_tx_2_1.tx', zk_tx_2_1.encode()); 65 | }); 66 | it('should create a zk transaction from a normal transaction', async () => { 67 | let zk_tx_2_2 = await bobZkWizard.shield({ tx: txs.tx_2_2, toMemo: 1 }); 68 | zkTxs.push(zk_tx_2_2); 69 | fs.writeFileSync('data/txs/zk_tx_2_2.tx', zk_tx_2_2.encode()); 70 | }); 71 | it('should create a zk transaction from a normal transaction', async () => { 72 | let zk_tx_3 = await aliceZkWizard.shield({ tx: txs.tx_3 }); 73 | zkTxs.push(zk_tx_3); 74 | fs.writeFileSync('data/txs/zk_tx_3.tx', zk_tx_3.encode()); 75 | }); 76 | it('should create a zk transaction from a normal transaction', async () => { 77 | let zk_tx_4 = await aliceZkWizard.shield({ tx: txs.tx_4 }); 78 | zkTxs.push(zk_tx_4); 79 | fs.writeFileSync('data/txs/zk_tx_4.tx', zk_tx_4.encode()); 80 | }); 81 | }); 82 | after('Using prebuilt zk txs for quick test', () => { 83 | zkTxs = loadPrebuiltZkTxs(); 84 | }); 85 | }); 86 | context('class ZkTransaction @ zk_transaction.js', () => { 87 | let encoded: Buffer[]; 88 | let decoded: ZkTransaction[]; 89 | describe('encode()', () => { 90 | it('should be serialized in a compact way.', () => { 91 | encoded = zkTxs.map(zkTx => zkTx.encode()); 92 | }), 93 | it.skip('should be decodable in solidity', () => {}); 94 | }); 95 | describe('static decode()', () => { 96 | it('should be retrieved from serialized bytes data', () => { 97 | decoded = encoded.map(ZkTransaction.decode); 98 | expect(decoded[0].hash()).to.eq(zkTxs[0].hash()); 99 | expect(decoded[1].hash()).to.eq(zkTxs[1].hash()); 100 | expect(decoded[2].hash()).to.eq(zkTxs[2].hash()); 101 | expect(decoded[3].hash()).to.eq(zkTxs[3].hash()); 102 | expect(decoded[4].hash()).to.eq(zkTxs[4].hash()); 103 | }); 104 | }); 105 | describe('hash()', () => { 106 | it.skip('should return same hash value with the solidity hash function', () => {}); 107 | }); 108 | }); 109 | after(async () => { 110 | await preset.close(); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | 3 | /** 4 | * Use this file to configure your truffle project. It's seeded with some 5 | * common settings for different networks and features like migrations, 6 | * compilation and testing. Uncomment the ones you need or modify 7 | * them to suit your project as necessary. 8 | * 9 | * More information about configuration can be found at: 10 | * 11 | * truffleframework.com/docs/advanced/configuration 12 | * 13 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 14 | * to sign your transactions before they're sent to a remote public node. Infura accounts 15 | * are available for free at: infura.io/register. 16 | * 17 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 18 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 19 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 20 | * 21 | */ 22 | 23 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 24 | // const infuraKey = "fj4jll3k....."; 25 | // 26 | // const fs = require('fs'); 27 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 28 | 29 | module.exports = { 30 | test_file_extension_regexp: /.*.soltest.ts$/, 31 | /** 32 | * Networks define how you connect to your ethereum client and let you set the 33 | * defaults web3 uses to send transactions. If you don't specify one truffle 34 | * will spin up a development blockchain for you on port 9545 when you 35 | * run `develop` or `test`. You can ask a truffle command to use a specific 36 | * network from the command line, e.g 37 | * 38 | * $ truffle test --network 39 | */ 40 | 41 | networks: { 42 | // Useful for testing. The `development` name is special - truffle uses it by default 43 | // if it's defined here and no other network is specified at the command line. 44 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 45 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 46 | // options below to some value. 47 | // 48 | // development: { 49 | // host: "127.0.0.1", // Localhost (default: none) 50 | // port: 8545, // Standard Ethereum port (default: none) 51 | // network_id: "*", // Any network (default: none) 52 | // }, 53 | // Another network with more advanced options... 54 | // advanced: { 55 | // port: 8777, // Custom port 56 | // network_id: 1342, // Custom network 57 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 58 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 59 | // from:
, // Account to send txs from (default: accounts[0]) 60 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 61 | // }, 62 | // Useful for deploying to a public network. 63 | // NB: It's important to wrap the provider as a function. 64 | // ropsten: { 65 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 66 | // network_id: 3, // Ropsten's id 67 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 68 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 69 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 70 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets) 71 | // }, 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | reporter: 'eth-gas-reporter', 83 | useColors: true 84 | // timeout: 100000 85 | }, 86 | 87 | // Configure your compilers 88 | compilers: { 89 | solc: { 90 | version: '0.6.1', 91 | evmVersion: 'istanbul', 92 | settings: { 93 | optimize: true, 94 | runs: 1000000 95 | } 96 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 97 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 98 | // settings: { // See the solidity docs for advice about optimization and evmVersion 99 | // optimizer: { 100 | // enabled: false, 101 | // runs: 200 102 | // }, 103 | // evmVersion: "byzantium" 104 | // } 105 | }, 106 | external: { 107 | command: 'node utils/mimcGenerator.js && node utils/poseidonGenerator.js && node utils/erc20Generator.js', 108 | targets: [ 109 | { 110 | path: 'build/generated/*.json' 111 | } 112 | ] 113 | } 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "allowJs": true, 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true, 7 | "noImplicitAny": false, 8 | "outDir": "dist", 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "declaration": true, 12 | "typeRoots": ["src/types", "node_modules/@types"], 13 | "paths": { 14 | "*": ["node_modules/*", "src/types/*", "test/types/*"] 15 | }, 16 | "lib": ["es2015"] 17 | }, 18 | "exclude": ["node_modules", "test", "node_modules/@types/jest", "dist", "example"] 19 | } 20 | -------------------------------------------------------------------------------- /utils/erc20Generator.js: -------------------------------------------------------------------------------- 1 | console.log('> Compiling ERC20'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const solc = require('solc'); 5 | const Artifactor = require('truffle-artifactor'); 6 | 7 | let erc20Code = fs.readFileSync('./utils/TestERC20.sol', 'utf8'); 8 | 9 | let input = { 10 | language: 'Solidity', 11 | sources: { 12 | 'TestERC20.sol': { 13 | content: erc20Code 14 | } 15 | }, 16 | settings: { 17 | outputSelection: { 18 | '*': { 19 | '*': ['*'] 20 | } 21 | } 22 | } 23 | }; 24 | 25 | let output = JSON.parse(solc.compile(JSON.stringify(input))); 26 | let sourceFile = output.contracts['TestERC20.sol']; 27 | let contract = sourceFile['TestERC20']; 28 | 29 | const contractsDir = path.join(__dirname, '..', 'build/generated'); 30 | let artifactor = new Artifactor(contractsDir); 31 | fs.mkdirSync(contractsDir, { recursive: true }); 32 | (async () => { 33 | await artifactor.save({ 34 | contractName: 'TestERC20', 35 | abi: contract.abi, 36 | bytecode: contract.evm.bytecode.object 37 | }); 38 | })(); 39 | -------------------------------------------------------------------------------- /utils/mimcGenerator.js: -------------------------------------------------------------------------------- 1 | console.log('> Compiling MiMC library'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const mimcGenContract = require('circomlib/src/mimcsponge_gencontract.js'); 6 | const Artifactor = require('truffle-artifactor'); 7 | const SEED = 'mimcsponge'; 8 | 9 | const contractsDir = path.join(__dirname, '..', 'build/generated'); 10 | let artifactor = new Artifactor(contractsDir); 11 | let mimcContractName = 'MiMC'; 12 | fs.mkdirSync(contractsDir, { recursive: true }); 13 | (async () => { 14 | await artifactor.save({ 15 | contractName: mimcContractName, 16 | abi: mimcGenContract.abi, 17 | unlinked_binary: mimcGenContract.createCode(SEED, 220) 18 | }); 19 | })(); 20 | -------------------------------------------------------------------------------- /utils/poseidonGenerator.js: -------------------------------------------------------------------------------- 1 | console.log('> Compiling Poseidon library'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const poseidonGenContract = require('circomlib/src/poseidon_gencontract.js'); 6 | const Artifactor = require('truffle-artifactor'); 7 | const SEED = 'poseidon'; 8 | const NROUNDSF = 8; 9 | const NROUNDSP = 57; 10 | const T = 6; 11 | 12 | const contractsDir = path.join(__dirname, '..', 'build/generated'); 13 | let artifactor = new Artifactor(contractsDir); 14 | let poseidonContractName = 'Poseidon'; 15 | fs.mkdirSync(contractsDir, { recursive: true }); 16 | (async () => { 17 | await artifactor.save({ 18 | contractName: poseidonContractName, 19 | abi: poseidonGenContract.abi, 20 | unlinked_binary: poseidonGenContract.createCode(T, NROUNDSF, NROUNDSP, SEED) 21 | }); 22 | })(); 23 | --------------------------------------------------------------------------------