Cryptographic Hashing and HMACs
Duration: 75 minutes
Prerequisites: Basic TypeScript knowledge, understanding of cryptographic concepts
Learning Goals
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:
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:
// 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)
Working with Different Data Types
The Hash functions can process various data formats:
// 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))
Other Hash Algorithms
The SDK supports multiple hash algorithms:
// 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-Specific Hash Functions
Double SHA-256 (hash256)
Bitcoin uses double SHA-256 hashing for block headers and transaction IDs:
// 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))
Hash160 (SHA-256 + RIPEMD-160)
Used for Bitcoin address generation:
// 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))
HMAC Implementation
HMACs (Hash-based Message Authentication Codes) provide both data integrity and authentication by incorporating a secret key into the hashing process.
Basic HMAC Usage
// 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))
HMAC with Different Algorithms
// 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)
HMAC Key Management
// 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))
Practical Applications
Data Integrity Verification
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
Message Authentication System
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
Transaction Metadata Protection
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))
Performance Optimization
Batch Hashing
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()
Memory-Efficient Streaming
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)
Security Best Practices
Secure Key Generation and Storage
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)
Input Validation and Error Handling
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)
}
Testing Your Implementation
// 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()
Troubleshooting Common Issues
Issue 1: Encoding Problems
// 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')
Issue 2: Key Management
// 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'))
Issue 3: Performance Problems
// 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
})
}
Summary
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 and AES Symmetric Encryption tutorials to build complete cryptographic systems.