Skip to content

Commit f3234f1

Browse files
authored
feat(avm): plumb execution hints from TS to AVM prover (#6806)
1 parent 2a3a098 commit f3234f1

File tree

21 files changed

+452
-59
lines changed

21 files changed

+452
-59
lines changed

barretenberg/cpp/src/barretenberg/bb/main.cpp

+19-10
Original file line numberDiff line numberDiff line change
@@ -521,27 +521,33 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path)
521521
*
522522
* @param bytecode_path Path to the file containing the serialised bytecode
523523
* @param calldata_path Path to the file containing the serialised calldata (could be empty)
524-
* @param crs_path Path to the file containing the CRS (ignition is suitable for now)
524+
* @param public_inputs_path Path to the file containing the serialised avm public inputs
525+
* @param hints_path Path to the file containing the serialised avm circuit hints
525526
* @param output_path Path (directory) to write the output proof and verification keys
526527
*/
527528
void avm_prove(const std::filesystem::path& bytecode_path,
528529
const std::filesystem::path& calldata_path,
530+
const std::filesystem::path& public_inputs_path,
531+
const std::filesystem::path& hints_path,
529532
const std::filesystem::path& output_path)
530533
{
531534
// Get Bytecode
532-
std::vector<uint8_t> const avm_bytecode =
535+
std::vector<uint8_t> const bytecode =
533536
bytecode_path.extension() == ".gz" ? gunzip(bytecode_path) : read_file(bytecode_path);
534-
std::vector<uint8_t> call_data_bytes{};
535-
if (std::filesystem::exists(calldata_path)) {
536-
call_data_bytes = read_file(calldata_path);
537+
std::vector<fr> const calldata = many_from_buffer<fr>(read_file(calldata_path));
538+
std::vector<fr> const public_inputs_vec = many_from_buffer<fr>(read_file(public_inputs_path));
539+
std::vector<uint8_t> avm_hints;
540+
try {
541+
avm_hints = read_file(hints_path);
542+
} catch (std::runtime_error const& err) {
543+
vinfo("No hints were provided for avm proving.... Might be fine!");
537544
}
538-
std::vector<fr> const call_data = many_from_buffer<fr>(call_data_bytes);
539545

540546
// Hardcoded circuit size for now, with enough to support 16-bit range checks
541547
init_bn254_crs(1 << 17);
542548

543549
// Prove execution and return vk
544-
auto const [verification_key, proof] = avm_trace::Execution::prove(avm_bytecode, call_data);
550+
auto const [verification_key, proof] = avm_trace::Execution::prove(bytecode, calldata, public_inputs_vec);
545551
// TODO(ilyas): <#4887>: Currently we only need these two parts of the vk, look into pcs_verification key reqs
546552
std::vector<uint64_t> vk_vector = { verification_key.circuit_size, verification_key.num_public_inputs };
547553
std::vector<fr> vk_as_fields = { verification_key.circuit_size, verification_key.num_public_inputs };
@@ -890,11 +896,14 @@ int main(int argc, char* argv[])
890896
std::string output_path = get_option(args, "-o", vk_path + "_fields.json");
891897
vk_as_fields(vk_path, output_path);
892898
} else if (command == "avm_prove") {
893-
std::filesystem::path avm_bytecode_path = get_option(args, "-b", "./target/avm_bytecode.bin");
894-
std::filesystem::path calldata_path = get_option(args, "-d", "./target/call_data.bin");
899+
std::filesystem::path avm_bytecode_path = get_option(args, "--avm-bytecode", "./target/avm_bytecode.bin");
900+
std::filesystem::path avm_calldata_path = get_option(args, "--avm-calldata", "./target/avm_calldata.bin");
901+
std::filesystem::path avm_public_inputs_path =
902+
get_option(args, "--avm-public-inputs", "./target/avm_public_inputs.bin");
903+
std::filesystem::path avm_hints_path = get_option(args, "--avm-hints", "./target/avm_hints.bin");
895904
// This outputs both files: proof and vk, under the given directory.
896905
std::filesystem::path output_path = get_option(args, "-o", "./proofs");
897-
avm_prove(avm_bytecode_path, calldata_path, output_path);
906+
avm_prove(avm_bytecode_path, avm_calldata_path, avm_public_inputs_path, avm_hints_path, output_path);
898907
} else if (command == "avm_verify") {
899908
return avm_verify(proof_path, vk_path) ? 0 : 1;
900909
} else if (command == "prove_ultra_honk") {

barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp

+23-12
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ namespace bb::avm_trace {
3232
std::vector<FF> Execution::getDefaultPublicInputs()
3333
{
3434
std::vector<FF> public_inputs_vec(PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH);
35-
public_inputs_vec.at(DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = 1000000000;
36-
public_inputs_vec.at(L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = 1000000000;
35+
public_inputs_vec.at(DA_START_GAS_LEFT_PCPI_OFFSET) = 1000000000;
36+
public_inputs_vec.at(L2_START_GAS_LEFT_PCPI_OFFSET) = 1000000000;
3737
return public_inputs_vec;
3838
}
3939

@@ -51,19 +51,25 @@ std::tuple<AvmFlavor::VerificationKey, HonkProof> Execution::prove(std::vector<u
5151
std::vector<FF> const& public_inputs_vec,
5252
ExecutionHints const& execution_hints)
5353
{
54-
// TODO: temp
55-
info("logging to silence warning for now: ", public_inputs_vec.size());
54+
if (public_inputs_vec.size() != PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH) {
55+
throw_or_abort("Public inputs vector is not of PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH");
56+
}
5657

5758
auto instructions = Deserialization::parse(bytecode);
5859
std::vector<FF> returndata{};
59-
auto trace = gen_trace(instructions, returndata, calldata, getDefaultPublicInputs(), execution_hints);
60+
auto trace = gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints);
6061
auto circuit_builder = bb::AvmCircuitBuilder();
6162
circuit_builder.set_trace(std::move(trace));
6263

6364
auto composer = AvmComposer();
6465
auto prover = composer.create_prover(circuit_builder);
6566
auto verifier = composer.create_verifier(circuit_builder);
66-
auto proof = prover.construct_proof();
67+
68+
// The proof starts with the serialized public inputs
69+
HonkProof proof(public_inputs_vec);
70+
auto raw_proof = prover.construct_proof();
71+
// append the raw proof after the public inputs
72+
proof.insert(proof.end(), raw_proof.begin(), raw_proof.end());
6773
// TODO(#4887): Might need to return PCS vk when full verify is supported
6874
return std::make_tuple(*verifier.key, proof);
6975
}
@@ -130,8 +136,8 @@ VmPublicInputs Execution::convert_public_inputs(std::vector<FF> const& public_in
130136
// Transaction fee
131137
kernel_inputs[TRANSACTION_FEE_SELECTOR] = public_inputs_vec[TRANSACTION_FEE_OFFSET];
132138

133-
kernel_inputs[DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET] = public_inputs_vec[DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET];
134-
kernel_inputs[L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET] = public_inputs_vec[L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET];
139+
kernel_inputs[DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET] = public_inputs_vec[DA_START_GAS_LEFT_PCPI_OFFSET];
140+
kernel_inputs[L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET] = public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET];
135141

136142
return public_inputs;
137143
}
@@ -146,10 +152,15 @@ bool Execution::verify(AvmFlavor::VerificationKey vk, HonkProof const& proof)
146152
// crs_factory_);
147153
// output_state.pcs_verification_key = std::move(pcs_verification_key);
148154

149-
// TODO: We hardcode public inputs for now
150-
VmPublicInputs public_inputs = convert_public_inputs(getDefaultPublicInputs());
151-
std::vector<std::vector<FF>> public_inputs_vec = copy_public_inputs_columns(public_inputs);
152-
return verifier.verify_proof(proof, public_inputs_vec);
155+
std::vector<FF> public_inputs_vec;
156+
std::vector<FF> raw_proof;
157+
std::copy(
158+
proof.begin(), proof.begin() + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, std::back_inserter(public_inputs_vec));
159+
std::copy(proof.begin() + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, proof.end(), std::back_inserter(raw_proof));
160+
161+
VmPublicInputs public_inputs = convert_public_inputs(public_inputs_vec);
162+
std::vector<std::vector<FF>> public_inputs_columns = copy_public_inputs_columns(public_inputs);
163+
return verifier.verify_proof(raw_proof, public_inputs_columns);
153164
}
154165

155166
/**

barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.hpp

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ class Execution {
3333
std::vector<FF> const& public_inputs,
3434
ExecutionHints const& execution_hints);
3535

36-
static std::tuple<AvmFlavor::VerificationKey, bb::HonkProof> prove(std::vector<uint8_t> const& bytecode,
37-
std::vector<FF> const& calldata = {},
38-
std::vector<FF> const& public_inputs_vec = {},
39-
ExecutionHints const& execution_hints = {});
36+
static std::tuple<AvmFlavor::VerificationKey, bb::HonkProof> prove(
37+
std::vector<uint8_t> const& bytecode,
38+
std::vector<FF> const& calldata = {},
39+
std::vector<FF> const& public_inputs_vec = getDefaultPublicInputs(),
40+
ExecutionHints const& execution_hints = {});
4041
static bool verify(AvmFlavor::VerificationKey vk, HonkProof const& proof);
4142
};
4243

barretenberg/cpp/src/barretenberg/vm/avm_trace/constants.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ inline const uint32_t FEE_PER_DA_GAS_OFFSET = PCPI_GLOBALS_START + 6;
3131
inline const uint32_t FEE_PER_L2_GAS_OFFSET = PCPI_GLOBALS_START + 7;
3232

3333
inline const uint32_t TRANSACTION_FEE_OFFSET = PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH - 1;
34-
inline const uint32_t DA_GAS_LEFT_PCPI_OFFSET = PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH - 3 - GAS_LENGTH;
35-
inline const uint32_t L2_GAS_LEFT_PCPI_OFFSET = PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH - 2 - GAS_LENGTH;
34+
inline const uint32_t DA_START_GAS_LEFT_PCPI_OFFSET = PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH - 3 - GAS_LENGTH;
35+
inline const uint32_t L2_START_GAS_LEFT_PCPI_OFFSET = PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH - 2 - GAS_LENGTH;
3636

3737
// Kernel output pil offset (Where update objects are inlined)
3838

barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ class AvmExecutionTests : public ::testing::Test {
3333
void SetUp() override
3434
{
3535
srs::init_crs_factory("../srs_db/ignition");
36-
public_inputs_vec.at(DA_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = DEFAULT_INITIAL_DA_GAS;
37-
public_inputs_vec.at(L2_GAS_LEFT_CONTEXT_INPUTS_OFFSET) = DEFAULT_INITIAL_L2_GAS;
36+
public_inputs_vec.at(DA_START_GAS_LEFT_PCPI_OFFSET) = DEFAULT_INITIAL_DA_GAS;
37+
public_inputs_vec.at(L2_START_GAS_LEFT_PCPI_OFFSET) = DEFAULT_INITIAL_L2_GAS;
3838
};
3939

4040
/**

yarn-project/bb-prover/src/avm_proving.test.ts

+31-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AvmCircuitInputs, Gas, PublicCircuitPublicInputs } from '@aztec/circuits.js';
12
import { Fr } from '@aztec/foundation/fields';
23
import { createDebugLogger } from '@aztec/foundation/log';
34
import { AvmSimulator } from '@aztec/simulator';
@@ -7,6 +8,10 @@ import fs from 'node:fs/promises';
78
import { tmpdir } from 'node:os';
89
import path from 'path';
910

11+
import {
12+
convertAvmResultsToPxResult,
13+
createPublicExecution,
14+
} from '../../simulator/src/public/transitional_adaptors.js';
1015
import { type BBSuccess, BB_RESULT, generateAvmProof, verifyAvmProof } from './bb/execute.js';
1116
import { extractVkData } from './verification_key/verification_key_data.js';
1217

@@ -16,8 +21,14 @@ describe('AVM WitGen, proof generation and verification', () => {
1621
it(
1722
'Should prove valid execution contract function that performs addition',
1823
async () => {
24+
const startSideEffectCounter = 0;
1925
const calldata: Fr[] = [new Fr(1), new Fr(2)];
20-
const context = initContext({ env: initExecutionEnvironment({ calldata }) });
26+
const environment = initExecutionEnvironment({ calldata });
27+
const context = initContext({ env: environment });
28+
29+
const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas);
30+
const oldPublicExecution = createPublicExecution(startSideEffectCounter, environment, calldata);
31+
2132
const internalLogger = createDebugLogger('aztec:avm-proving-test');
2233
const logger = (msg: string, _data?: any) => internalLogger.verbose(msg);
2334
const bytecode = getAvmTestContractBytecode('add_args_return');
@@ -27,18 +38,30 @@ describe('AVM WitGen, proof generation and verification', () => {
2738

2839
// First we simulate (though it's not needed in this simple case).
2940
const simulator = new AvmSimulator(context);
30-
const results = await simulator.executeBytecode(bytecode);
31-
expect(results.reverted).toBe(false);
41+
const avmResult = await simulator.executeBytecode(bytecode);
42+
expect(avmResult.reverted).toBe(false);
3243

33-
// Then we prove.
44+
const pxResult = convertAvmResultsToPxResult(
45+
avmResult,
46+
startSideEffectCounter,
47+
oldPublicExecution,
48+
startGas,
49+
context,
50+
simulator.getBytecode(),
51+
);
52+
// TODO(dbanks12): public inputs should not be empty.... Need to construct them from AvmContext?
3453
const uncompressedBytecode = simulator.getBytecode()!;
35-
const proofRes = await generateAvmProof(
36-
bbPath,
37-
bbWorkingDirectory,
54+
const publicInputs = PublicCircuitPublicInputs.empty();
55+
publicInputs.startGasLeft = startGas;
56+
const avmCircuitInputs = new AvmCircuitInputs(
3857
uncompressedBytecode,
3958
context.environment.calldata,
40-
logger,
59+
publicInputs,
60+
pxResult.avmHints,
4161
);
62+
63+
// Then we prove.
64+
const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger);
4265
expect(proofRes.status).toEqual(BB_RESULT.SUCCESS);
4366

4467
// Then we test VK extraction.

yarn-project/bb-prover/src/bb/execute.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Fr } from '@aztec/circuits.js';
1+
import { type AvmCircuitInputs } from '@aztec/circuits.js';
22
import { sha256 } from '@aztec/foundation/crypto';
33
import { type LogFn } from '@aztec/foundation/log';
44
import { Timer } from '@aztec/foundation/timer';
@@ -265,8 +265,7 @@ export async function generateProof(
265265
export async function generateAvmProof(
266266
pathToBB: string,
267267
workingDirectory: string,
268-
bytecode: Buffer,
269-
calldata: Fr[],
268+
input: AvmCircuitInputs,
270269
log: LogFn,
271270
): Promise<BBFailure | BBSuccess> {
272271
// Check that the working directory exists
@@ -277,8 +276,10 @@ export async function generateAvmProof(
277276
}
278277

279278
// Paths for the inputs
280-
const calldataPath = join(workingDirectory, 'calldata.bin');
281279
const bytecodePath = join(workingDirectory, 'avm_bytecode.bin');
280+
const calldataPath = join(workingDirectory, 'avm_calldata.bin');
281+
const publicInputsPath = join(workingDirectory, 'avm_public_inputs.bin');
282+
const avmHintsPath = join(workingDirectory, 'avm_hints.bin');
282283

283284
// The proof is written to e.g. /workingDirectory/proof
284285
const outputPath = workingDirectory;
@@ -296,19 +297,45 @@ export async function generateAvmProof(
296297

297298
try {
298299
// Write the inputs to the working directory.
299-
await fs.writeFile(bytecodePath, bytecode);
300+
await fs.writeFile(bytecodePath, input.bytecode);
300301
if (!filePresent(bytecodePath)) {
301302
return { status: BB_RESULT.FAILURE, reason: `Could not write bytecode at ${bytecodePath}` };
302303
}
303304
await fs.writeFile(
304305
calldataPath,
305-
calldata.map(fr => fr.toBuffer()),
306+
input.calldata.map(fr => fr.toBuffer()),
306307
);
307308
if (!filePresent(calldataPath)) {
308309
return { status: BB_RESULT.FAILURE, reason: `Could not write calldata at ${calldataPath}` };
309310
}
310311

311-
const args = ['-b', bytecodePath, '-d', calldataPath, '-o', outputPath];
312+
// public inputs are used directly as a vector of fields in C++,
313+
// so we serialize them as such here instead of just using toBuffer
314+
await fs.writeFile(
315+
publicInputsPath,
316+
input.publicInputs.toFields().map(fr => fr.toBuffer()),
317+
);
318+
if (!filePresent(publicInputsPath)) {
319+
return { status: BB_RESULT.FAILURE, reason: `Could not write publicInputs at ${publicInputsPath}` };
320+
}
321+
322+
await fs.writeFile(avmHintsPath, input.avmHints.toBuffer());
323+
if (!filePresent(avmHintsPath)) {
324+
return { status: BB_RESULT.FAILURE, reason: `Could not write avmHints at ${avmHintsPath}` };
325+
}
326+
327+
const args = [
328+
'--avm-bytecode',
329+
bytecodePath,
330+
'--avm-calldata',
331+
calldataPath,
332+
'--avm-public-inputs',
333+
publicInputsPath,
334+
'--avm-hints',
335+
avmHintsPath,
336+
'-o',
337+
outputPath,
338+
];
312339
const timer = new Timer();
313340
const logFunction = (message: string) => {
314341
log(`AvmCircuit (prove) BB out - ${message}`);

yarn-project/bb-prover/src/prover/bb_prover.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -465,13 +465,7 @@ export class BBNativeRollupProver implements ServerCircuitProver {
465465
private async generateAvmProofWithBB(input: AvmCircuitInputs, workingDirectory: string): Promise<BBSuccess> {
466466
logger.debug(`Proving avm-circuit...`);
467467

468-
const provingResult = await generateAvmProof(
469-
this.config.bbBinaryPath,
470-
workingDirectory,
471-
input.bytecode,
472-
input.calldata,
473-
logger.debug,
474-
);
468+
const provingResult = await generateAvmProof(this.config.bbBinaryPath, workingDirectory, input, logger.debug);
475469

476470
if (provingResult.status === BB_RESULT.FAILURE) {
477471
logger.error(`Failed to generate proof for avm-circuit: ${provingResult.reason}`);

yarn-project/circuit-types/src/tx/processed_tx.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
UnencryptedTxL2Logs,
1111
} from '@aztec/circuit-types';
1212
import {
13+
type AvmExecutionHints,
1314
Fr,
1415
type Gas,
1516
type GasFees,
@@ -55,6 +56,7 @@ export type AvmProvingRequest = {
5556
type: typeof AVM_REQUEST;
5657
bytecode: Buffer;
5758
calldata: Fr[];
59+
avmHints: AvmExecutionHints;
5860
kernelRequest: PublicKernelNonTailRequest;
5961
};
6062

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { randomInt } from '@aztec/foundation/crypto';
2+
3+
import { makeAvmCircuitInputs, makeAvmExecutionHints, makeAvmHint } from '../../tests/factories.js';
4+
import { AvmCircuitInputs, AvmExecutionHints, AvmHint } from './avm.js';
5+
6+
describe('Avm circuit inputs', () => {
7+
describe('AvmHint', () => {
8+
let avmHint: AvmHint;
9+
10+
beforeAll(() => {
11+
avmHint = makeAvmHint(randomInt(1000));
12+
});
13+
14+
it(`serializes to buffer and deserializes it back`, () => {
15+
const buffer = avmHint.toBuffer();
16+
const res = AvmHint.fromBuffer(buffer);
17+
expect(res).toEqual(avmHint);
18+
expect(res.isEmpty()).toBe(false);
19+
});
20+
});
21+
describe('AvmExecutionHints', () => {
22+
let avmExecutionHints: AvmExecutionHints;
23+
24+
beforeAll(() => {
25+
avmExecutionHints = makeAvmExecutionHints(randomInt(1000));
26+
});
27+
28+
it(`serializes to buffer and deserializes it back`, () => {
29+
const buffer = avmExecutionHints.toBuffer();
30+
const res = AvmExecutionHints.fromBuffer(buffer);
31+
expect(res).toEqual(avmExecutionHints);
32+
expect(res.isEmpty()).toBe(false);
33+
});
34+
});
35+
describe('AvmCircuitInputs', () => {
36+
let avmCircuitInputs: AvmCircuitInputs;
37+
38+
beforeAll(() => {
39+
avmCircuitInputs = makeAvmCircuitInputs(randomInt(2000));
40+
});
41+
42+
it(`serializes to buffer and deserializes it back`, () => {
43+
const buffer = avmCircuitInputs.toBuffer();
44+
const res = AvmCircuitInputs.fromBuffer(buffer);
45+
expect(res).toEqual(avmCircuitInputs);
46+
expect(res.isEmpty()).toBe(false);
47+
});
48+
});
49+
});

0 commit comments

Comments
 (0)