UNPKG

@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

377 lines (324 loc) 12.3 kB
require('dotenv').config() const { utils } = require('ethers'); const path = require("path"); const fs = require("fs"); const exec = require('child_process').exec; const { mkdirSync, existsSync, readFileSync, writeFileSync } = require("fs"); const getCircuitInput = require("../src/circuitInput"); const Transaction = require("../src/transaction"); const AccountTree = require("../src/accountTree"); const Account = require("../src/account"); const treeHelper = require("../src/treeHelper"); const TxTree = require("../src/txTree"); const buildMimc7 = require("circomlibjs").buildMimc7; const buildBabyJub = require("circomlibjs").buildBabyJub; const buildEddsa = require("circomlibjs").buildEddsa; const ff = require("ffjavascript"); const unstringifyBigInts = ff.utils.unstringifyBigInts const {prover} = require('@ieigen/plonkjs-node') const ZKIT = process.env.ZKIT || "zkit" const CIRCUIT_PATH = process.env.CIRCUIT_PATH || "" const TEST_PATH = process.env.TEST_PATH || "" const UPDATE_STATE_CIRCUIT_NAME = "update_state_verifier" const ACCOUNT_DEPTH = 4 // FIXME: We set account depth to 4 in the zkzru demo. Should set in .env later. const WITHDRAW_SIGNATURE_CIRCUIT_NAME = 'withdraw_signature_verifier' const numLeaves = 2**ACCOUNT_DEPTH; const TXS_PER_SNARK = 4; const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); const toHexString = (bytes) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); function run(cmd) { return new Promise((resolve, reject) => { exec(cmd, (error, stdout, stderr) => { if (error) return reject(error) if (stderr) return reject(stderr) resolve(stdout) }) }) } function parsePublicKey(uncompressKey) { uncompressKeyStr = uncompressKey.toString() if (!uncompressKeyStr.startsWith("0x04")) { throw new Error("Invalid public key:" + uncompressKey) } const address = utils.computeAddress(uncompressKeyStr) const xy = uncompressKey.substr(4) const x = xy.substr(0, 64) const y = xy.substr(64) return {"address": address, "x": x, "y": y} } async function generateWitness (inputPath, outputPath, circuitName) { let wasm = path.join(CIRCUIT_PATH, circuitName+"_js", circuitName+".wasm"); let zkey = path.join(CIRCUIT_PATH, "setup_2^20.key"); let requirePath = path.join(CIRCUIT_PATH, circuitName + "_js", "witness_calculator") const wc = require(requirePath); const buffer = readFileSync(wasm); const witnesssCalculator = await wc(buffer); const input = JSON.parse(readFileSync(inputPath, "utf8")); const witnessBuffer = await witnesssCalculator.calculateWTNSBin( input, 0 ); writeFileSync(outputPath, witnessBuffer, "utf-8"); } async function generateInput (accArray, txArray, curTime) { await treeHelper.initialize() let mimcjs = await buildMimc7(); let F = mimcjs.F; let zeroAccount = new Account(); await zeroAccount.initialize(); const paddedAccounts = treeHelper.padArray(accArray, zeroAccount, numLeaves); const accountTree = new AccountTree(paddedAccounts) const txTree = new TxTree(txArray) const stateTransaction = await accountTree.processTxArray(txTree); const txRoot = F.toString(stateTransaction.txTree.root) const inputs = await getCircuitInput(stateTransaction); const inputPath = join(TEST_PATH, "inputs", curTime + ".json") writeFileSync( inputPath, JSON.stringify(inputs), "utf-8" ); console.log("Generate input.json successfully in:", inputPath) return {inputPath, txRoot}; } async function generateWithdrawSignatureInput(pubkey, sig, msg, curTime) { let mimcjs = await buildMimc7(); let F = mimcjs.F; const inputs = { Ax: F.toString(pubkey[0]), Ay: F.toString(pubkey[1]), R8x: F.toString(sig.R8[0]), R8y: F.toString(sig.R8[1]), S: sig.S.toString(), M: F.toString(msg) } const inputPath = join(TEST_PATH, "withdraw_signature_inputs", curTime + ".json") writeFileSync( inputPath, JSON.stringify(inputs), "utf-8" ); return inputPath; } function join (base, ...pathes) { let filename = path.join(base, ...pathes) const finalPath = path.dirname(filename) if (!existsSync(finalPath)) { mkdirSync(finalPath, true) } return filename } module.exports = { async parseDBData(accountInDB, txInDB) { let accArray = new Array() for (var i = 0; i < accountInDB.length; i ++) { const acc = accountInDB[i] let account; if (acc["pubkey"] == "0") { account = new Account(); } else { const pk = parsePublicKey(acc["pubkey"]) account = new Account( acc["account_index"], fromHexString(pk["x"]), fromHexString(pk["y"]), BigInt(acc["balance"]), acc["nonce"], acc["token_type"], ) } await account.initialize() accArray.push(account) } if (txInDB.length != TXS_PER_SNARK) { throw new Error("Invalid tx batch length:" + txInDB.length) } var txArray= Array(TXS_PER_SNARK); for (var i=0; i < txInDB.length; i++) { var res = txInDB[i] var tx; var senderPK = parsePublicKey(res["sender_pubkey"]) if (res["receiver_pubkey"] == "0") { tx = new Transaction( fromHexString(senderPK["x"]), fromHexString(senderPK["y"]), res["from_index"], 0, 0, res["nonce"], BigInt(res["amount"]), res["token_type_from"], fromHexString(res["r8x"]), fromHexString(res["r8y"]), unstringifyBigInts(res["s"]) ) } else { var receiverPK = parsePublicKey(res["receiver_pubkey"]) tx = new Transaction( fromHexString(senderPK["x"]), fromHexString(senderPK["y"]), res["from_index"], fromHexString(receiverPK["x"]), fromHexString(receiverPK["y"]), res["nonce"], BigInt(res["amount"]), res["token_type_from"], fromHexString(res["r8x"]), fromHexString(res["r8y"]), unstringifyBigInts(res["s"]) ) } await tx.initialize(); tx.hashTx(); txArray[i] = tx; } return {accArray, txArray} }, async prove(accArray, txArrary) { // generate input const curTime = Date.now().toString() const {inputPath, txRoot} = await generateInput(accArray, txArrary, curTime); const outputPath = join(TEST_PATH, "witness", curTime+".wtns") // generate witness await generateWitness(inputPath, outputPath, "update_state_verifier") let circuit_file = path.join(CIRCUIT_PATH, "update_state_verifier.r1cs"); let circuit_file_content = fs.readFileSync(circuit_file); let wtns_content = fs.readFileSync(outputPath); let srs_monomial_form = path.join(CIRCUIT_PATH, "setup_2^20.key"); let srs_monomial_form_content = fs.readFileSync(srs_monomial_form); let proof_js = prover.prove( circuit_file_content.toJSON().data, wtns_content.toJSON().data, srs_monomial_form_content.toJSON().data, "keccak" ); // generate verify key let vk_js = prover.export_verification_key( srs_monomial_form_content.toJSON().data, circuit_file_content.toJSON().data ); let vk = vk_js.vk_bin let proof = proof_js.proof_bin let proofJson = proof_js.proof_json let publicJson = proof_js.public_json let inputJson = inputPath return {vk, proof, inputJson, proofJson, publicJson, txRoot} }, /* async prove(accArray, txArrary) { // generate input const curTime = Date.now().toString() const {inputPath, txRoot} = await generateInput(accArray, txArrary, curTime); const outputPath = join(TEST_PATH, "witness", curTime+".wtns") // generate witness await generateWitness(inputPath, outputPath, "update_state_verifier") // use cmd to export verification key let zkey = path.join(CIRCUIT_PATH, "setup_2^20.key"); const vk = join(TEST_PATH, "vk", curTime+"_vk.bin") const cmd1 = ZKIT + " export_verification_key -s " + zkey + " -c " + CIRCUIT_PATH + UPDATE_STATE_CIRCUIT_NAME + ".r1cs -v " + vk; console.log(cmd1) let result = await run(cmd1); console.log(result) // use cmd to generate proof let proof = join(TEST_PATH, "proof", curTime+"_proof.bin") let publicJson = join(TEST_PATH, "public", curTime+"_public.json") let proofJson = join(TEST_PATH, "proof", curTime+"_proof.json") const cmd2 = ZKIT + " prove -c " + CIRCUIT_PATH + UPDATE_STATE_CIRCUIT_NAME + ".r1cs -w " + outputPath + " -s " + zkey + " -b " + proof + " -j " + proofJson + " -p " + publicJson; console.log(cmd2) result = await run(cmd2); console.log(result) let inputJson = inputPath return {vk, proof, inputJson, proofJson, publicJson, txRoot}; }, */ async verify(vk, proof) { // verify let verify_ok = prover.verify( Array.from(vk), Array.from(proof), "keccak" ) return verify_ok }, /* async verify(vk, proof) { const cmd = ZKIT + " verify -p " + proof + " -v " + vk; console.log(cmd) const result = await run(cmd); return result.toString().startsWith('Proof is valid'); }, */ async proveWithdrawSignature(pubkey, sig, msg) { // generate input const curTime = Date.now().toString() const inputPath = await generateWithdrawSignatureInput(pubkey, sig, msg, curTime); const outputPath = join(TEST_PATH, "withdraw_signature_witness", curTime+".wtns") // generate witness await generateWitness(inputPath, outputPath, "withdraw_signature_verifier") let circuit_file = path.join(CIRCUIT_PATH, "withdraw_signature_verifier.r1cs"); let circuit_file_content = fs.readFileSync(circuit_file); let wtns_content = fs.readFileSync(outputPath); let srs_monomial_form = path.join(CIRCUIT_PATH, "setup_2^20.key"); let srs_monomial_form_content = fs.readFileSync(srs_monomial_form); let proof_js = prover.prove( circuit_file_content.toJSON().data, wtns_content.toJSON().data, srs_monomial_form_content.toJSON().data, "keccak" ); // generate verify key let vk_js = prover.export_verification_key( srs_monomial_form_content.toJSON().data, circuit_file_content.toJSON().data ); let vk = vk_js.vk_bin let proof = proof_js.proof_bin let proofJson = proof_js.proof_json return {vk, proof, proofJson} }, /* async proveWithdrawSignature(pubkey, sig, msg) { // generate input const curTime = Date.now().toString() const inputPath = await generateWithdrawSignatureInput(pubkey, sig, msg, curTime); const outputPath = join(TEST_PATH, "withdraw_signature_witness", curTime+".wtns") // generate witness await generateWitness(inputPath, outputPath, "withdraw_signature_verifier") // use cmd to export verification key let zkey = path.join(CIRCUIT_PATH, "setup_2^20.key"); const vk = join(TEST_PATH, "withdraw_signature_vk", curTime+"_vk.bin") const cmd1 = ZKIT + " export_verification_key -s " + zkey + " -c " + CIRCUIT_PATH + WITHDRAW_SIGNATURE_CIRCUIT_NAME + ".r1cs -v " + vk; console.log(cmd1) let result = await run(cmd1); console.log(result) // use cmd to generate proof let proof = join(TEST_PATH, "withdraw_signature_proof", curTime+"_proof.bin") let publicJson = join(TEST_PATH, "withdraw_signature_public", curTime+"_public.json") let proofJson = join(TEST_PATH, "withdraw_signature_proof", curTime+"_proof.json") const cmd2 = ZKIT + " prove -c " + CIRCUIT_PATH + WITHDRAW_SIGNATURE_CIRCUIT_NAME + ".r1cs -w " + outputPath + " -s " + zkey + " -b " + proof + " -j " + proofJson + " -p " + publicJson; console.log(cmd2) result = await run(cmd2); console.log(result) return {vk, proof, proofJson}; }, */ async verifyWithdrawSignature(vk, proof) { // verify let verify_ok = prover.verify( Array.from(vk), Array.from(proof), "keccak" ) return verify_ok }, /* async verifyWithdrawSignature(vk, proof) { const cmd = ZKIT + " verify -p " + proof + " -v " + vk; console.log(cmd) const result = await run(cmd); return result.toString().startsWith('Proof is valid'); }, */ }