Skip to main content

Merkle Proofs

Every resolution the API returns comes with a Merkle proof. It lets you check the answer against a state root instead of trusting the server that sent it.

The tree

PiNS keeps all names in a compact Sparse Merkle Tree over a 128-bit key space. A name's key is the first 16 bytes of SHA-256(domain_name), so names distribute evenly and no one can choose where their name lands.

"Compact" means a leaf sits at the shallowest depth where its key prefix is unique, rather than at a fixed depth of 128. In a tree holding a few thousand names most proofs are only a dozen or so hashes — small enough to verify on a phone, in a browser extension, or inside a wallet.

Two consequences worth knowing before you write a verifier:

  • Proof length varies. Read proof_depth from the response; never assume a fixed size.
  • An empty subtree is 32 zero bytes at every height. So the root of an empty tree is all zeros — a freshly initialised indexer reporting 0000…0000 is correct, not broken.

What a proof contains

FieldMeaning
merkle_proofsibling hashes, [0] deepest (closest to the leaf)
proof_depthnumber of siblings — must equal merkle_proof.length
proof_terminalwhat sits at the end of the path: Occupied, Vacant or Blocked
smt_rootthe root these siblings reconstruct

Presence and absence

The terminal is what lets a proof say "this name is not registered" as convincingly as "it is":

  • Occupied — the key's own leaf is at the end of the path. The name exists, and the record you were given is the one in the tree.
  • Vacant — an empty subtree is there. The name is unregistered.
  • Blocked — a different name's leaf occupies that position. The name is unregistered; the response carries the blocking record so you can verify it hashes to the leaf found there, and that its key really does share the prefix.

Without Blocked, an indexer could hide a registration by claiming a slot was empty. Because the blocking record is included in full, you can check the claim rather than accept it.

Verifying

Hash the record into a leaf, then fold the siblings back to the root using the key's bits to decide left or right at each step. If you arrive at smt_root, the record is exactly what the tree commits to.

That still leaves one question — is smt_root the real root? Answer it by asking the anchor contract on BNB Smart Chain. Both steps, with tested code in Python, PHP and JavaScript, are on Verifying Proofs.