Skip to main content

SP1 Zero-Knowledge Proofs

Each REGISTER transaction is proven correct inside SP1's RISC-V zkVM. The guest program verifies four things:

  1. Name validity -- 3-63 chars, lowercase alphanumeric + hyphens
  2. Fee sufficiency -- payment meets the tiered fee schedule
  3. Ed25519 signature -- verifies over "REGISTER:{name}:{address}"
  4. SMT non-membership + insert -- proves the name is not already taken, then computes the new root

Public outputs committed by the proof: name, address, pubkey, fee paid (value_zat), old SMT root, new SMT root.

Architecture

ComponentDescription
zns-prover/program/SP1 guest ELF (zns-register-program) -- no_std RISC-V binary
zns-prover/lib/zns-smt crate -- depth-128 SHA-256 SMT with hardcoded empty hashes
zns-prover/script/Host-side prover + prover daemon

Proof Pipeline

  1. Scanner processes a REGISTER, captures SMT non-membership proof (128 siblings) + old/new roots, stores a proof record with status pending
  2. Prover daemon polls the DB for pending records, builds SP1Stdin, generates compressed SP1 proof, verifies it, stores proof bytes with status proved
  3. API serves the proof via GET /v1/proof/{name}

Performance (CPU proving, Apple Silicon)

MetricValue
Ed25519 verification~95,000 cycles
SMT verify + insert (128 levels)~270,000 cycles
Total per registration~382,000 cycles
Compressed proof time (CPU)~105 seconds

Running

# Test prover (standalone, hardcoded test data)
cd zns-prover/script
cargo run --release --bin zns-register # Execute mode (logic check, ~7s)
cargo run --release --bin zns-register -- --prove # Prove mode (compressed proof, ~105s)

# Prover daemon (production, reads from scanner DB)
cd zns-prover/script
cargo run --release --bin zns-prover-daemon -- --db ../../zns/zns.db # Real proofs
cargo run --release --bin zns-prover-daemon -- --db ../../zns/zns.db --execute-only # Logic check only

Proof Chaining

Each registration proof takes the previous root as input and outputs the new root. The chain of proofs creates a verifiable history: root_0 -> root_1 -> root_2 -> ... -> root_current.

SMT Root Persistence

The SMT root is stored in SQLite after every block with state changes:

  • GET /v1/state/root -- current SMT root + block height + total registrations
  • GET /v1/status -- includes smt_root field

Independent indexers can compare roots. If two indexers process the same blocks and get different roots, one of them is wrong.