Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Light-client verification

A light client checks what a node tells it, with proofs, against one value it pinned: the chain identity. Verify as a light client explains sion verify line by line. This guide covers the other checks, the options, and what happens when a check cannot be made.

The commands

CommandChecks
sion verify [--chain] [--pulse] [--samples] [--no-world]A whole Pulse: the chain, the committees, the certificate, forced inclusion, receipts, a state proof, data availability, the World Proof and lane certificates
sion receipt <digest> [--chain]One transaction: its Pulse’s certificate, its place in the certified order, and its receipt proven against the receipts root
MCP sion_verify_pulse, sion_fetch_receiptThe same, for agents
sion_light (Rust)The building blocks: ChainSpec, EpochChain, verify_certificate, verify_object, verify_claim, verify_receipt, availability::sample

Pin the identity once per shell with export SION_CHAIN=<identity>, or pass --chain each time.

Receipts

$ sion receipt 0b4b87c8ed9db1d35d2548e9dfa2d7c529c680a2cd264d9cfcd683f85b897d39
Pulse 5: Done: paid 12.5 USD to alice; fee 0.0012 USD.
  certified by 300000 of the committee's stake (quorum 266667), dual post-quantum signatures ✓
  receipt proven against the certified header's receipts root ✓
  chain 6345655c (NOT pinned: pass --chain to pin it)

With --chain, the last line reads chain 6345655c (pinned). A transaction that was never certified cannot have a receipt:

$ sion receipt 8f26f159c0723de6c19a5e1f2fa4f8ea5a2e2ec2eace19d8ffe823db99df69e5
error: verification failed: never certified: object ObjectRef { id: ObjectId(27657e3b), version: Version(0) } is locked to transaction TxDigest(93d6ac50)

Objects and claims

Every object lives in the sparse Merkle state tree, and every claim in the knowledge log. The node serves both with proofs:

  • GET /v1/objects/{id} returns the object (or null) with a proof against the latest state root. A proof for a missing object proves its absence.
  • GET /v1/claims/{id} returns the claim with its inclusion proof in the knowledge log.

sion verify checks an account object against the Pulse’s state root when the node’s latest state is the Pulse being verified:

state: an account object proven against the state root ✓

When the node has already moved on, it says so instead:

state: proof skipped (node already at Pulse 7)

In Rust, sion_light::verify_object(header, proof) and sion_light::verify_claim(header, proof) check such proofs against a certified header.

Data availability

sion verify samples random shares of the Pulse’s extended data square, at positions it draws from fresh randomness, and checks each against the certified data root. More samples, more confidence:

$ sion verify --chain 6345655cfd2a714ed1dab69c0660f2f48f429ce2981e25629d0467d7b7ee97b7 --no-world --samples 30
…
availability: 30 of 30 random shares of the 32×32 square verified; a withholding would go unnoticed with probability < 4.0e-5 ✓

The bound is (3/4)^s for s samples: to prevent reconstruction, a withholder must hide at least a quarter of the extended square. See Data availability.

Across epochs

On a devnet whose validator set changes, sion verify follows the committee from epoch 0 through every boundary, using GET /v1/epochs/{e}, before it checks a certificate:

committees: followed from the pinned epoch 0 to epoch 1 through 1 certified boundaries and validator-set proofs ✓
certificate: 350000 of 450000 stake signed (quorum 300001), each signature ML-DSA-44 + SLH-DSA-SHA2-128f ✓

See Staking and epochs.

Choosing a Pulse

By default sion verify takes the most recent certified Pulse that executed transactions (and, unless --no-world, has a World Proof), looking back up to 32 Pulses, and otherwise the latest certified one. --pulse <n> picks one. A node keeps headers and receipts for the last 256 Pulses, data shares for the last 64 and World Proofs for the last 32:

$ sion pulse 99999
error: unknown or pruned Pulse

When a check cannot be made

Without a prover (sion devnet --no-prove) there are no World Proofs, and sion verify without --no-world fails with a misleading message:

$ sion verify
error: verification failed: no certified Pulse yet
$ sion verify --no-world
chain: 6345655c (NOT PINNED: learned from the node itself; pass --chain 6345655cfd2a714ed1dab69c0660f2f48f429ce2981e25629d0467d7b7ee97b7 to pin it)
Pulse 2 header 9b0f5f15 (state root f4149220, knowledge root b3950ae7, 0 claims)
certificate: 300000 of 400000 stake signed (quorum 266667), each signature ML-DSA-44 + SLH-DSA-SHA2-128f ✓
…

A node that serves another chain than the pinned one fails the first check:

$ sion verify --chain 0000000000000000000000000000000000000000000000000000000000000000
error: verification failed: header chain: the node serves chain 6345655c, not the pinned chain 00000000

On a devnet started with --insecure-fast-signatures, certificates are simulation MACs that only the devnet can check. The chain identity records the mode, and verification reports the certificate check as skipped rather than passed. That devnet closes Pulses so fast (about 300 a second on the machine used for this book) that the CLI’s 20-Pulse expiry and the 256-Pulse history are useless:

$ sion send dana alice 1
dana signs: Pay 1 USD to alice. Fee 0.0012 USD; valid until Pulse 1332.
error: not certified: the transaction has expired
$ sion verify --no-world
error: unknown or pruned Pulse

What it costs

On the machine used for this book, sion verify took 3.5 to 6.4 seconds for a one-transaction Pulse with its World Proof, and 3.7 seconds without it; for so small a Pulse the proof is not the main cost. A World Proof is a replay proof, though: for a busy Pulse, re-verifying it costs as much as executing it (impl:replay-proofs). See Benchmarks.