PIR for infrastructure providers

How an RPC provider can offer balance and nonce lookups whose contents it never receives, what that costs to run, and how to deploy the PIR server.

Part 1: what PIR adds to an RPC service

One paragraph

Private Information Retrieval (PIR) lets an endpoint answer a read without receiving what was asked. The client encrypts the query, the server computes the answer over its whole database, and only the client can decrypt the response. For an RPC provider, a PIR endpoint serves balance and nonce lookups whose addresses never reach its systems, so there is no query data to store, secure or disclose.

From policy to property

Many providers already minimise query data through a no-logs policy and customer contracts. PIR enforces the same outcome in the protocol, so it holds without depending on configuration, staff access or incident handling, and a customer can check it from the open client code.

No-logs endpointPIR endpoint
Provider receives the addressYes, then discardedNo
What enforces the privacyContract and operationsThe client’s encryption
Customer can check it independentlyNoYes
Exposure in a breach or legal requestWhatever was retainedNone, never held

The two work together: PIR covers the lookups where customers need the stronger guarantee, and the existing policy covers the rest of the RPC surface.

Who asks for this

CustomerWhat they readWhy they would pay for PIR
Investigation, compliance and public-sector teamsAccounts under reviewTheir infrastructure provider cannot learn who is being investigated, which today rests on contract terms
Analytics firms serving those teamsClient-specified accountsThey can offer the same guarantee to their own customers
Trading desks and market makersAccount state before a transactionThe endpoint cannot see which accounts the desk is checking
WalletsThe user’s own accountsThey can offer users private balance lookups; see PIR for wallets
Institutions with data-minimisation dutiesAny account dataThey avoid sending query data to a third party at all

What it does not hide

State these limits next to the benefit in any customer-facing material.

  • Request metadata. The server sees that a request arrived, when, and from which IP address. Access logs still hold that metadata and fall under the provider’s usual log policy.
  • Correctness. The customer trusts the provider to answer honestly. Verifiable PIR, which returns a Merkle proof with each value, is research scheduled for Q1 2027.
  • Coverage. The current server answers ETH balance and nonce for accounts holding ETH. An account holding none answers as not found. Token balances and name lookups are planned for the Q4 2026 live demo. Other contract storage, contract calls, code and logs are not served over PIR yet.
  • Client changes. A standard JSON-RPC call cannot be made private on the server side. Customers use a PIR client; the reference client is Rust, and a TypeScript client is not published yet.

Claims you can make, and claims you cannot

Can sayCannot say
”Our servers answer without learning which address you asked about.""Fully anonymous”. IP and timing are visible.
”The guarantee holds even against us as the operator.""Trustless”. Answers are not verified against the state root yet.
”Based on the insPIRe scheme (IEEE S&P 2026).""Audited” or “production-grade”. Neither is true today.
”Private ETH balance and nonce lookups on mainnet.""Private RPC”. Other methods are not covered.

Cost to serve

These numbers are the Reads team’s own benchmarks, published in the inspire-gpu-serving README, measured on one NVIDIA H100 (80 GB) serving mainnet. They reflect the code as of September 2026 and are expected to improve as performance work continues.

MeasureValue
Accounts on one card206.8 million accounts holding ETH (of 418.8 million in total, September 2026), in a 16 GB PIR database that uses 25.4 GiB of GPU memory, and 53.6 GiB during a rebuild
ThroughputAbout 44 lookups per second per card over HTTP, about 158,000 per hour
Bandwidth per lookupAbout 742 KB in and 277 KB out, most of the download being the shared update broadcast
Database rebuildSeconds, with no downtime and no client resynchronisation, on a card with room for two database generations

A 32 GB card, such as an RTX 5090, holds the same database but cannot rebuild it in place, so it needs a second card to take over during rebuilds (see “Replicas and rolling swaps” in Part 2).

GPU cost per million lookups is the hourly price of the card divided by 0.158. At $1 per GPU-hour that is about $6.30 per million lookups at full load. Scale by the price of your card. Ingress bandwidth and the node that feeds chain updates come on top. A PIR lookup costs far more to serve than a plain eth_getBalance, so it suits a separate tier or add-on with its own pricing.

If you also serve data or indexing APIs

Address-keyed endpoints, such as portfolio, holdings and transaction history, reveal more per request than eth_getBalance: one history call returns an account’s full activity. PIR server cost grows with database size regardless of answer size, and every answer must have the same size, or its length identifies the account. The fit depends on whether the data reduces to a fixed-size record per address.

DataFitApproach
Per-address summaries: token and NFT holdings, namesGoodOne fixed-size record per address, with the holdings list capped or split into buckets
Recent activity: last N transactions per addressGoodA fixed N gives a fixed record size and a bounded database
Full transaction historyDesign-partner workPIR over a compact index from address to fixed-size pages of transaction pointers, with every client fetching the same number of pages. The transactions are public chain data, fetched separately in bulk or over an anonymous transport.
Small public datasets: prices, token lists, metadataPIR not neededThe client downloads the whole dataset, which hides the lookup at lower cost
Arbitrary or SQL queriesNot coveredPIR answers lookups by key. Private computation over arbitrary queries needs other techniques, such as FHE or TEEs.

Only ETH balance and nonce are served over PIR today. Token balances are planned for Q4 2026; the other rows are designs.

Ways to start

  • Proof of concept. Run the server with synthetic data, then with a mainnet snapshot, on one GPU (Part 2). An engineer familiar with Rust and CUDA can reach the first private lookup in one day.
  • Announce and measure demand. A page announcing PIR-backed lookups with a form to register interest. Record use case and expected volume, which is the input for pricing and prioritisation.
  • Design partner. Serve one named customer, for example an investigation or analytics team, on a single card before building a general offering. For a data API, the first case can be a private portfolio lookup. The Reads team can help identify and support that first customer.

Part 2: deployment guide

Components

RepositoryRole
keewoolee/inspire-gpuThe PIR engine. CUDA server for the insPIRe scheme, a CPU client in C++, a batch API, and a C ABI (src/capi.h). Retrieves entries by position.
keewoolee/inspire-gpu-servingThe service around the engine, in Rust. Keyword lookup by address (cuckoo hashing), an HTTP API, a chain follower, database rebuilds with no downtime, and the reference client. Vendors the engine as a git submodule.

An operator runs inspire-gpu-serving. The engine needs separate attention only to tune it or to build a different service on the C ABI.

Both READMEs 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, which derives the key and fixes the request shape. Neither repository declares a license yet; contact the team before building a product on it.

Hardware and toolchain

  • Server: one NVIDIA H100 (80 GB) serves the mainnet accounts holding ETH, with live updates. A 32 GB card holds the same database but needs a second card for rebuilds. Every account, about 419 million, needs the next size up, which is beyond one H100. The engine builds for the RTX 5090 (sm_120) by default. Set INSPIRE_CUDA_ARCH for other GPUs, for example 90 for an H100. Tested with CUDA 12.9, 13.1 and 13.3.
  • Build: Rust stable, a C++17 compiler, CMake and OpenSSL. nvcc under /usr/local/cuda* is found automatically.
  • Chain data: an execution node with the debug namespace enabled, for state diffs. No archive node is needed. Without the debug namespace the follower falls back to a feed that misses about a quarter of balance changes.

Step 1: run with synthetic data

git clone --recursive https://github.com/keewoolee/inspire-gpu-serving
cd inspire-gpu-serving

cargo build && cargo test           # client and CPU parts, any machine
cargo test -p pir-server            # server end-to-end tests, CUDA machine

# Serve one million synthetic accounts:
cargo run --release -p pir-server -- --synthetic 1000000 --buckets 4194304

# In a second shell, look one up privately:
cargo run --release -p pir-client -- --server http://127.0.0.1:8080 synthetic 12345
cargo run --release -p pir-client -- --server http://127.0.0.1:8080 canary

canary prints the block the server’s current snapshot represents. To watch updates and rebuilds, add --simulate 300 to the server command, or run scripts/live-sim-demo.sh.

Step 2: serve mainnet state

The server needs two inputs:

  1. A snapshot CSV. One row per account, address,nonce,balance_wei, with a # block=N header line. ethrex-statedump in the repository writes it from an ethrex node in a few minutes, keyed by the address hash, which the server publishes so clients can match it. Any other export works too.
  2. A JSON-RPC endpoint to stay current. It reads each block’s state diff with debug_traceBlockByNumber (prestateTracer), which needs the debug namespace, and falls back to eth_getBlockByNumber, eth_getBalance and eth_getTransactionCount without it. Your own node can supply it without rate limits.
# Dump an ethrex node's accounts, next to the node (--min-balance 1 keeps
# only accounts holding ETH, about half):
cargo run --release -p pir-chain --features ethrex-dump \
    --bin ethrex-statedump -- --datadir /path/to/ethrex/mainnet \
    --out accounts.csv --min-balance 1

# Catch the snapshot up to the chain head:
cargo run --release -p pir-chain --bin resync -- \
    --snapshot accounts.csv --eth-rpc https://RPC-ENDPOINT/KEY \
    --buckets 134217728

# Serve it. New blocks go into the update broadcast; a fresh database
# generation replaces the old one every --rebuild-secs (default 60):
cargo run --release -p pir-server -- --accounts-csv accounts.csv \
    --buckets 134217728 --eth-rpc https://RPC-ENDPOINT/KEY

# Query:
cargo run --release -p pir-client -- --server http://HOST:8080 \
    lookup 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045

Known limit of the fallback: without state diffs, the follower sees an account only when a transaction touches it, so a transfer inside a contract call leaves a balance stale until the address is next touched. Measured against mainnet diffs, this misses about a quarter of balance changes.

Step 3: put it behind your API

The server exposes /manifest, /lookup, /sidecar, /query and /healthz. The wire contract is documented in crates/server/src/http.rs.

  • Fixed request shape. Every lookup carries two encrypted queries and receives the same update broadcast, whether or not the address exists. A gateway, rate limiter or cache in front of the server must treat all lookups alike, or it reintroduces the leak.
  • Authentication and metering. API keys, quotas and billing sit in your gateway as usual. Meter by request count; the request body is ciphertext and says nothing about the address.
  • Replicas and rolling swaps. Every server that serves the same database shares one --crs-seed, so client queries stay valid across machines and rebuilds. crates/front is a switchable forwarder for moving traffic between machines; its admin endpoint has no authentication, so keep it inside the deployment boundary. scripts/roleswap-demo.sh shows a swap between two GPUs with zero failed lookups.
  • Logging. Access logs record source IP and timing, which PIR does not hide. Handle them under your existing log policy.

Capacity planning

  • A 16 GB PIR database holds the 206.8 million mainnet accounts holding ETH at 77% load. An H100 serves and rebuilds it on one card, while a 32 GB card needs a second card for rebuilds.
  • Every account, about 419 million, needs a 32 GB PIR database: about 50 GiB of GPU memory, and 107 GiB during a rebuild (extrapolated from the measured size).
  • Throughput is about 44 lookups per second per card at the 16 GB size over HTTP (the engine alone allows about 57), and scales by adding cards that each serve a full copy.
  • Batching queries from independent clients raises throughput. The server’s scheduler already does this.
  • A smaller dataset raises throughput: the engine alone answers 579 queries per second over a 1 GB database.

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, the sharded PIR design on ethresear.ch, and the roadmap.

← All projects