Skip to Content
DocsResearchBitcoin Integration & SPVGreenpaper SPV Implementation Guide

Greenpaper SPV Implementation Guide

The original can be found at Zenon Developer Commons .

ZENON GREENPAPER SPV IMPLEMENTATION GUIDE

Practical Companion for Builders

Status: Exploratory / Research Draft


Purpose

This guide provides a practical, builder-oriented companion to the Zenon Greenpaper’s SPV sections. It bridges the gap between theoretical feasibility and actual implementation by offering concrete specifications, code patterns, and decision frameworks.

Audience: Developers implementing Bitcoin SPV verification on Zenon

Prerequisites: Understanding of Bitcoin SPV basics, Zenon account-chain model


1. What You’re Building

The Core Function

VerifySPV(proof) -> bool Input: SPV proof package (headers, merkle branch, tx hash) Output: true if tx is validly included, false otherwise

What This Proves

A successful verification proves:

  • Transaction was included in a Bitcoin block
  • Block has valid proof-of-work
  • Block is part of a chain with specified cumulative work
  • Transaction has at least N confirmations

What This Does NOT Prove

  • Transaction script executed correctly
  • Transaction is on the current canonical Bitcoin chain
  • Transaction will remain confirmed (finality is probabilistic)

2. Data Structures

Bitcoin Header (80 bytes)

type BitcoinHeader struct { Version uint32 // 4 bytes, little-endian PrevBlock [32]byte // 32 bytes MerkleRoot [32]byte // 32 bytes Timestamp uint32 // 4 bytes, little-endian Bits uint32 // 4 bytes, little-endian (difficulty target) Nonce uint32 // 4 bytes, little-endian }

Merkle Branch

type MerkleBranch struct { Siblings [][32]byte // Sibling hashes at each level Positions []uint8 // 0 = sibling on left, 1 = sibling on right }

SPV Proof Package

type SPVProof struct { Headers []BitcoinHeader // Chain of headers (oldest first) TxHash [32]byte // Transaction to verify MerkleBranch MerkleBranch // Inclusion proof BlockIndex uint16 // Which header contains the tx }

3. Verification Algorithm

Step 1: Validate Header Chain

func ValidateHeaderChain(headers []BitcoinHeader) error { for i := 1; i < len(headers); i++ { prevHash := DoubleSHA256(headers[i-1]) if headers[i].PrevBlock != prevHash { return ErrBrokenChain } } return nil }

Step 2: Verify Proof-of-Work

func VerifyPoW(header BitcoinHeader) error { hash := DoubleSHA256(header) target := BitsToTarget(header.Bits) if BytesToBigInt(hash) > target { return ErrInvalidPoW } return nil }

Step 3: Verify Merkle Inclusion

func VerifyMerkle(txHash [32]byte, root [32]byte, branch MerkleBranch) error { current := txHash for i, sibling := range branch.Siblings { if branch.Positions[i] == 0 { // Sibling on left current = DoubleSHA256(concat(sibling, current)) } else { // Sibling on right current = DoubleSHA256(concat(current, sibling)) } } if current != root { return ErrMerkleMismatch } return nil }

Step 4: Combined Verification

func VerifySPV(proof SPVProof, minDepth int) error { // Check we have enough headers for depth if len(proof.Headers) - proof.BlockIndex - 1 < minDepth { return ErrInsufficientDepth } // Validate chain linkage if err := ValidateHeaderChain(proof.Headers); err != nil { return err } // Verify PoW for all headers for _, h := range proof.Headers { if err := VerifyPoW(h); err != nil { return err } } // Verify Merkle inclusion targetHeader := proof.Headers[proof.BlockIndex] if err := VerifyMerkle(proof.TxHash, targetHeader.MerkleRoot, proof.MerkleBranch); err != nil { return err } return nil // Verification successful }

4. Critical Implementation Details

Double SHA256

Bitcoin uses SHA256(SHA256(data)) throughout. Don’t forget the double hash.

func DoubleSHA256(data []byte) [32]byte { first := sha256.Sum256(data) return sha256.Sum256(first[:]) }

Endianness

Bitcoin uses little-endian for most values. Be careful when:

  • Parsing header fields
  • Comparing hashes (treated as big integers)
  • Displaying hashes (usually shown big-endian)

Bits to Target Conversion

func BitsToTarget(bits uint32) *big.Int { exponent := bits >> 24 mantissa := bits & 0x007fffff target := big.NewInt(int64(mantissa)) target.Lsh(target, uint(8*(exponent-3))) return target }

Confirmation Depth

Use CaseRecommended DepthRationale
Low value1-3Fast, acceptable risk
Medium value6Bitcoin standard
High value12+Extra security
Critical100+Near-final

Header Chain Length

Minimum: depth + 1 (tx block + confirmations) Recommended: depth + 6 (buffer for tip changes)

Proof Size Limits

  • Max headers: 2016 (one difficulty period)
  • Max Merkle depth: 14 (covers ~16,000 tx/block)

6. Error Handling

Error Types

var ( ErrInsufficientDepth = errors.New("not enough confirmations") ErrBrokenChain = errors.New("header chain broken") ErrInvalidPoW = errors.New("proof-of-work invalid") ErrMerkleMismatch = errors.New("merkle root mismatch") ErrInvalidProofSize = errors.New("proof exceeds size limits") )

Handling Failures

  • ErrInsufficientDepth: Wait for more blocks, resubmit proof later
  • ErrBrokenChain: Proof is invalid or corrupted; obtain new proof
  • ErrInvalidPoW: Header is fake; reject and possibly ban source
  • ErrMerkleMismatch: Wrong branch or wrong tx; obtain correct proof

7. Testing Strategy

Test Vectors

Use real Bitcoin mainnet data for test vectors:

  • Known transaction with known Merkle proof
  • Headers from specific heights
  • Edge cases (single-tx blocks, max-size blocks)

Fuzz Testing

Fuzz all parsing code:

  • Malformed headers
  • Invalid lengths
  • Extreme values for bits field

Integration Testing

  • Verify against Bitcoin full node
  • Cross-check with multiple SPV implementations
  • Test with testnet transactions

8. What’s NOT in This Guide

This guide covers verification only. NOT covered:

  • Header sourcing: How to get Bitcoin headers
  • Eclipse resistance: How to avoid header source attacks
  • Incentives: How to pay for header relay
  • State storage: How to store verified facts

These are covered in separate specifications.


9. Implementation Checklist

  • Double SHA256 implemented correctly
  • Endianness handled in all conversions
  • Header parsing handles all fields
  • Chain linkage verified
  • PoW verified for ALL headers
  • Merkle verification handles both positions
  • Depth calculation correct
  • Size limits enforced
  • Error handling complete
  • Test vectors pass
  • Fuzz testing clean

References


Document Status: Implementation Guide Version: Draft

Last updated on