feat(shielded-pool): draft PIR + epoch nullifiers extension spec#72
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…tion Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
… root Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
oskarth
left a comment
There was a problem hiding this comment.
This is a useful direction for extending the shielded-pool design. The two problems named here are real: state-read privacy and nullifier/state growth. I think the spec should tighten a few core protocol points before merge, mostly around whether the proposed construction actually gives the stated guarantees on an EVM-style deployment.
Main issues:
-
The nullifier tree data structure is underspecified/inconsistent. The spec says the active nullifier set uses LeanIMT and is updated incrementally, but the chain-update circuit later relies on sorted-low-leaf non-membership witnesses. An append-only LeanIMT root does not give sorted predecessor non-membership. Please specify the exact nullifier-tree construction: indexed Merkle tree, sparse Merkle tree, sorted tree with insertion proofs, etc., and how the contract updates that root on each spend.
-
The “bounded on-chain state” claim does not hold if
activeNullifiers[currentEpoch][η] = trueand active-tree nodes are written to EVM storage. Epoch namespacing stops old entries from being read, but it does not remove old storage slots from Ethereum state. If the goal is validator state boundedness, the spec needs a storage model that actually prunes/avoids historical nullifier slots, or it should narrow the claim to “bounded active logical set.” -
The PIR non-membership flow needs a private predecessor lookup story. For sorted-low-leaf non-membership, the wallet needs to find the predecessor of
η_ebefore it can PIR-fetch the witness. If the replica helps by value, it learns the nullifier being tested. Please specify whether clients maintain a local index, use a private lookup protocol, use a sparse key-derived path, or mark predecessor lookup as an explicit unresolved assumption. -
The spend circuit diff should explicitly update input commitment reconstruction for
epoch_created. Since commitments becomeposeidon(token, amount, owner_pubkey, salt, epoch_created), the transfer/withdraw membership constraints should restate that directly rather than saying membership is unchanged from the parent spec. -
The constant-cost language should distinguish circuit cost from contract cost. The chain proof keeps spend-circuit verification independent of note age, but
expectedChainAccumulator(epochCreated)is still linear in the number of frozen epochs. That may be acceptable with coarse epochs, but the guarantee should be phrased precisely.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
Thanks, all five addressed in 950865d The root mistake was the tree choice. Replaced leanIMT with indexed MT, for nullifiers non-membership proofs. The first draft applied LeanIMT uniformly, which was wrong. The active and frozen nullifier sets are now indexed Merkle trees; LeanIMT stays only for the commitment tree, where membership is all that's needed. That also surfaced your point #3: the low-leaf predecessor lookup can itself leak the queried nullifier. We split it by lifetime. Phantom nullifiers (unspent past epochs) have no on-chain footprint, so their witnesses are served in clear; the current-epoch lookup stays private (wallet-local sorted index + a single PIR query), since that value becomes the on-chain spend artifact. |
|
Pushed a spend-flow rework plus some audit fixes. The main change splits the spend into two proofs: a wallet spend proof (commitment membership, chain-proof recursion, nullifier derivation) and a relayer insertion proof that advances the active-tree root. The contract verifies both and binds them by matching the public nullifier list. This removes the in-circuit active-tree update and the second PIR query, so PIR is now only used for the commitment read. There's a new insertion circuit and a cross-proof-binding section, plus a relayer-liveness note in the limitations. On top of that, a few fixes: documented the on-chain note-age leak ( |
Spec for a new sibling PoC at
pocs/private-payment/shielded-pool-extension/that extends the existingshielded-poolPoC. The note format, deposit, and attestation flows from the parent are reused unchanged; this spec composes on top, adding:epoch_createdbound into the commitment.Roots are light-client-verified via a two-level MPT walk (pseudocode in spec). The off-chain state-replica server is untrusted. Spec is proof-system agnostic.
File:
pocs/private-payment/shielded-pool-extension/SPEC.md