SPV
Overview
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
Related Examples
See Also
Last updated
