SPV Validation

Complete examples for validating transactions, merkle proofs, and block headers using Simplified Payment Verification (SPV).

Overview

SPV validation involves cryptographically verifying that transactions are included in blocks without downloading the entire blockchain. This guide demonstrates merkle proof validation, block header validation, transaction verification, and building secure SPV validation systems with proper error handling.

Related SDK Components:

Basic Merkle Proof Validation

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

/**
 * Basic Merkle Proof Validator
 *
 * Validate merkle proofs for SPV
 */
class BasicMerkleValidator {
  /**
   * Validate merkle proof for a transaction
   */
  validateMerkleProof(
    txid: string,
    merkleProof: MerkleProofData,
    merkleRoot: string
  ): ValidationResult {
    try {
      console.log('Validating merkle proof...')
      console.log('TXID:', txid)
      console.log('Merkle root:', merkleRoot)

      // Start with transaction hash
      let hash = Hash.sha256(Hash.sha256(Buffer.from(txid, 'hex')))
      let index = merkleProof.index

      console.log(`Climbing merkle tree (${merkleProof.nodes.length} levels)`)

      // Climb the merkle tree
      for (let i = 0; i < merkleProof.nodes.length; i++) {
        const sibling = merkleProof.nodes[i]
        const siblingBuffer = Buffer.from(sibling, 'hex')

        // Determine if current node is on right or left
        const isRightNode = index % 2 === 1

        console.log(`Level ${i}: index=${index}, isRight=${isRightNode}`)

        // Concatenate in correct order
        const combined = isRightNode
          ? Buffer.concat([siblingBuffer, hash])
          : Buffer.concat([hash, siblingBuffer])

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

      const calculatedRoot = hash.toString('hex')
      const valid = calculatedRoot === merkleRoot

      console.log('Calculated root:', calculatedRoot)
      console.log('Expected root:', merkleRoot)
      console.log('Valid:', valid)

      return {
        valid,
        calculatedRoot,
        expectedRoot: merkleRoot,
        error: valid ? undefined : 'Merkle root mismatch'
      }
    } catch (error) {
      console.error('Validation failed:', error.message)

      return {
        valid: false,
        error: error.message
      }
    }
  }

  /**
   * Validate merkle proof with block header
   */
  validateWithBlockHeader(
    txid: string,
    merkleProof: MerkleProofData,
    blockHeader: BlockHeaderData
  ): ValidationResult {
    try {
      console.log('Validating with block header')

      // Validate merkle proof
      const proofResult = this.validateMerkleProof(
        txid,
        merkleProof,
        blockHeader.merkleRoot
      )

      if (!proofResult.valid) {
        return proofResult
      }

      // Additional header validation could go here

      console.log('Block header validation passed')

      return {
        valid: true,
        blockHash: blockHeader.hash,
        blockHeight: blockHeader.height
      }
    } catch (error) {
      return {
        valid: false,
        error: error.message
      }
    }
  }

  /**
   * Validate multiple proofs in batch
   */
  batchValidate(
    proofs: ProofToValidate[]
  ): BatchValidationResult[] {
    console.log(`Batch validating ${proofs.length} merkle proofs`)

    const results: BatchValidationResult[] = []

    for (let i = 0; i < proofs.length; i++) {
      const proof = proofs[i]

      try {
        const result = this.validateMerkleProof(
          proof.txid,
          proof.merkleProof,
          proof.merkleRoot
        )

        results.push({
          index: i,
          txid: proof.txid,
          valid: result.valid,
          error: result.error
        })
      } catch (error) {
        results.push({
          index: i,
          txid: proof.txid,
          valid: false,
          error: error.message
        })
      }
    }

    const validCount = results.filter(r => r.valid).length
    console.log(`Batch validation complete: ${validCount}/${proofs.length} valid`)

    return results
  }

  /**
   * Estimate proof size for a given block
   */
  estimateProofSize(txCount: number): number {
    // Merkle tree depth
    const depth = Math.ceil(Math.log2(txCount))

    // Each node is 32 bytes
    const proofSize = depth * 32

    // Add overhead for index and metadata
    const overhead = 8

    return proofSize + overhead
  }
}

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

interface BlockHeaderData {
  hash: string
  merkleRoot: string
  height?: number
}

interface ValidationResult {
  valid: boolean
  calculatedRoot?: string
  expectedRoot?: string
  blockHash?: string
  blockHeight?: number
  error?: string
}

interface ProofToValidate {
  txid: string
  merkleProof: MerkleProofData
  merkleRoot: string
}

interface BatchValidationResult {
  index: number
  txid: string
  valid: boolean
  error?: string
}

/**
 * Usage Example
 */
async function basicMerkleValidationExample() {
  const validator = new BasicMerkleValidator()

  console.log('=== Basic Merkle Proof Validation ===')

  // Validate merkle proof
  const txid = 'transaction-id...'
  const merkleProof: MerkleProofData = {
    index: 2,
    nodes: [
      'sibling1...',
      'parent-sibling...',
      'grandparent-sibling...'
    ]
  }
  const merkleRoot = 'merkle-root...'

  const result = validator.validateMerkleProof(txid, merkleProof, merkleRoot)

  if (result.valid) {
    console.log('Proof is valid!')
  } else {
    console.log('Proof is invalid:', result.error)
  }

  // Estimate proof size
  const proofSize = validator.estimateProofSize(10000)
  console.log('Estimated proof size for 10,000 txs:', proofSize, 'bytes')
}

Advanced SPV Validation

SPV Validation Service

See Also

SDK Components:

Learning Paths:

Last updated