Created
March 28, 2022 23:26
-
-
Save rizary/4065d13e26382f6cfa018eb80a9f1cd3 to your computer and use it in GitHub Desktop.
RollupNC review
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Deposits ETH or ERC20 tokens onto L2 | |
| function deposit( | |
| uint[2] memory pubkey, | |
| uint amount, | |
| uint tokenType | |
| ) public payable { | |
| if ( tokenType == 0 ) { | |
| // reserved the token type if sender is same as coordinator | |
| require( | |
| msg.sender == coordinator, | |
| "tokenType 0 is reserved for coordinator"); | |
| require( | |
| amount == 0 && msg.value == 0, | |
| "tokenType 0 does not have real value"); | |
| } else if ( tokenType == 1 ) { | |
| // ETH | |
| require( | |
| msg.value > 0 && msg.value >= amount, | |
| "msg.value must at least equal stated amount in wei"); | |
| } else if ( tokenType > 1 ) { | |
| // ERC20 token | |
| require( | |
| amount > 0, | |
| "token deposit must be greater than 0"); | |
| // Andika: Initiate interface | |
| address tokenContractAddress = tokenRegistry.registeredTokens(tokenType); | |
| tokenContract = IERC20(tokenContractAddress); | |
| // Andika: check if approval is already granted | |
| require( | |
| tokenContract.transferFrom(msg.sender, address(this), amount), | |
| "token transfer not approved" | |
| ); | |
| } | |
| // Andika: create deposit array and insert necessary value | |
| uint[] memory depositArray = new uint[](5); | |
| depositArray[0] = pubkey[0]; | |
| depositArray[1] = pubkey[1]; | |
| depositArray[2] = amount; | |
| depositArray[3] = 0; | |
| depositArray[4] = tokenType; | |
| // Andika: Hash the deposit | |
| uint depositHash = mimcMerkle.hashMiMC( | |
| depositArray | |
| ); | |
| // Store the deposit hash into pendingDeposits | |
| pendingDeposits.push(depositHash); | |
| // Broadcast deposit | |
| emit RequestDeposit(pubkey, amount, tokenType); | |
| queueNumber++; | |
| uint tmpDepositSubtreeHeight = 0; | |
| uint tmp = queueNumber; | |
| // Andika: this is where the computation of the root happened. So for For every 2^n, the root is computed and stored at pendingDeposits[0] | |
| // meanwhile, for every 2 deposits, compute branch(es) and store in pendingDeposits[1..] | |
| while(tmp % 2 == 0){ | |
| uint[] memory array = new uint[](2); | |
| array[0] = pendingDeposits[pendingDeposits.length - 2]; | |
| array[1] = pendingDeposits[pendingDeposits.length - 1]; | |
| pendingDeposits[pendingDeposits.length - 2] = mimcMerkle.hashMiMC( | |
| array | |
| ); | |
| removeDeposit(pendingDeposits.length - 1); | |
| tmp = tmp / 2; | |
| tmpDepositSubtreeHeight++; | |
| } | |
| // Increment deposit tree height when sufficient leafs are accumulated and a new level is formed | |
| if (tmpDepositSubtreeHeight > depositSubtreeHeight){ | |
| depositSubtreeHeight = tmpDepositSubtreeHeight; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| template Main(n,m) { | |
| // n is depth of balance tree | |
| // m is depth of transaction (tx) tree | |
| // for each proof, update 2**m (i.e. number of leafs) txs | |
| // Merkle root of tx tree | |
| signal input txRoot; | |
| // Merkle proofs of txs in tx tree needed for constructing root | |
| signal private input paths2txRoot[2**m, m]; | |
| // Binary vector indicating whether node in tx proof is left or right | |
| // 0 when node at left, 1 when at right | |
| signal private input paths2txRootPos[2**m, m]; | |
| // Merkle root of old balance tree | |
| signal input currentState; | |
| // Intermediate Merkle roots of balance tree | |
| // Two for each tx: | |
| // 2*i is before send, 2*i+1 is after send / before receive (2*i+2 is after receive / before next send) | |
| // The last element is the final root | |
| signal private input intermediateRoots[2**(m+1)+1]; | |
| // Merkle proofs of sender accounts in balance tree needed for constructing root | |
| signal private input paths2rootFrom[2**m, n]; | |
| // Binary vector indicating whether node in balance proof for sender account is left or right | |
| signal private input paths2rootFromPos[2**m, n]; | |
| // Merkle proofs of receiver accounts in balance tree needed for constructing root | |
| signal private input paths2rootTo[2**m, n]; | |
| // Binary vector indicating whether node in balance proof for receiver account is left or right | |
| signal private input paths2rootToPos[2**m, n]; | |
| // Tx info, 10 fields | |
| signal private input fromX[2**m]; //sender address x coordinate | |
| signal private input fromY[2**m]; //sender address y coordinate | |
| signal private input fromIndex[2**m]; //sender account leaf index | |
| signal private input toX[2**m]; // receiver address x coordinate | |
| signal private input toY[2**m]; // receiver address y coordinate | |
| signal private input nonceFrom[2**m]; // sender account nonce | |
| signal private input amount[2**m]; // amount being transferred | |
| signal private input tokenTypeFrom[2**m]; // sender token type | |
| signal private input R8x[2**m]; // sender signature | |
| signal private input R8y[2**m]; // sender signature | |
| signal private input S[2**m]; // sender signature | |
| // Additional account info (not included in tx) | |
| signal private input balanceFrom[2**m]; // sender token balance | |
| signal private input balanceTo[2**m]; // receiver token balance | |
| signal private input nonceTo[2**m]; // receiver account nonce | |
| signal private input tokenTypeTo[2**m]; // receiver token type | |
| // New balance tree Merkle root | |
| signal output out; | |
| var NONCE_MAX_VALUE = 100; | |
| // Constant zero address | |
| var ZERO_ADDRESS_X = 0; | |
| var ZERO_ADDRESS_Y = 0; | |
| component txExistence[2**m]; | |
| component senderExistence[2**m]; | |
| component ifBothHighForceEqual[2**m]; | |
| component newSender[2**m]; | |
| component computedRootFromNewSender[2**m]; | |
| component receiverExistence[2**m]; | |
| component newReceiver[2**m]; | |
| component allLow[2**m]; | |
| component ifThenElse[2**m]; | |
| component computedRootFromNewReceiver[2**m]; | |
| currentState === intermediateRoots[0]; | |
| for (var i = 0; i < 2**m; i++) { | |
| // Andika: Make sure the transaction is existed in the | |
| // transaction tree. | |
| txExistence[i] = TxExistence(m); | |
| txExistence[i].fromX <== fromX[i]; | |
| txExistence[i].fromY <== fromY[i]; | |
| txExistence[i].fromIndex <== fromIndex[i]; | |
| txExistence[i].toX <== toX[i]; | |
| txExistence[i].toY <== toY[i]; | |
| txExistence[i].nonce <== nonceFrom[i]; | |
| txExistence[i].amount <== amount[i]; | |
| txExistence[i].tokenType <== tokenTypeFrom[i]; | |
| // Andika: check whether the txRoot computed is the same with current input txRoot | |
| txExistence[i].txRoot <== txRoot; | |
| for (var j = 0; j < m; j++){ | |
| txExistence[i].paths2rootPos[j] <== paths2txRootPos[i, j] ; | |
| txExistence[i].paths2root[j] <== paths2txRoot[i, j]; | |
| } | |
| // Andika: make sure signatures are valid | |
| txExistence[i].R8x <== R8x[i]; | |
| txExistence[i].R8y <== R8y[i]; | |
| txExistence[i].S <== S[i]; | |
| // Andika: Make sure senders exist in balance tree | |
| senderExistence[i] = BalanceExistence(n); | |
| senderExistence[i].x <== fromX[i]; | |
| senderExistence[i].y <== fromY[i]; | |
| senderExistence[i].balance <== balanceFrom[i]; | |
| senderExistence[i].nonce <== nonceFrom[i]; | |
| senderExistence[i].tokenType <== tokenTypeFrom[i]; | |
| // Andika: Make sure that input intermediate balance roots equal roots computed | |
| senderExistence[i].balanceRoot <== intermediateRoots[2*i]; | |
| for (var j = 0; j < n; j++){ | |
| senderExistence[i].paths2rootPos[j] <== paths2rootFromPos[i, j]; | |
| senderExistence[i].paths2root[j] <== paths2rootFrom[i, j]; | |
| } | |
| // Sanity check of amounts being transferred | |
| balanceFrom[i] - amount[i] <= balanceFrom[i]; | |
| balanceTo[i] + amount[i] >= balanceTo[i]; | |
| // Check that nonce is within limit | |
| nonceFrom[i] != NONCE_MAX_VALUE; | |
| //-----CHECK TOKEN TYPES === IF NON-WITHDRAWS-----// | |
| ifBothHighForceEqual[i] = IfBothHighForceEqual(); | |
| // Andika: make sure that toX and toY are not 0 | |
| ifBothHighForceEqual[i].check1 <== toX[i]; | |
| ifBothHighForceEqual[i].check2 <== toY[i]; | |
| // Andika: If yes, then make sure that tokenTypes are identical | |
| ifBothHighForceEqual[i].a <== tokenTypeTo[i]; | |
| ifBothHighForceEqual[i].b <== tokenTypeFrom[i]; | |
| //-----END CHECK TOKEN TYPES-----// | |
| //-----CHECK SENDER IN TREE 2 AFTER DEDUCTING-----// | |
| // Compute new sender leafs | |
| newSender[i] = BalanceLeaf(); | |
| newSender[i].x <== fromX[i]; | |
| newSender[i].y <== fromY[i]; | |
| newSender[i].balance <== balanceFrom[i] - amount[i]; // subtract amount from sender balance | |
| newSender[i].nonce <== nonceFrom[i] + 1; // increase sender nonce | |
| newSender[i].tokenType <== tokenTypeFrom[i]; | |
| // Compute intermediate roots from new sender leafs | |
| computedRootFromNewSender[i] = GetMerkleRoot(n); | |
| computedRootFromNewSender[i].leaf <== newSender[i].out; | |
| for (var j = 0; j < n; j++){ | |
| computedRootFromNewSender[i].paths2root[j] <== paths2rootFrom[i, j]; | |
| computedRootFromNewSender[i].paths2rootPos[j] <== paths2rootFromPos[i, j]; | |
| } | |
| // Andika: make sure that computed intermediate roots are consistent with input intermediate balance roots | |
| computedRootFromNewSender[i].out === intermediateRoots[2*i + 1]; | |
| //-----END SENDER IN TREE 2 AFTER DEDUCTING CHECK-----// | |
| // Andika: make sure receivers exist in balance tree | |
| receiverExistence[i] = BalanceExistence(n); | |
| receiverExistence[i].x <== toX[i]; | |
| receiverExistence[i].y <== toY[i]; | |
| receiverExistence[i].balance <== balanceTo[i]; | |
| receiverExistence[i].nonce <== nonceTo[i]; | |
| receiverExistence[i].tokenType <== tokenTypeTo[i]; | |
| //Andika: make sure input intermediate balance roots equal roots computed | |
| receiverExistence[i].balanceRoot <== intermediateRoots[2*i + 1]; | |
| for (var j = 0; j < n; j++){ | |
| receiverExistence[i].paths2rootPos[j] <== paths2rootToPos[i, j] ; | |
| receiverExistence[i].paths2root[j] <== paths2rootTo[i, j]; | |
| } | |
| //-----CHECK RECEIVER IN TREE 3 AFTER INCREMENTING-----// | |
| // Compute new receiver leafs | |
| newReceiver[i] = BalanceLeaf(); | |
| newReceiver[i].x <== toX[i]; | |
| newReceiver[i].y <== toY[i]; | |
| // If receiver is zero address, do not change balance | |
| // Otherwise add amount to receiver balance | |
| allLow[i] = AllLow(2); | |
| allLow[i].in[0] <== toX[i]; | |
| allLow[i].in[1] <== toY[i]; | |
| ifThenElse[i] = IfAThenBElseC(); | |
| ifThenElse[i].aCond <== allLow[i].out; // aCond = 1 if toX and toY are zeroes | |
| ifThenElse[i].bBranch <== balanceTo[i]; | |
| ifThenElse[i].cBranch <== balanceTo[i] + amount[i]; | |
| newReceiver[i].balance <== ifThenElse[i].out; | |
| newReceiver[i].nonce <== nonceTo[i]; | |
| newReceiver[i].tokenType <== tokenTypeTo[i]; | |
| // Compute intermediate roots from new receiver leafs | |
| computedRootFromNewReceiver[i] = GetMerkleRoot(n); | |
| computedRootFromNewReceiver[i].leaf <== newReceiver[i].out; | |
| for (var j = 0; j < n; j++){ | |
| computedRootFromNewReceiver[i].paths2root[j] <== paths2rootTo[i, j]; | |
| computedRootFromNewReceiver[i].paths2rootPos[j] <== paths2rootToPos[i, j]; | |
| } | |
| // Andika: make sure computed intermediate roots are consistent with input intermediate balance roots | |
| computedRootFromNewReceiver[i].out === intermediateRoots[2*i + 2]; | |
| //-----END CHECK RECEIVER IN TREE 3 AFTER INCREMENTING-----// | |
| } | |
| // Output final Merkle root of balance tree | |
| out <== computedRootFromNewReceiver[2**m - 1].out; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Andika: This function updates the Merkle root stored on L1 | |
| function updateState( | |
| // Andika: this is where the parameter from circuit | |
| uint[2] memory a, | |
| uint[2][2] memory b, | |
| uint[2] memory c, | |
| uint[3] memory input // [newRoot, txRoot, oldRoot] | |
| ) public onlyCoordinator { | |
| // Andika: make sure that the current root on L1 was used as old root during proof generation | |
| require(currentRoot == input[2], "input does not match current root"); | |
| // Andika: see if zero-knowledge proof is valid | |
| require(update_verifyProof(a,b,c,input), | |
| "SNARK proof is invalid"); | |
| // Andika: updating the merkle root stored on L1 | |
| currentRoot = input[0]; | |
| // Andika: update the contract | |
| updateNumber++; | |
| updates[input[1]] = updateNumber; | |
| // Andika: send broadcast on the state update contain new, tx and old Root | |
| emit UpdatedState(input[0], input[1], input[2]); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Andika: This function is to withdraw ETH or ERC20 tokens from L2 | |
| function withdraw( | |
| uint[9] memory txInfo, // [pubkeyX, pubkeyY, index, toX ,toY, nonce, amount, token_type_from, txRoot] | |
| uint[] memory position, | |
| uint[] memory proof, | |
| address payable recipient, | |
| // Andika: this is the parameter from the circuit's proof generated | |
| uint[2] memory a, | |
| uint[2][2] memory b, | |
| uint[2] memory c | |
| ) public{ | |
| // Andika: make sure token type is not of reserved type | |
| require(txInfo[7] > 0, "invalid tokenType"); | |
| // Andika: make sure that transaction (tx) tree exists | |
| require(updates[txInfo[8]] > 0, "txRoot does not exist"); | |
| uint[] memory txArray = new uint[](8); | |
| for (uint i = 0; i < 8; i++){ | |
| txArray[i] = txInfo[i]; | |
| } | |
| // Andika: Hash the tx | |
| uint txLeaf = mimcMerkle.hashMiMC(txArray); | |
| // Andika: Check if txRoot matches with the root derived from the provided tx | |
| require(txInfo[8] == mimcMerkle.getRootFromProof( | |
| txLeaf, position, proof), | |
| "transaction does not exist in specified transactions root" | |
| ); | |
| // Andika: add the nonce and recipient address into a message array | |
| uint[] memory msgArray = new uint[](2); | |
| msgArray[0] = txInfo[5]; // nonce | |
| msgArray[1] = uint(recipient); | |
| // Andika: see if zero-knowledge proof is valid | |
| require(withdraw_verifyProof( | |
| a, b, c, | |
| [txInfo[0], txInfo[1], mimcMerkle.hashMiMC(msgArray)] // [pubkeyX, pubkeyY, msgHash] | |
| ), | |
| "eddsa signature is not valid"); | |
| // Andika: Transfer token to recipient on L1 | |
| if (txInfo[7] == 1){ | |
| // ETH | |
| recipient.transfer(txInfo[6]); | |
| } else { | |
| // ERC20 token | |
| address tokenContractAddress = tokenRegistry.registeredTokens(txInfo[7]); | |
| tokenContract = IERC20(tokenContractAddress); | |
| require( | |
| tokenContract.transfer(recipient, txInfo[6]), | |
| "transfer failed" | |
| ); | |
| } | |
| // Andika: send Broadcast withdrawal | |
| emit Withdraw(txInfo, recipient); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment