Skip to content

Commit f5e77a9

Browse files
ledwards2225federicobarbacoviAztecBot
authored
feat: multi-app kernel circuits (#23076)
Integrates init/inner kernels that process up to 3 apps into the PXE --------- Co-authored-by: federicobarbacovi <171914500+federicobarbacovi@users.noreply.github.com> Co-authored-by: AztecBot <tech@aztec-labs.com>
1 parent 809cf8a commit f5e77a9

55 files changed

Lines changed: 1812 additions & 305 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

INIT_3_HANDOFF.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Handoff: `private_kernel_init_3` is wired end-to-end
2+
3+
`private_kernel_init_3` — the batched first-iteration kernel that verifies three app
4+
calls in a single circuit — is now plumbed end-to-end behind a `PXE_USE_INIT_3`
5+
flag. The new path captures, proves, and verifies on real client flows, with
6+
measured savings in the expected shape (saving 2 prev-kernel HN verifications +
7+
2 Oink proves per tx).
8+
9+
This branch is `lde/integrate-init-3`, based on `merge-train/barretenberg`.
10+
11+
## What's done — 7 commits
12+
13+
```
14+
69362fbe test(end-to-end): reproducer for the PXE_USE_INIT_3 canary
15+
397224be feat(pxe): orchestrator init_3 path behind PXE_USE_INIT_3 flag
16+
43f203c1 feat(bb-prover): implement generateInit3Output / simulateInit3
17+
3bcad728 feat(noir-protocol-circuits-types): wire private_kernel_init_3 artifact and VK
18+
7de74078 feat(stdlib): add PrivateKernelInit3CircuitPrivateInputs and prover interface methods
19+
0dc31e27 docs(barretenberg/cpp): note that find-bb defaults to bb-avm
20+
94b52e31 feat(protocol-circuits): allocate PRIVATE_KERNEL_INIT_3_VK_INDEX and accept it as a previous kernel
21+
```
22+
23+
Layered top-down, each commit corresponds to one layer of the stack:
24+
25+
| Commit | Layer | What changed |
26+
|---|---|---|
27+
| `94b52e31` | Noir protocol circuits | `PRIVATE_KERNEL_INIT_3_VK_INDEX = 65`, extends `ALLOWED_PREVIOUS_CIRCUITS` for inner / reset / tail / tail_to_public |
28+
| `7de74078` | TS stdlib | New `PrivateKernelInit3CircuitPrivateInputs`, new `generateInit3Output` / `simulateInit3` on `PrivateKernelProver` |
29+
| `3bcad728` | TS noir-protocol-circuits-types | New `PrivateKernelInit3Artifact` in the union, bundle, vks/client, vks/server. Simulated lookup reuses the constrained artifact (no `private-kernel-init-3-simulated` crate exists) |
30+
| `43f203c1` | TS bb-prover | New witness-map converters + `BBPrivateKernelProver` methods |
31+
| `397224be` | TS PXE orchestrator | `useInit3 = process.env.PXE_USE_INIT_3 === '1' && totalAppCalls >= 3`. Falls back to standard init when fewer than 3 apps. Refactored top-of-loop into `consumeNextApp` helper |
32+
| `0dc31e27` | Docs | `find-bb` defaults to `bb-avm` — flagged because we lost a session debugging the wrong binary |
33+
| `69362fbe` | Reproducer | `yarn-project/end-to-end/scripts/reproduce_init3_canary.sh`, plus a `SKIP_STEP_COUNT_CHECK=1` opt-out in `captureProfile` |
34+
35+
## Verify it works
36+
37+
After `./bootstrap.sh` from the repo root, run the reproducer:
38+
39+
```bash
40+
yarn-project/end-to-end/scripts/reproduce_init3_canary.sh
41+
```
42+
43+
This runs the `deploy_ecdsar1+sponsored_fpc` client flow with `PXE_USE_INIT_3=1`,
44+
captures the IVC inputs, proves with `bb prove --scheme chonk`, and verifies the
45+
proof. Asserts the capture references `PrivateKernelInit3Artifact` so a
46+
regression in flag plumbing is caught at capture time. Total runtime ~2–3 min.
47+
48+
Final output on success:
49+
50+
```
51+
init_3 canary verified end-to-end.
52+
Captured inputs: /tmp/init3-canary/captures/deploy_ecdsar1+sponsored_fpc/ivc-inputs.msgpack
53+
Proof artifacts: /tmp/init3-canary/proof/
54+
```
55+
56+
To verify a different flow, set the right test name + flow path:
57+
58+
```bash
59+
# transfer_0 (3 apps total — init_3 absorbs all of them)
60+
PXE_USE_INIT_3=1 \
61+
CAPTURE_IVC_FOLDER=/tmp/init3-out \
62+
SKIP_STEP_COUNT_CHECK=1 \
63+
BENCHMARK_CONFIG=key_flows \
64+
LOG_LEVEL=warn \
65+
node --experimental-vm-modules yarn-project/node_modules/.bin/jest \
66+
--testTimeout=300000 --no-cache --runInBand \
67+
--testNamePattern "ecdsar1.*0 recursions.*sponsored_fpc" \
68+
--rootDir yarn-project/end-to-end/src \
69+
client_flows/transfers
70+
# then bb prove + bb verify against /tmp/init3-out/<flow>/ivc-inputs.msgpack
71+
```
72+
73+
## Measured benefit (single run, remote bench machine, HARDWARE_CONCURRENCY=16)
74+
75+
| Flow | Apps | Baseline | init_3 | Δ |
76+
|---|---:|---:|---:|---:|
77+
| `deploy_ecdsar1+sponsored_fpc` | 6 | 6.29 s | 5.99 s | **-0.30 s (-4.8%)** |
78+
| `ecdsar1+transfer_0_recursions+sponsored_fpc` | 3 | 5.09 s | 4.65 s | **-0.44 s (-8.6%)** |
79+
80+
Savings come exactly where the design predicted: -2 `HypernovaFoldingProver::fold`
81+
calls, -2 `OinkProver::prove`, -2 `Chonk::complete_kernel_circuit_logic`. ECCVM
82+
shrinks ~23% in trace rows but only ~7% in time (fixed sumcheck overhead).
83+
Translator stays constant at 8192 rows by design (proof is constant-size).
84+
85+
## What was deferred and why
86+
87+
**Phase 6 (audit one-app-per-kernel assumptions) — folded into Phase 7.** The
88+
canary proving + verifying real client flows on real bb is a stronger integration
89+
gate than a code audit. If a one-app assumption had been violated, prove would
90+
have crashed.
91+
92+
**Phase 8 (VK pin update) — not needed yet.** The pin script
93+
(`barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh`) compares
94+
VKs derived from *pinned bytecode* (frozen msgpack from S3) against pinned VKs in
95+
the same msgpack. It catches C++-side bb regressions, not Noir-side circuit
96+
changes. Our changes are Noir-side, so the pin test still passes on this branch
97+
without intervention. The pin will need updating when init_3 stops being
98+
flag-gated and becomes the default — at that point newly captured pins will
99+
include init_3 steps, and the reference msgpack on S3 should be rotated.
100+
101+
## Follow-up opportunities
102+
103+
In rough order of payoff:
104+
105+
1. **`inner_3`** — the next obvious win. The deployment flow has 6 apps; init_3
106+
collapses the first 3 but the remaining 3 still go through 3 inner kernels.
107+
An inner that batches 3 apps would collapse those, saving another ~2 prev-kernel
108+
HN verifications. Same shape as init_3 but for the middle of the chain.
109+
110+
2. **Skip inner kernels entirely when init_3 absorbs everything.** Marked with
111+
`WORKTODO(luke)` at the bottom of the init_3 branch in
112+
`private_kernel_execution_prover.ts`. If `executionStack.length === 0` after
113+
init_3, the chain can go directly to reset+tail. The protocol-circuit
114+
ALLOWED_PREVIOUS lists already accept init_3 as a predecessor of
115+
reset/tail/tail_to_public, so this is purely an orchestrator change.
116+
Relevant for short flows like transfer_0.
117+
118+
3. **`init_2` / `inner_2` / `init_1` / `inner_1`.** Round out the variant set so
119+
the orchestrator can pick the largest batch that fits. Today the dispatch is
120+
binary (init_3 if ≥3 apps, else init); a graceful fallback through init_2
121+
would help any flow with exactly 2 apps. Probably low priority — most real
122+
flows have 3+.
123+
124+
4. **Generic `PrivateKernelInitNCircuitPrivateInputs<N>`.** The TS class is
125+
currently concrete (three explicit `privateCallN` fields). When a second
126+
variant lands, generalize.
127+
128+
5. **Remove the `PXE_USE_INIT_3` flag.** Once a few weeks of green CI confirm
129+
stability, the flag and its `totalAppCalls >= 3` gate can collapse into
130+
unconditional use. Coordinate with Phase 8 (pin update) at that point.
131+
132+
## Known papercuts
133+
134+
- **`find-bb` defaults to `bb-avm`** (`AVM=1`). Rebuilding only the non-AVM `bb`
135+
target leaves a stale `bb-avm` and downstream bootstraps keep failing with the
136+
same error after the supposed fix. This is now documented in
137+
`barretenberg/cpp/CLAUDE.md`.
138+
- **`SKIP_STEP_COUNT_CHECK=1`** is required when running client_flow tests with
139+
`PXE_USE_INIT_3=1` because the `captureProfile` `expectedSteps` assertion is
140+
hard-coded for the standard init+inner chain. The reproducer script sets it;
141+
if you write new test paths that exercise init_3, set it explicitly.
142+
- **Pre-existing stale gitignored derivatives** in
143+
`aztec.js/src/contract/protocol_contracts/` and `noir-contracts.js/src/` may
144+
exist on a tree that hasn't been bootstrapped recently. Symptom: `aztecVersion
145+
is missing in type ContractArtifact` build errors. Fix: run `./bootstrap.sh`
146+
from the repo root.
147+
148+
## Out of scope of this work
149+
150+
- Any change to the C++ Chonk recursive-verifier internals. The existing
151+
`bool is_init_kernel = front().type == OINK` (`chonk.cpp:314`) and the rest
152+
of `complete_kernel_circuit_logic` already correctly handle a 3-entry queue
153+
`[OINK, HN, HN]` — confirmed by the canary running real proofs. No bb code
154+
changes were needed.
155+
- Changes to reset / tail config dimensions. init_3 produces the same
156+
`PrivateKernelCircuitPublicInputs` shape as init, so downstream kernels see
157+
no new shape.
158+
159+
## Files of interest
160+
161+
- `noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_init_n.nr`
162+
— the lib that init_3's binary instantiates with N=3.
163+
- `noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/src/main.nr`
164+
— the concrete crate.
165+
- `yarn-project/pxe/src/private_kernel/private_kernel_execution_prover.ts`
166+
— the orchestrator. The init_3 dispatch is in the `firstIteration` branch.
167+
- `yarn-project/end-to-end/scripts/reproduce_init3_canary.sh` — the reproducer.

barretenberg/cpp/CLAUDE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ Bootstrap modes:
66
- `./bootstrap.sh build` => standard build
77
- `AVM=0 ./bootstrap.sh build_native` => quick build without slow bb-avm target. Good for verifying compilation works. Needed to build ts/
88

9+
## `bb` vs `bb-avm`: which binary do downstream scripts pick?
10+
11+
`barretenberg/cpp/scripts/find-bb` returns `bb-avm` by default (when `AVM` is unset or `AVM=1`) and `bb` only when `AVM=0`. `noir-projects/noir-protocol-circuits/bootstrap.sh` and most other downstream tooling go through `find-bb`, so when those scripts run "the bb binary", they are running `bb-avm`.
12+
13+
Consequence: when changing C++ that affects VK derivation, proving, or anything else exercised by downstream bootstrap scripts, `cmake --build build --target bb` is **not enough**`bb` is non-AVM and will not be picked up. You must rebuild the AVM-enabled binary:
14+
15+
```bash
16+
cd barretenberg/cpp
17+
cmake --preset default -DAVM=ON
18+
cmake --build build --target bb-avm
19+
```
20+
21+
Or just run `./bootstrap.sh` (full build), which produces both. Symptom of forgetting: downstream scripts keep failing with the *same* error after your "fix" because they are still running the stale `bb-avm`.
22+
923
Development commands (from barretenberg/cpp):
1024
```bash
1125
cmake --preset default # Configure (AVM disabled by default)

barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ script_path="$root/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_cha
2121
# - Generate a hash for versioning: sha256sum bb-chonk-inputs.tar.gz
2222
# - Upload the compressed results: aws s3 cp bb-chonk-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-chonk-inputs-[hash(0:8)].tar.gz
2323
# Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0
24-
pinned_short_hash="20c388cc"
24+
pinned_short_hash="aafbeabe"
2525
pinned_chonk_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-chonk-inputs-${pinned_short_hash}.tar.gz"
2626

2727
function update_pinned_hash_in_script {

barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class ChonkTests : public ::testing::Test {
113113

114114
/**
115115
* @brief Helper function to test tampering with AppIO pairing inputs
116-
* @details Accumulates circuits, doubles the app pairing points (creating valid but different points),
116+
* @details Accumulates circuits, changes the app pairing points (creating valid but different points),
117117
* and verifies that the final Chonk proof fails verification.
118118
*/
119119
static void test_app_io_tampering()
@@ -122,9 +122,7 @@ class ChonkTests : public ::testing::Test {
122122

123123
TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
124124
auto [proof, vk] = run_ivc(/*num_app_circuits=*/2, settings, [](Chonk& ivc, size_t idx) {
125-
if (idx == 2) {
126-
EXPECT_EQ(ivc.verification_queue.size(), 2);
127-
125+
if (idx == 1) {
128126
auto& app_entry = ivc.verification_queue[1];
129127
ASSERT_FALSE(app_entry.is_kernel) << "Expected second queue entry to be an app";
130128

@@ -154,10 +152,8 @@ class ChonkTests : public ::testing::Test {
154152
BB_DISABLE_ASSERTS();
155153

156154
TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
157-
auto [proof, vk] = run_ivc(/*num_app_circuits=*/2, settings, [field_to_tamper](Chonk& ivc, size_t idx) {
158-
if (idx == 2) {
159-
EXPECT_EQ(ivc.verification_queue.size(), 2);
160-
155+
auto [proof, vk] = run_ivc(/*num_app_circuits=*/4, settings, [field_to_tamper](Chonk& ivc, size_t idx) {
156+
if (idx == 3) {
161157
auto& kernel_entry = ivc.verification_queue[0];
162158
ASSERT_TRUE(kernel_entry.is_kernel) << "Expected first queue entry to be a kernel";
163159

@@ -212,13 +208,15 @@ class ChonkTests : public ::testing::Test {
212208
using KernelIOSerde = bb::stdlib::recursion::honk::KernelIOSerde;
213209

214210
const size_t NUM_APP_CIRCUITS = 2;
215-
const size_t NUM_TOTAL_CIRCUITS = NUM_APP_CIRCUITS * 2 + /*num_trailing_kernels*/ 3;
211+
const size_t NUM_TOTAL_CIRCUITS =
212+
NUM_APP_CIRCUITS + static_cast<size_t>(ceil(static_cast<double>(NUM_APP_CIRCUITS) / MAX_APPS_PER_KERNEL)) +
213+
/*num_trailing_kernels*/ 3;
216214
TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
217215

218216
// Extract tail kernel IO before the hiding kernel consumes the verification queue.
219217
KernelIOSerde tail_io;
220-
auto [proof, vk_and_hash] =
221-
run_ivc(/*num_app_circuits=*/NUM_APP_CIRCUITS, settings, [&tail_io](Chonk& ivc, size_t idx) {
218+
auto [proof, vk_and_hash] = run_ivc(
219+
/*num_app_circuits=*/NUM_APP_CIRCUITS, settings, [&tail_io, &NUM_TOTAL_CIRCUITS](Chonk& ivc, size_t idx) {
222220
// With 2 apps the layout is [app, kernel, app, kernel, reset, tail, hiding].
223221
if (idx == NUM_TOTAL_CIRCUITS - 2) {
224222
for (auto& it : std::ranges::reverse_view(ivc.verification_queue)) {
@@ -351,7 +349,6 @@ TEST_F(ChonkTests, BadProofFailure)
351349
}
352350

353351
if (idx == 2) {
354-
EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
355352
tamper_with_proof(ivc.verification_queue[0].proof,
356353
num_public_inputs); // tamper with first proof
357354
}
@@ -372,8 +369,7 @@ TEST_F(ChonkTests, BadProofFailure)
372369
circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES });
373370
ivc.accumulate(circuit, vk);
374371

375-
if (idx == 2) {
376-
EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
372+
if (idx == 1) {
377373
tamper_with_proof(ivc.verification_queue[1].proof,
378374
circuit.num_public_inputs()); // tamper with second proof
379375
}
@@ -428,8 +424,7 @@ HEAVY_TEST(ChonkKernelCapacity, MaxCapacityPassing)
428424
{
429425
bb::srs::init_file_crs_factory(bb::srs::bb_crs_path());
430426

431-
const size_t NUM_APP_CIRCUITS = (CHONK_MAX_NUM_CIRCUITS - /*trailing kernels*/ 3) / 2;
432-
auto [proof, vk] = ChonkTests::accumulate_and_prove_ivc(NUM_APP_CIRCUITS);
427+
auto [proof, vk] = ChonkTests::accumulate_and_prove_ivc(CHONK_MAX_NUM_APPS);
433428

434429
bool verified = ChonkTests::verify_chonk(proof, vk);
435430
EXPECT_TRUE(verified);

barretenberg/cpp/src/barretenberg/chonk/chonk_transcript_invariants.test.cpp

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,33 +58,34 @@ class ChonkTranscriptInvariantTests : public ::testing::Test {
5858
* Any change to this count indicates a structural change in how transcripts are managed, which
5959
* could have security implications (e.g., unexpected transcript isolation or sharing).
6060
*
61-
* The 2-app IVC flow creates 7 circuits: app0 -> kernel0 -> app1 -> kernel1 -> reset -> tail -> hiding
61+
* The 4-app IVC flow creates 6 circuits: app0 -> app1 -> app2 -> kernel1 -> app3 -> kernel2 -> reset -> tail -> hiding
6262
*
6363
* Per-circuit transcript breakdown (from complete_kernel_circuit_logic):
64-
* - App circuits (0, 2): 0 transcripts - use native HN folding prover
65-
* - Init kernel (1): 2 transcripts:
64+
* - App circuits (0, 1, 2): 0 transcripts - use native HN folding prover
65+
* - Init kernel (3): 3 transcripts:
6666
* 1. accumulation_recursive_transcript
67-
* 2. hash_transcript - for computing accumulator hash to propagate in public inputs
68-
* - Intermediate kernel (3): 3 transcripts:
67+
* 2. PairingPoints::aggregate_multiple - for batching pairing points with Fiat-Shamir separator
68+
* 3. hash_transcript - for computing accumulator hash to propagate in public inputs
69+
* - Intermediate kernel (4): 3 transcripts:
6970
* 1. accumulation_recursive_transcript - shared across recursive verification
7071
* 2. PairingPoints::aggregate_multiple - for batching pairing points with Fiat-Shamir separator
7172
* 3. hash_transcript - for computing accumulator hash to propagate in public inputs
72-
* - Reset and tail kernels (4, 5): 2 transcripts each:
73+
* - Reset and tail kernels (5, 6): 2 transcripts each:
7374
* 1. accumulation_recursive_transcript
7475
* 2. hash_transcript - for computing accumulator hash to propagate in public inputs
75-
* - Hiding kernel (6): 3 transcripts:
76+
* - Hiding kernel (7): 3 transcripts:
7677
* 1. accumulation_recursive_transcript
7778
* 2. batch_merge_transcript - for final batch merge verification
7879
* 3. PairingPoints::aggregate_multiple
7980
*
80-
* Total: 0 + 2 + 0 + 3 + 2 + 2 + 3 = 12 transcripts
81+
* Total: 0 + 0 + 0 + 3 + 3 + 2 + 2 + 3 = 13 transcripts
8182
*/
8283
TEST_F(ChonkTranscriptInvariantTests, AccumulationTranscriptCount)
8384
{
84-
// Pinned expected transcript count for 2 app circuits
85-
constexpr size_t EXPECTED_TOTAL_TRANSCRIPTS = 12;
86-
constexpr size_t EXPECTED_NUM_CIRCUITS = 7;
87-
constexpr std::array<size_t, EXPECTED_NUM_CIRCUITS> EXPECTED_CIRCUIT_TRANSCRIPTS = { 0, 2, 0, 3, 2, 2, 3 };
85+
// Pinned expected transcript count for 4 app circuits
86+
constexpr size_t EXPECTED_TOTAL_TRANSCRIPTS = 13;
87+
constexpr size_t EXPECTED_NUM_CIRCUITS = 9;
88+
constexpr std::array<size_t, EXPECTED_NUM_CIRCUITS> EXPECTED_CIRCUIT_TRANSCRIPTS = { 0, 0, 0, 3, 0, 3, 2, 2, 3 };
8889

8990
// Record transcript index before IVC
9091
size_t index_before_ivc = bb::unique_transcript_index.load();
@@ -93,8 +94,8 @@ TEST_F(ChonkTranscriptInvariantTests, AccumulationTranscriptCount)
9394
std::vector<size_t> indices_before_accumulation;
9495
std::vector<size_t> indices_after_accumulation;
9596

96-
// Create IVC with 2 app circuits
97-
constexpr size_t NUM_APP_CIRCUITS = 2;
97+
// Create IVC with 4 app circuits
98+
constexpr size_t NUM_APP_CIRCUITS = 4;
9899
PrivateFunctionExecutionMockCircuitProducer circuit_producer(NUM_APP_CIRCUITS);
99100
const size_t num_circuits = circuit_producer.total_num_circuits;
100101
ASSERT_EQ(num_circuits, EXPECTED_NUM_CIRCUITS) << "Circuit count mismatch - test assumptions invalid";
@@ -112,7 +113,7 @@ TEST_F(ChonkTranscriptInvariantTests, AccumulationTranscriptCount)
112113

113114
// Pin the total number of transcripts created during accumulation
114115
EXPECT_EQ(total_transcripts, EXPECTED_TOTAL_TRANSCRIPTS)
115-
<< "Total transcript count during 2-app IVC accumulation changed. "
116+
<< "Total transcript count during 4-app IVC accumulation changed. "
116117
<< "If intentional, update EXPECTED_TOTAL_TRANSCRIPTS. "
117118
<< "Unexpected changes may indicate security-relevant transcript isolation issues.";
118119

0 commit comments

Comments
 (0)