fix(shielded-pq): quantify + floor-pin pool-v2 tree exhaustion (PQV2-04) - #22
Merged
Conversation
PQV2-04 has two halves. The boundary defect (root() returning the empty-tree root at exactly size==2^20) is already fixed on main by capping usable capacity at MAX_V2_NOTES = 2^20-1; re-verified this session by the release-only frontier_matches_the_reference_tree_at_full_capacity (real appends to the 2^20 boundary, root pinned against the reference tree — 1 passed in 68s). This commit closes the economic half: - Trace the real per-commitment cost: fee = gas_for(ShieldedV2) x gas_price (10 grains/gas mainnet), paid transparently to the miner. Conservative floor to fill 2^20-1 leaves is >=~13.6k XUS (realistically ~39k counting bundle bytes) AND ~11 days / 6,554 consecutive saturated blocks (block-weight caps commitments/block). Pinned by two regression tests so a pre-arming gas retune cannot silently cheapen the attack. - Record depth-20 as a documented PROTOTYPE shortfall: capacity is below the issuance/confirmation horizon (exhaustible by honest growth too), and the robust fix is a deeper tree. Adds HORIZON_SAFE_TREE_DEPTH = 32 (Orchard parity, horizon-derived) with a test pinning TREE_DEPTH < it. The depth increase is deliberately NOT shipped: it is a STARK spend-circuit re-derivation (the AIR trace row-map bakes the Merkle path length into fixed literals and TRACE_LENGTH=1024; all proof KATs and the measured verify-cost basis depend on it). Per reserve-grade rules it is deferred to an audited, re-proven circuit revision and recorded as a hard prerequisite for arming bit 2. Additive + dormant: no consensus digest, gas value, or TREE_DEPTH changes; bit 2 unarmed everywhere. Workspace + Station build green.
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.
Closes audit Medium PQV2-04 — "the depth-20 commitment tree can be economically exhausted" — for the DORMANT pool-v2 (
shielded-pq, BIP-9 bit 2, unarmed on every canonical chain).Two halves
1.
root()/ capacity boundary bug — already fixed onmain, re-verified here.At exactly
size == 2^20the O(depth) frontier's low bits are all zero, soroot()would return the empty-tree root for a full tree (a full-pool anchor colliding with the empty-pool anchor). Fixed by capping usable capacity atMAX_V2_NOTES = MAX_TREE_LEAVES = 2^20 − 1(final leaf slot deliberately unusable) with a compile-timeMAX_V2_NOTES < 2^TREE_DEPTHguard; both mutators reject the insertion reaching exact capacity with a typedTreeFull, mutating nothing. Proven — not by an early refusal — by the release-onlyfrontier_matches_the_reference_tree_at_full_capacity, which appends2^20 − 1real leaves and pins the frontier root against the reference tree at full capacity (NOT the empty root). Re-verified this session: 1 passed in 68 s.2. Economic/growth exhaustion — quantified, floor-pinned; real fix gated.
Traced from code (post-activation, mainnet schedule):
fee = gas_for(ShieldedV2) × 10 grains/gas, paid transparently to the miner (in-circuit fee leg pinned to zero). Each bundle appends ≤NUM_SLOTS = 4leaves. Conservative floor to fill2^20 − 1leaves:The gain is pure griefing (fees enrich miners; holders can still de-shield out). But depth-20 is a prototype capacity — exhaustible for a bounded one-time cost AND by honest growth (1.05M notes). Pricing cannot fix honest exhaustion; the robust fix is a deeper tree. Horizon-derived (
MAX_V2_COMMITMENTS_PER_BLOCK × ~20 yr of blocks ≈ 6.7×10^8⇒ depth ~30) → Orchard-parityHORIZON_SAFE_TREE_DEPTH = 32, >6× headroom.Why the depth change is NOT in this PR (reserve-grade honesty): raising
TREE_DEPTHis a STARK spend-circuit re-derivation — the AIR trace row-map bakes the Merkle path length into fixed literals (INPUT_SEGMENT_ROWS,root_row,nf_row) andTRACE_LENGTH = 1024; depth 32 doubles the trace to 2048 and invalidates every proof KAT and the measured verify-cost basis. It is a re-audited, re-proven change to the trust path, deferred and recorded as a hard prerequisite for arming bit 2, not shipped half-done.What this PR adds
pool_v2_exhaustion_has_a_pinned_fee_floor(runtime) — ≥13k-XUS fee floor.filling_the_tree_takes_thousands_of_saturated_blocks— ≥10 days / 6,554 blocks.depth_20_is_a_documented_prototype_shortfall_pending_a_circuit_upgrade+HORIZON_SAFE_TREE_DEPTHconst with derivation.notes/pq-pool-v2-audit-response.md.Dormancy / byte-identity
Additive only: no consensus digest, gas value, or
TREE_DEPTHchanges — the sole non-test addition is a documentation constant. Bit 2 unarmed on every canonical chain (shielded_v2_is_dormant_everywheregreen), so no v2 action executes. Safe to change only while dormant; the depth upgrade becomes a hard fork once armed.Verification (real output)
cargo test -p sov-shielded-pq -p sov-runtime— green (runtime 97, shielded-pq 70 +1 ignored, decode_hardening 18, kat 23, security_level 2, verify_cost 2).cargo fmt --check+cargo clippy --all-targets -D warnings— clean on both crates.cargo build --workspace— green; standalone Station (node/) build — green.