SPV

Complete examples for implementing Simplified Payment Verification (SPV) clients and systems for BSV.

Overview

Simplified Payment Verification (SPV) allows lightweight clients to verify transactions without downloading the entire blockchain. This guide demonstrates building SPV clients, managing block headers, creating merkle proofs, and implementing efficient payment verification systems.

Related SDK Components:

Basic SPV Client

import { Transaction, BlockHeader, MerkleProof, Hash } from '@bsv/sdk'

/**
 * Basic SPV Client
 *
 * Lightweight client for verifying transactions using SPV
 */
class BasicSPVClient {
  private headers: Map<string, StoredHeader> = new Map()
  private verifiedTxs: Set<string> = new Set()
  private chainTip: string | null = null

  /**
   * Add block header to the client
   */
  addHeader(header: BlockHeader): void {
    try {
      const blockHash = header.hash()

      console.log('Adding block header:', blockHash)

      // Store header
      this.headers.set(blockHash, {
        header,
        height: this.calculateHeight(header),
        addedAt: Date.now()
      })

      // Update chain tip
      if (!this.chainTip || this.isNewTip(header)) {
        this.chainTip = blockHash
        console.log('New chain tip:', blockHash)
      }

      console.log('Header added successfully')
    } catch (error) {
      throw new Error(`Failed to add header: ${error.message}`)
    }
  }

  /**
   * Get block header by hash
   */
  getHeader(blockHash: string): BlockHeader | null {
    const stored = this.headers.get(blockHash)
    return stored ? stored.header : null
  }

  /**
   * Verify transaction with merkle proof
   */
  async verifyTransaction(
    tx: Transaction,
    blockHash: string,
    merkleProof: MerkleProofData
  ): Promise<VerificationResult> {
    try {
      const txid = tx.id('hex')

      console.log('Verifying transaction:', txid)
      console.log('Block:', blockHash)

      // Get header
      const stored = this.headers.get(blockHash)
      if (!stored) {
        return {
          verified: false,
          error: 'Block header not found'
        }
      }

      // Verify merkle proof
      const proofValid = this.verifyMerkleProof(
        txid,
        merkleProof,
        stored.header.merkleRoot
      )

      if (!proofValid) {
        return {
          verified: false,
          error: 'Invalid merkle proof'
        }
      }

      // Mark as verified
      this.verifiedTxs.add(txid)

      console.log('Transaction verified successfully')

      return {
        verified: true,
        blockHeight: stored.height,
        confirmations: this.calculateConfirmations(blockHash)
      }
    } catch (error) {
      return {
        verified: false,
        error: error.message
      }
    }
  }

  /**
   * Verify merkle proof
   */
  private verifyMerkleProof(
    txid: string,
    proof: MerkleProofData,
    merkleRoot: string
  ): boolean {
    try {
      let hash = Hash.sha256(Hash.sha256(Buffer.from(txid, 'hex')))
      let index = proof.index

      // Climb the merkle tree
      for (const sibling of proof.nodes) {
        const siblingBuffer = Buffer.from(sibling, 'hex')
        const isRightNode = index % 2 === 1

        const combined = isRightNode
          ? Buffer.concat([siblingBuffer, hash])
          : Buffer.concat([hash, siblingBuffer])

        hash = Hash.sha256(Hash.sha256(combined))
        index = Math.floor(index / 2)
      }

      return hash.toString('hex') === merkleRoot
    } catch (error) {
      console.error('Merkle proof verification failed:', error.message)
      return false
    }
  }

  /**
   * Calculate block height
   */
  private calculateHeight(header: BlockHeader): number {
    // Find previous header and increment height
    const prevHeader = this.headers.get(header.prevBlockHash)
    return prevHeader ? prevHeader.height + 1 : 0
  }

  /**
   * Check if header is new chain tip
   */
  private isNewTip(header: BlockHeader): boolean {
    if (!this.chainTip) return true

    const currentTip = this.headers.get(this.chainTip)
    if (!currentTip) return true

    const newHeight = this.calculateHeight(header)
    return newHeight > currentTip.height
  }

  /**
   * Calculate confirmations for a block
   */
  private calculateConfirmations(blockHash: string): number {
    if (!this.chainTip) return 0

    const block = this.headers.get(blockHash)
    const tip = this.headers.get(this.chainTip)

    if (!block || !tip) return 0

    return Math.max(0, tip.height - block.height + 1)
  }

  /**
   * Check if transaction has been verified
   */
  isVerified(txid: string): boolean {
    return this.verifiedTxs.has(txid)
  }

  /**
   * Get client statistics
   */
  getStats(): SPVStats {
    return {
      headersCount: this.headers.size,
      verifiedTxsCount: this.verifiedTxs.size,
      chainTip: this.chainTip,
      tipHeight: this.chainTip
        ? this.headers.get(this.chainTip)?.height || 0
        : 0
    }
  }

  /**
   * Sync headers from a source
   */
  async syncHeaders(
    startHeight: number,
    count: number,
    headerSource: (height: number) => Promise<BlockHeader>
  ): Promise<number> {
    console.log(`Syncing ${count} headers from height ${startHeight}`)

    let synced = 0

    for (let i = 0; i < count; i++) {
      const height = startHeight + i

      try {
        const header = await headerSource(height)
        this.addHeader(header)
        synced++

        if ((i + 1) % 100 === 0) {
          console.log(`Synced ${i + 1}/${count} headers`)
        }
      } catch (error) {
        console.error(`Failed to sync header at height ${height}:`, error.message)
        break
      }
    }

    console.log(`Sync complete: ${synced} headers synced`)

    return synced
  }
}

interface StoredHeader {
  header: BlockHeader
  height: number
  addedAt: number
}

interface MerkleProofData {
  index: number
  nodes: string[]
}

interface VerificationResult {
  verified: boolean
  blockHeight?: number
  confirmations?: number
  error?: string
}

interface SPVStats {
  headersCount: number
  verifiedTxsCount: number
  chainTip: string | null
  tipHeight: number
}

/**
 * Usage Example
 */
async function basicSPVExample() {
  const client = new BasicSPVClient()

  console.log('=== Basic SPV Client ===')

  // Add block headers
  const header = new BlockHeader({
    version: 1,
    prevBlockHash: '0000000000000000000000000000000000000000000000000000000000000000',
    merkleRoot: 'merkle-root-hash...',
    time: Date.now(),
    bits: 0x1d00ffff,
    nonce: 12345
  })

  client.addHeader(header)

  // Verify transaction
  const tx = Transaction.fromHex('tx-hex...')
  const blockHash = header.hash()

  const merkleProof: MerkleProofData = {
    index: 0,
    nodes: ['node1...', 'node2...']
  }

  const result = await client.verifyTransaction(tx, blockHash, merkleProof)

  console.log('Verification result:', result)

  // Get statistics
  const stats = client.getStats()
  console.log('SPV Client stats:', stats)
}

Advanced SPV Client with Header Chain Management

SPV Payment Verification System

See Also

SDK Components:

Learning Paths:

Last updated