Skip to content

Instantly share code, notes, and snippets.

@Savio-Sou
Created March 25, 2022 09:18
Show Gist options
  • Select an option

  • Save Savio-Sou/60e864c370fd056250f0e041b5f317a6 to your computer and use it in GitHub Desktop.

Select an option

Save Savio-Sou/60e864c370fd056250f0e041b5f317a6 to your computer and use it in GitHub Desktop.
Review RollupNC
// 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;
}
}
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;
}
// 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]);
}
// 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