soft3/zheng/specs/constraints.md

constraints

the AIR constraint format and encoding rules for zheng. each nox reduction pattern maps to a polynomial transition constraint over the Goldilocks field. SuperSpartan verifies these constraints via CCS and the sumcheck protocol.

constraint structure

a constraint is a polynomial equation C_p(t) that must equal zero at every trace row where pattern p fires:

C_p(t) = polynomial_expression(registers_at_row_t, registers_at_row_{t+1})

the combined constraint over all patterns — the universal step, one CCS instance for every Layer-1 row:

C(t) = Σ_{p=0}^{17} s_p(t) × C_p(t)

where the selectors s_0..s_17 are one-hot witness columns, bound to r0_t by two constraint families:

s_p × (r0_t − p) = 0      for every p        (a non-zero s_p forces r0 = p)
Σ_p s_p = 1                                  (some s_p is non-zero)

together: exactly one s_p is non-zero and it equals 1 — two non-zero selectors would need two values of r0, and the sum then fixes the survivor to 1. every other pattern's terms vanish identically, so multiplying C_p by s_p raises its degree by exactly one. Lagrange selectors interpolated over the 18 tag values would raise it by 17 — rejected for that reason. a row whose tag is outside 0..17 admits no selector and is unprovable.

because one instance covers every row, one HyperNova accumulator and one decider cover the whole Layer-1 trace; the proof is ~4 KiB whatever the program (see decider).

hash rows

pattern 15 rows carry a second one-hot family, the round selectors u_0..u_24 on r14_t:

u_k × (r14_t − k) = 0     for every k ∈ 0..25
Σ_k u_k = s_15                               (one round on hash rows, none elsewhere)

three derived columns are bound linearly to the round selectors:

π  = Σ_{j=3..18} u_j                         partial-round flag
κ  = s_15 − u_24                             round-counter flag (0 on the squeeze row)
rc = Σ_{j=3..18} RC[128 + j − 3] × u_j       the hemera partial-round constant

the Poseidon2 partial-round constraints are gated by π, the r14 counter by κ, and the round constant is the witness column rc — one instance serves all 25 rows of a hash block instead of one instance per constant set. the same rows serve the synthetic Poseidon2 rounds of the Fiat-Shamir transcript replay (axis and look openings) and the BBG root chain: they are laid out as hash rows (r0 = 15, state in r4-r7 / r10-r13, r14 = k).

pattern constraint table

pattern name gated constraint (row t unless noted) degree (with gate)
0 axis r9 − r8 + 1 = 0 (budget); opening bound by the binding group 2
1 quote r7 − r4 = 0 2
2 compose none in-row (cross-row wiring pending) —
3 cons none in-row (particle-identity wiring pending) —
4 branch r10 × (1 − r4 r5) = 0 ; r4 × (1 − r10) = 0 4, 3
5 add r6 − r4 − r5 = 0 2
6 sub r6 − r4 + r5 = 0 2
7 mul r6 − r4 r5 = 0 3
8 inv r6 × (r6 r4 − 1) = 0 — r6 = 0 on chain rows, v⁻¹ on the final row; the square-and-multiply chain is unconstrained witness 4
9 eq (r4−r5)(1−r6) = 0 ; r6(1−r6) = 0 ; (r4−r5) r7 − r6 = 0 3
10 lt r10, r11 ∈ {0,1} (block decomposition pending) 3
11 xor r10 + r11 − 2 r10 r11 − r12 = 0 ; r10, r11 ∈ {0,1} 3
12 and r10 r11 − r12 = 0 ; r10, r11 ∈ {0,1} 3
13 not r10 + r12 − 1 = 0 ; r11 = 0 2
14 shl r12 − r11 = 0 2
15 hash κ × (r14_{t+1} − r14_t − 1) = 0 ; π × Poseidon2 partial round (16 rows: y(state_0 + rc) = 1, matmul_internal) 2, 3
16 call r6 = 0 (check formula result) 2
17 look none in-row; openings, leaves, root and Statement.bbg_root bound by the binding group —

every gated constraint is verified against real nox traces (real_traces_satisfy_pattern_family, real_call_trace_satisfies_and_roundtrips in rs/src/lib.rs): a stale encoding makes honest programs unprovable at commit time, so the table is exactly what the code enforces. constraints listed as pending (cross-row wiring, bit decomposition sums, full Poseidon2 rounds) are not enforced.

universal constraints

these hold at every row regardless of pattern:

focus accounting

r7_t = r6_t - cost(r0_t)

focus_after = focus_before − pattern cost. cost is fixed per pattern.

step index

r15_{t+1} = r15_t + 1

monotonically increasing step counter.

halting

when r14_t transitions to halted, all subsequent rows must be identity (padding):

if r14_t = HALTED: r_k_{t+1} = r_k_t  for all k

boundary constraints

boundary register row value
input r3, r4 0 program input operands
output r5 last active program output
status r14 last active HALTED
focus r7 last active 0 (or insufficient for next pattern)

boundary constraints are point evaluations of the trace polynomial at specific binary coordinates. Brakedown proves these directly as evaluation claims.

CCS encoding

all constraints map to one CCS instance, the universal step. the key advantage: high-degree constraints cost only field operations in the SuperSpartan prover. no cryptographic cost increase over degree-1 constraints.

Σ_{i ∈ [q]} c_i · ∏_{j ∈ S_i} (M_j · z) = 0

where z is the witness vector of one step, M_j are sparse matrices that select linear forms over z, S_i are index sets specifying which M_j outputs multiply together, and c_i are Goldilocks field coefficients.

universal witness layout (Z_LEN = 96, padded to 128)

z[0..16]   r0_t .. r15_t                 z[16..32]  r0_{t+1} .. r15_{t+1}     z[32]  1
z[33..51]  s_0 .. s_17                   pattern selectors
z[51..76]  u_0 .. u_24                   round selectors
z[76] π    z[77] κ    z[78] rc           derived hash flags and round constant
z[79..87]  state_k[8..16]                capacity at row t   (prover-supplied)
z[87..95]  state_{k+1}[8..16]            capacity at row t+1 (prover-supplied)
z[95] y    inv(state_k[0] + rc)          partial-round S-box witness

universal instance shape (m = 64 rows, 15 matrices, 6 terms, degree ≤ 4)

the instance is built from 15 slot matrices and 6 product terms of fixed shape, all with coefficient 1: {A0,B0}, {C0,D0,E0}, {A1,B1}, {C1,D1,E1}, {F,G,H,I}, {P}. a row places one linear form per slot it uses; signs live in the linear forms. rows:

 0..18   s_p × (r0 − p)                   {A0,B0}
 18      Σ s_p − 1                        {P}
19..44   u_k × (r14 − k)                  {A0,B0}
 44      Σ u_k − s_15                     {P}
 45      π − Σ_{3..18} u_j                {P}
 46      κ − s_15 + u_24                  {P}
 47      rc − Σ RC_j u_j                  {P}
48..64   pattern rows: π × Poseidon2 partial round in slot family 0
         (row 48: S-box, rows 49..63: matmul_internal), every other
         pattern's gadgets in slot family 1 and {F,G,H,I}, sharing rows —
         at most one gate is live on a satisfying witness

the 15 matrices are what the decider opens (15 matrix evaluations); the 64 rows are what the accumulator's error vector spans. both are constants of the system, independent of the program.

the binding instance

opening bindings (axis commitment/point/value, hash-block replay, look leaf/root/statement) are degree-1 eq steps z[0] − z[1] = 0 over Z = [a, b, 1], folded into a second accumulator. degree 1 means satisfied steps fold to exactly zero error, which the verifier enforces (the zero-error rule). the universal instance is not degree 1 — its error vector carries genuine cross terms — so a violated Layer-1 row is refused by commit()'s per-row gate, not detected by the verifier (see decider soundness).

CCS encoding per pattern (reference derivations)

the derivations below show each pattern's constraint in CCS form before gating; in the universal instance each term is additionally multiplied by the pattern's selector (or π/κ for hash rows) and lives on the rows listed above. the register indices in these derivations follow the original AIR sketch; the enforced register map is the table above and nox specs/trace.md.

arithmetic patterns (5, 6, 7, 8)

these have the simplest CCS decomposition: one constraint, few matrices.

pattern 5: add

polynomial constraint:

C_5(t) = r5_{t+1} − r3_t − r4_t = 0

CCS decomposition (q = 1 term, degree 1):

  • M_0: selects r5_{t+1} (row t+1, register 5 → z index 21)
  • M_1: selects r3_t (row t, register 3 → z index 3)
  • M_2: selects r4_t (row t, register 4 → z index 4)
  • S_0 = {0}, c_0 = 1 — extracts r5_{t+1}
  • S_1 = {1}, c_1 = −1 — subtracts r3_t
  • S_2 = {2}, c_2 = −1 — subtracts r4_t
1·(M_0·z) + (−1)·(M_1·z) + (−1)·(M_2·z) = 0

pattern 6: sub

polynomial constraint:

C_6(t) = r5_{t+1} − r3_t + r4_t = 0

CCS decomposition (q = 1 term, degree 1):

  • M_0: selects r5_{t+1} (z index 21)
  • M_1: selects r3_t (z index 3)
  • M_2: selects r4_t (z index 4)
  • S_0 = {0}, c_0 = 1
  • S_1 = {1}, c_1 = −1
  • S_2 = {2}, c_2 = 1
1·(M_0·z) + (−1)·(M_1·z) + 1·(M_2·z) = 0

pattern 7: mul

polynomial constraint:

C_7(t) = r5_{t+1} − r3_t × r4_t = 0

CCS decomposition (q = 2 terms, degree 2):

  • M_0: selects r5_{t+1} (z index 21)
  • M_1: selects r3_t (z index 3)
  • M_2: selects r4_t (z index 4)
  • S_0 = {0}, c_0 = 1 — linear term: r5_{t+1}
  • S_1 = {1, 2}, c_1 = −1 — degree-2 term: r3_t × r4_t
1·(M_0·z) + (−1)·(M_1·z ⊙ M_2·z) = 0

where ⊙ denotes Hadamard (entry-wise) product.

pattern 8: inv

polynomial constraint:

C_8(t) = r5_{t+1} × r3_t − 1 = 0

CCS decomposition (q = 2 terms, degree 2):

  • M_0: selects r5_{t+1} (z index 21)
  • M_1: selects r3_t (z index 3)
  • M_2: selects constant 1 (z index 32)
  • S_0 = {0, 1}, c_0 = 1 — degree-2 term: r5_{t+1} × r3_t
  • S_1 = {2}, c_1 = −1 — constant term: −1
1·(M_0·z ⊙ M_1·z) + (−1)·(M_2·z) = 0

logic patterns (9, 10, 11, 12, 13, 14)

these patterns require bit decomposition. auxiliary registers r8–r13 hold intermediate values (individual bits or partial sums). each bit position generates a sub-constraint, yielding ~64 constraints per pattern invocation.

pattern 9: eq

polynomial constraint (two sub-constraints):

C_9a(t) = (r3_t − r4_t) × r8_t − r9_t = 0      (r8 = inverse-or-zero, r9 = flag)
C_9b(t) = r5_{t+1} − (1 − r9_t) = 0             (result is 1 when equal)

CCS decomposition for C_9a (q = 2 terms, degree 2):

  • M_0: selects r3_t (z index 3)
  • M_1: selects r4_t (z index 4)
  • M_2: selects r8_t (z index 8) — auxiliary: conditional inverse
  • M_3: selects r9_t (z index 9) — auxiliary: equality flag
  • S_0 = {0, 2}, c_0 = 1 — r3_t × r8_t
  • S_1 = {1, 2}, c_1 = −1 — r4_t × r8_t
  • S_2 = {3}, c_2 = −1 — −r9_t

the prover sets r9_t = 0 when r3_t = r4_t, and r9_t = 1 otherwise, with r8_t the modular inverse of (r3_t − r4_t) when they differ.

CCS decomposition for C_9b (q = 3 terms, degree 1):

  • M_4: selects r5_{t+1} (z index 21)
  • M_5: selects constant 1 (z index 32)
  • M_6: selects r9_t (z index 9)
  • S_3 = {4}, c_3 = 1
  • S_4 = {5}, c_4 = −1
  • S_5 = {6}, c_5 = 1
C_9b: 1·(M_4·z) + (−1)·(M_5·z) + 1·(M_6·z) = 0

pattern 10: lt

polynomial constraint (range decomposition):

the difference d = r3_t − r4_t is decomposed into 64 bits b_0 … b_63 stored across auxiliary registers (spread over multiple trace rows). two families of sub-constraints:

C_10_bit_k: b_k × (b_k − 1) = 0                 (each bit is boolean)
C_10_recompose: r3_t − r4_t − Σ_{k=0}^{63} b_k × 2^k = 0   (bits reconstruct the difference)

the result r5_{t+1} = b_63 (the sign bit, indicating r3 < r4 in unsigned interpretation).

CCS decomposition for each bit constraint (q = 2 terms, degree 2):

  • M_bit: selects b_k from an auxiliary register
  • M_const: selects constant 1 (z index 32)
  • S = {bit, bit}, c = 1 — b_k²
  • S = {bit}, c = −1 — −b_k

the recomposition constraint is degree 1 with 65 terms (one per bit plus the difference).

pattern 11: xor

polynomial constraint (per-bit XOR via arithmetic identity):

r3_t and r4_t are decomposed into bits a_k, b_k. for each bit position k:

C_11_bit_k: a_k + b_k − 2 × a_k × b_k − c_k = 0

where c_k is the k-th bit of the result. the result is recomposed: r5_{t+1} = Σ c_k × 2^k.

CCS decomposition per bit (q = 4 terms, degree 2):

  • M_a: selects a_k, M_b: selects b_k, M_c: selects c_k (from auxiliary registers)
  • S_0 = {a}, c_0 = 1 — a_k
  • S_1 = {b}, c_1 = 1 — b_k
  • S_2 = {a, b}, c_2 = −2 — −2 × a_k × b_k
  • S_3 = {c}, c_3 = −1 — −c_k

plus boolean constraints for a_k, b_k, c_k and recomposition constraints for both operands and the result. total: ~64 × 4 sub-constraints.

pattern 12: and

polynomial constraint (per-bit AND):

C_12_bit_k: a_k × b_k − c_k = 0

CCS decomposition per bit (q = 2 terms, degree 2):

  • M_a: selects a_k, M_b: selects b_k, M_c: selects c_k
  • S_0 = {a, b}, c_0 = 1 — a_k × b_k
  • S_1 = {c}, c_1 = −1 — −c_k

plus boolean and recomposition constraints (same structure as pattern 11).

pattern 13: not

polynomial constraint (per-bit complement):

C_13_bit_k: a_k + c_k − 1 = 0

CCS decomposition per bit (q = 3 terms, degree 1):

  • M_a: selects a_k, M_c: selects c_k, M_const: selects constant 1
  • S_0 = {a}, c_0 = 1
  • S_1 = {c}, c_1 = 1
  • S_2 = {const}, c_2 = −1

plus boolean and recomposition constraints. all degree 1.

pattern 14: shl

polynomial constraint:

C_14(t) = r5_{t+1} − r3_t × 2^{r4_t} = 0   (mod 2^64)

the shift amount r4_t is decomposed into bits to compute 2^{r4_t} via repeated squaring. auxiliary registers hold intermediate powers. the overflow is handled by range-checking the result to 64 bits.

CCS decomposition: a chain of degree-2 constraints for each bit of the shift amount:

C_14_pow_k: pow_{k+1} = pow_k × pow_k × b_k + pow_k × (1 − b_k)

simplified: pow_{k+1} = pow_k × (1 + (pow_k − 1) × b_k). each step is degree 2 when b_k is binary. the final constraint multiplies r3_t by the accumulated power and range-checks the result.

tree patterns (0, 1, 2, 3)

these patterns navigate the subject tree (a binary tree representing the program's data). operand hashes in r1, r2 anchor Merkle-path verification.

pattern 0: axis

polynomial constraint:

C_0(t) = r1_{t+1} − select(r3_t, r1_t, direction_bit) = 0

axis navigates one level in the subject tree. the direction bit (extracted from r3_t, the axis number) determines whether to descend left or right. the constraint enforces that r1_{t+1} (the new operand hash) equals the child hash consistent with the Merkle path:

C_0_step: r1_{t+1} − (direction × r8_t + (1 − direction) × r9_t) = 0

where r8_t and r9_t hold the left and right child hashes provided by the prover (verified against the parent hash via a separate Poseidon2 check). the direction bit extraction uses one degree-2 constraint per depth level.

CCS decomposition per depth step (q = 3 terms, degree 2):

  • M_0: selects r1_{t+1} (z index 17)
  • M_1: selects direction bit from auxiliary register
  • M_2: selects r8_t (left child hash, z index 8)
  • M_3: selects r9_t (right child hash, z index 9)
  • M_4: selects constant 1 (z index 32)
  • S_0 = {0}, c_0 = 1 — r1_{t+1}
  • S_1 = {1, 2}, c_1 = −1 — direction × left_child
  • S_2 = {1}, c_2 = 1; combined with S_3 = {3}, c_3 = −1 and S_4 = {4, 3} for the (1 − direction) × right_child term

each depth level of the axis also requires a Poseidon2 hash constraint to verify the parent-child relationship, reusing pattern 15's constraint structure.

pattern 1: quote

polynomial constraint:

C_1(t) = r5_{t+1} − r3_t = 0

the simplest pattern: the result is the literal operand itself.

CCS decomposition (q = 2 terms, degree 1):

  • M_0: selects r5_{t+1} (z index 21)
  • M_1: selects r3_t (z index 3)
  • S_0 = {0}, c_0 = 1
  • S_1 = {1}, c_1 = −1
1·(M_0·z) + (−1)·(M_1·z) = 0

pattern 2: compose

polynomial constraint:

compose chains two evaluations: first evaluate sub-expression x, then evaluate sub-expression y using x's output as input. the constraint enforces correct threading of intermediate results:

C_2a(t) = r3_{t_mid} − r5_{t_x} = 0     (y's input = x's output)
C_2b(t) = r5_{t+1} − r5_{t_y} = 0       (compose result = y's output)

these are degree-1 constraints linking trace rows at three time points (t, t_mid, t_end). the CCS encoding uses M matrices that select registers from the appropriate rows, with the row offsets baked into the matrix structure.

CCS decomposition for each sub-constraint: same shape as pattern 1 (two matrices, degree 1, two terms).

pattern 3: cons

polynomial constraint:

cons pairs two sub-results into a new tree node:

C_3(t) = r1_{t+1} − poseidon2(r5_{t_left}, r5_{t_right}) = 0

the result hash is the Poseidon2 hash of the left and right sub-results. this decomposes into a hash constraint (reusing pattern 15's structure) plus a linking constraint that threads sub-expression outputs:

C_3_link_left:  input_left  − r5_{t_left}  = 0
C_3_link_right: input_right − r5_{t_right} = 0
C_3_hash:       r1_{t+1} − hash_output = 0

CCS decomposition: the linking constraints are degree 1 (two terms each). the hash constraint follows pattern 15's encoding.

control patterns (4, 16)

pattern 4: branch

polynomial constraint:

C_4(t) = r8_t × (r5_{t+1} − r3_t) + (1 − r8_t) × (r5_{t+1} − r4_t) = 0

where r8_t is the selector (derived from the condition evaluation): r8_t = 1 selects r3_t (the "yes" branch), r8_t = 0 selects r4_t (the "no" branch).

expanding: r5_{t+1} − r8_t × r3_t − r4_t + r8_t × r4_t = 0

CCS decomposition (q = 4 terms, degree 2):

  • M_0: selects r5_{t+1} (z index 21)
  • M_1: selects r8_t (z index 8) — selector
  • M_2: selects r3_t (z index 3) — yes branch
  • M_3: selects r4_t (z index 4) — no branch
  • S_0 = {0}, c_0 = 1 — r5_{t+1}
  • S_1 = {1, 2}, c_1 = −1 — −selector × yes
  • S_2 = {3}, c_2 = −1 — −no
  • S_3 = {1, 3}, c_3 = 1 — +selector × no
1·(M_0·z) + (−1)·(M_1·z ⊙ M_2·z) + (−1)·(M_3·z) + 1·(M_1·z ⊙ M_3·z) = 0

an additional boolean constraint enforces r8_t ∈ {0, 1}:

C_4_bool: r8_t × (r8_t − 1) = 0

pattern 16: call

polynomial constraint:

call injects externally computed values into the trace. the constraint structure varies by call type. the prover supplies a value in r5_{t+1} along with a proof that the external verifier (Layer 1) accepted it. the minimal constraint:

C_16(t) = r14_{t+1} − r14_t = 0   (status unchanged through hint)

additional constraints depend on the hint category (e.g., Layer 1 state root verification, oracle value attestation). these are encoded as separate CCS instances composed with the main trace via the SuperSpartan folding mechanism.

CCS decomposition for the base constraint (q = 2 terms, degree 1):

  • M_0: selects r14_{t+1} (z index 30)
  • M_1: selects r14_t (z index 14)
  • S_0 = {0}, c_0 = 1
  • S_1 = {1}, c_1 = −1

pattern 17: look

polynomial constraint:

look reads an authenticated value from BBG state. the BBG commitment root is:

BBG_root = Hemera(Lens.commit(BBG_poly) || Lens.commit(A) || Lens.commit(N))

where BBG_poly(index, key, t) is the authenticated state polynomial committed via Brakedown (Lens). a look row proves: BBG_poly(eval(r4)) = r6 under BBG_root = r5.

two inline constraints bind the row to the folded sub-proof:

C_17a(t) = r5_t − BBG_root_instance = 0      (root binding: r5 equals the instance BBG_root)
C_17b(t) = eval_point_t − f(r4_t) = 0        (key binding: evaluation point derived from key r4)

both are degree 1. the actual Brakedown opening — Lens.verify(r5, eval(r4), r6, proof) — is a separate CCS instance, folded in via HyperNova (identical mechanism to pattern 16 call). the folded sub-proof carries ~825 constraints (CCS jet + batch Brakedown).

CCS decomposition for C_17a (q = 2 terms, degree 1):

  • M_0: selects r5_t (z index 5)
  • M_1: selects BBG_root from instance constants (z index 32, with coefficient = BBG_root)
  • S_0 = {0}, c_0 = 1
  • S_1 = {1}, c_1 = −1
1·(M_0·z) + (−1)·(M_1·z) = 0

CCS decomposition for C_17b (degree 1): same shape as C_17a, selecting the evaluation point auxiliary register and the derived function of r4. the exact derivation of eval(r4) (how the BBG lookup key maps to a polynomial evaluation point) is specified in bbg/specs/indexes.md.

the folded Brakedown sub-instance is not part of the main CCS trace — it is a separate CCS proof composed via HyperNova. constraint count for the folded sub-instance: ~825 (CCS jet + batch Brakedown, from verifier.md).

hash pattern (15)

pattern 15: hash (Poseidon2 round)

polynomial constraint:

Poseidon2 operates on a state of width w (typically 8 field elements). each round applies: S-box (x → x^7), then MDS matrix multiplication, then round constant addition. the state elements are spread across registers r3–r5 and auxiliary registers r8–r13 (six state elements per trace row, requiring two rows per full state).

for a single S-box + MDS step on one state element s:

C_15_sbox: r8_t − (s_t)^7 = 0

since (s_t)^7 = s_t × s_t × s_t × s_t × s_t × s_t × s_t, the prover decomposes this into intermediate squares:

C_15_sq1: r9_t − s_t × s_t = 0                    (s^2)
C_15_sq2: r10_t − r9_t × r9_t = 0                  (s^4)
C_15_cube: r11_t − r10_t × r9_t = 0                (s^6)
C_15_final: r8_t − r11_t × s_t = 0                 (s^7)

each sub-constraint is degree 2. this decomposes the degree-7 S-box into four degree-2 constraints per state element.

CCS decomposition for C_15_sq1 (q = 2 terms, degree 2):

  • M_0: selects r9_t (z index 9)
  • M_1: selects s_t (whichever register holds the state element)
  • S_0 = {0}, c_0 = 1
  • S_1 = {1, 1}, c_1 = −1 — s_t × s_t (same matrix used twice in the product set)

the remaining sub-constraints follow the same shape: one degree-2 product term and one degree-1 extraction term.

the MDS matrix multiplication is a linear operation, encoded as a single degree-1 constraint per output element:

C_15_mds_j: state_j_{t+1} − Σ_{k} MDS[j][k] × sbox_output_k = 0

CCS decomposition for MDS (q = w+1 terms, degree 1):

  • one M matrix per sbox output element, with the MDS coefficient baked into c
  • S_k = {k}, c_k = MDS[j][k] for each input k
  • S_{w} = {out_j}, c_{w} = −1 for the output element

round constants are added by adjusting the c coefficient of the constant-1 wire (z index 32).

total per Poseidon2 round: 4 × w degree-2 constraints (S-box decomposition) + w degree-1 constraints (MDS). for w = 8: 32 + 8 = 40 constraints per round. a full Poseidon2 permutation with 8 rounds uses ~320 constraints.

constraint budget by proof type

proof type dominant patterns total constraints
identity (preimage) 15 (one hash) ~736
anonymous cyberlink 15, 4, 9 ~13,000
delivery (per hop) 15, 7, 4 ~60,000
private transfer (BBG) 15, 7, 9 ~50,000
recursive verification (generic) 15, 5, 7, 4 ~8,000
recursive verification (CCS jet + batch) 5, 7 ~825
recursive verification (+ algebraic FS) 5, 7 ~89

see SuperSpartan for the IOP that verifies constraints, sumcheck for the reduction mechanism, nox for pattern definitions, transcript for Fiat-Shamir challenge derivation

Graph