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

Configuration

This section covers the parameters you need to choose when setting up recursive verification.

Field selection

The library currently supports two base fields:

FieldModulusBitsStatus
KoalaBear0x7F00000131Recommended
BabyBear0x7800000131Fully supported

All fields support degree-4 binomial extensions (BinomialExtensionField<F, 4>), which is a currently fixed parameter for the recursion stack, with plans to lift it to a runtime parameter in the future.

FRI parameters

FRI parameters control the trade-off between proof size, verifier cost, and security level.

ParameterTypical valueEffect
log_blowup3LDE blowup factor (2^log_blowup). Higher = more redundancy, fewer queries needed.
max_log_arity4Maximum folding factor per FRI round (2^max_log_arity). Controls how quickly polynomial degree reduces.
log_final_poly_len5Degree of the final polynomial after folding. Smaller = more folding rounds but simpler final check.
query_pow_bits16Proof-of-work bits during the query phase. Higher = fewer queries needed for same security.
commit_pow_bits0Proof-of-work bits during the commit phase. Usually 0.
cap_height0Height at which Merkle trees are truncated for commitments. 0 = single root hash.

Security level

The number of FRI queries is derived as:

num_queries = (target_security_bits - query_pow_bits) / log_blowup

With default parameters (target = 100 bits, query_pow_bits = 16, log_blowup = 3):

num_queries = (100 - 16) / 3 = 28

See related section in the Soundness and Security chapter for a more thorough analysis of the security estimate in the light of recent findings against the underlying assumptions used in these heuristics.

Intermediate layer relaxation

For intermediate recursive layers (not the final one), soundness requirements compose, so relaxed parameters can be used:

  • query_pow_bits = 20, num_queries = 26 — saves 2 queries worth of in-circuit work
  • query_pow_bits = 24, num_queries = 25 — more aggressive, suitable for deeply nested layers

The outermost (final) layer should use full-strength parameters.

FriVerifierParams

The recursion circuit uses FriVerifierParams to know the FRI structure without accessing the native FriParameters directly:

let fri_verifier_params = FriVerifierParams::with_mmcs(
    log_blowup,
    log_final_poly_len,
    commit_pow_bits,
    query_pow_bits,
    poseidon2_config,
);

This is stored in your config wrapper and returned via FriRecursionConfig::pcs_verifier_params().

Poseidon2 configuration

The Poseidon2Config enum selects the hash function parameters for in-circuit hashing (MMCS verification and Fiat-Shamir):

ConfigFieldDWIDTHRATE
BabyBearD4Width16BabyBear4168
BabyBearD1Width16BabyBear1168
BabyBearD4Width24BabyBear42412
KoalaBearD4Width16KoalaBear4168
KoalaBearD1Width16KoalaBear1168
KoalaBearD4Width24KoalaBear42412

For standard recursive verification, use the D4Width16 variant matching your field. The D1 variants use base field challenges (lower overhead per duplexing, but different security trade-offs). The Width24 variants use a wider permutation for more efficient hashing.

The Poseidon2Config must be consistent between:

  • The FriRecursionBackend constructor
  • The FriVerifierParams
  • The Poseidon2 permutation enabled on the CircuitBuilder

Table packing

TablePacking controls how circuit operations are distributed across table lanes. Each lane adds columns to a table; more lanes means shorter (fewer rows) but wider (more columns) tables.

TablePacking::new(witness_lanes, public_lanes, alu_lanes)
ParameterControlsTrade-off
witness_lanesWitness table widthMore lanes → fewer rows, but wider table
public_lanesPublic input table widthOften the bottleneck for row count
alu_lanesALU (add/mul) table widthMost operations land here

The total row count of each table is ceil(num_ops / num_lanes), padded to the next power of two. The maximum table height across all tables determines the FRI polynomial degree and dominates proving cost.

Choosing packing values

The goal is to balance table heights so no single table forces a large power-of-two padding.

Example — a recursive verification circuit with ~65K witness ops, ~43K public ops, ~60K ALU ops:

PackingMax heightNotes
(5, 1, 3)2^16 = 65,536Public table (43K/1 = 43K rows) forces 2^16
(5, 2, 3)2^15 = 32,768Public drops to 21.5K, halving the max
(5, 3, 3)2^15 = 32,768Public at 14.3K, ALU at 20K — both fit in 2^15
(8, 4, 4)2^14 = 16,384Everything fits, but tables are very wide

Halving the max table height cuts FRI proving time by roughly 40-50% (one fewer folding round, half the polynomial size).

Use .with_fri_params(log_final_poly_len, log_blowup) to set minimum row counts:

let packing = TablePacking::new(5, 2, 3)
    .with_fri_params(log_final_poly_len, log_blowup);

Layer-specific packing

The first recursive layer (verifying the original proof) often has a different operation distribution than subsequent layers. It's common to use different packing per layer:

let packing = if layer == 1 {
    TablePacking::new(1, 1, 1)
} else {
    TablePacking::new(5, 1, 3)
}.with_fri_params(log_final_poly_len, log_blowup);