Security Best Practices
This comprehensive guide covers essential security practices when developing Bitcoin applications with the BSV TypeScript SDK. Following these guidelines will help you build secure, production-ready applications that protect user funds and data.
Prerequisites
- Understanding of Bitcoin cryptography fundamentals
- Familiarity with the BSV TypeScript SDK
- Basic knowledge of secure coding practices
- Understanding of common attack vectors in cryptocurrency applications
📚 Related Concepts: This guide builds on Key Management, Trust Model, Digital Signatures, and Transaction Verification concepts.
Key Security Principles
1. Private Key Management
Never Expose Private Keys
// ❌ NEVER do this - exposing private key in logs or UI
console.log('Private key:', privateKey.toWif())
alert(`Your key: ${privateKey.toWif()}`)
// ✅ Proper handling - keep private keys secure
const privateKey = PrivateKey.fromRandom()
// Use the key for operations without exposing it
const publicKey = privateKey.toPublicKey()
Secure Key Generation
import { PrivateKey } from '@bsv/sdk'
// ✅ Use cryptographically secure random generation
const secureKey = PrivateKey.fromRandom()
// ❌ Never use predictable sources
// const weakKey = PrivateKey.fromString('1') // Predictable
// const timeKey = PrivateKey.fromString(Date.now().toString()) // Predictable
Key Storage Best Practices
// ✅ For production applications, use secure storage
class SecureKeyManager {
private encryptionKey: SymmetricKey
constructor() {
// Derive encryption key from user password or hardware security module
this.encryptionKey = SymmetricKey.fromRandom()
}
async storePrivateKey(privateKey: PrivateKey, identifier: string): Promise<void> {
const keyData = privateKey.toWif()
const encrypted = this.encryptionKey.encrypt(keyData)
// Store encrypted key in secure storage (not localStorage for production)
await this.secureStorage.set(identifier, Buffer.from(encrypted).toString('base64'))
}
async retrievePrivateKey(identifier: string): Promise<PrivateKey> {
const encryptedData = await this.secureStorage.get(identifier)
const encryptedBuffer = Buffer.from(encryptedData, 'base64')
const decrypted = this.encryptionKey.decrypt(Array.from(encryptedBuffer), 'utf8')
return PrivateKey.fromWif(decrypted as string)
}
}
2. Transaction Security
Input Validation and Sanitization
import { Transaction, PrivateKey, P2PKH } from '@bsv/sdk'
class SecureTransactionBuilder {
static validateAmount(satoshis: number): void {
if (!Number.isInteger(satoshis)) {
throw new Error('Amount must be an integer')
}
if (satoshis <= 0) {
throw new Error('Amount must be positive')
}
if (satoshis > 21000000 * 100000000) {
throw new Error('Amount exceeds maximum possible Bitcoin supply')
}
}
static validateAddress(address: string): void {
try {
// Validate address format
P2PKH.unlock('', 'all', {
publicKey: address, // This will throw if invalid
signature: { inputIndex: 0, outputs: [], inputScript: '' }
})
} catch (error) {
throw new Error('Invalid Bitcoin address format')
}
}
static async createSecureTransaction(
privateKey: PrivateKey,
recipientAddress: string,
amount: number
): Promise<Transaction> {
// Validate all inputs
this.validateAmount(amount)
this.validateAddress(recipientAddress)
// Create transaction with validated inputs
const tx = new Transaction()
// ... transaction construction logic
return tx
}
}
Fee Calculation Security
// ✅ Always validate fee calculations to prevent fee attacks
class SecureFeeCalculator {
private static readonly MIN_FEE_RATE = 0.5 // satoshis per byte
private static readonly MAX_FEE_RATE = 1000 // satoshis per byte
static calculateFee(transactionSize: number, feeRate: number): number {
// Validate fee rate is within reasonable bounds
if (feeRate < this.MIN_FEE_RATE || feeRate > this.MAX_FEE_RATE) {
throw new Error(`Fee rate must be between ${this.MIN_FEE_RATE} and ${this.MAX_FEE_RATE} sat/byte`)
}
const fee = Math.ceil(transactionSize * feeRate)
// Additional validation to prevent excessive fees
if (fee > 100000) { // 0.001 BSV maximum fee
throw new Error('Calculated fee is unreasonably high')
}
return fee
}
}
3. Cryptographic Operations Security
Secure Random Number Generation
import { PrivateKey, SymmetricKey } from '@bsv/sdk'
// ✅ Always use the SDK's secure random generation
const securePrivateKey = PrivateKey.fromRandom()
const secureSymmetricKey = SymmetricKey.fromRandom()
// ❌ Never use Math.random() for cryptographic purposes
// const insecureKey = PrivateKey.fromString(Math.random().toString())
ECDH Key Exchange Security
import { PrivateKey, PublicKey } from '@bsv/sdk'
class SecureECDH {
static performKeyExchange(
myPrivateKey: PrivateKey,
theirPublicKey: PublicKey
): Buffer {
try {
// The SDK automatically validates the public key and prevents twist attacks
const sharedSecret = myPrivateKey.deriveSharedSecret(theirPublicKey)
// ✅ Always derive keys from the shared secret, never use it directly
if (!sharedSecret.x) {
throw new Error('Invalid shared secret')
}
const sharedSecretBuffer = Buffer.from(sharedSecret.x.toArray())
const contextBuffer = Buffer.from('application-specific-context', 'utf8')
const combinedBuffer = Buffer.concat([sharedSecretBuffer, contextBuffer])
const derivedKey = Hash.sha256(Array.from(combinedBuffer))
return Buffer.from(derivedKey)
} catch (error) {
throw new Error('Key exchange failed: Invalid public key')
}
}
}
AES Encryption Security
import { SymmetricKey, Hash } from '@bsv/sdk'
class SecureEncryption {
// ✅ Proper key derivation from passwords
static deriveKeyFromPassword(password: string, salt: Buffer): SymmetricKey {
if (password.length < 12) {
throw new Error('Password must be at least 12 characters')
}
// Use multiple rounds of hashing for key derivation
let derived = Array.from(Buffer.concat([Buffer.from(password, 'utf8'), salt]))
for (let i = 0; i < 10000; i++) {
derived = Hash.sha256(derived)
}
return new SymmetricKey(derived)
}
// ✅ Secure encryption with proper error handling
static encryptData(data: string, key: SymmetricKey): string {
try {
const encrypted = key.encrypt(data)
return Buffer.from(encrypted).toString('base64')
} catch (error) {
// Don't expose internal error details
throw new Error('Encryption failed')
}
}
// ✅ Secure decryption with validation
static decryptData(encryptedData: string, key: SymmetricKey): string {
try {
const encrypted = Buffer.from(encryptedData, 'base64')
const decrypted = key.decrypt(Array.from(encrypted), 'utf8')
return decrypted as string
} catch (error) {
throw new Error('Decryption failed: Invalid data or key')
}
}
}
4. Wallet Integration Security
Secure WalletClient Usage
import { WalletClient } from '@bsv/sdk'
class SecureWalletManager {
private wallet: WalletClient | null = null
private connectionAttempts = 0
private readonly MAX_CONNECTION_ATTEMPTS = 3
async connectWallet(): Promise<void> {
if (this.connectionAttempts >= this.MAX_CONNECTION_ATTEMPTS) {
throw new Error('Maximum connection attempts exceeded')
}
try {
this.wallet = new WalletClient('auto', 'localhost')
// Connection is established during construction
this.connectionAttempts = 0 // Reset on successful connection
} catch (error) {
this.connectionAttempts++
throw new Error('Wallet connection failed')
}
}
async createSecureTransaction(outputs: any[]): Promise<any> {
if (!this.wallet) {
throw new Error('Wallet not connected')
}
// Validate all outputs before creating transaction
for (const output of outputs) {
if (!output.satoshis || output.satoshis <= 0) {
throw new Error('Invalid output amount')
}
if (!output.lockingScript) {
throw new Error('Missing locking script')
}
}
try {
return await this.wallet.createAction({
description: 'Secure transaction',
outputs,
// ✅ Always include proper error handling options
options: {
acceptDelayedBroadcast: false, // Ensure immediate feedback
randomizeOutputs: true // Enhance privacy
}
})
} catch (error) {
// Log error securely without exposing sensitive data
console.error('Transaction creation failed:', error.message)
throw new Error('Transaction creation failed')
}
}
}
When using the WalletClient
interface, follow these security practices:
Secure WalletClient
Usage
The WalletClient
provides built-in security features, but proper usage is essential:
WalletClient
Connection Management
When working with WalletClient
connections:
5. Network Security
Secure Chain Tracker Usage
import { ChainTracker, WhatsOnChain } from '@bsv/sdk'
class SecureChainTracker {
private trackers: ChainTracker[]
private currentTrackerIndex = 0
constructor() {
// ✅ Use multiple chain trackers for redundancy
this.trackers = [
new WhatsOnChain('main'),
// Add additional trackers for failover
]
}
async getTransactionWithRetry(txid: string): Promise<any> {
let lastError: Error | null = null
// Try each tracker
for (let i = 0; i < this.trackers.length; i++) {
try {
const tracker = this.trackers[this.currentTrackerIndex]
const result = await tracker.getTransaction(txid)
// Validate the response
if (!result || !result.id) {
throw new Error('Invalid transaction response')
}
return result
} catch (error) {
lastError = error as Error
this.currentTrackerIndex = (this.currentTrackerIndex + 1) % this.trackers.length
}
}
throw new Error(`All chain trackers failed: ${lastError?.message}`)
}
}
6. SPV Verification Security
Secure Merkle Proof Verification
import { Transaction, MerklePath } from '@bsv/sdk'
class SecureSPVVerifier {
static async verifyTransaction(
transaction: Transaction,
merklePath: MerklePath,
blockHeader: any
): Promise<boolean> {
try {
// ✅ Always verify the merkle proof
const txid = Buffer.from(transaction.id()).toString('hex')
const computedRoot = merklePath.computeRoot(txid)
if (computedRoot !== blockHeader.merkleRoot) {
throw new Error('Merkle proof verification failed')
}
// ✅ Verify the transaction itself
const isValid = await transaction.verify()
if (!isValid) {
throw new Error('Transaction verification failed')
}
return true
} catch (error) {
console.error('SPV verification failed:', error.message)
return false
}
}
}
7. Error Handling Security
Secure Error Reporting
class SecureErrorHandler {
// ✅ Sanitize error messages to prevent information leakage
static sanitizeError(error: Error): string {
const sensitivePatterns = [
/private.*key/i,
/seed/i,
/mnemonic/i,
/password/i,
/secret/i
]
let message = error.message
for (const pattern of sensitivePatterns) {
message = message.replace(pattern, '[REDACTED]')
}
return message
}
// ✅ Secure error logging
static logError(error: Error, context: string): void {
const sanitizedMessage = this.sanitizeError(error)
console.error(`[${context}] ${sanitizedMessage}`)
// In production, send to secure logging service
// Never log sensitive information
}
}
Common Security Vulnerabilities
1. Private Key Exposure
// ❌ Common mistakes that expose private keys
class InsecureExamples {
// Never store keys in plain text
private userKey = 'L1234567890abcdef...' // Exposed in source code
// Never log private keys
debugTransaction(privateKey: PrivateKey) {
console.log('Signing with key:', privateKey.toWif()) // Logged
}
// Never send keys over insecure channels
async sendKeyToServer(key: PrivateKey) {
await fetch('http://api.example.com/keys', { // HTTP not HTTPS
method: 'POST',
body: JSON.stringify({ key: key.toWif() })
})
}
}
2. Insufficient Input Validation
// ❌ Vulnerable to various attacks
class VulnerableTransaction {
async createTransaction(amount: string, address: string) {
// No validation - vulnerable to injection and overflow
const tx = new Transaction()
tx.addOutput({
satoshis: parseInt(amount), // No validation
lockingScript: address // No validation
})
return tx
}
}
3. Weak Random Number Generation
// ❌ Predictable and insecure
class WeakRandomness {
generatePrivateKey(): PrivateKey {
// Predictable seed
const seed = Date.now().toString() + Math.random().toString()
return PrivateKey.fromString(seed)
}
}
Security Resources
Additional Reading
- Bitcoin Security Best Practices
- Cryptographic Best Practices
- OWASP Cryptographic Storage Cheat Sheet
Conclusion
Security in Bitcoin applications requires constant vigilance and adherence to best practices. The BSV TypeScript SDK provides many security features out of the box, but proper implementation and configuration are crucial for maintaining security.