@bsv/sdk
Version:
BSV Blockchain Software Development Kit
789 lines (609 loc) • 22.7 kB
Markdown
**Duration**: 75 minutes
**Prerequisites**: Basic TypeScript knowledge, understanding of cryptographic concepts
By completing this tutorial, you will:
- Understand cryptographic hash functions and their properties
- Master the Hash module classes and helper functions in the BSV TypeScript SDK
- Implement various hash algorithms (SHA-256, SHA-512, SHA-1, RIPEMD-160)
- Create and verify HMACs for message authentication
- Apply Bitcoin-specific hashing patterns (hash256, hash160)
- Build practical applications using hashing for data integrity and authentication
- Understand performance considerations and security best practices
## Introduction to Cryptographic Hashing
Cryptographic hash functions are mathematical algorithms that transform input data of any size into fixed-size output values. They are fundamental to Bitcoin's security architecture, providing:
- **Data Integrity**: Detect any changes to data
- **Digital Fingerprints**: Unique identifiers for data
- **Proof of Work**: Foundation for Bitcoin's consensus mechanism
- **Address Generation**: Converting public keys to Bitcoin addresses
The BSV TypeScript SDK provides comprehensive hashing capabilities through the `Hash` module, supporting both class-based and functional approaches.
## Setting Up Your Environment
First, import the necessary modules from the BSV SDK:
```typescript
import { Hash, Utils } from '@bsv/sdk'
```
The `Hash` module contains:
- Hash function classes (`SHA256`, `SHA512`, `SHA1`, `RIPEMD160`)
- HMAC classes (`SHA256HMAC`, `SHA512HMAC`, `SHA1HMAC`)
- Helper functions (`sha256`, `sha512`, `hash256`, `hash160`, `sha256hmac`)
- Utility functions for data conversion and encoding
## Basic Hash Function Usage
### SHA-256 Hashing
SHA-256 is Bitcoin's primary hash function. Here's how to use it:
```typescript
// Method 1: Using the SHA256 class
const sha256Hasher = new Hash.SHA256()
sha256Hasher.update('Message to hash')
const hashedMessage = sha256Hasher.digestHex()
console.log('SHA-256 hash:', hashedMessage)
// Output: f1aa45b0f5f6703468f9b9bc2b9874d4fa6b001a170d0f132aa5a26d00d0c7e5
// Method 2: Using the helper function
const message = 'Hello, Bitcoin!'
const hashResult = Hash.sha256(Utils.toArray(message, 'utf8'))
console.log('SHA-256 hash (binary):', hashResult)
// Convert to hex for display
const hashHex = Utils.toHex(hashResult)
console.log('SHA-256 hash (hex):', hashHex)
```
The Hash functions can process various data formats:
```typescript
// String input
const stringHash = Hash.sha256(Utils.toArray('Hello World', 'utf8'))
// Hex string input
const hexHash = Hash.sha256(Utils.toArray('deadbeef', 'hex'))
// Binary array input
const binaryData = [0x01, 0x02, 0x03, 0x04]
const binaryHash = Hash.sha256(binaryData)
// JSON data hashing
const jsonData = { name: 'Alice', amount: 100 }
const jsonString = JSON.stringify(jsonData)
const jsonHash = Hash.sha256(Utils.toArray(jsonString, 'utf8'))
console.log('String hash:', Utils.toHex(stringHash))
console.log('Hex hash:', Utils.toHex(hexHash))
console.log('Binary hash:', Utils.toHex(binaryHash))
console.log('JSON hash:', Utils.toHex(jsonHash))
```
The SDK supports multiple hash algorithms:
```typescript
// SHA-512
const sha512Hasher = new Hash.SHA512()
sha512Hasher.update('Message for SHA-512')
const sha512Result = sha512Hasher.digestHex()
console.log('SHA-512 hash:', sha512Result)
// SHA-1 (legacy, use with caution)
const sha1Hasher = new Hash.SHA1()
sha1Hasher.update('Message for SHA-1')
const sha1Result = sha1Hasher.digestHex()
console.log('SHA-1 hash:', sha1Result)
// RIPEMD-160 (used in Bitcoin address generation)
const ripemdHasher = new Hash.RIPEMD160()
ripemdHasher.update('Message for RIPEMD-160')
const ripemdResult = ripemdHasher.digestHex()
console.log('RIPEMD-160 hash:', ripemdResult)
// Using helper functions
const sha512Helper = Hash.sha512(Utils.toArray('Hello', 'utf8'))
console.log('SHA-512 helper result:', Utils.toHex(sha512Helper))
```
Bitcoin uses double SHA-256 hashing for block headers and transaction IDs:
```typescript
// Double SHA-256 using hash256 helper
const message = 'Bitcoin transaction data'
const doubleHash = Hash.hash256(Utils.toArray(message, 'utf8'))
console.log('Double SHA-256 hash:', Utils.toHex(doubleHash))
// Manual double hashing
const firstHash = Hash.sha256(Utils.toArray(message, 'utf8'))
const secondHash = Hash.sha256(firstHash)
console.log('Manual double hash:', Utils.toHex(secondHash))
// Both methods produce the same result
console.log('Results match:', Utils.toHex(doubleHash) === Utils.toHex(secondHash))
```
Used for Bitcoin address generation:
```typescript
// Hash160: SHA-256 followed by RIPEMD-160
const publicKeyData = 'compressed_public_key_hex_data'
const hash160Result = Hash.hash160(Utils.toArray(publicKeyData, 'hex'))
console.log('Hash160 result:', Utils.toHex(hash160Result))
// Manual implementation
const sha256First = Hash.sha256(Utils.toArray(publicKeyData, 'hex'))
const ripemd160Second = new Hash.RIPEMD160().update(sha256First).digest()
console.log('Manual Hash160:', Utils.toHex(ripemd160Second))
// Results should match
console.log('Hash160 results match:',
Utils.toHex(hash160Result) === Utils.toHex(ripemd160Second))
```
HMACs (Hash-based Message Authentication Codes) provide both data integrity and authentication by incorporating a secret key into the hashing process.
```typescript
// SHA-256 HMAC
const key = 'secret_key'
const message = 'Message to authenticate'
// Method 1: Using HMAC class
const hmacHasher = new Hash.SHA256HMAC(key)
hmacHasher.update(message)
const hmacResult = hmacHasher.digestHex()
console.log('HMAC-SHA256:', hmacResult)
// Output: b4d897472c73a052733d0796a5f71cf8253bab7d3969811b64f41ff6aa89d86f
// Method 2: Using helper function
const hmacHelper = Hash.sha256hmac(key, message)
console.log('HMAC helper result:', Utils.toHex(hmacHelper))
// Both methods produce the same result
console.log('HMAC results match:', hmacResult === Utils.toHex(hmacHelper))
```
```typescript
// SHA-512 HMAC
const sha512Hmac = new Hash.SHA512HMAC('my_secret_key')
sha512Hmac.update('Data to authenticate')
const sha512HmacResult = sha512Hmac.digestHex()
console.log('HMAC-SHA512:', sha512HmacResult)
// SHA-1 HMAC (legacy)
const sha1Hmac = new Hash.SHA1HMAC('legacy_key')
sha1Hmac.update('Legacy data')
const sha1HmacResult = sha1Hmac.digestHex()
console.log('HMAC-SHA1:', sha1HmacResult)
```
```typescript
// Strong key generation
function generateHmacKey(): string {
const randomBytes = new Array(32)
for (let i = 0; i < 32; i++) {
randomBytes[i] = Math.floor(Math.random() * 256)
}
return Utils.toHex(randomBytes)
}
// Key derivation from password
function deriveKeyFromPassword(password: string, salt: string): number[] {
const combined = password + salt
return Hash.sha256(Utils.toArray(combined, 'utf8'))
}
// Example usage
const strongKey = generateHmacKey()
const derivedKey = deriveKeyFromPassword('user_password', 'random_salt')
console.log('Strong key:', strongKey)
console.log('Derived key:', Utils.toHex(derivedKey))
// Use derived key for HMAC
const secureHmac = Hash.sha256hmac(derivedKey, 'sensitive_data')
console.log('Secure HMAC:', Utils.toHex(secureHmac))
```
```typescript
class DataIntegrityChecker {
private data: string
private hash: string
constructor(data: string) {
this.data = data
this.hash = this.calculateHash(data)
}
private calculateHash(data: string): string {
const hashResult = Hash.sha256(Utils.toArray(data, 'utf8'))
return Utils.toHex(hashResult)
}
verify(): boolean {
const currentHash = this.calculateHash(this.data)
return currentHash === this.hash
}
getData(): string {
return this.data
}
getHash(): string {
return this.hash
}
// Simulate data corruption
corruptData(): void {
this.data += '_corrupted'
}
}
// Example usage
const checker = new DataIntegrityChecker('Important document content')
console.log('Original hash:', checker.getHash())
console.log('Data is valid:', checker.verify()) // true
checker.corruptData()
console.log('After corruption, data is valid:', checker.verify()) // false
```
```typescript
class MessageAuthenticator {
private secretKey: string
constructor(secretKey: string) {
this.secretKey = secretKey
}
createAuthenticatedMessage(message: string): {
message: string
hmac: string
timestamp: number
} {
const timestamp = Date.now()
const messageWithTimestamp = `${message}:${timestamp}`
const hmac = Hash.sha256hmac(this.secretKey, messageWithTimestamp)
return {
message,
hmac: Utils.toHex(hmac),
timestamp
}
}
verifyMessage(authenticatedMessage: {
message: string
hmac: string
timestamp: number
}): boolean {
try {
const messageWithTimestamp = `${authenticatedMessage.message}:${authenticatedMessage.timestamp}`
const expectedHmac = Hash.sha256hmac(this.secretKey, messageWithTimestamp)
const expectedHmacHex = Utils.toHex(expectedHmac)
// Constant-time comparison to prevent timing attacks
return this.constantTimeCompare(authenticatedMessage.hmac, expectedHmacHex)
} catch (error) {
console.error('Verification error:', error)
return false
}
}
private constantTimeCompare(a: string, b: string): boolean {
if (a.length !== b.length) {
return false
}
let result = 0
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i)
}
return result === 0
}
}
// Example usage
const authenticator = new MessageAuthenticator('super_secret_key_123')
const authMessage = authenticator.createAuthenticatedMessage('Transfer 100 satoshis to Alice')
console.log('Authenticated message:', authMessage)
const isValid = authenticator.verifyMessage(authMessage)
console.log('Message is authentic:', isValid) // true
// Tamper with the message
authMessage.message = 'Transfer 1000 satoshis to Alice'
const isTamperedValid = authenticator.verifyMessage(authMessage)
console.log('Tampered message is authentic:', isTamperedValid) // false
```
```typescript
interface TransactionMetadata {
description: string
category: string
tags: string[]
amount: number
}
class SecureTransactionMetadata {
private key: number[]
constructor(password: string) {
// Derive key from password
this.key = Hash.sha256(Utils.toArray(password, 'utf8'))
}
protectMetadata(metadata: TransactionMetadata): {
data: string
integrity: string
} {
const jsonData = JSON.stringify(metadata)
const dataBytes = Utils.toArray(jsonData, 'utf8')
// Create integrity hash
const integrity = Hash.sha256hmac(this.key, dataBytes)
return {
data: Utils.toBase64(dataBytes),
integrity: Utils.toHex(integrity)
}
}
verifyAndExtract(protectedData: {
data: string
integrity: string
}): TransactionMetadata | null {
try {
const dataBytes = Array.from(Buffer.from(protectedData.data, 'base64'))
const expectedIntegrity = Hash.sha256hmac(this.key, dataBytes)
const expectedIntegrityHex = Utils.toHex(expectedIntegrity)
if (protectedData.integrity !== expectedIntegrityHex) {
console.error('Integrity check failed')
return null
}
const jsonString = Utils.toUTF8(dataBytes)
return JSON.parse(jsonString) as TransactionMetadata
} catch (error) {
console.error('Extraction error:', error)
return null
}
}
}
// Example usage
const metadataProtector = new SecureTransactionMetadata('user_password_123')
const originalMetadata: TransactionMetadata = {
description: 'Payment for services',
category: 'business',
tags: ['consulting', 'development'],
amount: 100
}
const protectedData = metadataProtector.protectMetadata(originalMetadata)
console.log('Protected metadata:', protectedData)
const extracted = metadataProtector.verifyAndExtract(protectedData)
console.log('Extracted metadata:', extracted)
console.log('Metadata matches:', JSON.stringify(originalMetadata) === JSON.stringify(extracted))
```
```typescript
class BatchHashProcessor {
private hasher: Hash.SHA256
constructor() {
this.hasher = new Hash.SHA256()
}
hashMultipleMessages(messages: string[]): string[] {
const results: string[] = []
for (const message of messages) {
// Reset hasher for each message
this.hasher = new Hash.SHA256()
this.hasher.update(message)
results.push(this.hasher.digestHex())
}
return results
}
createMerkleRoot(hashes: string[]): string {
if (hashes.length === 0) {
throw new Error('Cannot create merkle root from empty array')
}
if (hashes.length === 1) {
return hashes[0]
}
const nextLevel: string[] = []
for (let i = 0; i < hashes.length; i += 2) {
const left = hashes[i]
const right = i + 1 < hashes.length ? hashes[i + 1] : left
const combined = left + right
const combinedBytes = Utils.toArray(combined, 'hex')
const hash = Hash.sha256(combinedBytes)
nextLevel.push(Utils.toHex(hash))
}
return this.createMerkleRoot(nextLevel)
}
}
// Performance testing
function performanceTest() {
const processor = new BatchHashProcessor()
const testMessages = Array.from({ length: 1000 }, (_, i) => `Message ${i}`)
console.time('Batch hashing 1000 messages')
const hashes = processor.hashMultipleMessages(testMessages)
console.timeEnd('Batch hashing 1000 messages')
console.time('Creating merkle root')
const merkleRoot = processor.createMerkleRoot(hashes)
console.timeEnd('Creating merkle root')
console.log('Merkle root:', merkleRoot)
console.log('Processed', hashes.length, 'messages')
}
performanceTest()
```
```typescript
class StreamingHasher {
private hasher: Hash.SHA256
constructor() {
this.hasher = new Hash.SHA256()
}
processLargeData(data: string, chunkSize: number = 1024): string {
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize)
this.hasher.update(chunk)
}
return this.hasher.digestHex()
}
reset(): void {
this.hasher = new Hash.SHA256()
}
}
// Example with large data
const streamingHasher = new StreamingHasher()
const largeData = 'A'.repeat(1000000) // 1MB of data
const streamHash = streamingHasher.processLargeData(largeData)
console.log('Streaming hash result:', streamHash)
```
```typescript
class SecureKeyManager {
static generateSecureKey(length: number = 32): number[] {
// In production, use a cryptographically secure random number generator
const key = new Array(length)
for (let i = 0; i < length; i++) {
key[i] = Math.floor(Math.random() * 256)
}
return key
}
static deriveKeyFromPassword(
password: string,
salt: number[],
iterations: number = 10000
): number[] {
let derived = Hash.sha256(Utils.toArray(password + Utils.toHex(salt), 'utf8'))
for (let i = 1; i < iterations; i++) {
derived = Hash.sha256(derived)
}
return derived
}
static secureCompare(a: number[], b: number[]): boolean {
if (a.length !== b.length) {
return false
}
let result = 0
for (let i = 0; i < a.length; i++) {
result |= a[i] ^ b[i]
}
return result === 0
}
static clearSensitiveData(data: number[]): void {
for (let i = 0; i < data.length; i++) {
data[i] = 0
}
}
}
// Example secure usage
const salt = SecureKeyManager.generateSecureKey(16)
const derivedKey = SecureKeyManager.deriveKeyFromPassword('user_password', salt)
console.log('Salt:', Utils.toHex(salt))
console.log('Derived key:', Utils.toHex(derivedKey))
// Clear sensitive data when done
SecureKeyManager.clearSensitiveData(derivedKey)
SecureKeyManager.clearSensitiveData(salt)
```
```typescript
class SafeHasher {
static validateInput(input: any): void {
if (input === null || input === undefined) {
throw new Error('Input cannot be null or undefined')
}
if (typeof input === 'string' && input.length === 0) {
throw new Error('Input string cannot be empty')
}
if (Array.isArray(input) && input.length === 0) {
throw new Error('Input array cannot be empty')
}
}
static safeHash(input: string | number[], algorithm: 'sha256' | 'sha512' = 'sha256'): string {
try {
this.validateInput(input)
let data: number[]
if (typeof input === 'string') {
data = Utils.toArray(input, 'utf8')
} else {
data = input
}
let result: number[]
switch (algorithm) {
case 'sha256':
result = Hash.sha256(data)
break
case 'sha512':
result = Hash.sha512(data)
break
default:
throw new Error(`Unsupported algorithm: ${algorithm}`)
}
return Utils.toHex(result)
} catch (error) {
console.error('Hashing error:', error)
throw new Error(`Failed to hash input: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
static safeHmac(key: string | number[], message: string | number[]): string {
try {
this.validateInput(key)
this.validateInput(message)
const result = Hash.sha256hmac(key, message)
return Utils.toHex(result)
} catch (error) {
console.error('HMAC error:', error)
throw new Error(`Failed to create HMAC: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
}
// Example safe usage
try {
const hash = SafeHasher.safeHash('Valid input')
console.log('Safe hash:', hash)
const hmac = SafeHasher.safeHmac('secret_key', 'message')
console.log('Safe HMAC:', hmac)
} catch (error) {
console.error('Operation failed:', error)
}
```
```typescript
// Comprehensive test suite
function runHashTests(): void {
console.log('Running hash function tests...')
// Test SHA-256 consistency
const testMessage = 'Hello, Bitcoin!'
const hash1 = Hash.sha256(Utils.toArray(testMessage, 'utf8'))
const hash2 = new Hash.SHA256().update(testMessage).digest()
console.assert(
Utils.toHex(hash1) === Utils.toHex(hash2),
'SHA-256 methods should produce same result'
)
// Test HMAC consistency
const key = 'test_key'
const message = 'test_message'
const hmac1 = Hash.sha256hmac(key, message)
const hmac2 = new Hash.SHA256HMAC(key).update(message).digest()
console.assert(
Utils.toHex(hmac1) === Utils.toHex(hmac2),
'HMAC methods should produce same result'
)
// Test Bitcoin-specific functions
const data = 'bitcoin_data'
const hash256Result = Hash.hash256(Utils.toArray(data, 'utf8'))
const manualDouble = Hash.sha256(Hash.sha256(Utils.toArray(data, 'utf8')))
console.assert(
Utils.toHex(hash256Result) === Utils.toHex(manualDouble),
'hash256 should equal double SHA-256'
)
console.log('All tests passed!')
}
runHashTests()
```
```typescript
// Problem: Incorrect encoding leads to different hashes
// Note: Hash.sha256() requires number[] input, not strings directly
const correctHash = Hash.sha256(Utils.toArray('hello', 'utf8')) // Correct approach
console.log('Correct hash with proper encoding:', Utils.toHex(correctHash))
console.log('Always use Utils.toArray() for proper encoding')
```
```typescript
// Problem: Weak keys or improper key derivation
const weakKey = 'password123' // Weak
const strongKey = SecureKeyManager.generateSecureKey() // Strong
// Problem: Reusing keys across different purposes
const hmacKey = strongKey
const encryptionKey = strongKey // Wrong: same key for different purposes
// Solution: Derive different keys for different purposes
const hmacKeyDerived = Hash.sha256(Utils.toArray('hmac:' + Utils.toHex(strongKey), 'utf8'))
const encryptionKeyDerived = Hash.sha256(Utils.toArray('encrypt:' + Utils.toHex(strongKey), 'utf8'))
```
```typescript
// Problem: Creating new hasher instances unnecessarily
function inefficientHashing(messages: string[]): string[] {
return messages.map(msg => {
const hasher = new Hash.SHA256() // Inefficient: new instance each time
return hasher.update(msg).digestHex()
})
}
// Solution: Reuse hasher or use helper functions
function efficientHashing(messages: string[]): string[] {
return messages.map(msg => {
return Utils.toHex(Hash.sha256(Utils.toArray(msg, 'utf8'))) // Efficient
})
}
```
This tutorial covered comprehensive usage of cryptographic hashing and HMACs in the BSV TypeScript SDK:
**Key Concepts Learned:**
- Hash function fundamentals and Bitcoin-specific applications
- SHA-256, SHA-512, SHA-1, and RIPEMD-160 implementation
- HMAC creation and verification for message authentication
- Bitcoin-specific functions: hash256 (double SHA-256) and hash160
- Performance optimization techniques for batch processing
- Security best practices for key management and validation
**Practical Applications:**
- Data integrity verification systems
- Message authentication protocols
- Transaction metadata protection
- Merkle tree construction
- Secure key derivation patterns
**Security Considerations:**
- Proper input validation and error handling
- Constant-time comparison to prevent timing attacks
- Secure key generation and storage practices
- Memory management for sensitive data
The Hash module in the BSV TypeScript SDK provides both low-level control through classes and high-level convenience through helper functions, enabling developers to implement robust cryptographic solutions for Bitcoin applications.
Continue exploring advanced cryptographic topics with the [ECDH Key Exchange](./ecdh-key-exchange.md) and [AES Symmetric Encryption](./aes-encryption.md) tutorials to build complete cryptographic systems.