test_helpers.ts 9.56 KB
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)
}