feat(pool-v2 PQV2-04): raise note-commitment tree to horizon-safe depth 32 - #25
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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. NoAction::ShieldedV2has 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, withconst _: () = 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 − 1nf_row = input_base + (4 + TREE_DEPTH)·CYCLE_LENGTH − 1TRACE_LENGTH = ACTIVE_ROWS.next_power_of_two()(1024 → 2048)INPUT_SEGMENT_CYCLES;MAX_V2_NOTES = 2^32−1; frontier/root/exhaustion logic; verify-cost & weight docs; downstreamruntime/scan/hashcomments.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) viawith_claimed_size+ thesize == 2^depthcollision 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)
107,453 B stays well under
MAX_PROOF_LEN(128 KiB) andMAX_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 forsov-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