Skip to content

feat(pool-v2 PQV2-04): raise note-commitment tree to horizon-safe depth 32 - #25

Merged
cloudzombie merged 3 commits into
mainfrom
feat/pqv2-04-tree-depth-32
Jul 28, 2026
Merged

feat(pool-v2 PQV2-04): raise note-commitment tree to horizon-safe depth 32#25
cloudzombie merged 3 commits into
mainfrom
feat/pqv2-04-tree-depth-32

Conversation

@cloudzombie

Copy link
Copy Markdown
Owner

What

Re-derives the STARK spend circuit of the post-quantum shielded pool v2 to raise the note-commitment tree from the depth-20 prototype to the horizon-safe depth 32 (audit PQV2-04 capacity upgrade). TREE_DEPTH == HORIZON_SAFE_TREE_DEPTH == 32; the "depth-20 is a prototype" framing is removed.

DORMANT / no activation change. Signal bit 2 (shielded-v2) stays UNARMED on every network. No Action::ShieldedV2 has ever been accepted on mainnet, so there is no historical v2 proof to preserve. The non-v2 consensus KAT (sov-verify::kat_vectors_are_reproduced_byte_for_byte) is untouched and green.

How the coupling was respected

The AIR's transition constraints are depth-independent (masked per-row); only the trace row-map baked the path length in. Every such constant is now derived symbolically from TREE_DEPTH, with const _: () = assert!(…) guards so no literal can silently assume the old depth:

  • INPUT_SEGMENT_ROWS = (3 + TREE_DEPTH + 1)·CYCLE_LENGTH (24 → 36 cycles)
  • root_row = input_base + (3 + TREE_DEPTH)·CYCLE_LENGTH − 1
  • nf_row = input_base + (4 + TREE_DEPTH)·CYCLE_LENGTH − 1
  • TRACE_LENGTH = ACTIVE_ROWS.next_power_of_two() (1024 → 2048)
  • prover cycle literals → INPUT_SEGMENT_CYCLES; MAX_V2_NOTES = 2^32−1; frontier/root/exhaustion logic; verify-cost & weight docs; downstream runtime/scan/hash comments.

The canonical context bytes changed by exactly one byte (trace-length exponent 0a → 0b); the trailing constraint-count vint is identical, confirming the deeper path added trace rows, not constraints.

Correctness — the deep path is actually constrained

New negative test spend_circuit_constrains_the_full_32_deep_path: an honest 32-deep spend verifies, and a proof built over a tampered level-28 node (a level that exists only because depth is 32) does not verify under the honest anchor. The full-capacity boundary is proven algebraically (a literal 2^32 fill is infeasible) via with_claimed_size + the size == 2^depth collision argument, plus exhaustive small-depth frontier↔reference equivalence and a many-thousand-append deep-prefix pin at the real depth.

Measured before → after (release, Apple Silicon)

quantity depth 20 depth 32
serialized proof 98,494 B 107,453 B
verify (median of 11) ~0.9 ms 1.60 ms
prove ~68 ms
proven classical soundness 128 bits 128 bits (floor green)

107,453 B stays well under MAX_PROOF_LEN (128 KiB) and MAX_SHIELDED_V2_BUNDLE_BYTES (144 KiB); 1.60 ms keeps ~10× margin under the 16 ms verify-weight budget.

KATs regenerated (each justified in code, all explained by the depth increase)

tree-root digest; v2 state-commitment (empty + one-note); wire publics-header (anchors are deeper roots); canonical proof-context; proof-size pin. Nothing was re-blessed to pass — note commitment, owner tag, and nullifier KATs are unchanged because they do not depend on depth.

Exhaustion economics (flipped prototype → horizon-safe)

Filling the tree now costs ~55.9M XUS (exceeds the entire ~21M supply ~2.6×) and ~127 years of sustained block saturation, so honest-growth and griefing exhaustion are both off the table.

Verification

cargo build --workspace ✓, Station (node/) build ✓, cargo fmt --check ✓, cargo clippy --all-targets -- -D warnings ✓ (chain + Station), tests green for sov-shielded-pq, sov-runtime, sov-types, sov-verify, sov-rpc (v2 e2e), sov-wallet, sov-node, sov-chain.

Updates notes/pq-pool-v2-audit-response.md: PQV2-04 depth upgrade moved from open prerequisite to code-complete, pending the same external circuit audit as the rest of pool v2. Bit 2 stays a named audit-gated item on the arming bar.

🤖 Generated with Claude Code

…th 32

Re-derive the STARK spend circuit so the note-commitment tree is the
horizon-safe depth 32 instead of the depth-20 prototype (audit PQV2-04
capacity upgrade). DORMANT: signal bit 2 stays UNARMED; no activation
change; non-v2 consensus KAT untouched.

Circuit / row-map (chain/crates/shielded-pq):
- TREE_DEPTH 20 -> 32; TREE_DEPTH == HORIZON_SAFE_TREE_DEPTH asserted.
- air.rs: INPUT_SEGMENT_ROWS, root_row, nf_row, TRACE_LENGTH all DERIVED
  symbolically from TREE_DEPTH (no depth literals); trace 1024 -> 2048;
  const _ guards pin the geometry (pow2, whole cycles, rc fit, row map).
- prover.rs: seg-cycle / nf-cycle literals replaced with INPUT_SEGMENT_CYCLES.
- Merkle-verify segment now proves a full 32-deep authentication path; the
  transition constraints are unchanged (masked per-row), only the masks and
  row counts grow — confirmed by an IDENTICAL constraint-count in the
  canonical context bytes (only the trace-length exponent moved 0a -> 0b).

State / economics:
- MAX_V2_NOTES = 2^32-1; boundary proven algebraically (a literal 2^32 fill
  is infeasible) via with_claimed_size + the size==2^depth collision arg,
  plus exhaustive small-depth equivalence and a deep-prefix real-append pin.
- Exhaustion economics flipped from prototype to horizon-safe: filling costs
  ~55.9M XUS (> total supply) and ~127 years of saturation.

KATs regenerated from MEASURED values, each justified in code:
- proof size 98,494 -> 107,453 bytes; verify median ~0.9 -> 1.60 ms
  (16 ms weight budget still ~10x); prove ~68 ms; proven soundness 128 bits.
- tree-root, v2 state-commitment, publics-header and canonical-context KATs.

Negative test spend_circuit_constrains_the_full_32_deep_path proves the deep
path is really constrained: a proof over a tampered level-28 node does not
verify under the honest anchor; an honest 32-deep spend does.

Verified: cargo build --workspace, Station build, fmt --check, clippy
--all-targets -D warnings, and tests for shielded-pq/runtime/types/verify/
rpc/wallet/node/chain all green.

Updates notes/pq-pool-v2-audit-response.md: PQV2-04 depth upgrade moved from
open prerequisite to code-complete (pending the same external audit).
…e depth-32 row-map

The circuit re-derivation (98494->depth-32) moved the trace row-map, but two
negative tests in tests/kat.rs still wrote their tampers to hard-coded depth-20
cycles/levels, so at depth 32 they landed on the wrong rows (corrupting a REAL
input segment) and tripped the prover-side debug_assert in get_pub_inputs BEFORE
the constraint under test could fire. Debug-only symptom; the tests were stale,
not the circuit.

Verified this is a stale-test issue, NOT a soundness regression:
- range_check_landing_must_match_value_register: output-0 commitment cycles
  96/97 -> NUM_SLOTS*INPUT_SEGMENT_CYCLES (144/145), derived from TREE_DEPTH.
- dummy_with_nonzero_value_rejected: slot-2 base cycle 2*24 -> 2*INPUT_SEGMENT_CYCLES
  (72), Merkle loop 0..20 -> 0..TREE_DEPTH, nf cycle 23 -> 3+TREE_DEPTH.

The expected_violation identifiers are UNCHANGED and now carry a derivation
comment explaining why they are depth-INDEPENDENT: the dummy value-zero boundary
assertion is single(VAL_COL+2, 0, 0) = main_trace(23,0); the range-check landing
is result[120 + (NUM_SLOTS+0)] = transition constraint 124. Constraint indices
do not move with TREE_DEPTH; only the trace rows the tamper targets did.

Verifier-side soundness confirmed independently at depth 32: with both the
prover-side debug_assert and winterfell's debug trace-validation compiled out
(release), instrumented runs showed PROOF_PRODUCED + verify_is_err=true for BOTH
tampers — i.e. the constraint system (winterfell::verify), not the builder
assert, rejects them. The prover.rs debug_assert is belt-and-suspenders backed
by the boundary assertions single(4+k, root_row/out_cm_row, ...) in get_assertions.

Full debug suite green (coordinator's command): sov-runtime 97/0, sov-shielded-pq
lib 71/0, decode_hardening 18/0, kat 25/0, verify_cost 2/0, security_level 2/0;
cargo build --workspace ok; fmt clean.
@cloudzombie
cloudzombie merged commit 09ee106 into main Jul 28, 2026
35 of 36 checks passed
@cloudzombie
cloudzombie deleted the feat/pqv2-04-tree-depth-32 branch July 28, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant