Verify
Trust, and verify.
Where Secondlayer can prove an answer, it serves the proof: a hash, a merkle path, a signature, or a replay you run yourself. A check that passes leaves you trusting a smaller, named thing. A check that can't run says so, and is never a pass.
Every layer has a check. Each one leaves you trusting a smaller, named thing.
verifySecondlayerSignature(rawBody, headers, key)The signing key: proves who sent it, not that the chain agreessecondlayer verify subgraph <name> --replayThe checkpoint; state subgraphs onlyGET /v1/index/transactions/:tx_id/proofThe signer set in the proof, unless you pass your ownsecondlayer verify raw --against <latest.json>The archive key, and the range's assurance levelsecondlayer verify block <height>The checkpoint shipped in the releaseOne command walks every link from the checkpoint to the block's state. It works at any height, including below the checkpoint.
Try it in your browser, no install:
verifyTransactionProof recomputes the txid, merkle root, block hash, and signer weight from one proof.
Proof levels
A 200 returns the raw transaction, raw header, merkle path, and (only when the reward set could be resolved) consensus.
| Level | Checks |
|---|---|
anchored | txid to merkle path to the header's tx_merkle_root, plus header self-consistency. Any Stacks node can corroborate it. |
consensus | Also recovers the header's signer signatures and confirms at least 70% of the reward cycle's signer weight signed. |
With no reward set, consensus uses the one embedded in the proof, which trusts Secondlayer for the signer set alone. fetchRewardSet resolves it from your own stacks-node (/v3/stacker_set/{cycle}) instead:
| Status / code | Meaning |
|---|---|
404 PROOF_UNAVAILABLE | The transaction, or the block containing it, was not found. |
503 PROOF_TX_SET_INCOMPLETE | The server couldn't reproduce the block's tx_merkle_root from its stored transactions, so it refuses to emit a proof that wouldn't verify. |
503 PROOF_NODE_UNAVAILABLE | The signed-header source (a stacks-node) was unreachable. Retryable. |
The transaction verifier uses Node's crypto (via @secondlayer/shared), so it is server-side only.
Bitcoin transactions
verifyBitcoinTx follows a transaction from its bytes to its txid, merkle root and block header, then checks that header against the checkpoint's Bitcoin chain, with no contract call.
To read the proof from your own Bitcoin node, spread a source over it. The node needs txindex=1, and the verifier re-checks every byte, so an Esplora endpoint works the same way.
Pair it with a state proof of sBTC completed-withdrawal-sweep to follow a withdrawal from Stacks state to its Bitcoin output.
A block below the checkpoint skips the Bitcoin and signer links. The checkpoint's header commits to every ancestor by hash, so an ancestry link ties the block to it, by parent links or one MARF proof.
- The skipped links read
not needed: the block is hash-chained to the checkpoint. txsreadsnot checked: no transaction bytesfor an epoch 2.x block or when the source served the header only.
| Exit | Meaning |
|---|---|
0 | Every link holds |
1 | A link is broken; the first is named |
2 | Could not check |
The tradeoff: a lying source can fail a link but never pass one, and the price is work. The first check syncs Bitcoin headers from the checkpoint, and each block fetches a state witness.
| Flag | Effect |
|---|---|
--node <url> | Read blocks and MARF proofs from your Stacks node instead of the API |
--checkpoint <file> | Trust your own checkpoint JSON instead of the one built into the release |
--rows | Also check the indexed vm_events rows against the diff. Billed as Index rows; everything else is free |
--json | Full result on stdout |
@secondlayer/verify is the same check as a library.
verifyBlock never throws for bad source data: it returns ok: false and the first broken link.
With your own node, blocks and MARF proofs come from its RPC. Witnesses, burn preimages and Bitcoin headers still come from the API, checked the same way. Use BlockVerifier to verify many blocks from one checkpoint and reuse the synced headers and signer sets.
Pass rows: true to also check the indexed vm_events rows (billed as Index rows).
result.transactionsholds the block's checked transactions;result.writesits named writes.- The library uses
fetchand pure-JS crypto (@noble), with no Node built-ins.
The manifest states a level per range. Most of the chain is db-reconstructive.
A published node.json upgrades only its coverage window.
| Level | Means |
|---|---|
db-reconstructive | Exports from one repeatable-read snapshot restore deterministically. Does not prove no historical callback was omitted. |
node-attested | Block identities also match an independently synced stacks-node. Transactions and events stay db-reconstructive. |
observer-attested | Digests match independently retained raw observer history. |
The HTTP proofs behind every check, and what can't be proven yet.
Proofs API
The raw material verify block reads. Free, never metered, no key needed.
On the hosted API the limits are per IP, and an account key doubles them:
| Route | Per IP | With an account key |
|---|---|---|
| Any proofs route | 10/s | 20/s |
| Witnesses | 1/s | 2/s |
Answers addressed by a hash never change: they carry an ETag and Cache-Control: immutable.
On your own box the witness, burn and Bitcoin-header routes need a proof sidecar: Self-host.
| Route | Returns |
|---|---|
/v1/proofs/block/height/{height} | Signed block by height |
/v1/proofs/block/{index_block_hash} | Signed block by id |
/v1/proofs/witness/{index_block_hash} | State witness the header's state root must hash to |
/v1/proofs/writes/{height} | Every written leaf of the block, named |
/v1/proofs/marf/{path} | MARF inclusion proof for a key at a block |
/v1/proofs/burn/{consensus_hash} | Burn block a consensus hash commits to |
/v1/proofs/bitcoin-headers | Raw Bitcoin headers from a height |
/v1/proofs/bitcoin-tx/{txid} | Bitcoin transaction, merkle branch and header |
/v1/proofs/epoch2-header/{index_block_hash} | Epoch 2.x header by id |
The transaction proof above is /v1/index/transactions/{tx_id}/proof.
What it does not cover yet
| Gap | Effect |
|---|---|
| Print events and transaction results | Stacks headers don't commit to them, so no block check covers them. |
| Which transaction made a write | The transaction root binds each transaction's bytes, not which one wrote a leaf or whether it succeeded. |
events and none subgraphs | Only state subgraphs can be replayed. Rows anyone can recompute. |
| The checkpoint | Trusted as-is. Pass your own to change that. |
| Bitcoin reorgs | Headers are checked for valid work, not against a competing chain, and reorgs past the synced tip are not followed. |
__MARF_* keys from a node alone | A node serves no proof for them, so with NodeRpcProofSource alone, blocks more than 16 below a trusted block fail ancestry as unavailable. |
| Epoch 2.x transactions | Skipped with a note. |
| Unnamed writes | Without state_writes for the block, writes are proven, not named. |