@ieigen/zkzru
Version:
An implementation of [ZK-ZKRollup](https://github.com/ieigen/ZKZRU) in which the relayer **does not** publish transaction data to the main chain, but only publishes the new Merkle root at every update. This provides gas savings but not data availability g
154 lines (128 loc) • 4.6 kB
JavaScript
const Tree = require("./tree.js");
const Transaction = require("./transaction.js")
const buildMimc7 = require("circomlibjs").buildMimc7;
const toHexString = (bytes) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
module.exports = class AccountTree extends Tree {
constructor(
_accounts
) {
super(_accounts.map((x) => x.hashAccount()))
this.accounts = _accounts
}
async processTxArray(txTree) {
let mimcjs = await buildMimc7()
let F = mimcjs.F
const originalState = this.root;
const txs = txTree.txs;
let paths2txRoot = new Array(txs.length);
let paths2txRootPos = new Array(txs.length);
let deltas = new Array(txs.length);
for (let i = 0; i < txs.length; i++) {
const tx = txs[i];
// verify tx exists in tx tree
const [txProof, txProofPos] = txTree.getTxProofAndProofPos(tx);
txTree.checkTxExistence(tx, txProof);
paths2txRoot[i] = [F.toString(txProof[0]), F.toString(txProof[1])];
paths2txRootPos[i] = txProofPos;
// process transaction
console.log("Debug: processing tx:", i);
deltas[i] = this.processTx(tx);
}
return {
originalState: originalState,
txTree: txTree,
paths2txRoot: paths2txRoot,
paths2txRootPos: paths2txRootPos,
deltas: deltas
}
}
processTx(tx) {
const sender = this.findAccountByPubkey(tx.fromX, tx.fromY);
const indexFrom = sender.index;
const balanceFrom = sender.balance;
const receiver = this.findAccountByPubkey(tx.toX, tx.toY);
const indexTo = receiver.index;
const balanceTo = receiver.balance;
const nonceTo = receiver.nonce;
const tokenTypeTo = receiver.tokenType;
const [senderProof, senderProofPos] = this.getAccountProof(sender);
this.checkAccountExistence(sender, senderProof);
tx.checkSignature();
this.checkTokenTypes(tx);
sender.debitAndIncreaseNonce(tx.amount);
this.leafNodes[sender.index] = sender.hash;
this.updateInnerNodes(sender.hash, sender.index, senderProof);
this.root = this.innerNodes[0][0]
const rootFromNewSender = this.root;
const [receiverProof, receiverProofPos] = this.getAccountProof(receiver);
this.checkAccountExistence(receiver, receiverProof);
receiver.credit(tx.amount);
this.leafNodes[receiver.index] = receiver.hash;
this.updateInnerNodes(receiver.hash, receiver.index, receiverProof);
this.root = this.innerNodes[0][0]
const rootFromNewReceiver = this.root;
return {
senderProof: senderProof,
senderProofPos: senderProofPos,
rootFromNewSender: rootFromNewSender,
receiverProof: receiverProof,
receiverProofPos: receiverProofPos,
rootFromNewReceiver: rootFromNewReceiver,
indexFrom: indexFrom,
balanceFrom: balanceFrom,
indexTo: indexTo,
balanceTo: balanceTo,
nonceTo: nonceTo,
tokenTypeTo: tokenTypeTo
}
}
checkTokenTypes(tx) {
const sender = this.findAccountByPubkey(tx.fromX, tx.fromY)
const receiver = this.findAccountByPubkey(tx.toX, tx.toY)
const sameTokenType = (
(tx.tokenType == sender.tokenType && tx.tokenType == receiver.tokenType) ||
receiver.tokenType == 0 // withdraw token type doesn't have to match
);
if (!sameTokenType) {
throw new Error("token types do not match")
}
}
checkAccountExistence(account, accountProof) {
if (!this.verifyProof(account.hash, account.index, accountProof)) {
console.log("given account hash", account.hash)
console.log("given account proof", accountProof)
throw new Error("account does not exist")
}
}
getAccountProof(account) {
const proofObj = this.getProof(account.index)
return [proofObj.proof, proofObj.proofPos]
}
findAccountByPubkey(pubkeyX, pubkeyY) {
if (pubkeyX == 0 && pubkeyY == 0) {
return this.accounts[0]
}
// use slice to skip zeroAccount
const account = this.accounts.slice(1).filter(
(acct) => (
acct.pubkeyX != 0 &&
acct.pubkeyY != 0 &&
toHexString(acct.pubkeyX) == toHexString(pubkeyX) &&
toHexString(acct.pubkeyY) == toHexString(pubkeyY)
)
)[0];
return account
}
generateEmptyTx(pubkeyX, pubkeyY, index, prvkey) {
const sender = this.findAccountByPubkey(pubkeyX, pubkeyY);
const nonce = sender.nonce;
const tokenType = sender.tokenType;
let tx = new Transaction(
pubkeyX, pubkeyY, index,
pubkeyX, pubkeyY,
nonce, 0, tokenType
);
tx.signTxHash(prvkey);
}
}