| name | kernel-gate-counts |
| description | Compile noir-protocol-circuits (kernels, rollup, etc.) and compute their gate counts with `bb gates`, for both the chonk (client IVC) and ultra_honk schemes. Use when asked to regenerate circuit variants, compile protocol circuits, or measure gate/circuit sizes. |
Protocol-Circuit Gate Counts & Variant Regeneration
When to Use
Use this skill when asked to:
- Compile protocol circuits (private kernels, rollup circuits, etc.) with
nargo
- Regenerate circuit variants (e.g. after adding
init-N / inner-N / reset crates)
- Measure gate counts / circuit sizes with
bb gates, for either:
--scheme chonk — client-IVC circuits (private kernels)
--scheme ultra_honk — standalone UltraHonk circuits (rollup circuits)
Key Paths
All paths relative to the git root.
- Protocol circuits workspace:
noir-projects/fnd/noir-protocol-circuits/
- Circuit crates:
noir-projects/fnd/noir-protocol-circuits/crates/<crate-name>/
- Variant generator:
noir-projects/fnd/noir-protocol-circuits/scripts/generate_variants.js
- Workspace manifest (generated):
noir-projects/fnd/noir-protocol-circuits/Nargo.toml
- Workspace template (source of truth):
noir-projects/fnd/noir-protocol-circuits/Nargo.template.toml
nargo binary: noir/noir-repo/target/release/nargo — do NOT use a globally
installed nargo; version mismatches produce opaque bytecode failures.
bb binary: barretenberg/cpp/build/bin/bb
bb-avm binary: barretenberg/cpp/build/bin/bb-avm — required for circuits that
recurse the AVM (see Gotchas).
- Compiled artifacts:
noir-projects/fnd/noir-protocol-circuits/target/<package_name>.json
Step 1 — Regenerate Variants (only when crates were added/removed)
generate_variants.js regenerates the reset-circuit variants under
crates/autogenerated/ AND rewrites the workspace Nargo.toml from
Nargo.template.toml. Run it after adding or removing crates so the new crates
are actually included in the workspace — otherwise nargo won't see them.
cd noir-projects/fnd/noir-protocol-circuits
node scripts/generate_variants.js
Verify the crates you expect are now listed, e.g.:
grep -nE "inner|init" Nargo.toml
Note: numbered kernel variants (init-N / inner-N) are hand-written crates
committed under crates/, not auto-generated. The generator only (re)writes
Nargo.toml and the crates/autogenerated/ reset variants. Running node
before compiling newly-added crates is needed purely to refresh Nargo.toml
from the template. If no crates changed, you can skip this step.
Step 2 — Compile
Package names use underscores, not dashes. A directory like
crates/private-kernel-inner-N has package name private_kernel_inner_N
(check the name = field in <crate>/Nargo.toml). Passing the dashed form to
--package fails with "invalid character '-' in package name".
cd noir-projects/fnd/noir-protocol-circuits
NARGO=../../noir/noir-repo/target/release/nargo
$NARGO compile --package <package_name> --silence-warnings
Discover package names for a family of crates automatically:
for d in crates/private-kernel-init crates/private-kernel-init-* \
crates/private-kernel-inner crates/private-kernel-inner-*; do
[ -d "$d" ] && grep -h '^name' "$d/Nargo.toml"
done
The *_simulated crates are unconstrained witness-generation variants, NOT
proving circuits — exclude them from gate counting unless explicitly asked.
Step 3 — Gate Counts
Pass the compiled .json artifact as the bytecode path:
bb gates --scheme <chonk|ultra_honk> -b target/<package_name>.json
Reading the result (works for both schemes): bb gates prints a JSON
object to stdout with the gate count in functions[].circuit_size
(acir_opcodes is the ACIR opcode count):
{"functions": [
{
"acir_opcodes": 8653,
"circuit_size": 32209
}
]}
Extract it robustly with jq (ignores any warnings on stderr):
bb gates --scheme chonk -b target/private_kernel_init.json 2>/dev/null \
| jq '.functions[].circuit_size'
The chonk scheme additionally prints a human-readable line to stderr
(capture with 2>&1, not 2>/dev/null):
ChonkStats - circuit: ivc_circuit, acir_opcodes: 8653, circuit_size: 32209 (mem: 30.22 MiB)
Example A — chonk circuit: private kernel init
cd noir-projects/fnd/noir-protocol-circuits
NARGO=../../noir/noir-repo/target/release/nargo
BB=../../barretenberg/cpp/build/bin/bb
$NARGO compile --package private_kernel_init --silence-warnings
$BB gates --scheme chonk -b target/private_kernel_init.json 2>/dev/null \
| jq '.functions[].circuit_size'
Example B — ultra_honk circuit: private base rollup
Rollup circuits recursively verify proofs that carry IPA (the rollup uses IPA
for the inner proofs), so you must pass --verifier_target noir-rollup.
Without it the construction aborts with IPA proofs present when not expected.
cd noir-projects/fnd/noir-protocol-circuits
NARGO=../../noir/noir-repo/target/release/nargo
BB=../../barretenberg/cpp/build/bin/bb
$NARGO compile --package rollup_tx_base_private --silence-warnings
$BB gates --scheme ultra_honk -t noir-rollup -b target/rollup_tx_base_private.json 2>/dev/null \
| jq '.functions[].circuit_size'
Recursive circuits print warnings during construction (e.g.
ChonkRecursiveVerifier: Databus Consistency check failure,
bigfield: remainder not zero!). These are expected when no real proof inputs
are supplied — bb gates only builds the constraint system to count gates, so
the consistency checks have nothing to satisfy. The circuit_size on stdout is
still valid. Discard stderr (2>/dev/null) to keep output clean.
bb gates reference
-b, --bytecode_path — path to the compiled .json artifact.
-s, --scheme — {chonk, avm, ultra_honk}. Use chonk for client-IVC
kernels, ultra_honk for standalone circuits like the rollup circuits.
-t, --verifier_target — hash/ZK settings. Relevant values:
noir-recursive (poseidon2, ZK; default-ish for recursive noir circuits),
noir-rollup (IPA + poseidon2, ZK — required for rollup circuits),
plus evm, starknet, and -no-zk variants of each.
--include_gates_per_opcode — adds a per-opcode breakdown.
Proof-Size Assertion Failures
bb gates on a recursive circuit may abort with a proof-length assertion, e.g.:
create_honk_recursion_constraints: ACIR proof size mismatch. Actual: 480, Expected: 481
Assertion failed: (proof_size == ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS)
Actual : 1243
Expected: 1244
The first is the Honk recursion path (e.g. rollup_tx_merge, which recurses
child rollup Honk proofs); the second is the Chonk recursion path (e.g.
rollup_tx_base_private, which recurses the tx/kernel Chonk proof). In both
cases the constraint system encodes one proof length while bb expects
another. The proof length must agree across three places:
- barretenberg C++ — the
bb binary.
- the noir stdlib — the pinned
noir/noir-repo submodule, baked into the
compiled circuit bytecode at nargo compile time.
- the TS constants in
yarn-project/constants/src/constants.gen.ts —
RECURSIVE_ROLLUP_HONK_PROOF_LENGTH / NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH
(Honk, e.g. 480) and CHONK_PROOF_LENGTH (e.g. 1271). These fill the
fake recursive-proof arrays in the committed Prover.toml fixtures.
There are two causes. Diagnose by comparing the assertion's Actual (what the
bytecode/Prover.toml encodes) against Expected (what bb wants).
Cause 1 — stale bb binary (try this first)
Common when proof-format changes land in bb before the noir submodule is synced
to match. The on-disk bb may have been built from a commit with a different
proof length. Rebuild it:
cd barretenberg/cpp
cmake --build build --target bb
Then retry bb gates.
Cause 2 — stale Prover.toml fixtures
The committed Prover.toml files carry fake recursive proofs whose length is
frozen at the value present when they were last generated. After a proof-length
change they must be regenerated (or hacked — see below). Two commands cover all
protocol circuits, split by whether the sample needs a real client-proved
transaction.
Block-root and above rollup circuits — prover-client suite (simulated
orchestrator, no L1 sandbox):
AZTEC_GENERATE_TEST_DATA=1 \
yarn workspace @aztec/prover-client test regenerate_rollup_sample_inputs
Regenerates Prover.toml for rollup-block-root-first-empty-tx,
rollup-block-root-first, rollup-block-root-first-single-tx,
rollup-block-root, rollup-block-root-single-tx, rollup-block-root-msgs-only,
rollup-block-merge, rollup-checkpoint-root, rollup-checkpoint-root-single-block,
rollup-checkpoint-merge, rollup-tx-merge, rollup-root.
Private-kernel + transaction-base circuits — e2e prover full test (spins up
an L1/anvil sandbox):
AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1 \
yarn workspace @aztec/end-to-end test:e2e single-node/prover/server/full.test
Regenerates Prover.toml for: private-kernel-init and its private-kernel-init-N
variants, private-kernel-inner and its private-kernel-inner-N variants,
private-kernel-reset-tail, private-kernel-reset-tail-to-public,
rollup-tx-base-private, rollup-tx-base-public. These need real client-proved
transactions the simulated orchestrator cannot produce. (private-kernel-reset,
the inner reset, is commented out and hand-maintained — not regenerated.)
The circuit lists live in the updateProtocolCircuitSampleInputs(...) loops in
yarn-project/end-to-end/src/single-node/prover/server/full.test.ts and the
scenarios array in
yarn-project/prover-client/src/test/regenerate_rollup_sample_inputs.test.ts;
re-read them if a circuit seems missing (entries get commented in/out).
Build prerequisites for regeneration
The fake-proof lengths come from @aztec/constants, so the TS build must be
current after any proof-length change — otherwise regeneration re-emits the
old length. Build in dependency order (simplest: make yarn-project from the
git root, which does bb → noir → l1-contracts → yarn-project):
- bb / bb-avm native at
barretenberg/cpp/build/bin/{bb,bb-avm}. The
prover-client test looks for bb-avm and the e2e test for bb; both fall
back to WASM if absent, but you want a current native build.
- acvm at
noir/noir-repo/target/release/acvm (witness generation; WASM
fallback if absent).
- Protocol circuits compiled under
noir-projects/fnd/noir-protocol-circuits
(artifacts are bundled into @aztec/noir-protocol-circuits-types).
- TS build current, especially
@aztec/constants (regenerated from
noir-projects/.../constants.nr), then @aztec/noir-protocol-circuits-types,
@aztec/bb-prover, @aztec/simulator, and the test's own package.
- The e2e command additionally needs
l1-contracts built (it runs anvil).
After regenerating, recompile the affected circuit (Step 2) and re-run
bb gates.
Hack path — trim/extend the proof field by hand
(Note: bb gates reads only the compiled bytecode, not Prover.toml, so a
stale fixture cannot break gate counting — that's purely Cause 1. The
Prover.toml length only bites when you actually prove the circuit, e.g. via
the prover-client/e2e tests above or bb prove.)
If you just need to prove the circuit once and don't want to stand up the full
regeneration pipeline, edit the affected Prover.toml directly instead: the
assertion tells you the delta (Actual vs Expected). Trim or pad the
fake-proof array(s) in that file to the expected length — drop the extra
trailing elements, or append zero entries ("0x00") — so the field matches what
bb wants. The proof values are dummy under FAKE_PROOFS, so only the array
length matters. This is a stopgap; the proper fix is regeneration.
Gotchas
- Dashed dir name vs underscored package name —
--package private_kernel_inner_N,
not private-kernel-inner-N.
- Gate count is
functions[].circuit_size in the stdout JSON for both
schemes; chonk also prints a ChonkStats line to stderr.
- Rollup circuits need
-t noir-rollup (IPA), or construction aborts with
IPA proofs present when not expected.
- AVM-recursing circuits need
bb-avm, not bb. The public base rollup
(rollup_tx_base_public) recursively verifies the AVM and fails under plain
bb with AVM recursion is not supported in this build. Please use the 'bb-avm' binary. Use barretenberg/cpp/build/bin/bb-avm for those. The
private base rollup (rollup_tx_base_private) does not recurse the AVM and
works with plain bb.
- Run
node scripts/generate_variants.js before compiling newly-added
crates, or the workspace Nargo.toml won't include them.
- Always use the repo-local
nargo at noir/noir-repo/target/release/nargo.
- Warnings on stderr from recursive circuits are expected; the stdout
circuit_size is still authoritative.