From 37233072a64a1b73842ec5fb637b4b13c9e72904 Mon Sep 17 00:00:00 2001 From: codygunton Date: Thu, 24 Oct 2024 02:03:26 +0000 Subject: [PATCH] Integration test via bb.js --- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 53 ++++++++- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 6 +- barretenberg/ts/src/barretenberg_api/index.ts | 24 ++-- barretenberg/ts/src/main.ts | 59 ++++++++- yarn-project/bb-prover/src/bb/execute.ts | 48 ++++++++ .../src/wasm_client_ivc_integration.test.ts | 112 ++++++++++++++++++ 6 files changed, 280 insertions(+), 22 deletions(-) create mode 100644 yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 729747c89bb..5b01d09674b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -207,7 +207,7 @@ WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_compose write(out_key_hash, vk_hash); } -WASM_EXPORT void acir_prove_aztec_client(uint8_t const* acir_stack, uint8_t const* witness_stack, bool* result) +WASM_EXPORT void acir_prove_aztec_client(uint8_t const* acir_stack, uint8_t const* witness_stack, uint8_t** out) { using Program = acir_format::AcirProgram; std::vector> witnesses = from_buffer>>(witness_stack); @@ -250,7 +250,56 @@ WASM_EXPORT void acir_prove_aztec_client(uint8_t const* acir_stack, uint8_t cons info("calling ivc.prove"); auto proof = ivc.prove(); - *result = false; + *out = to_heap_buffer(to_buffer(proof)); +} + +WASM_EXPORT void acir_prove_and_verify_aztec_client(uint8_t const* acir_stack, + uint8_t const* witness_stack, + bool* verified) +{ + using Program = acir_format::AcirProgram; + std::vector> witnesses = from_buffer>>(witness_stack); + info("read witnesses from buffer"); + std::vector> acirs = from_buffer>>(acir_stack); + info("read acirs from buffer"); + std::vector folding_stack; + + for (auto [bincode, wit] : zip_view(acirs, witnesses)) { + acir_format::AcirFormat constraints = + acir_format::circuit_buf_to_acir_format(bincode, /*honk_recursion=*/false); + acir_format::WitnessVector witness = acir_format::witness_buf_to_witness_data(wit); + + folding_stack.push_back(Program{ constraints, witness }); + } + info("created folding stack"); + // TODO(#7371) dedupe this with the rest of the similar code + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1101): remove use of auto_verify_mode + ClientIVC ivc; + ivc.auto_verify_mode = true; + ivc.trace_structure = TraceStructure::E2E_FULL_TEST; + + // Accumulate the entire program stack into the IVC + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1116): remove manual setting of is_kernel once databus + // has been integrated into noir kernel programs + bool is_kernel = false; + for (Program& program : folding_stack) { + // Construct a bberg circuit from the acir representation then accumulate it into the IVC + auto circuit = + create_circuit(program.constraints, 0, program.witness, false, ivc.goblin.op_queue); + + // Set the internal is_kernel flag based on the local mechanism only if it has not already been set to true + if (!circuit.databus_propagation_data.is_kernel) { + circuit.databus_propagation_data.is_kernel = is_kernel; + } + is_kernel = !is_kernel; + info("calling ivc.accumulate:"); + ivc.accumulate(circuit); + } + + info("calling ivc.prove"); + bool result = ivc.prove_and_verify(); + + *verified = result; } WASM_EXPORT void acir_prove_ultra_honk(uint8_t const* acir_vec, uint8_t const* witness_vec, uint8_t** out) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index e3433c311f1..352ecc3184e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -45,7 +45,11 @@ WASM_EXPORT void acir_prove_and_verify_mega_honk(uint8_t const* constraint_syste WASM_EXPORT void acir_prove_aztec_client(uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - bool* result); + uint8_t** out); + +WASM_EXPORT void acir_prove_and_verify_aztec_client(uint8_t const* constraint_system_buf, + uint8_t const* witness_buf, + bool* result); WASM_EXPORT void acir_verify_aztec_client_proof(in_ptr acir_composer_ptr, uint8_t const* proof_buf, bool* result); diff --git a/barretenberg/ts/src/barretenberg_api/index.ts b/barretenberg/ts/src/barretenberg_api/index.ts index fb62a28bb75..0ef7b188bba 100644 --- a/barretenberg/ts/src/barretenberg_api/index.ts +++ b/barretenberg/ts/src/barretenberg_api/index.ts @@ -432,19 +432,6 @@ export class BarretenbergApi { return out[0]; } - async acirProveAndVerifyClientIvc(constraintSystemBuf: Uint8Array[], witnessBuf: Uint8Array[]): Promise { - const inArgs = [constraintSystemBuf, witnessBuf].map(serializeBufferable); - console.log("serialized the unpacked unpacked and unzipped circuit and witness data"); - const outTypes: OutputType[] = [BoolDeserializer()]; - const result = await this.wasm.callWasmExport( - 'acir_prove_aztec_client', - inArgs, - outTypes.map(t => t.SIZE_IN_BYTES), - ); - const out = result.map((r, i) => outTypes[i].fromBuffer(r)); - return out[0]; - } - async acirLoadVerificationKey(acirComposerPtr: Ptr, vkBuf: Uint8Array): Promise { const inArgs = [acirComposerPtr, vkBuf].map(serializeBufferable); const outTypes: OutputType[] = []; @@ -545,6 +532,17 @@ export class BarretenbergApi { return out as any; } + async acirProveAndVerifyAztecClient(acirVec: Uint8Array[], witnessVec: Uint8Array[]): Promise { + const inArgs = [acirVec, witnessVec].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer()]; + const result = await this.wasm.callWasmExport( + 'acir_prove_and_verify_aztec_client', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } async acirProveAztecClient(acirVec: Uint8Array[], witnessVec: Uint8Array[]): Promise { const inArgs = [acirVec, witnessVec].map(serializeBufferable); diff --git a/barretenberg/ts/src/main.ts b/barretenberg/ts/src/main.ts index 35ffcf1f81a..83cf8639418 100755 --- a/barretenberg/ts/src/main.ts +++ b/barretenberg/ts/src/main.ts @@ -201,15 +201,39 @@ export async function proveAndVerifyMegaHonk(bytecodePath: string, witnessPath: /* eslint-enable camelcase */ } -export async function proveAndVerifyClientIvc(bytecodePath: string, witnessPath: string, crsPath: string) { +export async function proveAndVerifyAztecClient(bytecodePath: string, witnessPath: string, crsPath: string) { /* eslint-disable camelcase */ const { api } = await initClientIVC(crsPath); try { - const bytecode = readStack(bytecodePath, 1); + const bytecode = readStack(bytecodePath, 0); const witness = readStack(witnessPath, 0); - const result = await api.acirProveAndVerifyClientIvc(bytecode, witness); - return false; + const verified = await api.acirProveAndVerifyAztecClient(bytecode, witness); + return verified; + } finally { + await api.destroy(); + } + /* eslint-enable camelcase */ +} + +export async function proveAztecClient(bytecodePath: string, witnessPath: string, crsPath: string, outputPath: string) { + /* eslint-disable camelcase */ + const { api } = await initClientIVC(crsPath); + try { + debug(`creating proof...`); + const bytecode = readStack(bytecodePath, 0); + const witness = readStack(witnessPath, 0); + const proof = await api.acirProveAztecClient(bytecode, witness); + debug(`finished creating proof.`); + + if (outputPath === '-') { + process.stdout.write(proof); + debug(`proof written to stdout`); + } else { + writeFileSync(outputPath, proof); + debug(`proof written to: ${outputPath}`); + } + } finally { await api.destroy(); } @@ -531,13 +555,13 @@ program }); program - .command('client_ivc_prove_output_all') + .command('client_ivc_prove_and_verify') .description('Generate a ClientIVC proof.') .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acirs.msgpack') .option('-w, --witness-path ', 'Specify the witness path', './target/witnesses.msgpack') .action(async ({ bytecodePath, witnessPath, crsPath }) => { handleGlobalOptions(); - const result = await proveAndVerifyClientIvc(bytecodePath, witnessPath, crsPath); + const result = await proveAndVerifyAztecClient(bytecodePath, witnessPath, crsPath); process.exit(result ? 0 : 1); }); @@ -563,6 +587,29 @@ program await prove(bytecodePath, witnessPath, crsPath, outputPath); }); +program + .command('client_ivc_prove') + .description('Generate a ClientIVC proof.') + .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acirs.msgpack') + .option('-w, --witness-path ', 'Specify the witness path', './target/witnesses.msgpack') + .option('-o, --output-path ', 'Specify the proof output path', './proofs/proof') + .action(async ({ bytecodePath, witnessPath, outputPath, crsPath }) => { + handleGlobalOptions(); + await proveAztecClient(bytecodePath, witnessPath, outputPath, crsPath); + }); + + program + .command('client_ivc_prove_and_verify') + .description('Generate a ClientIVC proof.') + .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acirs.msgpack') + .option('-w, --witness-path ', 'Specify the witness path', './target/witnesses.msgpack') + .option('-o, --output-path ', 'Specify the proof output path', './proofs/proof') + .action(async ({ bytecodePath, witnessPath, crsPath }) => { + handleGlobalOptions(); + const result = await proveAndVerifyAztecClient(bytecodePath, witnessPath, crsPath); + process.exit(result ? 0 : 1); + }); + program .command('gates') .description('Print Ultra Builder gate count to standard output.') diff --git a/yarn-project/bb-prover/src/bb/execute.ts b/yarn-project/bb-prover/src/bb/execute.ts index 22c4b5e9393..e7063eab90c 100644 --- a/yarn-project/bb-prover/src/bb/execute.ts +++ b/yarn-project/bb-prover/src/bb/execute.ts @@ -247,6 +247,54 @@ export async function executeBbClientIvcProof( } } +export async function executeBbClientIvcProveAndVerify( + pathToBB: string, + workingDirectory: string, + bytecodeStackPath: string, + witnessStackPath: string, + log: LogFn, +): Promise { + // Check that the working directory exists + try { + await fs.access(workingDirectory); + } catch (error) { + return { status: BB_RESULT.FAILURE, reason: `Working directory ${workingDirectory} does not exist` }; + } + + const binaryPresent = await fs + .access(pathToBB, fs.constants.R_OK) + .then(_ => true) + .catch(_ => false); + if (!binaryPresent) { + return { status: BB_RESULT.FAILURE, reason: `Failed to find bb binary at ${pathToBB}` }; + } + + try { + // Write the bytecode to the working directory + log(`bytecodePath ${bytecodeStackPath}`); + const args = ['-b', bytecodeStackPath, '-w', witnessStackPath, '-v']; + const timer = new Timer(); + const logFunction = (message: string) => { + log(`client ivc proof BB out - ${message}`); + }; + + const result = await executeBB(pathToBB, 'client_ivc_prove_and_verify', args, logFunction); + const durationMs = timer.ms(); + + if (result.status == BB_RESULT.SUCCESS) { + return { status: BB_RESULT.SUCCESS, durationMs: durationMs }; + } + // Not a great error message here but it is difficult to decipher what comes from bb + return { + status: BB_RESULT.FAILURE, + reason: `Failed to prove and verify. Exit code ${result.exitCode}. Signal ${result.signal}.`, + }; + } catch (error) { + return { status: BB_RESULT.FAILURE, reason: `${error}` }; + } +} + + /** * Used for generating verification keys of noir circuits. * It is assumed that the working directory is a temporary and/or random directory used solely for generating this VK. diff --git a/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts b/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts new file mode 100644 index 00000000000..528a44ba373 --- /dev/null +++ b/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts @@ -0,0 +1,112 @@ +import { BB_RESULT, executeBbClientIvcProveAndVerify } from '@aztec/bb-prover'; +import { ClientIvcProof } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; + +import { jest } from '@jest/globals'; +import { encode } from '@msgpack/msgpack'; +import fs from 'fs/promises'; +import os from 'os'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { + MOCK_MAX_COMMITMENTS_PER_TX, + MockAppCreatorCircuit, + MockAppReaderCircuit, + MockPrivateKernelInitCircuit, + MockPrivateKernelInnerCircuit, + MockPrivateKernelResetCircuit, + MockPrivateKernelTailCircuit, + witnessGenCreatorAppMockCircuit, + witnessGenMockPrivateKernelInitCircuit, + witnessGenMockPrivateKernelInnerCircuit, + witnessGenMockPrivateKernelResetCircuit, + witnessGenMockPrivateKernelTailCircuit, + witnessGenReaderAppMockCircuit, +} from './index.js'; + +/* eslint-disable camelcase */ + +const logger = createDebugLogger('aztec:clientivc-integration'); + +jest.setTimeout(120_000); + +describe('Client IVC Integration', () => { + let bbWorkingDirectory: string; + let bbBinaryPath: string; + + beforeEach(async () => { + // Create a temp working dir + bbWorkingDirectory = await fs.mkdtemp(path.join('/mnt/user-data/cody/bb-tmp/', 'bb-client-ivc-integration-')); + bbBinaryPath = path.join( + path.dirname(fileURLToPath(import.meta.url)), + '../../../barretenberg/ts/dest/node', + 'main.js', + ); + logger.debug(`bbBinaryPath is ${bbBinaryPath}`); + }); + + async function proveAndVerifyAztecClient(witnessStack: Uint8Array[], bytecodes: string[]): Promise { + await fs.writeFile( + path.join(bbWorkingDirectory, 'acir.msgpack'), + encode(bytecodes.map(bytecode => Buffer.from(bytecode, 'base64'))), + ); + logger.debug('wrote acir.msgpack'); + + await fs.writeFile(path.join(bbWorkingDirectory, 'witnesses.msgpack'), encode(witnessStack)); + logger.debug('wrote witnesses.msgpack'); + + const provingResult = await executeBbClientIvcProveAndVerify( + bbBinaryPath, + bbWorkingDirectory, + path.join(bbWorkingDirectory, 'acir.msgpack'), + path.join(bbWorkingDirectory, 'witnesses.msgpack'), + logger.info, + ); + + if (provingResult.status === BB_RESULT.FAILURE) { + throw new Error(provingResult.reason); + } else { + return true + } + } + + // This test will verify a client IVC proof of a simple tx: + // 1. Run a mock app that creates two commitments + // 2. Run the init kernel to process the app run + // 3. Run the tail kernel to finish the client IVC chain. + it('Should generate a verifiable client IVC proof from a simple mock tx via bb.js', async () => { + const tx = { + number_of_calls: '0x1', + }; + // Witness gen app and kernels + const appWitnessGenResult = await witnessGenCreatorAppMockCircuit({ commitments_to_create: ['0x1', '0x2'] }); + logger.debug('generated app mock circuit witness'); + + const initWitnessGenResult = await witnessGenMockPrivateKernelInitCircuit({ + app_inputs: appWitnessGenResult.publicInputs, + tx, + }); + logger.debug('generated mock private kernel init witness'); + + const tailWitnessGenResult = await witnessGenMockPrivateKernelTailCircuit({ + prev_kernel_public_inputs: initWitnessGenResult.publicInputs, + }); + logger.debug('generated mock private kernel tail witness'); + + // Create client IVC proof + const bytecodes = [ + MockAppCreatorCircuit.bytecode, + MockPrivateKernelInitCircuit.bytecode, + MockPrivateKernelTailCircuit.bytecode, + ]; + logger.debug('built bytecode array'); + const witnessStack = [appWitnessGenResult.witness, initWitnessGenResult.witness, tailWitnessGenResult.witness]; + logger.debug('built witness stack'); + + const verifyResult = await proveAndVerifyAztecClient(witnessStack, bytecodes); + logger.debug('generated and verified proof'); + + expect(verifyResult).toEqual(true); + }); +});