Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UltraHonk backend: real proving with Barretenberg

How crucible-prover generates and verifies real UltraHonk proofs through the Barretenberg binary (bb), and what this repository has validated about that pairing.

Toolchain pairing (validated)

ComponentVersionRole
nargo1.0.0-beta.26compile circuits, solve witnesses
bb6.0.0-nightly.20260903UltraHonk prove / verify

This exact pairing is what the repository is validated against: the compatibility matrix in crates/ultrahonk/src/backend.rs (BACKEND_COMPAT), the TESTED_BB_VERSION pin in the crate root, and the CI circuits job (which installs bb via bbup at that version) all agree on it. Proofs are only reproducible when the backend version is known: an UltraHonk proof produced by one Barretenberg version may not verify with another.

Installing bb: bbup normally resolves the right bb for your nargo automatically, but its mapping (bb-versions.json) can lag new Noir releases. When that happens, pin explicitly:

curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/refs/heads/next/barretenberg/bbup/install | bash
bbup -v 6.0.0-nightly.20260903 --no-modify-path

scripts/check-bb.sh reports the installed version and repeats these instructions.

The tool split (why bb exists at all)

Modern Noir (1.0.0-beta.x) removed proving from nargo:

StageToolProduces
Compilenargo compileACIR bytecode JSON
Executenargo executesolved witness (.gz) from Prover.toml
Provebb proveUltraHonk proof + verification key
Verifybb verifyaccept/reject against the VK

nargo test output is an in-process interpreter run and is not a proof; only bb produces cryptographic evidence.

The bb CLI surface (what this adapter drives)

The validated bb exposes a small, stable command surface (no more prove_ultra_honk / write_vk_ultra_honk subcommands):

bb prove -b <bytecode.json> -w <witness.gz> -o <dir> --write_vk --output_format json
bb verify -p <dir>/proof.json -k <dir>/vk.json -i <dir>/public_inputs.json
  • The scheme for Noir ACIR is ultra_honk.
  • With --output_format json, every artifact is self-describing and carries scheme, bb_version, and the backend-native verification-key digest (vk_hash / hash):
    • proof.json{ proof: [field words…], vk_hash, bb_version, scheme }
    • public_inputs.json{ public_inputs: [field words…], bb_version, scheme }
    • vk.json{ vk: [field words…], hash, bb_version, scheme }
  • Field words are 0x-prefixed 32-byte big-endian hex. Public inputs are listed in circuit order: pub parameters first, then returned values.
  • bb verify exits 0 on acceptance and non-zero on rejection; a rejected proof prints e.g. UltraVerifier: verification failed at reduction step.

Repository mapping

  • crates/ultrahonk/src/toolchain.rsBbToolchain: locating bb (BB_BIN override), parsing bb --version, major-version floor (pre-2026 CLI generations are rejected).
  • crates/ultrahonk/src/exec.rsprove() / verify(): process execution, JSON artifact parsing, provenance validation (scheme must be ultra_honk, bb_version must be present, and the digest a proof embeds must equal the digest of the VK written alongside it). A proof that fails verification is an outcome, not an error.
  • crates/ultrahonk/src/store.rs — the filesystem verification-key store (VkStore): providers write the vk.json a proof was produced with and verifiers resolve it by id under uhk/<circuit>/<version>/<artifact-sha>.
  • crates/ultrahonk/src/provider.rsUltraHonkProvider implementing [ProofProvider]: request bags → Prover.toml (witness encoder, 0600, in a scratch package copy) → nargo executebb prove → a [ProofResponse] whose public outputs are named from the pinned circuit surface.
  • crates/ultrahonk/src/verifier.rsUltraHonkVerifier implementing [Verifier]: resolves the key by id, re-encodes the submitted public outputs, and lets bb verify decide; precise context rejections are detected before cryptography runs.
  • crates/ultrahonk/src/backend.rs — the compatibility matrix.
  • crates/ultrahonk/src/calldata.rs — public-input encoding for an on-chain verifier (candidate layout; calibration against a deployed Soroban verifier is deferred to the Soroban batch).

Witness and bytecode are referenced by path only on the exec layer; private values leave memory once, through the witness encoder into a 0600 scratch Prover.toml. Errors carry paths and exit codes, never values or raw stderr.

Live test coverage

tests/tests/ultrahonk.rs runs real cryptography end to end, gated on both nargo and bb being on PATH:

  • witnesses solved from the committed vector catalog against the real register / transfer circuit packages;
  • bb provebb verify round trips;
  • binding: a register proof’s single public input must equal the committed account address; a transfer proof exposes exactly nine public words — token, sender, recipient, old commitment, root_hi, root_lo, then the three returned values — checked word by word against the fixture;
  • rejection: tampered proofs, tampered/wrong verification keys, and proofs submitted against changed public inputs all fail verification.

These are the cryptographic counterparts of the wrong-context rejections the mock backend can only simulate, and they run in the CI circuits job where the validated toolchain pair is installed.

Trait-level wiring and state binding

UltraHonkProvider/UltraHonkVerifier implement the same [ProofProvider]/[Verifier] seams crucible-mock implements, so simulators and scenario runners swap backends without changing code. The VkStore is the resolution layer: a proof never carries key material — verifiers resolve it by the id stamped on the response.

For the state-bound operations (merge, transfer, withdraw) the state reference is now a cryptographic binding: the circuits fold the two halves of the state root into their public inputs and into every emitted nullifier (see docs/circuit-model.md). A proof cut for root A therefore embeds different public words than one cut for root B. UltraHonkVerifier enforces the binding at two layers:

  1. Structural — before bb runs, the submitted state reference must agree with the root_hi/root_lo words the proof committed to; a stale submission is rejected with StateReferenceMismatch, one stripped of its binding with MissingStateBinding.
  2. Cryptographic — if the submitter rewrites both the state reference and the root words to root B, bb rejects: the proof was cut for root A.

tests/tests/real_backend.rs exercises both layers, plus the honest counterpart: register proofs remain deliberately unbound (no state is consumed) and deposit carries only an envelope-level reference — each pinned by regression tests so the boundary cannot silently widen.

The trait-seam suite needs nargo + bb on PATH and compiled bytecode under circuits/target/ (CI compiles before running it).

What is deliberately not here yet

  • On-chain verification (Soroban UltraHonk verifier, calldata calibration, verifier_target selection) is the Soroban adapter’s job.
  • The circuits in this repository encode the shape of Confidential Token semantics; exact commitment layout and key derivation must be aligned with the real OpenZeppelin spec before any production use.