Skip to content

Commit 2bacc0f

Browse files
author
dbanks12
committed
feat(avm): plumb execution hints from TS to AVM prover
1 parent f4814d3 commit 2bacc0f

14 files changed

Lines changed: 193 additions & 36 deletions

File tree

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -516,27 +516,33 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path)
516516
*
517517
* @param bytecode_path Path to the file containing the serialised bytecode
518518
* @param calldata_path Path to the file containing the serialised calldata (could be empty)
519-
* @param crs_path Path to the file containing the CRS (ignition is suitable for now)
519+
* @param public_inputs_path Path to the file containing the serialised avm public inputs
520+
* @param hints_path Path to the file containing the serialised avm circuit hints
520521
* @param output_path Path (directory) to write the output proof and verification keys
521522
*/
522523
void avm_prove(const std::filesystem::path& bytecode_path,
523524
const std::filesystem::path& calldata_path,
525+
const std::filesystem::path& public_inputs_path,
526+
const std::filesystem::path& hints_path,
524527
const std::filesystem::path& output_path)
525528
{
526529
// Get Bytecode
527-
std::vector<uint8_t> const avm_bytecode =
530+
std::vector<uint8_t> const bytecode =
528531
bytecode_path.extension() == ".gz" ? gunzip(bytecode_path) : read_file(bytecode_path);
529-
std::vector<uint8_t> call_data_bytes{};
530-
if (std::filesystem::exists(calldata_path)) {
531-
call_data_bytes = read_file(calldata_path);
532+
std::vector<fr> const calldata = many_from_buffer<fr>(read_file(calldata_path));
533+
std::vector<fr> const public_inputs_vec = many_from_buffer<fr>(read_file(public_inputs_path));
534+
std::vector<uint8_t> avm_hints;
535+
try {
536+
avm_hints = read_file(hints_path);
537+
} catch (std::runtime_error const& err) {
538+
vinfo("No hints were provided for avm proving.... Might be fine!");
532539
}
533-
std::vector<fr> const call_data = many_from_buffer<fr>(call_data_bytes);
534540

535541
// Hardcoded circuit size for now, with enough to support 16-bit range checks
536542
init_bn254_crs(1 << 17);
537543

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

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

Lines changed: 31 additions & 8 deletions
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

Lines changed: 34 additions & 7 deletions
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

Lines changed: 1 addition & 7 deletions
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

Lines changed: 2 additions & 0 deletions
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

yarn-project/circuits.js/src/structs/avm/avm.ts

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,86 @@
11
import { type Fr } from '@aztec/foundation/fields';
2-
import { serializeToBuffer } from '@aztec/foundation/serialize';
2+
import { serializeArrayOfBufferableToVector, serializeToBuffer } from '@aztec/foundation/serialize';
33
import { type FieldsOf } from '@aztec/foundation/types';
44

5-
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/6774): add public inputs.
5+
import { type PublicCircuitPublicInputs } from '../public_circuit_public_inputs.js';
6+
7+
export class AvmHint {
8+
constructor(public readonly key: Fr, public readonly value: Fr) {}
9+
10+
/**
11+
* Serializes the inputs to a buffer.
12+
* @returns - The inputs serialized to a buffer.
13+
*/
14+
toBuffer() {
15+
return serializeToBuffer(...AvmHint.getFields(this));
16+
}
17+
18+
/**
19+
* Serializes the inputs to a hex string.
20+
* @returns The instance serialized to a hex string.
21+
*/
22+
toString() {
23+
return this.toBuffer().toString('hex');
24+
}
25+
26+
/**
27+
* Extracts fields from an instance.
28+
* @param fields - Fields to create the instance from.
29+
* @returns An array of fields.
30+
*/
31+
static getFields(fields: FieldsOf<AvmHint>) {
32+
return [fields.key, fields.value] as const;
33+
}
34+
}
35+
36+
export class AvmExecutionHints {
37+
constructor(
38+
public readonly storageValues: AvmHint[],
39+
public readonly noteHashExists: AvmHint[],
40+
public readonly nullifierExists: AvmHint[],
41+
public readonly l1ToL2MessageExists: AvmHint[],
42+
) {}
43+
44+
/**
45+
* Serializes the inputs to a buffer.
46+
* @returns - The inputs serialized to a buffer.
47+
*/
48+
toBuffer() {
49+
// Need to use serializeArrayOfBufferableToVector here to include array length in serialization
50+
return serializeToBuffer(
51+
AvmExecutionHints.getFields(this).map(hintArray => serializeArrayOfBufferableToVector(hintArray)),
52+
);
53+
}
54+
55+
/**
56+
* Serializes the inputs to a hex string.
57+
* @returns The instance serialized to a hex string.
58+
*/
59+
toString() {
60+
return this.toBuffer().toString('hex');
61+
}
62+
63+
/**
64+
* Extracts fields from an instance.
65+
* @param fields - Fields to create the instance from.
66+
* @returns An array of fields.
67+
*/
68+
static getFields(fields: FieldsOf<AvmExecutionHints>) {
69+
return [fields.storageValues, fields.noteHashExists, fields.nullifierExists, fields.l1ToL2MessageExists] as const;
70+
}
71+
72+
static empty() {
73+
return new AvmExecutionHints([], [], [], []);
74+
}
75+
}
76+
677
export class AvmCircuitInputs {
7-
constructor(public readonly bytecode: Buffer, public readonly calldata: Fr[]) {}
78+
constructor(
79+
public readonly bytecode: Buffer,
80+
public readonly calldata: Fr[],
81+
public readonly publicInputs: PublicCircuitPublicInputs,
82+
public readonly avmHints: AvmExecutionHints,
83+
) {}
884

985
/**
1086
* Serializes the inputs to a buffer.
@@ -37,6 +113,6 @@ export class AvmCircuitInputs {
37113
* @returns An array of fields.
38114
*/
39115
static getFields(fields: FieldsOf<AvmCircuitInputs>) {
40-
return [fields.bytecode, fields.calldata] as const;
116+
return [fields.bytecode, fields.calldata, fields.publicInputs, fields.avmHints] as const;
41117
}
42118
}

yarn-project/prover-client/src/orchestrator/orchestrator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ export class ProvingOrchestrator {
765765
const inputs: AvmCircuitInputs = new AvmCircuitInputs(
766766
publicFunction.vmRequest!.bytecode,
767767
publicFunction.vmRequest!.calldata,
768+
publicFunction.vmRequest!.kernelRequest.inputs.publicCall.callStackItem.publicInputs,
769+
publicFunction.vmRequest!.avmHints,
768770
);
769771
try {
770772
return await this.prover.getAvmProof(inputs, signal);

yarn-project/simulator/src/avm/journal/trace.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ describe('world state access trace', () => {
8686
leafIndex: leafIndex,
8787
msgHash: utxo,
8888
exists: exists,
89+
counter: new Fr(0),
8990
};
9091
expect(trace.l1ToL2MessageChecks).toEqual([expectedCheck]);
9192
expect(trace.getAccessCounter()).toEqual(1);

yarn-project/simulator/src/avm/journal/trace.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AvmExecutionHints, AvmHint } from '@aztec/circuits.js';
12
import { Fr } from '@aztec/foundation/fields';
23

34
import {
@@ -131,6 +132,7 @@ export class WorldStateAccessTrace {
131132
leafIndex: msgLeafIndex,
132133
msgHash: msgHash,
133134
exists: exists,
135+
counter: new Fr(this.accessCounter),
134136
//endLifetime: Fr.ZERO, // FIXME
135137
};
136138
this.l1ToL2MessageChecks.push(traced);
@@ -169,4 +171,14 @@ export class WorldStateAccessTrace {
169171
// it is assumed that the incoming trace was initialized with this as parent, so accept counter
170172
this.accessCounter = incomingTrace.accessCounter;
171173
}
174+
175+
// TODO(dbanks12): should only return hints for one call.... shouldn't include nested calls (merged in traces)
176+
public toHints(): AvmExecutionHints {
177+
return new AvmExecutionHints(
178+
this.publicStorageReads.map(read => new AvmHint(read.counter, read.value)),
179+
this.noteHashChecks.map(check => new AvmHint(check.counter, new Fr(check.exists ? 1 : 0))),
180+
this.nullifierChecks.map(check => new AvmHint(check.counter, new Fr(check.exists ? 1 : 0))),
181+
this.l1ToL2MessageChecks.map(check => new AvmHint(check.counter, new Fr(check.exists ? 1 : 0))),
182+
);
183+
}
172184
}

yarn-project/simulator/src/avm/journal/trace_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type TracedL1toL2MessageCheck = {
7171
leafIndex: Fr;
7272
msgHash: Fr;
7373
exists: boolean;
74+
counter: Fr;
7475
//endLifetime: Fr;
7576
};
7677

0 commit comments

Comments
 (0)