Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/noir-lang/noir/llms.txt

Use this file to discover all available pages before exploring further.

Recursive proofs let you verify the proof of one Noir program inside another Noir program. This is the foundation for circuit aggregation: rather than proving a single monolithic computation, you can split it into stages, prove each stage independently, and then produce a succinct proof that all the stage proofs are valid.

What recursive proofs enable

A standard Noir circuit has a fixed structure and a fixed proof size. Recursion breaks that constraint:
  • Incremental computation — prove step N and carry a proof of steps 0..N-1 forward.
  • Proof composition — combine proofs from different circuits into one.
  • Compression — repeatedly fold many proofs into a single constant-size proof, regardless of how many steps are involved.

How it works in Noir

Noir exposes one entry point for recursive verification:
#[foreign(recursive_aggregation)]
pub fn verify_proof(
    verification_key: [Field],
    proof: [Field],
    public_inputs: [Field],
    key_hash: Field,
) {}
This is a black box function — Noir does not check proof validity internally. The witness execution will succeed even if the proof passed to verify_proof is invalid. The backend (Barretenberg) enforces correctness when generating the outer proof. An invalid inner proof will cause the backend to fail when it tries to generate a valid outer proof.
Do not rely on Noir witness execution to catch invalid inner proofs. Only the backend enforces the recursive constraint. Always test your recursive circuits end-to-end with a real prover.

Using bb_proof_verification

The Aztec team publishes a higher-level library — bb_proof_verification — that wraps verify_proof with the correct plumbing for Barretenberg proofs. This is the recommended starting point.
1

Add the dependency

Add bb_proof_verification to your Nargo.toml. Check the library’s published version tag.
[dependencies]
bb_proof_verification = { tag = "...", git = "https://github.com/AztecProtocol/aztec-packages" }
2

Declare the inner program's inputs

Your outer program receives the inner proof, the verification key, and the inner program’s public inputs as private witnesses.
use bb_proof_verification::verify;

fn main(
    verification_key: [Field; 114],
    proof: [Field],
    public_inputs: [Field; 1],
    key_hash: Field,
) {
    verify(verification_key, proof, public_inputs, key_hash);
}
3

Generate the inner proof

Use nargo prove (or the Barretenberg CLI bb prove) to generate the proof for the inner program and export the verification key.
4

Prove the outer program

Pass the inner proof, verification key, and public inputs as witnesses to the outer program and prove it normally.

verify_proof parameters

ParameterTypeDescription
verification_key[Field]The verification key of the inner circuit
proof[Field]The serialised proof bytes from the inner circuit
public_inputs[Field]Public inputs that were used when generating the inner proof
key_hashFieldA hash of the verification key, used by Barretenberg to prevent key substitution
All four values must come from the same proof generation run. Mixing a key with a proof from a different run will produce an invalid circuit.

Circuit aggregation pattern

The general pattern for multi-step aggregation:
// step_n_circuit.nr
use bb_proof_verification::verify;

// Accepts a proof of step N-1 and proves step N
fn main(
    // Proof that steps 0..N-1 were computed correctly
    prev_verification_key: [Field; 114],
    prev_proof: [Field],
    prev_public_inputs: [Field; K],
    prev_key_hash: Field,

    // Inputs specific to step N
    step_input: Field,
) -> pub Field {
    // Verify the previous step
    verify(prev_verification_key, prev_proof, prev_public_inputs, prev_key_hash);

    // Prove the current step
    let step_output = compute_step(step_input);
    step_output
}
Each step’s circuit is identical in structure; only the witness values change. This produces a single proof at the end that implicitly encodes the entire computation history.

Further resources

Build docs developers (and LLMs) love