Skip to Content
DocsResearchBitcoin Integration & SPVBitcoin SPV Verifier Spec

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 target

4.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 <= target

5.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 current

5.4 Chainwork Computation

ComputeChainwork(headers: BitcoinHeader[]) -> uint256: total = 0 for header in headers: target = BitsToTarget(header.bits) work = ChainworkFromTarget(target) total += work return total

6. Error Codes

CodeNameDescription
E_TOO_FEW_HEADERSToo few headersNeed at least 2 headers
E_TOO_MANY_HEADERSToo many headersExceeds 2016 limit
E_INVALID_BLOCK_INDEXInvalid block indexIndex >= header count
E_INSUFFICIENT_CONFIRMATIONSInsufficient confirmationsDepth < required
E_BROKEN_CHAINBroken chainprev_block mismatch
E_INVALID_POWInvalid PoWHash > target
E_MERKLE_MISMATCHMerkle mismatchRoot doesn’t match
E_INVALID_MERKLE_DEPTHInvalid Merkle depthBranch too deep

7. Serialization

7.1 Header Serialization

Headers are serialized as 80 consecutive bytes in the order:

  1. version (4 bytes, little-endian)
  2. prev_block (32 bytes)
  3. merkle_root (32 bytes)
  4. timestamp (4 bytes, little-endian)
  5. bits (4 bytes, little-endian)
  6. 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:

  1. Accept any proof that satisfies the structural constraints
  2. Reject any proof that fails any verification step
  3. Return the same error code for the same invalid proof
  4. Compute identical chainwork values
  5. Pass all provided test vectors

A conforming implementation MAY:

  1. Use different internal representations
  2. Optimize computation (as long as results are identical)
  3. 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


Document Type: Technical Specification Status: Draft Test Vectors: See Bitcoin SPV Test Vectors document

Last updated on