StorageProof
Retrieves a Merkle proof for contract storage values, proving that specific storage entries exist in the state at a given block.
Method Signature
func (p *Provider) StorageProof(
ctx context.Context,
input StorageProofInput,
) (*StorageProofResult, error)Parameters
ctx(context.Context): Context for request cancellation and timeoutsinput(StorageProofInput): Specification of which storage proofs to retrieve
Returns
*StorageProofResult: Merkle proofs for the requested storage valueserror: Error if the block is not found or storage proofs are not supported
Description
StorageProof returns cryptographic Merkle proofs that verify the existence and values of specific contract storage entries at a given block height. This enables trustless verification of storage state without downloading the entire state tree.
Use Cases:- Light Clients: Verify storage values without full node data
- Cross-Chain Bridges: Prove storage state to other chains
- Trustless Verification: Cryptographically verify contract state
- State Snapshots: Create verifiable snapshots of contract storage
StorageProofInput Structure
type StorageProofInput struct {
BlockID BlockID
ContractAddress *felt.Felt
Keys []*felt.Felt
}StorageProofResult Structure
type StorageProofResult struct {
ClassCommitment *felt.Felt
ContractProof []ProofNode
ContractData ContractData
StateCommitment *felt.Felt
}
type ContractData struct {
ClassHash *felt.Felt
Nonce *felt.Felt
Root *felt.Felt
StorageProofs [][]ProofNode
}
type ProofNode struct {
Binary *BinaryNode
Edge *EdgeNode
}Usage Example
package main
import (
"context"
"fmt"
"log"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/rpc"
)
func main() {
client, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
if err != nil {
log.Fatal(err)
}
// Contract address to prove storage for
contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
// Storage keys to prove
key1, _ := new(felt.Felt).SetString("0x1")
key2, _ := new(felt.Felt).SetString("0x2")
// Create proof input
input := rpc.StorageProofInput{
BlockID: rpc.BlockID{Tag: "latest"},
ContractAddress: contractAddr,
Keys: []*felt.Felt{key1, key2},
}
// Get storage proof
proof, err := client.StorageProof(context.Background(), input)
if err != nil {
log.Fatal(err)
}
fmt.Printf("State Commitment: %s\n", proof.StateCommitment)
fmt.Printf("Class Commitment: %s\n", proof.ClassCommitment)
fmt.Printf("Contract Class Hash: %s\n", proof.ContractData.ClassHash)
fmt.Printf("Contract Nonce: %s\n", proof.ContractData.Nonce)
fmt.Printf("Contract Storage Root: %s\n", proof.ContractData.Root)
fmt.Printf("Contract Proof Nodes: %d\n", len(proof.ContractProof))
fmt.Printf("Storage Proofs: %d\n", len(proof.ContractData.StorageProofs))
// Example output:
// State Commitment: 0x1a2b3c...
// Class Commitment: 0x4d5e6f...
// Contract Class Hash: 0x7a8b9c...
// Contract Nonce: 0x1
// Contract Storage Root: 0x0d1e2f...
// Contract Proof Nodes: 15
// Storage Proofs: 2
}Verifying a Storage Proof
// After getting the proof, you can verify it
func verifyStorageProof(proof *rpc.StorageProofResult, key, value *felt.Felt) bool {
// 1. Verify contract proof against state commitment
contractRoot := verifyMerklePath(
proof.StateCommitment,
proof.ContractAddress,
proof.ContractProof,
)
// 2. Verify storage proof against contract storage root
for i, storageProof := range proof.ContractData.StorageProofs {
storageValue := verifyMerklePath(
proof.ContractData.Root,
key,
storageProof,
)
if storageValue.Equal(value) {
return true
}
}
return false
}Multiple Storage Keys
// Prove multiple storage values at once
keys := []*felt.Felt{
mustFelt("0x1"),
mustFelt("0x2"),
mustFelt("0x5"),
}
input := rpc.StorageProofInput{
BlockID: blockID,
ContractAddress: contractAddr,
Keys: keys,
}
proof, err := client.StorageProof(ctx, input)
if err != nil {
log.Fatal(err)
}
// Each key gets a corresponding proof
for i, storageProof := range proof.ContractData.StorageProofs {
fmt.Printf("Proof for key %s: %d nodes\n", keys[i], len(storageProof))
}Historical Proofs
// Get proof at specific block height
blockID := rpc.BlockID{Number: 100000}
input := rpc.StorageProofInput{
BlockID: blockID,
ContractAddress: contractAddr,
Keys: keys,
}
// Proof is valid for the state at block 100000
proof, err := client.StorageProof(ctx, input)Use Cases
1. Light Client Verification
// Light client can verify storage without full state
proof, _ := client.StorageProof(ctx, input)
isValid := verifyProofAgainstStateRoot(proof, stateRoot)2. Cross-Chain Bridge
// Prove token balance to another chain
balanceKey := calculateStorageKey("balances", userAddress)
proof, _ := client.StorageProof(ctx, rpc.StorageProofInput{
BlockID: finalizedBlock,
ContractAddress: tokenContract,
Keys: []*felt.Felt{balanceKey},
})
// Submit proof to bridge contract on other chain3. State Snapshot Verification
// Create verifiable snapshot of multiple contracts
proofs := make(map[string]*rpc.StorageProofResult)
for _, contract := range importantContracts {
proof, _ := client.StorageProof(ctx, rpc.StorageProofInput{
BlockID: snapshotBlock,
ContractAddress: contract,
Keys: relevantKeys,
})
proofs[contract.String()] = proof
}Error Handling
proof, err := client.StorageProof(ctx, input)
if err != nil {
switch {
case errors.Is(err, rpc.ErrBlockNotFound):
log.Println("Block not found")
case errors.Is(err, rpc.ErrStorageProofNotSupported):
log.Println("Node doesn't support storage proofs")
case errors.Is(err, rpc.ErrProofLimitExceeded):
log.Println("Too many keys requested")
default:
log.Printf("RPC error: %v", err)
}
}Limitations
- Not all Starknet nodes support storage proofs
- There may be limits on the number of keys per request
- Historical proofs may not be available for very old blocks
- Pending blocks cannot be used (proofs require finalized state)
Related Methods
- StorageAt - Get storage value without proof
- StateUpdate - Get all state changes in a block
- BlockWithTxs - Get block details
RPC Specification
- Method:
starknet_getStorageProof - Version: RPC v0.9.0
- Returns: Merkle proofs for storage verification
Performance Notes
- Storage proofs can be large for deep Merkle trees
- Consider batching multiple keys in a single request
- Cache proofs for frequently verified storage values
- Proofs for recent blocks are faster to generate
Security Considerations
- Verify Against Trusted Root: Always verify proofs against a trusted state root
- Check Proof Completeness: Ensure all requested keys have corresponding proofs
- Validate Proof Structure: Verify Merkle path is well-formed
- Use Finalized Blocks: Don't rely on proofs from pending blocks

