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 Name Resolution Proof

When a client resolves a ZCash domain name, the process is executed in four logical stages to guarantee absolute security, sync validity, and state integrity without trusting the indexer node:

  1. Domain Resolution Query: The client makes a request to a ZcashNames Resolution Indexer endpoint (e.g. GET /v1.0/resolve/richard.zcash). The indexer returns the domain details including the target ZCash address, owner public key, price, nonce, SMT root, and 128 sibling hashes representing the path in the Sparse Merkle Tree (SMT).

  2. Local SMT Proof Verification: To prevent the indexer from returning a fake resolution, the client validates the Merkle membership proof locally. The leaf data is constructed by concatenating: domain_name (UTF-8 bytes) + owner_pubkey (32 bytes from hex) + target_address (UTF-8 bytes) + price (u64 little-endian) + nonce (u64 little-endian). We compute the SHA-256 hash of this leaf data, and traverse the SMT up to 128 levels using the sibling hashes. If the calculated root matches the indexer's smt_root, the proof is cryptographically sound.

  3. Latest Root Sync Validation: To check if the indexer's root is in sync with the blockchain, we query the latest validated SMT root on-chain from the EVM (Arbitrum) contract using the currentRoot() function (selector 0xfdab463d). If it matches the indexer's smt_root, the indexer is fully synced.

  4. Historical Root Validation (EVM RPC fallback): If the indexer's root does not match the latest contract root (which can occur due to sync lag), we query the contract's mapping rootHistory(bytes32) (selector 0x66dd97ab) to verify if the indexer's root is historically valid:

    • If the mapping returns a non-zero value: The root was historically verified on the contract. The client is warned about a temporary indexer delay but can safely proceed.
    • If the mapping returns 0: The root has never been committed or validated on the blockchain. The resolution is blocked immediately for security reasons.

Local SMT Proof Code Examples

Here are examples in Python, PHP, and JavaScript showing how to verify the SMT proof of a resolved name using the full leaf hash layout:

import hashlib
import struct

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, owner_pubkey, price, nonce, 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 concatenated leaf values
domain_buf = domain_name.encode('utf-8')
pubkey_buf = bytes.fromhex(owner_pubkey)
address_buf = target_address.encode('utf-8')
price_buf = struct.pack('<Q', price) # 64-bit unsigned integer in little-endian
nonce_buf = struct.pack('<Q', nonce) # 64-bit unsigned integer in little-endian

leaf_data = domain_buf + pubkey_buf + address_buf + price_buf + nonce_buf
current = hashlib.sha256(leaf_data).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()

On-Chain Checkpoint Sync Verification

If the indexer's root mismatches the latest root validated on the contract, we check if it is historically valid by querying the EVM RPC node. Below are code examples in Python, PHP, and JavaScript querying rootHistory(bytes32) via an EVM RPC node:

import requests

def verify_root_on_contract(rpc_url, contract_address, smt_root):
# Selector for rootHistory(bytes32) is 66dd97ab
clean_root = smt_root.replace("0x", "").lower()
data = f"0x66dd97ab{clean_root.zfill(64)}"

payload = {
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": contract_address,
"data": data
},
"latest"
],
"id": 1
}

response = requests.post(rpc_url, json=payload).json()
if "error" in response:
raise Exception(f"EVM RPC Error: {response['error']['message']}")

result = response.get("result", "0x")
if not result or result == "0x":
return False

height = int(result.replace("0x", ""), 16)
return height > 0