Skip to content

feat(shielded-pool): draft PIR + epoch nullifiers extension spec#72

Merged
Meyanis95 merged 10 commits into
masterfrom
feat/shielded-pool-pir-extension
Jun 2, 2026
Merged

feat(shielded-pool): draft PIR + epoch nullifiers extension spec#72
Meyanis95 merged 10 commits into
masterfrom
feat/shielded-pool-pir-extension

Conversation

@Meyanis95

@Meyanis95 Meyanis95 commented May 19, 2026

Copy link
Copy Markdown
Contributor

Spec for a new sibling PoC at pocs/private-payment/shielded-pool-extension/ that extends the existing shielded-pool PoC. The note format, deposit, and attestation flows from the parent are reused unchanged; this spec composes on top, adding:

  • State-read privacy: InsPIRe PIR over Merkle-path reads; root fetches on a fixed schedule for k-anonymity over the active user base.
  • Bounded on-chain nullifier set: coarse epoch-based nullifiers.
  • Constant per-spend work: per-note recursive chain proof (IVC).
  • Cross-epoch double-spend soundness: epoch_created bound 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

@Meyanis95 Meyanis95 marked this pull request as ready for review May 22, 2026 19:21
@Meyanis95 Meyanis95 requested review from oskarth and rymnc May 22, 2026 19:22

@oskarth oskarth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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.

  2. The “bounded on-chain state” claim does not hold if activeNullifiers[currentEpoch][η] = true and 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.”

  3. 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 η_e before 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.

  4. The spend circuit diff should explicitly update input commitment reconstruction for epoch_created. Since commitments become poseidon(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.

  5. 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.

@Meyanis95

Copy link
Copy Markdown
Contributor Author

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.

@Meyanis95 Meyanis95 requested a review from oskarth May 26, 2026 22:48
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md Outdated
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md Outdated
@Meyanis95 Meyanis95 requested review from oskarth and rymnc May 27, 2026 19:37
@Meyanis95

Copy link
Copy Markdown
Contributor Author

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 (epoch_created is public so the contract can recompute the accumulator) and the shared-accumulator mitigation that closes it; clarified that only the commitment tree is PIR-served; documented the two ways to fetch frozen-epoch witnesses (in-clear query vs static-file download); noted the single-sequencer option for active-root contention; plus small consistency and notation cleanups.

Ready for re-review @oskarth @rymnc

Comment thread pocs/private-payment/shielded-pool-extension/README.md
@Meyanis95 Meyanis95 requested a review from rymnc May 29, 2026 14:42
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md
Comment thread pocs/private-payment/shielded-pool-extension/SPEC.md
@Meyanis95 Meyanis95 requested a review from rymnc June 1, 2026 16:09
@Meyanis95 Meyanis95 merged commit 1722568 into master Jun 2, 2026
2 checks passed
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.

3 participants