diff --git a/barretenberg/acir_tests/Dockerfile.bb.js b/barretenberg/acir_tests/Dockerfile.bb.js index e0838949964..fc4ffe48903 100644 --- a/barretenberg/acir_tests/Dockerfile.bb.js +++ b/barretenberg/acir_tests/Dockerfile.bb.js @@ -15,6 +15,8 @@ COPY . . ENV VERBOSE=1 # Run double_verify_proof through bb.js on node to check 512k support. RUN BIN=../ts/dest/node/main.js FLOW=prove_then_verify ./run_acir_tests.sh double_verify_proof +# RUn a single arbitrary test for separate prove and verify for UltraHonk +RUN BIN=../ts/dest/node/main.js FLOW=prove_then_verify_ultra_honk ./run_acir_tests.sh double_verify_proof # Run a single arbitrary test not involving recursion through bb.js for UltraHonk RUN BIN=../ts/dest/node/main.js FLOW=prove_and_verify_ultra_honk ./run_acir_tests.sh 6_array # Run a single arbitrary test not involving recursion through bb.js for GoblinUltraHonk 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 37797ae8bb5..bed4c92f83b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -204,8 +204,30 @@ WASM_EXPORT void acir_prove_ultra_honk(uint8_t const* acir_vec, uint8_t const* w *out = to_heap_buffer(to_buffer(proof)); } -WASM_EXPORT void acir_verify_proof(uint8_t const* proof_buf, bool* result) +WASM_EXPORT void acir_verify_ultra_honk(uint8_t const* proof_buf, uint8_t const* vk_buf, bool* result) { + using VerificationKey = UltraFlavor::VerificationKey; + using VerifierCommitmentKey = bb::VerifierCommitmentKey; + using Verifier = UltraVerifier_; + auto proof = from_buffer>(proof_buf); - auto verification_key = std::make_shared(from_buffer(read_file(vk_path))); + auto verification_key = std::make_shared(from_buffer(vk_buf)); + verification_key->pcs_verification_key = std::make_shared(); + + Verifier verifier{ verification_key }; + + *result = verifier.verify_proof(proof); } + +WASM_EXPORT void acir_write_vk_ultra_honk(uint8_t const* acir_vec, uint8_t** out) +{ + using ProverInstance = ProverInstance_; + using VerificationKey = UltraFlavor::VerificationKey; + + auto constraint_system = acir_format::circuit_buf_to_acir_format(from_buffer>(acir_vec)); + auto builder = acir_format::create_circuit(constraint_system, 0, {}); + + ProverInstance prover_inst(builder); + VerificationKey vk(prover_inst.proving_key); + *out = to_heap_buffer(to_buffer(vk)); +} \ No newline at end of file diff --git a/barretenberg/ts/src/barretenberg_api/index.ts b/barretenberg/ts/src/barretenberg_api/index.ts index 967fc1b03a2..96c2929bf2c 100644 --- a/barretenberg/ts/src/barretenberg_api/index.ts +++ b/barretenberg/ts/src/barretenberg_api/index.ts @@ -567,6 +567,42 @@ export class BarretenbergApi { const out = result.map((r, i) => outTypes[i].fromBuffer(r)); return out as any; } + + async acirProveUltraHonk(constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array): Promise { + const inArgs = [constraintSystemBuf, witnessBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer()]; + const result = await this.wasm.callWasmExport( + 'acir_prove_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + async acirVerifyUltraHonk(proofBuf: Uint8Array, vkBuf: Uint8Array): Promise { + const inArgs = [proofBuf, vkBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BoolDeserializer()]; + const result = await this.wasm.callWasmExport( + 'acir_verify_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + async acirWriteVkUltraHonk(constraintSystemBuf: Uint8Array): Promise { + const inArgs = [constraintSystemBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer()]; + const result = await this.wasm.callWasmExport( + 'acir_write_vk_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } } export class BarretenbergApiSync { constructor(protected wasm: BarretenbergWasm) {} @@ -1111,4 +1147,40 @@ export class BarretenbergApiSync { const out = result.map((r, i) => outTypes[i].fromBuffer(r)); return out as any; } + + acirUltraHonkProve(constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array): Uint8Array { + const inArgs = [constraintSystemBuf, witnessBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer()]; + const result = this.wasm.callWasmExport( + 'acir_prove_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + acirVerifyUltraHonk(proofBuf: Uint8Array, vkBuf: Uint8Array): boolean { + const inArgs = [proofBuf, vkBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BoolDeserializer()]; + const result = this.wasm.callWasmExport( + 'acir_verify_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } + + acirWriteVkUltraHonk(constraintSystemBuf: Uint8Array): Uint8Array { + const inArgs = [constraintSystemBuf].map(serializeBufferable); + const outTypes: OutputType[] = [BufferDeserializer()]; + const result = this.wasm.callWasmExport( + 'acir_write_vk_ultra_honk', + inArgs, + outTypes.map(t => t.SIZE_IN_BYTES), + ); + const out = result.map((r, i) => outTypes[i].fromBuffer(r)); + return out[0]; + } } diff --git a/barretenberg/ts/src/main.ts b/barretenberg/ts/src/main.ts index 0402c969abd..0c139bddce3 100755 --- a/barretenberg/ts/src/main.ts +++ b/barretenberg/ts/src/main.ts @@ -71,6 +71,32 @@ async function init(bytecodePath: string, crsPath: string, subgroupSizeOverride return { api, acirComposer, circuitSize, subgroupSize }; } +async function initUltraHonk(bytecodePath: string, crsPath: string, subgroupSizeOverride = -1) { + const api = await Barretenberg.new({ threads }); + + const circuitSize = await getGates(bytecodePath, api); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/811): remove subgroupSizeOverride hack for goblin + const subgroupSize = Math.max(subgroupSizeOverride, Math.pow(2, Math.ceil(Math.log2(circuitSize)))); + if (subgroupSize > MAX_CIRCUIT_SIZE) { + throw new Error(`Circuit size of ${subgroupSize} exceeds max supported of ${MAX_CIRCUIT_SIZE}`); + } + + debug(`circuit size: ${circuitSize}`); + debug(`subgroup size: ${subgroupSize}`); + debug('loading crs...'); + // Plus 1 needed! (Move +1 into Crs?) + const crs = await Crs.new(subgroupSize + 1, crsPath); + + // Important to init slab allocator as first thing, to ensure maximum memory efficiency. + await api.commonInitSlabAllocator(subgroupSize); + + // Load CRS into wasm global CRS state. + // TODO: Make RawBuffer be default behavior, and have a specific Vector type for when wanting length prefixed. + await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + + return { api, circuitSize, subgroupSize }; +} + async function initGoblin(bytecodePath: string, crsPath: string) { // TODO(https://github.com/AztecProtocol/barretenberg/issues/811): remove this subgroup size hack const hardcodedGrumpkinSubgroupSizeHack = 262144; @@ -358,10 +384,56 @@ export async function vkAsFields(vkPath: string, vkeyOutputPath: string) { } } -// export async function proveHonk(bytecodePath: string, witnessPath: string, outputPath: string) { +export async function proveUltraHonk(bytecodePath: string, witnessPath: string, crsPath: string, outputPath: string) { + const { api } = await initUltraHonk(bytecodePath, crsPath); + try { + debug(`creating proof...`); + const bytecode = getBytecode(bytecodePath); + const witness = getWitness(witnessPath); + const proof = await api.acirProveUltraHonk(bytecode, witness); + debug(`done.`); + + if (outputPath === '-') { + process.stdout.write(proof); + debug(`proof written to stdout`); + } else { + writeFileSync(outputPath, proof); + debug(`proof written to: ${outputPath}`); + } + } finally { + await api.destroy(); + } +} + +export async function writeVkUltraHonk(bytecodePath: string, crsPath: string, outputPath: string) { + const { api } = await initUltraHonk(bytecodePath, crsPath); + try { + const bytecode = getBytecode(bytecodePath); + debug('initing verification key...'); + const vk = await api.acirWriteVkUltraHonk(bytecode); -// } + if (outputPath === '-') { + process.stdout.write(vk); + debug(`vk written to stdout`); + } else { + writeFileSync(outputPath, vk); + debug(`vk written to: ${outputPath}`); + } + } finally { + await api.destroy(); + } +} +export async function verifyUltraHonk(proofPath: string, vkPath: string) { + const { api, acirComposer } = await initLite(); + try { + const verified = await api.acirVerifyUltraHonk(readFileSync(proofPath), new RawBuffer(readFileSync(vkPath))); + debug(`verified: ${verified}`); + return verified; + } finally { + await api.destroy(); + } +} const program = new Command(); program.option('-v, --verbose', 'enable verbose logging', false); @@ -509,4 +581,36 @@ program acvmInfo(outputPath); }); +program + .command('prove_ultra_honk') + .description('Generate a proof and write it to a file.') + .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acir.gz') + .option('-w, --witness-path ', 'Specify the witness path', './target/witness.gz') + .option('-o, --output-path ', 'Specify the proof output path', './proofs/proof') + .action(async ({ bytecodePath, witnessPath, outputPath, crsPath }) => { + handleGlobalOptions(); + await proveUltraHonk(bytecodePath, witnessPath, crsPath, outputPath); + }); + +program + .command('write_vk_ultra_honk') + .description('Output verification key.') + .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acir.gz') + .requiredOption('-o, --output-path ', 'Specify the path to write the key') + .action(async ({ bytecodePath, outputPath, crsPath }) => { + handleGlobalOptions(); + await writeVkUltraHonk(bytecodePath, crsPath, outputPath); + }); + +program + .command('verify_ultra_honk') + .description('Verify a proof. Process exists with success or failure code.') + .requiredOption('-p, --proof-path ', 'Specify the path to the proof') + .requiredOption('-k, --vk ', 'path to a verification key. avoids recomputation.') + .action(async ({ proofPath, vk }) => { + handleGlobalOptions(); + const result = await verifyUltraHonk(proofPath, vk); + process.exit(result ? 0 : 1); + }); + program.name('bb.js').parse(process.argv);