From 8c064d484c686fdf00a100f65f1f740be4ef13cb Mon Sep 17 00:00:00 2001 From: Cody Gunton Date: Tue, 10 Dec 2024 10:17:17 -0500 Subject: [PATCH] feat: CIVC browser proveThenVerify (#10431) Add c_binds for ClientIVC `prove` and `verify`. Wrap these in `proveThenVerify` and use replace use of `proveAndVerify` by this in the ivc-integration suite. --- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 82 ++++++++++++++++++- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 11 ++- barretenberg/ts/src/barretenberg/backend.ts | 10 +++ barretenberg/ts/src/barretenberg_api/index.ts | 24 ++++++ .../browser_client_ivc_integration.test.ts | 36 ++++++-- yarn-project/ivc-integration/src/index.ts | 32 +++----- yarn-project/ivc-integration/src/serve.ts | 6 +- .../src/wasm_client_ivc_integration.test.ts | 37 ++------- 8 files changed, 169 insertions(+), 69 deletions(-) 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 65bcb9fa506..2fa9d66dede 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -10,7 +10,6 @@ #include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/serialize/msgpack.hpp" -#include "barretenberg/srs/global_crs.hpp" #include "honk_contract.hpp" #include #include @@ -274,6 +273,87 @@ WASM_EXPORT void acir_prove_and_verify_aztec_client(uint8_t const* acir_stack, *verified = result; } +WASM_EXPORT void acir_prove_aztec_client(uint8_t const* acir_stack, + uint8_t const* witness_stack, + uint8_t** out_proof, + uint8_t** out_vk) +{ + using Program = acir_format::AcirProgram; + + std::vector> witnesses = from_buffer>>(witness_stack); + std::vector> acirs = from_buffer>>(acir_stack); + std::vector folding_stack; + + for (auto [bincode, wit] : zip_view(acirs, witnesses)) { + acir_format::WitnessVector witness = acir_format::witness_buf_to_witness_data(wit); + acir_format::AcirFormat constraints = + acir_format::circuit_buf_to_acir_format(bincode, /*honk_recursion=*/false); + folding_stack.push_back(Program{ constraints, witness }); + } + // 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{ { CLIENT_IVC_BENCH_STRUCTURE }, /*auto_verify_mode=*/true }; + + // 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; + auto start = std::chrono::steady_clock::now(); + for (Program& program : folding_stack) { + // Construct a bberg circuit from the acir representation then accumulate it into the IVC + vinfo("constructing circuit..."); + auto circuit = acir_format::create_circuit( + program.constraints, false, 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; + + vinfo("done constructing circuit. calling ivc.accumulate..."); + ivc.accumulate(circuit); + vinfo("done accumulating."); + } + auto end = std::chrono::steady_clock::now(); + auto diff = std::chrono::duration_cast(end - start); + vinfo("time to construct and accumulate all circuits: ", diff.count()); + + vinfo("calling ivc.prove ..."); + ClientIVC::Proof proof = ivc.prove(); + end = std::chrono::steady_clock::now(); + diff = std::chrono::duration_cast(end - start); + vinfo("time to construct, accumulate, prove all circuits: ", diff.count()); + + start = std::chrono::steady_clock::now(); + *out_proof = to_heap_buffer(to_buffer(proof)); + end = std::chrono::steady_clock::now(); + diff = std::chrono::duration_cast(end - start); + vinfo("time to serialize proof: ", diff.count()); + + start = std::chrono::steady_clock::now(); + auto eccvm_vk = std::make_shared(ivc.goblin.get_eccvm_proving_key()); + auto translator_vk = std::make_shared(ivc.goblin.get_translator_proving_key()); + *out_vk = to_heap_buffer(to_buffer(ClientIVC::VerificationKey{ ivc.honk_vk, eccvm_vk, translator_vk })); + end = std::chrono::steady_clock::now(); + diff = std::chrono::duration_cast(end - start); + vinfo("time to serialize vk: ", diff.count()); +} + +WASM_EXPORT void acir_verify_aztec_client(uint8_t const* proof_buf, uint8_t const* vk_buf, bool* result) +{ + + const auto proof = from_buffer(from_buffer>(proof_buf)); + const auto vk = from_buffer(from_buffer>(vk_buf)); + + vk.mega->pcs_verification_key = std::make_shared>(); + vk.eccvm->pcs_verification_key = + std::make_shared>(vk.eccvm->circuit_size + 1); + vk.translator->pcs_verification_key = std::make_shared>(); + + *result = ClientIVC::verify(proof, vk); +} + WASM_EXPORT void acir_prove_ultra_honk(uint8_t const* acir_vec, bool const* recursive, uint8_t const* witness_vec, 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 badca7ba106..64debff164e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -41,8 +41,8 @@ WASM_EXPORT void acir_prove_and_verify_ultra_honk(uint8_t const* constraint_syst bool* result); /** - * @brief Construct and verify a MegaHonk proof - * + * @brief Construct and verify a ClientIVC proof + * @deprecated */ WASM_EXPORT void acir_prove_and_verify_mega_honk(uint8_t const* constraint_system_buf, bool const* recursive, @@ -53,6 +53,13 @@ WASM_EXPORT void acir_prove_and_verify_aztec_client(uint8_t const* constraint_sy uint8_t const* witness_buf, bool* result); +WASM_EXPORT void acir_prove_aztec_client(uint8_t const* acir_stack, + uint8_t const* witness_stack, + uint8_t** out_proof, + uint8_t** out_vk); + +WASM_EXPORT void acir_verify_aztec_client(uint8_t const* proof_buf, uint8_t const* vk_buf, bool* result); + /** * @brief Fold and verify a set of circuits using ClientIvc * diff --git a/barretenberg/ts/src/barretenberg/backend.ts b/barretenberg/ts/src/barretenberg/backend.ts index 2518832f3d3..df541aa7de3 100644 --- a/barretenberg/ts/src/barretenberg/backend.ts +++ b/barretenberg/ts/src/barretenberg/backend.ts @@ -317,6 +317,16 @@ export class AztecClientBackend { } } + async prove(witnessMsgpack: Uint8Array[]): Promise<[Uint8Array, Uint8Array]> { + await this.instantiate(); + return this.api.acirProveAztecClient(this.acirMsgpack, witnessMsgpack); + } + + async verify(proof: Uint8Array, vk: Uint8Array): Promise { + await this.instantiate(); + return this.api.acirVerifyAztecClient(proof, vk); + } + async proveAndVerify(witnessMsgpack: Uint8Array[]): Promise { await this.instantiate(); return this.api.acirProveAndVerifyAztecClient(this.acirMsgpack, witnessMsgpack); diff --git a/barretenberg/ts/src/barretenberg_api/index.ts b/barretenberg/ts/src/barretenberg_api/index.ts index 287336092cf..4bd05354276 100644 --- a/barretenberg/ts/src/barretenberg_api/index.ts +++ b/barretenberg/ts/src/barretenberg_api/index.ts @@ -557,6 +557,30 @@ export class BarretenbergApi { return out[0]; } + async acirProveAztecClient(acirVec: Uint8Array[], witnessVec: Uint8Array[]): Promise<[Uint8Array, Uint8Array]> { + const inArgs = [acirVec, witnessVec].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer(), BufferDeserializer()]; + 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], out[1]]; + } + + async acirVerifyAztecClient(proofBuf: Uint8Array, vkBuf: Uint8Array): Promise { + const inArgs = [proofBuf, vkBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BoolDeserializer()]; + const result = await this.wasm.callWasmExport( + 'acir_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 acirProveUltraHonk(acirVec: Uint8Array, recursive: boolean, witnessVec: Uint8Array): Promise { const inArgs = [acirVec, recursive, witnessVec].map(serializeBufferable); const outTypes: OutputType[] = [BufferDeserializer()]; diff --git a/yarn-project/ivc-integration/src/browser_client_ivc_integration.test.ts b/yarn-project/ivc-integration/src/browser_client_ivc_integration.test.ts index 2c9332bc491..7e6956cd10b 100644 --- a/yarn-project/ivc-integration/src/browser_client_ivc_integration.test.ts +++ b/yarn-project/ivc-integration/src/browser_client_ivc_integration.test.ts @@ -11,7 +11,7 @@ import { import { generate3FunctionTestingIVCStack, generate6FunctionTestingIVCStack, - proveAndVerifyAztecClient, + proveThenVerifyAztecClient, } from './index.js'; /* eslint-disable camelcase */ @@ -49,11 +49,29 @@ function formatAndPrintLog(message: string): void { logger(formattedMessage); } +export async function proveThenVerifyAztecClientBrowser( + page: Page, + bytecodes: string[], + witnessStack: Uint8Array[], +): Promise { + const threads = 16; + + const result: boolean = await page.evaluate( + ([acir, witness, numThreads]) => { + (window as any).proveThenVerifyAztecClient = proveThenVerifyAztecClient; + return (window as any).proveThenVerifyAztecClient(acir, witness, numThreads); + }, + [bytecodes, witnessStack, threads], + ); + + return result; +} + describe('Client IVC Integration', () => { let page: Page; let browser: Browser; - beforeAll(async () => { + beforeEach(async () => { browser = await chromium.launch({ headless: true }); const context = await browser.newContext(); page = await context.newPage(); @@ -61,7 +79,7 @@ describe('Client IVC Integration', () => { await page.goto('http://localhost:8080'); }); - afterAll(async () => { + afterEach(async () => { await browser.close(); }); @@ -72,9 +90,9 @@ describe('Client IVC Integration', () => { it('Should generate a verifiable client IVC proof from a simple mock tx via bb.js', async () => { const [bytecodes, witnessStack] = await generate3FunctionTestingIVCStack(); - logger(`calling prove and verify...`); - const verifyResult = await proveAndVerifyAztecClient(page, bytecodes, witnessStack); - logger(`generated and verified proof. result: ${verifyResult}`); + logger(`calling prove then verify...`); + const verifyResult = await proveThenVerifyAztecClientBrowser(page, bytecodes, witnessStack); + logger(`generated then verified proof. result: ${verifyResult}`); expect(verifyResult).toEqual(true); }); @@ -89,9 +107,9 @@ describe('Client IVC Integration', () => { it('Should generate a verifiable client IVC proof from a simple mock tx via bb.js', async () => { const [bytecodes, witnessStack] = await generate6FunctionTestingIVCStack(); - logger(`calling prove and verify...`); - const verifyResult = await proveAndVerifyAztecClient(page, bytecodes, witnessStack); - logger(`generated and verified proof. result: ${verifyResult}`); + logger(`calling prove then verify...`); + const verifyResult = await proveThenVerifyAztecClientBrowser(page, bytecodes, witnessStack); + logger(`generated then verified proof. result: ${verifyResult}`); expect(verifyResult).toEqual(true); }); diff --git a/yarn-project/ivc-integration/src/index.ts b/yarn-project/ivc-integration/src/index.ts index dab8804a187..d3fc886bf43 100644 --- a/yarn-project/ivc-integration/src/index.ts +++ b/yarn-project/ivc-integration/src/index.ts @@ -3,7 +3,6 @@ import { type CLIENT_IVC_VERIFICATION_KEY_LENGTH_IN_FIELDS } from '@aztec/circui import { type ForeignCallOutput, Noir } from '@noir-lang/noir_js'; import createDebug from 'debug'; import { ungzip } from 'pako'; -import { type Page } from 'playwright'; import MockAppCreatorCircuit from '../artifacts/app_creator.json' assert { type: 'json' }; import MockAppReaderCircuit from '../artifacts/app_reader.json' assert { type: 'json' }; @@ -242,30 +241,19 @@ function base64ToUint8Array(base64: string): Uint8Array { return Uint8Array.from(atob(base64), c => c.charCodeAt(0)); } -export async function proveAndVerifyBrowser(bytecodes: string[], witnessStack: Uint8Array[], threads?: number) { - const { AztecClientBackend } = await import('@aztec/bb.js'); - const preparedBytecodes = bytecodes.map(base64ToUint8Array).map((arr: Uint8Array) => ungzip(arr)); - const backend = new AztecClientBackend(preparedBytecodes, { threads }); - const verified = await backend.proveAndVerify(witnessStack.map((arr: Uint8Array) => ungzip(arr))); - - await backend.destroy(); - return verified; -} - -export async function proveAndVerifyAztecClient( - page: Page, +export async function proveThenVerifyAztecClient( bytecodes: string[], witnessStack: Uint8Array[], + threads?: number, ): Promise { - const threads = 16; - - const result: boolean = await page.evaluate( - ([acir, witness, numThreads]) => { - (window as any).proveAndVerifyBrowser = proveAndVerifyBrowser; - return (window as any).proveAndVerifyBrowser(acir, witness, numThreads); - }, - [bytecodes, witnessStack, threads], + const { AztecClientBackend } = await import('@aztec/bb.js'); + const backend = new AztecClientBackend( + bytecodes.map(base64ToUint8Array).map((arr: Uint8Array) => ungzip(arr)), + { threads }, ); - return result; + const [proof, vk] = await backend.prove(witnessStack.map((arr: Uint8Array) => ungzip(arr))); + const verified = await backend.verify(proof, vk); + await backend.destroy(); + return verified; } diff --git a/yarn-project/ivc-integration/src/serve.ts b/yarn-project/ivc-integration/src/serve.ts index b16a125a05c..be23f105556 100644 --- a/yarn-project/ivc-integration/src/serve.ts +++ b/yarn-project/ivc-integration/src/serve.ts @@ -1,6 +1,6 @@ import createDebug from 'debug'; -import { generate3FunctionTestingIVCStack, proveAndVerifyBrowser } from './index.js'; +import { generate3FunctionTestingIVCStack, proveThenVerifyAztecClient } from './index.js'; createDebug.enable('*'); const logger = createDebug('aztec:ivc-test'); @@ -78,7 +78,7 @@ function setupConsoleOutput() { }; } -(window as any).proveAndVerifyBrowser = proveAndVerifyBrowser; +(window as any).proveThenVerifyAztecClient = proveThenVerifyAztecClient; document.addEventListener('DOMContentLoaded', function () { setupConsoleOutput(); // Initialize console output capture @@ -89,7 +89,7 @@ document.addEventListener('DOMContentLoaded', function () { logger(`generating circuit and witness...`); const [bytecodes, witnessStack] = await generate3FunctionTestingIVCStack(); logger(`done. proving and verifying...`); - const verified = await proveAndVerifyBrowser(bytecodes, witnessStack); + const verified = await proveThenVerifyAztecClient(bytecodes, witnessStack); logger(`verified? ${verified}`); }); document.body.appendChild(button); 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 index 1fbf8d7efcc..f6402e91cb3 100644 --- a/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts +++ b/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts @@ -1,7 +1,6 @@ import { createLogger } from '@aztec/foundation/log'; import { jest } from '@jest/globals'; -import { ungzip } from 'pako'; import { MOCK_MAX_COMMITMENTS_PER_TX, @@ -17,6 +16,7 @@ import { MockPrivateKernelResetVk, MockPrivateKernelTailCircuit, getVkAsFields, + proveThenVerifyAztecClient, witnessGenCreatorAppMockCircuit, witnessGenMockPrivateKernelInitCircuit, witnessGenMockPrivateKernelInnerCircuit, @@ -34,33 +34,6 @@ jest.setTimeout(120_000); describe('Client IVC Integration', () => { beforeEach(async () => {}); - function base64ToUint8Array(base64: string) { - const binaryString = atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; - } - - async function proveAndVerifyAztecClient( - witnessStack: Uint8Array[], - bytecodes: string[], - threads?: number, - ): Promise { - const { AztecClientBackend } = await import('@aztec/bb.js'); - const backend = new AztecClientBackend( - bytecodes.map(base64ToUint8Array).map((arr: Uint8Array) => ungzip(arr)), - { threads }, - ); - - const verified = await backend.proveAndVerify(witnessStack.map((arr: Uint8Array) => ungzip(arr))); - logger.debug(`finished running proveAndVerify ${verified}`); - await backend.destroy(); - return verified; - } - // 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 @@ -96,8 +69,8 @@ describe('Client IVC Integration', () => { 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. result: ${verifyResult}`); + const verifyResult = await proveThenVerifyAztecClient(bytecodes, witnessStack); + logger.debug(`generated then verified proof. result: ${verifyResult}`); expect(verifyResult).toEqual(true); }); @@ -163,8 +136,8 @@ describe('Client IVC Integration', () => { tailWitnessGenResult.witness, ]; - const verifyResult = await proveAndVerifyAztecClient(witnessStack, bytecodes); - logger.debug(`generated and verified proof. result: ${verifyResult}`); + const verifyResult = await proveThenVerifyAztecClient(bytecodes, witnessStack); + logger.debug(`generated then verified proof. result: ${verifyResult}`); expect(verifyResult).toEqual(true); });