Bitcoin SPV Verifier Spec
The original can be found at Zenon Developer Commons .
Bitcoin SPV Verifier Specification
Phase: 0 - Reference Model & Threat Formalization Status: Exploratory / Research Draft
1. Purpose
This document specifies a deterministic, platform-agnostic Bitcoin SPV verifier. Any conforming implementation must produce identical verification results for identical inputs.
Goal: Enable interoperability between implementations across languages and platforms.
2. Scope
In Scope
- SPV proof format specification
- Verification algorithm specification
- Error conditions and codes
- Test vectors for conformance
Out of Scope
- Header acquisition (see Header Relay Protocol)
- Network protocol (see P2P specification)
- Storage format (implementation choice)
- Performance optimization (implementation choice)
3. Data Structures
3.1 Bitcoin Header
BitcoinHeader {
version: int32 // 4 bytes, little-endian
prev_block: bytes32 // 32 bytes, internal byte order
merkle_root: bytes32 // 32 bytes, internal byte order
timestamp: uint32 // 4 bytes, little-endian
bits: uint32 // 4 bytes, little-endian
nonce: uint32 // 4 bytes, little-endian
}Total size: exactly 80 bytes
3.2 Merkle Branch
MerkleBranch {
siblings: bytes32[] // Sibling hashes at each tree level
positions: uint8[] // Position flags (0=left, 1=right)
}Constraints:
- len(siblings) == len(positions)
- len(siblings) ≤ 14 (maximum tree depth for 16,384 transactions)
- All position values are 0 or 1
3.3 SPV Proof
SPVProof {
headers: BitcoinHeader[] // Header chain (oldest first)
tx_hash: bytes32 // Transaction to verify
merkle_branch: MerkleBranch // Inclusion proof
block_index: uint16 // Index of tx's block in headers
}Constraints:
- 2 ≤ len(headers) ≤ 2016
- block_index < len(headers)
4. Cryptographic Functions
4.1 SHA256
Standard SHA-256 as defined in FIPS 180-4.
4.2 DoubleSHA256
DoubleSHA256(data) = SHA256(SHA256(data))Used for:
- Block hash computation
- Merkle tree construction
4.3 Target from Bits
BitsToTarget(bits: uint32) -> uint256:
exponent = bits >> 24
mantissa = bits & 0x007fffff
if exponent <= 3:
target = mantissa >> (8 * (3 - exponent))
else:
target = mantissa << (8 * (exponent - 3))
return target4.4 Chainwork from Target
ChainworkFromTarget(target: uint256) -> uint256:
// Work = 2^256 / (target + 1)
return (2^256 - 1) / (target + 1)5. Verification Algorithm
5.1 Main Verification Function
VerifySPV(proof: SPVProof, min_confirmations: uint16) -> Result:
// Step 1: Structural validation
if len(proof.headers) < 2:
return Error(E_TOO_FEW_HEADERS)
if len(proof.headers) > 2016:
return Error(E_TOO_MANY_HEADERS)
if proof.block_index >= len(proof.headers):
return Error(E_INVALID_BLOCK_INDEX)
depth = len(proof.headers) - proof.block_index - 1
if depth < min_confirmations:
return Error(E_INSUFFICIENT_CONFIRMATIONS)
// Step 2: Header chain validation
for i in 1..len(proof.headers):
prev_hash = DoubleSHA256(proof.headers[i-1])
if proof.headers[i].prev_block != prev_hash:
return Error(E_BROKEN_CHAIN)
// Step 3: Proof-of-work validation
for header in proof.headers:
if not VerifyPoW(header):
return Error(E_INVALID_POW)
// Step 4: Merkle inclusion validation
target_header = proof.headers[proof.block_index]
computed_root = ComputeMerkleRoot(proof.tx_hash, proof.merkle_branch)
if computed_root != target_header.merkle_root:
return Error(E_MERKLE_MISMATCH)
// Step 5: Return success with metadata
return Success {
tx_hash: proof.tx_hash,
block_hash: DoubleSHA256(target_header),
depth: depth,
chain_work: ComputeChainwork(proof.headers)
}5.2 Proof-of-Work Verification
VerifyPoW(header: BitcoinHeader) -> bool:
hash = DoubleSHA256(Serialize(header))
target = BitsToTarget(header.bits)
// Interpret hash as little-endian uint256
hash_value = BytesToUint256LE(hash)
return hash_value <= target5.3 Merkle Root Computation
ComputeMerkleRoot(tx_hash: bytes32, branch: MerkleBranch) -> bytes32:
current = tx_hash
for i in 0..len(branch.siblings):
sibling = branch.siblings[i]
position = branch.positions[i]
if position == 0:
// Sibling on left, current on right
current = DoubleSHA256(sibling || current)
else:
// Current on left, sibling on right
current = DoubleSHA256(current || sibling)
return current5.4 Chainwork Computation
ComputeChainwork(headers: BitcoinHeader[]) -> uint256:
total = 0
for header in headers:
target = BitsToTarget(header.bits)
work = ChainworkFromTarget(target)
total += work
return total6. Error Codes
| Code | Name | Description |
|---|---|---|
| E_TOO_FEW_HEADERS | Too few headers | Need at least 2 headers |
| E_TOO_MANY_HEADERS | Too many headers | Exceeds 2016 limit |
| E_INVALID_BLOCK_INDEX | Invalid block index | Index >= header count |
| E_INSUFFICIENT_CONFIRMATIONS | Insufficient confirmations | Depth < required |
| E_BROKEN_CHAIN | Broken chain | prev_block mismatch |
| E_INVALID_POW | Invalid PoW | Hash > target |
| E_MERKLE_MISMATCH | Merkle mismatch | Root doesn’t match |
| E_INVALID_MERKLE_DEPTH | Invalid Merkle depth | Branch too deep |
7. Serialization
7.1 Header Serialization
Headers are serialized as 80 consecutive bytes in the order:
- version (4 bytes, little-endian)
- prev_block (32 bytes)
- merkle_root (32 bytes)
- timestamp (4 bytes, little-endian)
- bits (4 bytes, little-endian)
- nonce (4 bytes, little-endian)
7.2 Proof Serialization
For network transmission and storage:
SerializedProof {
header_count: uint16 // Number of headers
headers: bytes[80][] // Raw header bytes
tx_hash: bytes32 // Transaction hash
branch_count: uint8 // Merkle branch depth
siblings: bytes32[] // Branch siblings
positions: bytes // Packed position bits
block_index: uint16 // Index of tx block
}8. Conformance Requirements
A conforming implementation MUST:
- Accept any proof that satisfies the structural constraints
- Reject any proof that fails any verification step
- Return the same error code for the same invalid proof
- Compute identical chainwork values
- Pass all provided test vectors
A conforming implementation MAY:
- Use different internal representations
- Optimize computation (as long as results are identical)
- Add additional validation (as long as valid proofs still pass)
9. Implementation Notes
9.1 Byte Order
Bitcoin uses internal byte order for hashes:
- When displaying: reverse to get “natural” order
- When computing: use internal order as-is
9.2 Integer Overflow
Chainwork computation can overflow uint256 for very long chains. Implementations should handle this gracefully.
9.3 Timing Attacks
Verification should be constant-time for the same proof structure to prevent timing-based attacks.
10. References
- Bitcoin Protocol Documentation: https://developer.bitcoin.org/reference/
- BIP 37 (Merkle Branch Format): https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
- FIPS 180-4 (SHA-256): https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
Document Type: Technical Specification Status: Draft Test Vectors: See Bitcoin SPV Test Vectors document