import { randomBytes } from 'crypto' import { readFileSync } from 'fs'; import { Fr } from '@aztec/bb.js/dest/node/types/index.js'; import { MerkleTree } from './MerkleTree.mjs'; import { keccak256 } from "@ethersproject/keccak256/lib/index.js"; import fs from 'fs' 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 getInputsObject() { let regex = /(.*)\: (pub )?(\[.*\; (.*)?\],)?.*/; let circuit = fs.readFileSync(circuitConfig.main).toString(); let inputs = {} let input_ordering = [] let structLine = circuit.split('\n').find(line => line.includes('fn main')); let circuitSplit = circuit.split('\n'); let structLines = circuitSplit.slice(circuitSplit.indexOf(structLine) + 1, circuitSplit.indexOf(circuitSplit.find(line => line.includes(') {')))); let line_maps = structLines.map(line => line.match(regex)).filter(x => x !== null); line_maps.forEach(line_map => { inputs[line_map[1].trim()] = [(line_map[2] ? true : false), (line_map[4] ? line_map[4] : 1)]; input_ordering.push(line_map[1].trim()); }); return [inputs, input_ordering]; } export function getInputs() { let inputs = getInputsObject()[0]; return inputs; } export function getInputsOrdered(pub) { let inputsObj = getInputsObject(); let inputs = inputsObj[1]; let input_ordering = inputs; if (pub) input_ordering = input_ordering.map(input => inputsObj[0][input][0] ? input : null).filter(x => x !== null); return input_ordering; } export function getPublicInputsOrdered() { return getInputsOrdered(true); } export function readToml(path) { let public_inputs = getPublicInputsOrdered(); let unordered_inputs = evalWithScopePrefix(fs.readFileSync(path).toString()); let ordered_inputs = public_inputs.map(input => unordered_inputs[input]); return ordered_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 public_inputs = getInputsOrdered(); let toml = []; public_inputs.forEach((input) => { toml.push(` ${input} = ${format(data[input])}`); }); toml = toml.join('\n'); fs.writeFileSync('./Prover.toml', toml); } export const dumpSolidity = () => { let contract = fs.readFileSync("./contracts/TechnicalPreview.sol").toString(); let structLine = contract.split('\n').find(line => line.includes('BatchPublicInputs')); let public_inputs = getInputsOrdered(true); let public_inputs_sizes = public_inputs.map(input => getInputs()[input][1]); let new_struct = `struct BatchPublicInputs {`; public_inputs.forEach((input, index) => { new_struct += `\n bytes32${public_inputs_sizes[index] !== 1 ? `[${public_inputs_sizes[index]}]` : ''} ${input};`; }); contract = contract.replace(structLine, new_struct); let flattenLine = contract.split('\n').find(line => line.includes('function flattenBatchPublicInputs(BatchPublicInputs memory input) public pure returns (bytes32[] memory) {')); let flattenLineIndex = contract.split('\n').indexOf(flattenLine); let flatArrayLine = ` bytes32[] memory flatArray = new bytes32[](${public_inputs_sizes.reduce((a, b) => parseInt(a) + parseInt(b))});`; contract = contract.split('\n').slice(0, flattenLineIndex + 1).join('\n') + '\n' + flatArrayLine + '\n' + contract.split('\n').slice(flattenLineIndex + 1).join('\n'); let idxLine = ` uint256 idx = 0;\n`; contract = contract.split('\n').slice(0, flattenLineIndex + 2).join('\n') + '\n' + idxLine + '\n' + contract.split('\n').slice(flattenLineIndex + 2).join('\n'); let forLoopLines = []; public_inputs.forEach((input, index) => { if (public_inputs_sizes[index] === 1) { forLoopLines.push(` flatArray[idx++] = input.${input};`); } else { forLoopLines.push(` for (uint i = 0; i < ${public_inputs_sizes[index]}; i++) flatArray[idx++] = input.${input}[i];`); } }); contract = contract.split('\n').slice(0, flattenLineIndex + 3).join('\n') + '\n' + forLoopLines.join('\n') + '\n' + contract.split('\n').slice(flattenLineIndex + 3).join('\n'); fs.writeFileSync('./contracts/TechnicalPreview.sol', contract); } 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 = []; for (var i = 0; i < hash_path.length; i++) { hash_path_input.push(`0x` + hash_path[i]); } return hash_path_input; } export function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) { let utxos = [] for (let i = 0; i < batchSize; i++) { let amountBN = amounts[i] let utxo = { secret: _secrets[i], owner: 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), } let commitment = BarretenbergApi.pedersenPlookupCompress( [utxoIn.owner, utxoIn.amount, utxoIn.assetType] ).toString() let old_root = await contract.getRootFromUtxo(commitment) let utxo_list = await contract.getUtxoFromRoot(old_root) let historic_roots = await contract.getValidRoots() let oracle = BarretenbergApi.pedersenPlookupCompress([new Fr(0n)]) for (let i = 0; i < utxo_list.length; i++) { trees.utxo_tree.insert(utxo_list[i]); } let utxo_root = trees.utxo_tree.root() trees.tx_tree.insert(utxo_root); let tx_root_Fr = Fr.fromString(trees.tx_tree.root()) let batch = BarretenbergApi.pedersenPlookupCompress([tx_root_Fr, oracle]) let new_root = BarretenbergApi.pedersenPlookupCompress([batch, Fr.fromString(old_root)]).toString() for (let i = 0; i < historic_roots.length; i++) { 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 function generateDataToml(oldRoot, newRoot, trees, api) { let zeroHash = api.pedersenPlookupCompress([Fr.fromString(toFixedHex(0, true))]) const data = { tx_in: new Array(16).fill(ZERO_VALUE), secrets: new Array(16).fill('0'), utxo_in: new Array(48).fill('0'), utxo_out: new Array(48).fill('0'), oracle: zeroHash.toString(), old_root_proof: new Array(16).fill('0'), old_root: ZERO_VALUE, new_root: ZERO_VALUE, current_root: trees.historic_tree.root(), indexes: new Array(48).fill('0'), hash_path: new Array(16 * treeSum).fill('0'), commitment_out: new Array(16).fill(ZERO_VALUE), amount_public_in: "0", amount_public_out: "0", nullifier_hashes: new Array(16).fill('0'), recipient: Fr.fromString(toFixedHex(0, true)).toString() } 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, recipient, 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 = BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]); let utxoLeaf = note_commitment.toString() data.secrets[i] = utxoIn[i].secret data.nullifier_hashes[i] = BarretenbergApi.pedersenPlookupCompressFields(utxoIn[i].secret, utxoIn[i].secret) data.old_root_proof[i] = await contract.getRootFromUtxo(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 = BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]); let utxoLeaf = note_commitment.toString() 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.amount_public_in = toFixedHex(Number(amountPublic.amountIn.toString()), true) data.amount_public_out = toFixedHex(Number(amountPublic.amountOut.toString()), true) data.recipient = recipient data.current_root = trees.historic_tree.root() dumpToml(data) } export function generateTestPublish(trees, data, api) { let utxoTree = trees.utxo_tree let txTree = trees.tx_tree let historicTree = trees.historic_tree let utxoRoot = utxoTree.root() 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 = api.pedersenPlookupCompress([oracleFr]) let batch = api.pedersenPlookupCompressFields(txRootFr, oracleHash) let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex')) let newHistoricRoot = api.pedersenPlookupCompress([batch, oldHistoricRoot]) let newHistoricRootHex = newHistoricRoot.toString() 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 trees.utxo_tree = new MerkleTree(4, api) trees.tx_tree = new MerkleTree(4, api) dumpToml(data) }