You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Shielded pools are one way to make a private payment on a public blockchain. Two earlier posts on this blog built one, first for [private bonds](/building-private-bonds-on-ethereum/), then for [private payments](/building-private-transfers-on-ethereum/). The primitive works. What comes next is a run of bigger iterations meant to make it both more resilient and more scalable: hard for any single party to watch, censor, or quietly take control of, and fast enough to carry payments at Visa scale. The trick is getting there without giving up the four things that make shielding worth having in the first place: censorship resistance, openness, privacy, and security.
10
10
11
-
A shielded pool has a few moving parts, and each is where that gets hard. Through a wallet, it writes and reads the Ethereum contract state. And it proves, on the user's own device, the zero-knowledge proof that makes each spend valid, which is also where the post-quantum question bites. We will walk the open problems, then go deep on the two our prototype takes on.
11
+
A shielded pool has a few moving parts, and each one makes that hard in its own way. Through a wallet, it writes and reads the Ethereum contract state. And it generates, on the user's own device, the zero-knowledge proof that makes each spend valid, which is also where the post-quantum question bites. We will walk the open problems, then go deep on the two our prototype takes on.
12
12
13
13
## The open problems
14
14
@@ -58,11 +58,11 @@ for i in siblingIndicesOnPath(leafIndex): // ~log(N) positions in the flat tre
58
58
59
59
### What it costs
60
60
61
-
None of this is free. PIR makes the server work over the whole database on every query, and schemes differ in how much precomputation each side keeps. The specification targets a recent scheme, InsPIRe, whose appeal is that it needs no per-database hint on the client: no chunk of client-side data, precomputed from the public database, that the wallet has to hold before queries can start. Our prototype ships an older, simpler one, SimplePIR, which gives the same privacy but does keep such a hint on the client, derived from the public database. That hint is not a secret, so losing your phone loses nothing sensitive: you recompute or re-download it, and it has to be refreshed as the database grows. For real figures, the [PSE writeup on scaling Semaphore with PIR](https://pse.dev/projects/scaling-semaphore-pir) and the tree-pir benchmarks are the places to look. PIR is practical enough to build with today, and getting cheaper.
61
+
None of this is free. PIR makes the server work over the whole database on every query, and schemes differ in how much precomputation each side keeps. The specification targets a recent scheme, InsPIRe, whose appeal is that it needs no per-database hint on the client: no chunk of client-side data, precomputed from the public database, that the wallet has to hold before queries can start. Our prototype ships an older, simpler one, SimplePIR, which gives the same privacy but does keep such a hint on the client. That hint is not a secret, so losing your phone loses nothing sensitive: you recompute or re-download it, and it has to be refreshed as the database grows. For real figures, the [PSE writeup on scaling Semaphore with PIR](https://pse.dev/projects/scaling-semaphore-pir) and the tree-pir benchmarks are the places to look. PIR is practical enough to build with today, and getting cheaper.
62
62
63
63
## Reducing trust in the server
64
64
65
-
So how much trust does this server need? Less than it first appears, and the design works to keep it minimal. On-chain, the contract keeps only a Merkle root, a single short fingerprint of every note. The server rebuilds the full tree from Ethereum's public event log and serves you the path you ask for. It cannot hand you a fake path: you fetch the current root separately, recompute a root from the path the server returned, and check that the two match. A forged path will not hash to the right root. In the target design the wallet gets that trusted root from a consensus light client, which verifies it against Ethereum rather than trusting the server. The prototype implements the storage-proof verifier behind a `RootVerifier` port and leaves wiring up a full consensus light client as production integration work; in its place, the in-process end-to-end test trusts a local development `stateRoot`.
65
+
So how much trust does this server need? Less than it first appears. On-chain, the contract keeps only a Merkle root, a single short fingerprint of every note. The server rebuilds the full tree from Ethereum's public event log and serves you the path you ask for. It cannot hand you a fake path: you fetch the current root separately, recompute a root from the path the server returned, and check that the two match. A forged path will not hash to the right root. In the target design the wallet gets that trusted root from a consensus light client, which verifies it against Ethereum rather than trusting the server. The prototype implements the storage-proof verifier behind a `RootVerifier` port and leaves wiring up a full consensus light client as production integration work; in its place, the in-process end-to-end test trusts a local development `stateRoot`.
66
66
67
67
```
68
68
// The wallet trusts a root from consensus, not from the server.
@@ -81,7 +81,7 @@ Now the write side, and the second problem we took on: keeping that permanent nu
81
81
82
82
### Closing the book each epoch
83
83
84
-
Think of accounting periods. Instead of one ledger that grows forever, you close the books at the end of each epoch, a month, say, and open a fresh one. The sealed book leaves only its Merkle root on-chain. The trick is in the nullifier itself, which now folds in the current epoch:
84
+
Think of accounting periods. Instead of one ledger that grows forever, you close the books at the end of each epoch, a month, say, and open a fresh one. The sealed book leaves only its Merkle root on-chain. The change is in the nullifier itself, which now folds in the current epoch:
85
85
86
86
```
87
87
nullifier = hash(commitment, spending_key, epoch)
@@ -101,24 +101,22 @@ But sorting breaks the clean PIR story. To prove your nullifier is absent, you f
101
101
102
102
## The hinge: finding a leaf, finding a note
103
103
104
-
Look at the shape of what we just hit. PIR fetches a row when you already know its index. Finding that neighbor leaf is a different kind of request: you are asking the server to find, among everything it holds, the one value sitting just below yours, without telling it your value. PIR does not search, it fetches, so here it is necessary but not enough.
104
+
Look at the shape of what we just hit. PIR fetches a row when you already know its index. Finding that neighbor leaf is a different kind of request: you are asking the server to find, among everything it holds, the one value sitting just below yours, without telling it your value. That is a search, so here PIR is necessary but not enough.
105
105
106
106
Now recall the other read-side problem, the cold wallet that takes minutes to find your money. It takes minutes because the wallet pulls every recent note and tries to decrypt each one, since nothing on the outside marks which are yours. You could ask the server to filter, but the thing that picks out your notes, your key, is exactly what you cannot hand over.
107
107
108
-
Both are the same private-selection problem. In each, a server holds a big set, you need the few elements that match a private criterion, and revealing the criterion is the whole risk. That is private selection, and it is strictly harder than PIR's known-index fetch. The two are not identical: finding a neighbor is an ordering question over public values, finding your notes is an ownership question over ciphertexts. Same family, different test. No single primitive drops cleanly into both, which is why our prototype punts on the neighbor problem, and why the same handful of primitives keeps reappearing across projects. Every team building private payments at scale runs into this and reaches for one of them.
108
+
Both are the same problem. In each, a server holds a big set, you need the few elements that match a private criterion, and revealing the criterion is the whole risk. That is private selection, and it is strictly harder than PIR's known-index fetch. The two are not identical: finding a neighbor is an ordering question over public values, finding your notes is an ownership question over ciphertexts. Same family, different test. No single primitive drops cleanly into both, which is why our prototype punts on the neighbor problem, and why every team building private payments at scale ends up reaching for the same short list of primitives.
109
109
110
110
## The primitives, and who's building with them
111
111
112
-
That shared problem, private selection, comes with a handful of primitives, and the teams building private payments at scale each reach for a different one.
113
-
114
112
PIR reads a known row without revealing which one. That is the part our prototype uses, for the commitment path. Oblivious Message Retrieval (OMR) hands you the messages addressed to you without the server learning which are yours, aimed straight at note discovery. Fuzzy Message Detection (FMD) is the lighter cousin: it flags your notes plus a tunable share of decoys, so you trial-decrypt a smaller, blurred set, and [Penumbra](https://protocol.penumbra.zone/main/crypto/fmd.html) runs it in production.
115
113
116
114
[Zcash's Tachyon](https://tachyon.z.cash/) takes a different route. Rather than layering PIR or OMR on top of existing keys, it redesigns the key hierarchy entirely: viewing keys are gone, replaced by granular delegation keys. You hand the sync service a bounded, per-note key covering only a specific epoch window, enough for it to derive and scan that note's nullifiers and prove non-spend, without learning the note's value, its commitment, or that two delegated notes belong to the same wallet. The evolving-nullifier idea underneath comes from [Bowe and Miers](https://eprint.iacr.org/2025/2031), the same paper our epoch nullifiers build on, and by keeping the wallet's payment protocol off-chain Tachyon picks up post-quantum privacy almost as a side effect. [Aztec](https://docs.aztec.network/developers/docs/foundational-topics/advanced/storage/note_discovery#advanced-cryptography-techniques) and [Miden](https://docs.miden.xyz/reference/protocol/note#note-discovery) start lighter, with note tags that narrow what a wallet has to fetch, and weigh the heavier options from there.
117
115
118
116
## What the PoC settles, and what it doesn't
119
117
120
-
Four problems stand between a working shielded pool and private payments at scale. The prototype settles two of them. Epoch nullifiers bound the *active* nullifier state, so the set every validator has to consult stays a fixed size instead of growing without end. PIR lets a wallet read a known row, its note's commitment path, without revealing which row to the provider. What it does not settle is the harder problem the work surfaced: private selection. Finding your neighbor leaf in a nullifier tree and finding your notes in the crowd are both private selection, and neither reduces to PIR's known-index fetch.
118
+
Four problems stand between a working shielded pool and private payments at scale. The prototype settles two of them. Epoch nullifiers bound the *active* nullifier state, so the set every validator has to consult stays a fixed size instead of growing without end. PIR lets a wallet read a known row, its note's commitment path, without revealing which row to the provider. What it does not settle is the harder problem the work surfaced. Finding the neighbor leaf in a nullifier tree and finding your notes in the crowd are both private selection, and neither reduces to a known-index fetch.
121
119
122
120
This was a proof-of-concept, built to make that gap concrete. The [specification and code](https://github.com/ethereum/iptf-pocs/tree/master/pocs/private-payment/shielded-pool-extension) are open, the [earlier](/building-private-bonds-on-ethereum/)[posts](/building-private-transfers-on-ethereum/) build the pool it extends, and the IPTF map carries the [use case](https://github.com/ethereum/iptf-map/blob/master/use-cases/private-stablecoins.md) and [approach](https://github.com/ethereum/iptf-map/blob/master/approaches/approach-private-payments.md) for context.
123
121
124
-
The work splits across two fronts. Ethereum's roadmap is taking on censorship resistance, gas abstraction, and scaling at the protocol level. Cryptographic research is closing the rest, private selection included, the part that stands between today's shielded pools and fully private payments at scale.
122
+
The work splits across two fronts. Ethereum's roadmap is taking on censorship resistance, gas abstraction, and scaling at the protocol level. Cryptographic research is closing the rest, and private selection is the hardest piece left.
0 commit comments