import { randomBytes } from 'crypto' import { readFileSync } from 'fs'; import { MerkleTree } from './MerkleTree'; import { keccak256 } from "@ethersproject/keccak256/lib/index.js"; import fs from 'fs' // @ts-ignore import { Fr } from '@aztec/bb.js'; const ZERO_VALUE = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; const MAX_VALUE = Fr.MAX_VALUE; export const treeConfig = { utxoDepth: 4, txDepth: 4, stateDepth: 9 } export const circuitConfig = { main: "./src/main.nr" } const treeSum = [treeConfig.utxoDepth, treeConfig.txDepth, treeConfig.stateDepth].reduce((a, b) => a + b) function evalWithScopePrefix(js) { let public_inputs = {}; js.split('\n').forEach(line => { const trimmedLine = line.trim(); if (trimmedLine.length > 0) { eval(`public_inputs.` + trimmedLine); } }); return public_inputs; } export function randomBytesFr(numBytes) { const bytes = randomBytes(numBytes) const bytesFr = Fr.fromBufferReduce(bytes) return bytesFr } export const format = (data) => { if (typeof data === "string") return `"${data}"`; if (data.length === 0) return "[]"; return `[\n "${data.join('",\n "')}"\n]`; } export const dumpToml = (data) => { let toml: any = []; Object.entries(data).forEach(([key, value]) => { toml.push(`${key} = ${format(value)}`); }); toml = toml.join('\n'); fs.writeFileSync('./circuits/main/Prover.toml', toml); } export const dumpTomlRecursive = (data) => { let toml: any = []; Object.entries(data).forEach(([key, value]) => { toml.push(`${key} = ${format(value)}`); }); toml = toml.join('\n'); fs.writeFileSync('./circuits/recursion/Prover.toml', toml); } export function path_to_uint8array(path) { let buffer = readFileSync(path); return new Uint8Array(buffer); } const toFixedHex = (number, pad0x, length = 32) => { let hexString = number.toString(16).padStart(length * 2, '0'); return (pad0x ? `0x` + hexString : hexString); } export function getSolidityHash(asset) { return keccak256(asset); } export function generateHashPathInput(hash_path) { let hash_path_input: string[] = []; for (var i = 0; i < hash_path.length; i++) { hash_path_input.push(`0x` + hash_path[i]); } return hash_path_input; } export async function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) { let utxos: any[] = [] for (let i = 0; i < batchSize; i++) { let amountBN = amounts[i] let utxo = { secret: _secrets[i], owner: await BarretenbergApi.pedersenPlookupCompress([_secrets[i]]), amountBN: amountBN, amount: Fr.fromString(toFixedHex(Number(amountBN.toString()), true)), assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), 'hex')), } utxos.push(utxo) } return utxos } export async function generateTreeProof(utxoIn, BarretenbergApi, contract) { let trees = { utxo_tree: new MerkleTree(treeConfig.utxoDepth, BarretenbergApi), tx_tree: new MerkleTree(treeConfig.txDepth, BarretenbergApi), historic_tree: new MerkleTree(treeConfig.stateDepth, BarretenbergApi), } await trees.utxo_tree.init() await trees.tx_tree.init() await trees.historic_tree.init() let commitment = await BarretenbergApi.pedersenPlookupCompress( [utxoIn.owner, utxoIn.amount, utxoIn.assetType] ) commitment = commitment.toString() let old_root = await contract.getRootFromUtxo(commitment) let utxo_list = await contract.getUtxoFromRoot(old_root) let historic_roots = await contract.getValidRoots() let oracle = await BarretenbergApi.pedersenPlookupCompress([new Fr(0n)]) for (let i = 0; i < utxo_list.length; i++) { await trees.utxo_tree.insert(utxo_list[i]); } let utxo_root = trees.utxo_tree.root() await trees.tx_tree.insert(utxo_root); let tx_root_Fr = Fr.fromString(trees.tx_tree.root()) let batch = await BarretenbergApi.pedersenPlookupCompress([tx_root_Fr, oracle]) let new_root = await BarretenbergApi.pedersenPlookupCompress([batch, Fr.fromString(old_root)]) new_root = new_root.toString() for (let i = 0; i < historic_roots.length; i++) { await trees.historic_tree.insert(historic_roots[i]); } let proofs = { utxo: { leaf: commitment, index: trees.utxo_tree.getIndex(commitment), root: utxo_root, hash_path: trees.utxo_tree.proof( trees.utxo_tree.getIndex(commitment) ).pathElements }, tx: { leaf: utxo_root, index: trees.tx_tree.getIndex(utxo_root), root: trees.tx_tree.root(), hash_path: trees.tx_tree.proof( trees.tx_tree.getIndex(utxo_root) ).pathElements }, historic: { leaf: new_root, index: trees.historic_tree.getIndex(new_root), root: trees.historic_tree.root(), hash_path: trees.historic_tree.proof( trees.historic_tree.getIndex(new_root) ).pathElements } } return proofs } export async function generateDataToml(oldRoot, newRoot, trees, api) { let zeroHex = toFixedHex(0, true) let zeroHash = await api.pedersenPlookupCompress([Fr.fromString(zeroHex)]) const data = { tx_in: new Array(16).fill(ZERO_VALUE), secrets: new Array(16).fill(zeroHex), utxo_in: new Array(48).fill(zeroHex), utxo_out: new Array(48).fill(zeroHex), oracle: zeroHash.toString(), old_root_proof: new Array(16).fill(zeroHex), old_root: ZERO_VALUE, new_root: ZERO_VALUE, current_root: trees.historic_tree.root(), indexes: new Array(48).fill(zeroHex), hash_path: new Array(16 * treeSum).fill(zeroHex), commitment_out: new Array(16).fill(ZERO_VALUE), deposit: zeroHex, withdrawals: new Array(16).fill(zeroHex), nullifier_hashes: new Array(16).fill(ZERO_VALUE), recipients: new Array(16).fill(zeroHex) } if (oldRoot !== "0") data.old_root = oldRoot; if (newRoot !== "0") data.new_root = newRoot; return data } export async function generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipients, BarretenbergApi, contract) { let utxoInLen = utxoIn.length let utxoOutLen = utxoOut.length for (let i = 0; i < utxoInLen; i++) { let ownerHex = utxoIn[i].owner.toString(); let amountHex = utxoIn[i].amount.toString(); let assetTypeHex = utxoIn[i].assetType.toString(); let note_commitment = await BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]); let utxoLeaf = note_commitment.toString() data.secrets[i] = utxoIn[i].secret.toString() data.nullifier_hashes[i] = (await BarretenbergApi.pedersenPlookupCompress([utxoIn[i].secret, utxoIn[i].secret])).toString() data.old_root_proof[i] = await contract.utxoPrevRoots(utxoLeaf) data.utxo_in[i * 3 + 0] = ownerHex data.utxo_in[i * 3 + 1] = amountHex data.utxo_in[i * 3 + 2] = assetTypeHex data.indexes[i * 4 + 0] = treeProof[i].utxo.index data.indexes[i * 4 + 1] = treeProof[i].tx.index data.indexes[i * 4 + 2] = treeProof[i].historic.index let utxoPath = treeProof[i].utxo.hash_path let txPath = treeProof[i].tx.hash_path let historicPath = treeProof[i].historic.hash_path for (let j = 0; j < utxoPath.length; j++) { data.hash_path[i * treeSum + 0 + j] = utxoPath[j] data.hash_path[i * treeSum + treeConfig.utxoDepth + j] = txPath[j] } for (let k = 0; k < historicPath.length; k++) { data.hash_path[i * treeSum + treeConfig.utxoDepth + treeConfig.txDepth + k] = historicPath[k] } } for (let i = 0; i < utxoOutLen; i++) { let ownerHex = utxoOut[i].owner.toString(); let amountHex = utxoOut[i].amount.toString(); let assetTypeHex = utxoOut[i].assetType.toString(); let note_commitment = await BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]); let utxoLeaf = note_commitment.toString() await trees.utxo_tree.insert(utxoLeaf) data.utxo_out[i * 3 + 0] = ownerHex data.utxo_out[i * 3 + 1] = amountHex data.utxo_out[i * 3 + 2] = assetTypeHex data.commitment_out[i] = utxoLeaf } data.tx_in[0] = trees.utxo_tree.root() data.deposit = toFixedHex(Number(amountPublic.amountIn.toString()), true) for (let w = 0; w < amountPublic.amountOut.length; w++) { data.withdrawals[w] = toFixedHex(Number(amountPublic.amountOut[w].toString()), true) } for (let r = 0; r < recipients.length; r++) { data.recipients[r] = recipients[r]; } data.current_root = trees.historic_tree.root() } export async function generateTestPublish(trees, data, api) { let utxoTree = trees.utxo_tree let txTree = trees.tx_tree let historicTree = trees.historic_tree let utxoRoot = utxoTree.root() await txTree.insert(utxoRoot) let txRoot = txTree.root() let txRootFr = Fr.fromBufferReduce(Buffer.from(txRoot.slice(2), 'hex')) let oracleFr = Fr.fromBufferReduce(toFixedHex(0, true)) let oracleHash = await api.pedersenPlookupCompress([oracleFr]) let batch = await api.pedersenPlookupCompressFields(txRootFr, oracleHash) let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex')) let newHistoricRoot = await api.pedersenPlookupCompress([batch, oldHistoricRoot]) let newHistoricRootHex = newHistoricRoot.toString() await historicTree.insert(newHistoricRootHex) data.old_root = oldHistoricRoot.toString() data.new_root = newHistoricRootHex trees.utxoTreeOld = trees.utxo_tree trees.txTreeOld = trees.tx_tree trees.newHistoricRoot = newHistoricRootHex const input = [ data.current_root, data.deposit, data.withdrawals, data.commitment_out, data.recipients, data.oracle, data.old_root_proof, data.nullifier_hashes, data.secrets, data.utxo_in, data.utxo_out, data.indexes, data.hash_path ]; dumpToml(data) }