PIR for wallets
How a wallet can read balances and nonces without the server learning which account it asked about, and how to integrate the PIR client.
Part 1: what PIR gives a wallet
One paragraph
To show a balance, a wallet asks a remote endpoint about the user’s address, and the request itself tells the endpoint which account the user holds. Private Information Retrieval (PIR) lets the endpoint answer without learning which address was asked for. The wallet encrypts the query, the server computes the answer over every account it holds, and only the wallet can decrypt the response.
What the user gets
- Holdings stay private. The server answers balance and nonce lookups without learning which accounts belong to the user.
- Accounts are not linked together. A wallet that manages several addresses can look them all up without the server grouping them as one person.
- Activity is not signalled. A nonce lookup before sending a transaction does not tell the server that this account is about to transact.
- No trust in the operator is needed for this. Privacy comes from the query the wallet builds, so it holds whichever server answers.
What it does not hide
- The fact of a query. The server sees that a request arrived, when, and from which IP address. Routing requests over an anonymity network removes the IP link; tor-js runs Tor inside the wallet for this.
- Correctness. The wallet trusts the server to answer honestly. Verifiable PIR, which returns a Merkle proof with each value, is research scheduled for Q1 2027.
- Other reads. The current server answers ETH balance and nonce by address. Token balances and name lookups are planned for the Q4 2026 live demo. Contract calls, storage, code and logs still go over ordinary RPC.
How to describe it to users
| Can say | Cannot say |
|---|---|
| ”The server does not learn which address you looked up." | "Fully anonymous” or “untraceable”, unless requests also go over Tor. |
| ”This holds even if the server is malicious." | "Trustless”. Answers are not verified against the state root yet. |
| ”Private balance and nonce lookups." | "Private wallet”. Other reads still go over ordinary RPC. |
| ”Built on lattice cryptography, which is believed to resist quantum attacks." | "Audited”. It is not, today. |
A settings label that states the property: Private balance lookups: the server answers without learning which address you asked about.
Part 2: integration guide
What the wallet runs
The reference client is crates/client in inspire-gpu-serving, a Rust library and CLI. It contains no GPU code and builds on Linux and macOS, including ARM, with Rust stable and a C++17 compiler. It handles single-round lookups, picks the answer locally, and detects when the server has been reconfigured.
A TypeScript or WASM client for browser and extension wallets is not published yet. Contact the team if that is your target.
Both repositories state that the code was written by an AI under the author’s direction. The engine README adds that the author reviewed its C++ client, which builds and decrypts the queries. The serving README says the implementation has not been thoroughly reviewed, and that covers the Rust reference client. Neither repository declares a license yet, so contact the team before shipping it.
Lookup flow
- Once, at setup: fetch
/manifest. It holds the hashing seed, the PIR parameters and the server’s fixed CRS seed, and it does not change when the database updates. - Per lookup: derive the key from the address as the manifest says (the address itself, or its keccak256 hash), hash it to its two candidate buckets, and send one request carrying two encrypted queries. Always send both, even if the first would be enough, so the request looks the same for every address.
- Server reply: two encrypted answers, plus an update broadcast that lists accounts changed since the server’s last rebuild. Every client receives the same broadcast.
- Locally: take the value from the broadcast if the address is in it, otherwise from the decrypted buckets. No match anywhere proves that the account does not exist at that snapshot. On a server that holds only accounts with ETH, as the live demo does, no match means the account holds no ETH, and its nonce is not served.
Try it locally
git clone --recursive https://github.com/keewoolee/inspire-gpu-serving
cd inspire-gpu-serving
cargo build && cargo test # client side, no GPU needed
# Against a PIR server (see the infrastructure guide to run one):
cargo run --release -p pir-client -- --server http://HOST:8080 canary
cargo run --release -p pir-client -- --server http://HOST:8080 \
lookup 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
canary prints the block the server’s current snapshot represents. Running a test server needs a CUDA machine; the infrastructure provider guide covers it.
Costs to plan for
Measured from a laptop over the internet against one RTX 5090 serving a 16 GB database (about 182 million accounts):
| Measure | Per lookup |
|---|---|
| End-to-end time | About 250 ms, of which about 60 ms is building the query on the client |
| Upload | 2 × 371 KB |
| Download | 2 × 12 KB, plus the update broadcast, which grows about 50 KB per block between rebuilds |
Against the live H100 demo, a lookup over a transatlantic link took about 1.5 s, most of it the upload.
The upload is the cost to design around. Looking up ten accounts sends about 7.4 MB, which matters on mobile data. Cache results per block, look up accounts the user is viewing rather than all of them, and refresh on new blocks rather than on a timer. Batch PIR, which lowers the cost of fetching many entries together, is research scheduled for Q4 2026.
Integration rules
- Keep the request shape fixed. Do not skip the second query, cancel early, or retry only for some addresses. Any difference between lookups can reveal which address was asked for.
- Do not fall back silently. If the PIR server is unreachable, do not send the same lookup over ordinary RPC without telling the user; that sends the address PIR was hiding.
- Combine with IP privacy. Send PIR requests over Tor where the wallet supports it. PIR hides the address inside the request, and Tor hides who sent it.
- Keep the manifest. Fetch it once and refetch only when the client detects a reconfiguration.
Where to ask
For problems running the code, contact Keewoo Lee, who maintains both repositories, at keewoo@ethereum.org, or open an issue on inspire-gpu-serving. For partnerships and integration planning, contact Andy Guzman at andy@ethereum.org. The team is at reads@ethereum.org. Background: the Private Information Retrieval foundation page and the sharded PIR design on ethresear.ch.