Created
March 25, 2022 09:18
-
-
Save Savio-Sou/60e864c370fd056250f0e041b5f317a6 to your computer and use it in GitHub Desktop.
Review RollupNC
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 token type | |
| 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"); | |
| // Initiate interface and check if approval is already granted | |
| address tokenContractAddress = tokenRegistry.registeredTokens(tokenType); | |
| tokenContract = IERC20(tokenContractAddress); | |
| require( | |
| tokenContract.transferFrom(msg.sender, address(this), amount), | |
| "token transfer not approved" | |
| ); | |
| } | |
| // Compute branch / root hash of a Merkle tree of deposits | |
| // Hash the deposit | |
| uint[] memory depositArray = new uint[](5); | |
| depositArray[0] = pubkey[0]; | |
| depositArray[1] = pubkey[1]; | |
| depositArray[2] = amount; | |
| depositArray[3] = 0; | |
| depositArray[4] = tokenType; | |
| 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; | |
| // For every 2^n deposits (i.e. when a perfect binary tree can form), compute the root hash and store at pendingDeposits[0] | |
| // 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++) { | |
| // Assure txs exist in tx tree (by assuring input txRoot equals roots computed) | |
| 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]; | |
| 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]; | |
| } | |
| // Assure signatures are valid | |
| txExistence[i].R8x <== R8x[i]; | |
| txExistence[i].R8y <== R8y[i]; | |
| txExistence[i].S <== S[i]; | |
| // Assure senders exist in balance tree (by assuring input intermediate balance roots equal roots computed) | |
| 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]; | |
| 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(); | |
| // Assure toX and toY are not 0 | |
| ifBothHighForceEqual[i].check1 <== toX[i]; | |
| ifBothHighForceEqual[i].check2 <== toY[i]; | |
| // If yes, assure 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]; | |
| } | |
| // Assure 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-----// | |
| // Assure receivers exist in balance tree (by assuring input intermediate balance roots equal roots computed) | |
| 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]; | |
| 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]; | |
| } | |
| // Assure 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
| // Updates the Merkle root stored on L1 | |
| function updateState( | |
| // Circom circuit's proof generated | |
| uint[2] memory a, | |
| uint[2][2] memory b, | |
| uint[2] memory c, | |
| // Circom circuit's outputs generated | |
| uint[3] memory input // [newRoot, txRoot, oldRoot] | |
| ) public onlyCoordinator { | |
| // Check if current root on L1 was used as old root during proof generation | |
| require(currentRoot == input[2], "input does not match current root"); | |
| // Check if zero-knowledge proof is valid | |
| require(update_verifyProof(a,b,c,input), | |
| "SNARK proof is invalid"); | |
| // Update merkle root stored on L1 | |
| currentRoot = input[0]; | |
| // Update contract records | |
| updateNumber++; | |
| updates[input[1]] = updateNumber; | |
| // Broadcast a state update of (newRoot, txRoot, oldRoot) | |
| 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
| // 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, | |
| // Circom circuit's proof generated | |
| uint[2] memory a, | |
| uint[2][2] memory b, | |
| uint[2] memory c | |
| ) public{ | |
| // Check if token type is not of reserved type | |
| require(txInfo[7] > 0, "invalid tokenType"); | |
| // Check if the root of transaction (tx) tree exists | |
| require(updates[txInfo[8]] > 0, "txRoot does not exist"); | |
| // Hash the tx | |
| uint[] memory txArray = new uint[](8); | |
| for (uint i = 0; i < 8; i++){ | |
| txArray[i] = txInfo[i]; | |
| } | |
| uint txLeaf = mimcMerkle.hashMiMC(txArray); | |
| // Check if txRoot matches with the root derived from the provided tx | |
| // i.e. if it exists in the tx tree | |
| require(txInfo[8] == mimcMerkle.getRootFromProof( | |
| txLeaf, position, proof), | |
| "transaction does not exist in specified transactions root" | |
| ); | |
| // Hash the nonce and recipient address into a message | |
| uint[] memory msgArray = new uint[](2); | |
| msgArray[0] = txInfo[5]; // nonce | |
| msgArray[1] = uint(recipient); | |
| // Check if zero-knowledge proof is valid | |
| // i.e. if valid signature was used as circuit input when generating proof | |
| require(withdraw_verifyProof( | |
| a, b, c, | |
| [txInfo[0], txInfo[1], mimcMerkle.hashMiMC(msgArray)] // [pubkeyX, pubkeyY, msgHash] | |
| ), | |
| "eddsa signature is not valid"); | |
| // 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" | |
| ); | |
| } | |
| // Broadcast withdrawal | |
| emit Withdraw(txInfo, recipient); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment