GOMTU Crypto
guidePart 38 of 41 in this guide

Ethereum eth_getProof Guide: Verify Account and Storage State

Learn what eth_getProof returns, how account and storage proofs connect to a block state root, and which trust and availability limits still matter.

GOMTU
GOMTU
Crypto Research · September 12, 2026 · 8 min read
Share𝕏in
Ethereum eth_getProof Guide: Verify Account and Storage State

An RPC server can tell you an Ethereum balance or contract value in one line. The harder question is: can you verify that answer against a block instead of trusting the server's database? This blockchain basics guide explains eth_getProof, the JSON-RPC method that returns an account proof and optional contract-storage proofs for exactly that job.

The method is useful, but it is not a magic “trustless” switch. A proof only establishes a value relative to a particular state root. You still need an authentic block header, an exact block reference, correct trie verification, and the right interpretation of the returned bytes.

What eth_getProof Does

Advertisement

Think of Ethereum's state as a warehouse with one tamper-evident seal on the door. A normal RPC read asks the warehouse operator to tell you what is on one shelf. eth_getProof asks for the shelf value plus the chain of sealed boxes connecting that shelf to the door seal. You can recompute the hashes locally and reject a path that does not end at the expected seal.

The formal name for that seal is the block's stateRoot. Ethereum's current Execution API specification defines eth_getProof as returning a Merkle proof for an account and, optionally, selected storage keys. The older EIP-1186 proposal describes the motivation and proof structure.

There is an important status nuance. EIP-1186 is marked Stagnant, so you should not call the proposal itself a newly finalized EIP. The method nevertheless appears in the current Execution API and is implemented by clients such as Geth. Check the exact client and provider you use rather than inferring support from the EIP status alone.

The Two Proof Layers

Ethereum execution state is nested. Understanding that nesting prevents most verification mistakes.

  1. The global state trie maps a hashed account address to an account record.
  2. That account record contains nonce, balance, storageRoot, and codeHash.
  3. A contract's separate storage trie maps hashed 32-byte storage positions to values.

The Ethereum Merkle Patricia trie guide explains the full data structure. For eth_getProof, the practical consequence is simple: accountProof connects the account record to the block stateRoot; each storageProof connects one storage key and value to the account's storageHash (the storage root returned by the API).

That means a storage check has two stages. First verify the account proof against the trusted block root. Then use the authenticated storage root inside that account to verify the requested slot. Skipping the first stage leaves you trusting the server-supplied storage root.

Requesting an Account and Storage Proof

The request takes three positional parameters:

  1. A 20-byte account address.
  2. An array of storage keys; use an empty array if you only need the account proof.
  3. A block number or supported block tag.

This example follows the parameter shapes in the current Execution API:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_getProof",
  "params": [
    "0xe5cB067E90D5Cd1F8052B83562Ae670bA4A211a8",
    [
      "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
    ],
    "finalized"
  ]
}

Do not copy that storage key into an unrelated contract and expect a meaningful variable. Solidity mappings, arrays, packed fields, proxies, and namespaced storage all change which slot you need. Work out the position using verified source and compiler metadata first; the Ethereum contract storage slots guide walks through that process.

For a reproducible audit, prefer an explicit block number and record its hash. Tags are moving references. The Execution API describes latest as the most recent canonical block observed by the client, while safe and finalized carry stronger consensus meanings. Use the same resolved block for both eth_getProof and eth_getBlockByNumber.

Reading the Response

The response contains several related fields:

FieldWhat it representsVerification role
addressRequested accountConfirm the response target
balanceAccount ETH balance as a hex quantityPart of the account record
nonceAccount nonce as a hex quantityPart of the account record
codeHashHash of the account's bytecodePart of the account record
storageHashRoot of the account's storage trieAnchor for storage proofs
accountProofEncoded trie nodes along the account pathVerify the account against stateRoot
storageProofKey, value, and trie-node path for each requested slotVerify slots against storageHash

The proof arrays are not ordinary sibling-hash lists from a simple binary Merkle tree. Ethereum's current execution state uses a modified Merkle Patricia trie. Nodes use RLP encoding, paths use nibble-oriented compact encoding, and short nodes may be embedded rather than referenced by a hash. Use a well-tested verifier or client library rather than inventing a production decoder from a blog example.

eth_getProof can also prove absence. A path ending in an empty branch position or a leaf whose remaining path differs can demonstrate that the requested account or slot is not present relative to the root. Absence is not the same as “the RPC server returned an error,” and a verifier must enforce the trie rules precisely.

A Safe Verification Workflow

Use this sequence when the result matters:

  1. Choose one block. Resolve a number and hash, not just a moving UI label.
  2. Authenticate the header. Obtain the block header through a consensus-aware light client, your own verified node, or another trust model you have documented.
  3. Read its stateRoot. This is the root the account proof must reproduce.
  4. Request eth_getProof at the same block. Include only the storage keys you actually need.
  5. Verify the account path. Derive the account trie key from the address, validate every encoded node, and compare the computed root with the header's stateRoot.
  6. Decode the account record. Check that its nonce, balance, storage root, and code hash match the response fields.
  7. Verify each storage path. Use the authenticated storage root, not an unverified copy from elsewhere.
  8. Decode application meaning separately. A proven 32-byte word still needs the correct contract, proxy context, storage layout, type, and byte offset.

This separation matters. Cryptography can prove “these bytes belonged at this path under this root.” It cannot prove “this variable name is correct,” “this contract is safe,” or “this asset is worth buying.”

Risks and Limits

A state root needs a trustworthy origin

If the same untrusted RPC server supplies both a fake header and a proof built for that fake header, the hashes can agree with each other. The proof becomes meaningful only when your verification boundary supplies an authentic header. This is why eth_getProof complements rather than replaces the light-client and trustless-RPC model.

Historical state may be unavailable

A node can validate the chain without retaining every old state for immediate random access. Ethereum's archive-node documentation explains that archive configurations retain historical states, while other nodes may prune them and reconstruct older state at extra cost. Providers can impose additional method, block-range, or rate limits. Test the exact endpoint and handle explicit failures.

Correct bytes can still be misinterpreted

A storage proof does not carry Solidity variable names or a verified ABI. Reading an implementation address instead of a proxy, using the wrong mapping key encoding, or decoding packed fields at the wrong offset can turn a valid proof into a false application claim.

Proof support is not uniform everywhere

Ethereum's JSON-RPC documentation recommends checking individual client documentation for current API support. EVM-compatible networks may use different state commitments, expose a similar method with different limits, or omit it. Do not assume Ethereum behavior across chains.

Proofs do not remove operational risk

Verification code can contain parsing bugs, denial-of-service weaknesses, or unsafe defaults. Pin library versions, use official test vectors when available, cap response sizes, test malformed nodes, and fail closed when the block, root, address, or key does not match exactly.

Practical Checklist

  • Did you record the chain ID, block number, and block hash?
  • Is the header authenticated independently of the proof provider?
  • Did the account proof reproduce that header's stateRoot?
  • Did you verify storage proofs against the root authenticated by the account proof?
  • Are the address and storage keys encoded exactly as the trie expects?
  • Does verified source and compiler metadata support your slot interpretation?
  • Have you accounted for proxies, packed fields, mappings, and custom layouts?
  • Does your chosen client or provider support the requested historical block?
  • Does the verifier reject malformed, incomplete, or mismatched proofs?

FAQ

Is eth_getProof the same as eth_getStorageAt?

No. eth_getStorageAt returns a storage word for an address, position, and block. eth_getProof can return that value together with the trie nodes needed to verify it against the account's storage root, plus the account proof needed to reach the block state root.

Can I verify an ETH balance without requesting storage keys?

Yes. Pass an empty storage-key array and verify the account record. The balance is part of that record, along with the nonce, storage root, and code hash.

Does a valid proof mean the block is finalized?

No. Proof validity and consensus finality answer different questions. The proof binds data to a root; your authenticated header and block-selection policy determine which chain state you accept.

Can eth_getProof reveal every storage slot in a contract?

No. You request known storage keys. The enormous key space and hashed locations used by mappings mean the method is not a portable storage enumerator.

Is EIP-1186 finalized?

No. Its EIP page is marked Stagnant. For current method syntax and response validation, use the live Ethereum Execution API specification and verify behavior against your chosen client.

Key Takeaway

eth_getProof turns an RPC assertion into something you can check against an authenticated Ethereum state root. The chain of trust is the whole point: header to state root, state root to account, account storage root to slot, and slot bytes to carefully verified application meaning.

Keep those layers separate, fail closed on every mismatch, and verify current client behavior against primary documentation. This guide is educational and does not provide financial advice. Crypto assets and applications carry technical, counterparty, and market risk; verify independently and DYOR.

Primary Sources

Advertisement

Keep learning

Explore related topics

More from GOMTU