diff --git a/barretenberg/acir_tests/bbjs-test/.yarnrc.yml b/barretenberg/acir_tests/bbjs-test/.yarnrc.yml new file mode 100644 index 000000000000..3186f3f0795a --- /dev/null +++ b/barretenberg/acir_tests/bbjs-test/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/barretenberg/acir_tests/bbjs-test/package.json b/barretenberg/acir_tests/bbjs-test/package.json new file mode 100644 index 000000000000..217bd2d98a86 --- /dev/null +++ b/barretenberg/acir_tests/bbjs-test/package.json @@ -0,0 +1,15 @@ +{ + "name": "bbjs-test", + "packageManager": "yarn@4.5.2", + "main": "dest/index.js", + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@aztec/bb.js": "portal:../../ts", + "commander": "^12.1.0" + }, + "devDependencies": { + "typescript": "^5.6.3" + } +} diff --git a/barretenberg/acir_tests/bbjs-test/src/index.ts b/barretenberg/acir_tests/bbjs-test/src/index.ts new file mode 100644 index 000000000000..5c17d73ddca8 --- /dev/null +++ b/barretenberg/acir_tests/bbjs-test/src/index.ts @@ -0,0 +1,85 @@ +import createDebug from "debug"; +import fs from "fs/promises"; +import path from "path"; +import { Command } from "commander"; +import assert from "assert"; + +createDebug.enable("*"); +const debug = createDebug("bbjs-test"); + +const proofPath = (dir: string) => path.join(dir, "proof"); +const publicInputsPath = (dir: string) => path.join(dir, "public-inputs"); +const vkeyPath = (dir: string) => path.join(dir, "vk"); + +async function generateProof({ + bytecodePath, + witnessPath, + outputDirectory, + oracleHash, + multiThreaded, +}: { + bytecodePath: string; + witnessPath: string; + outputDirectory: string; + oracleHash?: string; + multiThreaded?: boolean; +}) { + const { UltraHonkBackend } = await import("@aztec/bb.js"); + + debug(`Generating proof for ${bytecodePath}...`); + const circuitArtifact = await fs.readFile(bytecodePath); + const bytecode = JSON.parse(circuitArtifact.toString()).bytecode; + const backend = new UltraHonkBackend(bytecode, { threads: multiThreaded ? 8 : 1 }); + + const witness = await fs.readFile(witnessPath); + const proof = await backend.generateProof(new Uint8Array(witness), { keccak: (oracleHash === "keccak") }); + + await fs.writeFile(proofPath(outputDirectory), Buffer.from(proof.proof)); + debug("Proof written to " + proofPath(outputDirectory)); + + await fs.writeFile(publicInputsPath(outputDirectory), JSON.stringify(proof.publicInputs)); + debug("Public inputs written to " + publicInputsPath(outputDirectory)); + + const verificationKey = await backend.getVerificationKey({ keccak: (oracleHash === "keccak") }); + await fs.writeFile(vkeyPath(outputDirectory), Buffer.from(verificationKey)); + debug("Verification key written to " + vkeyPath(outputDirectory)); + + await backend.destroy(); +} + +async function verifyProof({ directory }: { directory: string }) { + const { BarretenbergVerifier } = await import("@aztec/bb.js"); + + const verifier = new BarretenbergVerifier(); + + const proof = await fs.readFile(proofPath(directory)); + const publicInputs = JSON.parse(await fs.readFile(publicInputsPath(directory), "utf8")); + const vkey = await fs.readFile(vkeyPath(directory)); + + const verified = await verifier.verifyUltraHonkProof( + { proof: new Uint8Array(proof), publicInputs }, + new Uint8Array(vkey) + ); + + await verifier.destroy(); + debug(`Proof verified: ${verified}`); +} + +// Prepare a minimal command line interface +const program = new Command(); + +program + .command("prove") + .option("-b, --bytecode-path ", "bytecode path") + .option("-w, --witness-path ", "witness path") + .option("-o, --output-directory ", "output directory") + .option("-h, --oracle-hash ", "oracle hash") + .option("-multi-threaded", "multi-threaded") + .action((args) => generateProof(args)); + +program + .command("verify") + .option("-d, --directory ", "directory") + .action((args) => verifyProof(args)); + +program.parse(process.argv); diff --git a/barretenberg/acir_tests/bbjs-test/tsconfig.json b/barretenberg/acir_tests/bbjs-test/tsconfig.json new file mode 100644 index 000000000000..13c452bfd97d --- /dev/null +++ b/barretenberg/acir_tests/bbjs-test/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["dom", "esnext", "es2017.object"], + "module": "NodeNext", + "strict": true, + "declaration": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "downlevelIteration": true, + "inlineSourceMap": true, + "declarationMap": true, + "importHelpers": true, + "resolveJsonModule": true, + "outDir": "dest", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/barretenberg/acir_tests/bootstrap.sh b/barretenberg/acir_tests/bootstrap.sh index e3787ba3487e..61b9d1c2bada 100755 --- a/barretenberg/acir_tests/bootstrap.sh +++ b/barretenberg/acir_tests/bootstrap.sh @@ -113,6 +113,8 @@ function build { # find {headless-test,browser-test-app} -exec touch -t 197001010000 {} + 2>/dev/null || true denoise "cd browser-test-app && yarn build" + + denoise "cd bbjs-test && yarn build" } function test { @@ -187,6 +189,22 @@ function test_cmds_internal { echo SYS=ultra_honk FLOW=prove_then_verify RECURSIVE=true $run_test double_verify_honk_proof echo SYS=ultra_honk FLOW=prove_then_verify HASH=keccak $run_test assert_statement echo SYS=ultra_honk FLOW=prove_then_verify ROLLUP=true $run_test verify_rollup_honk_proof + + # prove and verify using bb.js classes + echo SYS=ultra_honk FLOW=bbjs_prove_verify $run_test 1_mul + echo SYS=ultra_honk FLOW=bbjs_prove_verify THREAD_MODEL=mt $run_test assert_statement + + # prove with bb.js and verify with solidity verifier + echo SYS=ultra_honk FLOW=bbjs_prove_sol_verify $run_test 1_mul + echo SYS=ultra_honk FLOW=bbjs_prove_sol_verify $run_test assert_statement + + # prove with bb cli and verify with bb.js classes + echo SYS=ultra_honk FLOW=bb_prove_bbjs_verify $run_test 1_mul + echo SYS=ultra_honk FLOW=bb_prove_bbjs_verify $run_test assert_statement + + # prove with bb.js and verify with bb cli + echo SYS=ultra_honk FLOW=bbjs_prove_bb_verify $run_test 1_mul + echo SYS=ultra_honk FLOW=bbjs_prove_bb_verify $run_test assert_statement } function ultra_honk_wasm_memory { diff --git a/barretenberg/acir_tests/flows/bb_prove_bbjs_verify.sh b/barretenberg/acir_tests/flows/bb_prove_bbjs_verify.sh new file mode 100755 index 000000000000..52763418d05c --- /dev/null +++ b/barretenberg/acir_tests/flows/bb_prove_bbjs_verify.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# prove with bb.js and verify using bb classes +set -eu + +if [ "${SYS:-}" != "ultra_honk" ]; then + echo "Error: This flow only supports ultra_honk" + exit 1 +fi + +artifact_dir=$(realpath ./target) +output_dir=$artifact_dir/bbjs-bb-tmp +mkdir -p $output_dir + +# Cleanup on exit +trap "rm -rf $output_dir" EXIT + +# Generate the proof and VK using BB CLI (save as both bytes and fields) +$BIN prove \ + --scheme ultra_honk \ + -b $artifact_dir/program.json \ + -w $artifact_dir/witness.gz \ + --output_format bytes_and_fields \ + -o $output_dir + +# Generate the VK using BB CLI +$BIN write_vk \ + --scheme ultra_honk \ + -b $artifact_dir/program.json \ + -o $output_dir + +# bb.js expects proof and public inputs to be separate files, so we need to split them +# this will not be needed after #11024 + +# Save public inputs as separate file (first NUM_PUBLIC_INPUTS fields of proof_fields.json) +PROOF_FIELDS_LENGTH=$(jq 'length' $output_dir/proof_fields.json) +UH_PROOF_FIELDS_LENGTH=440 +NUM_PUBLIC_INPUTS=$((PROOF_FIELDS_LENGTH - UH_PROOF_FIELDS_LENGTH)) +jq ".[:$NUM_PUBLIC_INPUTS]" $output_dir/proof_fields.json > $output_dir/public-inputs + +# Remove NUM_PUBLIC_INPUTS*32 bytes from the proof +proof_hex=$(cat $output_dir/proof | xxd -p) +proof_start=${proof_hex:0:8} +proof_end=${proof_hex:$((8 + NUM_PUBLIC_INPUTS * 64))} +echo -n $proof_start$proof_end | xxd -r -p > $output_dir/proof + +# Verify the proof with bb.js classes +node ../../bbjs-test verify \ + -d $output_dir diff --git a/barretenberg/acir_tests/flows/bbjs_prove_bb_verify.sh b/barretenberg/acir_tests/flows/bbjs_prove_bb_verify.sh new file mode 100755 index 000000000000..0fbce76c813c --- /dev/null +++ b/barretenberg/acir_tests/flows/bbjs_prove_bb_verify.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# prove with bb.js and verify using bb cli +set -eu + +if [ "${SYS:-}" != "ultra_honk" ]; then + echo "Error: This flow only supports ultra_honk" + exit 1 +fi + +artifact_dir=$(realpath ./target) +output_dir=$artifact_dir/bb-bbjs-tmp +mkdir -p $output_dir + +# Cleanup on exit +trap "rm -rf $output_dir" EXIT + +# Writes the proof, public inputs ./target; this also writes the VK +node ../../bbjs-test prove \ + -b $artifact_dir/program.json \ + -w $artifact_dir/witness.gz \ + -o $output_dir + +# Join the proof and public inputs to a single file +# this will not be needed after #11024 + +proof_bytes=$(cat $output_dir/proof | xxd -p) +public_inputs=$(cat $output_dir/public-inputs | jq -r '.[]') +proof_start=${proof_bytes:0:8} +proof_end=${proof_bytes:8} + +public_inputs_bytes="" +for input in $public_inputs; do + public_inputs_bytes+=$input +done + +# Combine proof start, public inputs, and rest of proof +echo -n $proof_start$public_inputs_bytes$proof_end | xxd -r -p > $output_dir/proof + +# Print the length of the proof file in bytes +ls -l $output_dir/proof | awk '{print $5}' + +# Verify the proof with bb cli +$BIN verify \ + --scheme ultra_honk \ + -k $output_dir/vk \ + -p $output_dir/proof diff --git a/barretenberg/acir_tests/flows/bbjs_prove_sol_verify.sh b/barretenberg/acir_tests/flows/bbjs_prove_sol_verify.sh new file mode 100755 index 000000000000..7fbf519a5a91 --- /dev/null +++ b/barretenberg/acir_tests/flows/bbjs_prove_sol_verify.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# prove using bb.js and verify using solidity verifier +set -eu + +if [ "${SYS:-}" != "ultra_honk" ]; then + echo "Error: This flow only supports ultra_honk" + exit 1 +fi + +artifact_dir=$(realpath ./target) +output_dir=$artifact_dir/bbjs-sol-tmp +mkdir -p $output_dir + +# Cleanup on exit +trap "rm -rf $output_dir" EXIT + +# Generate the proof and VK +node ../../bbjs-test prove \ + -b $artifact_dir/program.json \ + -w $artifact_dir/witness.gz \ + -o $output_dir \ + --oracle-hash keccak + +# Write the solidity verifier to ./target +export VK=$output_dir/vk +export VERIFIER_PATH="$output_dir/Verifier.sol" + +# Use the BB CLI to write the solidity verifier - this can also be done with bb.js +$BIN write_solidity_verifier --scheme ultra_honk -k $VK -o $VERIFIER_PATH + +# Verify the proof using the solidity verifier +export PROOF=$output_dir/proof +export PUBLIC_INPUTS=$output_dir/public-inputs +export TEST_PATH=$(realpath "../../sol-test/HonkTest.sol") +export TESTING_HONK="true" +export TEST_NAME=$(basename $(realpath ./)) + +node ../../sol-test/src/index.js diff --git a/barretenberg/acir_tests/flows/bbjs_prove_verify.sh b/barretenberg/acir_tests/flows/bbjs_prove_verify.sh new file mode 100755 index 000000000000..a7ae25ec8a53 --- /dev/null +++ b/barretenberg/acir_tests/flows/bbjs_prove_verify.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# prove and verify using bb.js classes +set -eu + +if [ "${SYS:-}" != "ultra_honk" ]; then + echo "Error: This flow only supports ultra_honk" + exit 1 +fi + +artifact_dir=$(realpath ./target) +output_dir=$artifact_dir/bbjs-tmp +mkdir -p $output_dir + +# Cleanup on exit +trap "rm -rf $output_dir" EXIT + +# Writes the proof, public inputs ./target; this also writes the VK +node ../../bbjs-test prove \ + -b $artifact_dir/program.json \ + -w $artifact_dir/witness.gz \ + -o $output_dir \ + ${THREAD_MODEL:-st} = "mt" && echo "--multi-threaded" + +# Verify the proof by reading the files in ./target +node ../../bbjs-test verify \ + -d $output_dir diff --git a/barretenberg/acir_tests/package.json b/barretenberg/acir_tests/package.json index 17bde8f091e2..6ec7770e0284 100644 --- a/barretenberg/acir_tests/package.json +++ b/barretenberg/acir_tests/package.json @@ -5,6 +5,7 @@ "workspaces": [ "browser-test-app", "headless-test", - "sol-test" + "sol-test", + "bbjs-test" ] } diff --git a/barretenberg/acir_tests/sol-test/src/index.js b/barretenberg/acir_tests/sol-test/src/index.js index 95718939ca3f..7ba7dbb44cff 100644 --- a/barretenberg/acir_tests/sol-test/src/index.js +++ b/barretenberg/acir_tests/sol-test/src/index.js @@ -217,24 +217,43 @@ const killAnvil = () => { }; try { - const proofAsFieldsPath = getEnvVar("PROOF_AS_FIELDS"); - const proofAsFields = readFileSync(proofAsFieldsPath); - const [numPublicInputs, publicInputs] = readPublicInputs( - JSON.parse(proofAsFields.toString()) - ); - const proofPath = getEnvVar("PROOF"); - const proof = readFileSync(proofPath); + let publicInputsPath; + try { + publicInputsPath = getEnvVar("PUBLIC_INPUTS"); + } catch (e) { + // noop + } + + let proofStr = ''; + let publicInputs = []; + + // If "path to public inputs" is provided, it means that the proof and public inputs are saved as separate files + // A bit hacky, but this can go away once BB CLI saves them as separate files - #11024 + if (publicInputsPath) { + const proof = readFileSync(proofPath); + proofStr = proof.toString("hex"); + publicInputs = JSON.parse(readFileSync(publicInputsPath).toString()); // assumes JSON array of PI hex strings + } else { + // Proof and public inputs are saved in a single file; we need to extract the PI from the proof + const proof = readFileSync(proofPath); + proofStr = proof.toString("hex"); + + const proofAsFieldsPath = getEnvVar("PROOF_AS_FIELDS"); + const proofAsFields = readFileSync(proofAsFieldsPath); + + let numPublicInputs; + [numPublicInputs, publicInputs] = readPublicInputs( + JSON.parse(proofAsFields.toString()) + ); - // Cut the number of public inputs out of the proof string - let proofStr = proof.toString("hex"); + proofStr = proofStr.substring(32 * 2 * numPublicInputs); // Remove the publicInput bytes from the proof + } + + // Honk proof have field length as the first 4 bytes + // This should go away in the future if (testingHonk) { - // Cut off the serialised buffer size at start proofStr = proofStr.substring(8); - // Get the part after the public inputs - proofStr = proofStr.substring(64 * numPublicInputs); - } else { - proofStr = proofStr.substring(64 * numPublicInputs); } proofStr = "0x" + proofStr; diff --git a/barretenberg/acir_tests/yarn.lock b/barretenberg/acir_tests/yarn.lock index 437bc7f79910..785481d72c03 100644 --- a/barretenberg/acir_tests/yarn.lock +++ b/barretenberg/acir_tests/yarn.lock @@ -18,6 +18,21 @@ __metadata: languageName: unknown linkType: soft +"@aztec/bb.js@portal:../../ts::locator=bbjs-test%40workspace%3Abbjs-test": + version: 0.0.0-use.local + resolution: "@aztec/bb.js@portal:../../ts::locator=bbjs-test%40workspace%3Abbjs-test" + dependencies: + comlink: "npm:^4.4.1" + commander: "npm:^12.1.0" + debug: "npm:^4.3.4" + fflate: "npm:^0.8.0" + pako: "npm:^2.1.0" + tslib: "npm:^2.4.0" + bin: + bb.js: dest/node/main.js + languageName: node + linkType: soft + "@aztec/bb.js@portal:../../ts::locator=browser-test-app%40workspace%3Abrowser-test-app": version: 0.0.0-use.local resolution: "@aztec/bb.js@portal:../../ts::locator=browser-test-app%40workspace%3Abrowser-test-app" @@ -1058,6 +1073,16 @@ __metadata: languageName: node linkType: hard +"bbjs-test@workspace:bbjs-test": + version: 0.0.0-use.local + resolution: "bbjs-test@workspace:bbjs-test" + dependencies: + "@aztec/bb.js": "portal:../../ts" + commander: "npm:^12.1.0" + typescript: "npm:^5.6.3" + languageName: unknown + linkType: soft + "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -4941,7 +4966,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.4.2": +"typescript@npm:^5.4.2, typescript@npm:^5.6.3": version: 5.8.2 resolution: "typescript@npm:5.8.2" bin: @@ -4951,7 +4976,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.4.2#optional!builtin": +"typescript@patch:typescript@npm%3A^5.4.2#optional!builtin, typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": version: 5.8.2 resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" bin: