Skip to main content

Verifying Proofs

Verifying Proofs Yourself

Proofs are standard SP1 compressed proofs. Verify them with the SP1 SDK:

use sp1_sdk::{ProverClient, SP1ProofWithPublicValues};

let client = ProverClient::from_env();
let (_, vk) = client.setup(ELF);
let proof = SP1ProofWithPublicValues::load("proof.bin")?;
client.verify(&proof, &vk)?;

The verification key is deterministic, derived from the guest program binary. Anyone can reproduce it.
The immutability of the ZkEVM SP1 guest program is proved by Program Key. The prover source code is available on GitHub. Anyone can compile it and compare the Program Keys. The Program Key will be changed if any changes are performed in SP1 guest program.

Checking the names proof

When you resolve a name, the API returns a membership proof inside the merkle_proof field consisting of 128 sibling hashes. You can use these sibling hashes to verify the mapping between the domain name and the target shielded address locally.

Here are examples in Python, PHP, and JavaScript showing how to verify the Sparse Merkle Tree (SMT) proof of a resolved name:

import hashlib

def get_bit(key_bytes, bit_idx):
"""
Returns the bit value (0 or 1) at a given index (0 to 127)
from MSB to LSB of the 16-byte key.
"""
byte_pos = bit_idx // 8
bit_pos = 7 - (bit_idx % 8)
return (key_bytes[byte_pos] >> bit_pos) & 1

def verify_smt_proof(domain_name, target_address, merkle_proof, expected_root):
# 1. Compute key as SHA-256 of lowercase domain name
key_hash = hashlib.sha256(domain_name.lower().encode('utf-8')).digest()
# Take first 16 bytes (128 bits) for a depth-128 SMT
key = key_hash[:16]

# 2. Compute leaf hash as SHA-256 of target address
current = hashlib.sha256(target_address.encode('utf-8')).digest()

# 3. Hash up the tree using the 128 sibling hashes
# Sibling proof array goes from bottom (closest to leaf, index 0) to top (closest to root, index 127)
for i in range(128):
sibling = bytes.fromhex(merkle_proof[i])

# Level 127 (index 0) corresponds to the bottom-most bit (bit 127)
# Level 0 (index 127) corresponds to the top-most bit (bit 0)
bit_idx = 127 - i
bit = get_bit(key, bit_idx)

if bit == 1:
# Current node is on the right, sibling is on the left
current = hashlib.sha256(sibling + current).digest()
else:
# Current node is on the left, sibling is on the right
current = hashlib.sha256(current + sibling).digest()

return current.hex() == expected_root.lower()