SPV Verification
How Writz proves a Bitcoin transaction happened — without trusting anyone. SPV stands for Simplified Payment Verification. It is a technique — described in the original Bitcoin whitepaper by Satoshi Nakamoto — that allows a lightweight client to verify that a transaction is included in the Bitcoin blockchain without downloading the entire chain. Writz implements SPV inside a Soroban smart contract. This means the Stellar blockchain itself can verify Bitcoin transactions cryptographically. No oracle. No bridge operator. No committee. Just math.Why SPV Works
Bitcoin’s blockchain is a chain of blocks. Each block has a header that contains:- The hash of the previous block (linking the chain)
- A Merkle root — a single hash that commits to every transaction in the block
- A nonce and target — proof that miners did computational work to produce this block
- The block header — to verify it has valid proof-of-work
- A Merkle proof — a set of sibling hashes that, combined with your transaction’s hash, reproduce the Merkle root in the header
- The raw transaction — to verify the transaction data matches the hash
The Writz Approach: Stateless SPV
Most SPV implementations maintain state: they store a chain of Bitcoin block headers on-chain, and new headers are submitted by a relayer service. This creates a hard dependency on the relayer — if the relayer goes down, the system breaks. Writz uses stateless SPV: the caller provides all necessary headers at the time of verification. The contract validates them in a single transaction and does not store any headers. This has important implications:- No relayer dependency for core security: The SPV contract can be called by anyone with the right data. Writz runs a relayer service as a convenience, but it is not a critical path component.
- No growing on-chain state: A stateful header chain would grow indefinitely. Stateless SPV has zero persistent storage for headers.
- Simpler audit surface: The verification logic is a pure function — given inputs, it returns a result. No state transitions to reason about.
What the Contract Verifies
Thebitcoin-spv contract’s verify_transaction function takes:
- Parse the 80-byte header into its fields (version, prev_block, merkle_root, time, bits, nonce)
- Compute
SHA256d(header)— Bitcoin’s double-SHA256 block hash - Verify the hash meets the difficulty target encoded in
bits - Verify the chain is continuous:
headers[i].prev_block == SHA256d(headers[i-1])
- Compute
txid = SHA256d(SHA256d(raw_tx)) - Walk the Merkle proof: repeatedly hash
txidwith each sibling in the proof, alternating left/right based ontx_index - The final hash must equal
headers[0].merkle_root
len(headers) >= min_confirmations- Each header must extend the previous (Step 1 ensures this)
- Parse the raw transaction’s outputs
- Return
VerificationResult { txid, block_hash, confirmations, outputs }
SHA256d: Bitcoin’s Hash Function
Bitcoin uses double-SHA256 (SHA256 applied twice) for all cryptographic operations: block hashes, transaction IDs, and Merkle tree nodes.bitcoin-spv contract implements it in Wasm. This was the primary concern about feasibility: would the compute cost be acceptable?
Benchmarked instruction counts:
| Operation | Instructions |
|---|---|
| Single SHA256d | ~500,000 |
| Header PoW check (1 header) | ~600,000 |
| Merkle proof (20 levels) | ~10,000,000 |
| Full SPV verify (6 headers + proof) | ~37,000,000 |
| Soroban transaction limit | ~100,000,000 |
The Relayer Service
While stateless SPV means the relayer is not a critical security component, someone needs to assemble the proof bundle for users. The Writz relayer handles this. What the relayer does:- Watches a Bitcoin Esplora API for transactions to monitored addresses
- Fetches the raw transaction, the block header it was included in, and sufficient ancestor headers to meet
min_confirmations - Computes the Merkle proof from the block’s transaction list
- Packages everything into a
sorobanArgsbundle ready to submit to the SPV contract
Security Guarantees
6-confirmation requirement: Writz requires 6 Bitcoin block confirmations before accepting a deposit. A 6-block reorganization has never occurred in Bitcoin’s history. This provides practical certainty that a transaction will not be reversed. PoW validation: Each header’s proof-of-work is checked independently. A forged header chain would require generating valid PoW — computationally infeasible against Bitcoin’s current hash rate. Merkle proof soundness: The Merkle proof check is collision-resistant under SHA256. A fabricated proof would require a SHA256 preimage attack — computationally infeasible. What SPV does NOT protect against:- A user sending BTC to the wrong P2WSH address (user error)
- A deep reorg affecting more than 6 blocks (extraordinarily unlikely, but theoretically possible on a heavily attacked network)
- Bugs in the SPV contract itself (mitigated by audits and extensive testing)
Tested on Real Bitcoin Transactions
The SPV contract has been tested against:- Real Bitcoin mainnet transactions (correct
txidandblock_hashverified against Python-computed SHA256d) - Multiple block depths (1, 3, 6, 12 confirmations)
- Multi-transaction blocks with varying Merkle proof sizes
- Edge cases: single-transaction blocks, maximum-size transactions
CAE5L7BO2GNF7MIZWXB2BTUMLYNIMQZUSWN2BWLZQS7HRHLOUSL6VLWJ.
Next: The ZK Privacy Layer →