CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-bsv

JavaScript library for Bitcoin SV (BSV) blockchain development with cryptographic primitives, transaction handling, and protocol implementations

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

encryption.mddocs/

Encryption

Advanced encryption schemes including AES, CBC, and ECIES for secure data transmission and storage.

Imports

// ES6 imports
import { Aes, Aescbc, Ecies, Cbc } from 'bsv'

// CommonJS imports
const { Aes, Aescbc, Ecies, Cbc } = require('bsv')

AES (Advanced Encryption Standard)

Low-level AES block cipher for 128-bit block encryption.

Class Definition

class Aes {
  // Core encryption/decryption (single block only)
  static encrypt(
    messageBuf: Buffer,    // Plaintext (must be exactly 16 bytes)
    keyBuf: Buffer        // AES key (16, 24, or 32 bytes)
  ): Buffer               // Ciphertext (16 bytes)

  static decrypt(
    encBuf: Buffer,       // Ciphertext (must be exactly 16 bytes)
    keyBuf: Buffer        // AES key (16, 24, or 32 bytes)
  ): Buffer               // Plaintext (16 bytes)

  // Utility methods for word array conversion
  static buf2Words(buf: Buffer): number[]    // Convert buffer to word array
  static words2Buf(words: number[]): Buffer  // Convert word array to buffer
}

Usage Examples

// AES operates on single 16-byte blocks
const plaintext = Buffer.alloc(16, 0x41) // 16 bytes of 'A'
const key = Buffer.alloc(32, 0x42)       // 256-bit key of 'B'

// Encrypt single block
const ciphertext = Aes.encrypt(plaintext, key)
console.log('Encrypted:', ciphertext.toString('hex'))

// Decrypt single block
const decrypted = Aes.decrypt(ciphertext, key)
console.log('Decrypted:', decrypted.toString('hex'))
console.log('Match:', plaintext.equals(decrypted)) // true

// Different key sizes
const key128 = Buffer.alloc(16, 0x42) // 128-bit key
const key192 = Buffer.alloc(24, 0x42) // 192-bit key
const key256 = Buffer.alloc(32, 0x42) // 256-bit key

const encrypted128 = Aes.encrypt(plaintext, key128)
const encrypted192 = Aes.encrypt(plaintext, key192)
const encrypted256 = Aes.encrypt(plaintext, key256)

// Word array conversion (internal format)
const words = Aes.buf2Words(plaintext)
const buffer = Aes.words2Buf(words)
console.log('Conversion match:', plaintext.equals(buffer))

AES-CBC (Cipher Block Chaining)

AES in CBC mode for encrypting arbitrary-length data with padding.

Class Definition

class Aescbc {
  // CBC mode encryption/decryption
  static encrypt(
    messageBuf: Buffer,    // Plaintext (any length)
    keyBuf: Buffer,       // AES key (16, 24, or 32 bytes)
    ivBuf: Buffer,        // Initialization vector (16 bytes)
    pad?: boolean         // Apply PKCS#7 padding (default: true)
  ): Buffer               // Ciphertext

  static decrypt(
    encBuf: Buffer,       // Ciphertext 
    keyBuf: Buffer,       // AES key (16, 24, or 32 bytes)
    ivBuf: Buffer,        // Initialization vector (16 bytes)
    pad?: boolean         // Remove PKCS#7 padding (default: true)
  ): Buffer               // Plaintext
}

Usage Examples

// Encrypt arbitrary-length data with CBC
const message = Buffer.from('Hello, this is a long message that spans multiple blocks!', 'utf8')
const key = Random.getRandomBuffer(32)    // 256-bit key
const iv = Random.getRandomBuffer(16)     // 128-bit IV

// Encrypt with automatic padding
const encrypted = Aescbc.encrypt(message, key, iv, true)
console.log('Encrypted length:', encrypted.length)

// Decrypt with automatic padding removal
const decrypted = Aescbc.decrypt(encrypted, key, iv, true)
console.log('Decrypted:', decrypted.toString('utf8'))
console.log('Match:', message.equals(decrypted)) // true

// Encrypt without padding (message must be multiple of 16 bytes)
const alignedMessage = Buffer.alloc(32, 0x41) // 32 bytes
const encryptedNoPad = Aescbc.encrypt(alignedMessage, key, iv, false)
const decryptedNoPad = Aescbc.decrypt(encryptedNoPad, key, iv, false)

// Different IV for each encryption (important for security)
const iv1 = Random.getRandomBuffer(16)
const iv2 = Random.getRandomBuffer(16)
const enc1 = Aescbc.encrypt(message, key, iv1)
const enc2 = Aescbc.encrypt(message, key, iv2)
console.log('Different ciphertexts:', !enc1.equals(enc2)) // true - good!

CBC (Cipher Block Chaining Base)

Generic CBC mode implementation for any block cipher.

Class Definition

class Cbc {
  // Generic CBC encryption
  static encrypt(
    messageBuf: Buffer,    // Plaintext
    ivBuf: Buffer,        // Initialization vector  
    blockCipher: object,   // Block cipher instance
    cipherKeyBuf: Buffer   // Cipher key
  ): Buffer               // Ciphertext

  static decrypt(
    encBuf: Buffer,       // Ciphertext
    ivBuf: Buffer,        // Initialization vector
    blockCipher: object,  // Block cipher instance  
    cipherKeyBuf: Buffer  // Cipher key
  ): Buffer               // Plaintext
}

Usage Examples

// Use CBC with AES block cipher
const message = Buffer.from('Message for CBC encryption', 'utf8')
const key = Random.getRandomBuffer(32)
const iv = Random.getRandomBuffer(16)

// Create AES block cipher object
const aesBlockCipher = {
  encrypt: (block, key) => Aes.encrypt(block, key),
  decrypt: (block, key) => Aes.decrypt(block, key),
  blockSize: 16
}

// Encrypt with generic CBC
const encrypted = Cbc.encrypt(message, iv, aesBlockCipher, key)
const decrypted = Cbc.decrypt(encrypted, iv, aesBlockCipher, key)

console.log('Original:', message.toString('utf8'))
console.log('Decrypted:', decrypted.toString('utf8'))

ECIES (Elliptic Curve Integrated Encryption Scheme)

Public key encryption using elliptic curves, combining ECDH key agreement with AES encryption.

Class Definition

class Ecies {
  // Electrum-style ECIES encryption
  static electrumEncrypt(
    messageBuf: Buffer,     // Message to encrypt
    toPubKey: PubKey,      // Recipient's public key
    fromKeyPair?: KeyPair, // Sender's key pair (optional)
    noKey?: boolean        // Don't include public key in output
  ): Buffer                // Encrypted message

  static electrumDecrypt(
    encBuf: Buffer,        // Encrypted message
    toPrivKey: PrivKey,    // Recipient's private key
    fromPubKey?: PubKey    // Sender's public key (if noKey was used)
  ): Buffer                // Decrypted message

  // Bitcore-style ECIES encryption
  static bitcoreEncrypt(
    messageBuf: Buffer,     // Message to encrypt
    toPubKey: PubKey,      // Recipient's public key
    fromKeyPair?: KeyPair, // Sender's key pair (optional)
    ivBuf?: Buffer         // Custom IV (optional)
  ): Buffer                // Encrypted message

  static bitcoreDecrypt(
    encBuf: Buffer,        // Encrypted message
    toPrivKey: PrivKey     // Recipient's private key
  ): Buffer                // Decrypted message

  // Key derivation utility
  static ivkEkM(
    privKey: PrivKey,      // Private key
    pubKey: PubKey         // Public key
  ): {
    iv: Buffer,           // Initialization vector
    kE: Buffer,           // Encryption key
    kM: Buffer            // MAC key
  }

  // Async variants
  static asyncBitcoreEncrypt(
    messageBuf: Buffer,
    toPubKey: PubKey,
    fromKeyPair?: KeyPair,
    ivBuf?: Buffer
  ): Promise<Buffer>

  static asyncBitcoreDecrypt(
    encBuf: Buffer,
    toPrivKey: PrivKey
  ): Promise<Buffer>
}

Usage Examples

// ECIES encryption - Bitcore style (recommended)
const message = Buffer.from('Secret message for ECIES encryption', 'utf8')

// Generate recipient key pair
const recipientKeyPair = KeyPair.fromRandom()
const recipientPubKey = recipientKeyPair.pubKey
const recipientPrivKey = recipientKeyPair.privKey

// Encrypt message (sender can be anonymous)
const encrypted = Ecies.bitcoreEncrypt(message, recipientPubKey)
console.log('Encrypted:', encrypted.toString('hex'))

// Decrypt message
const decrypted = Ecies.bitcoreDecrypt(encrypted, recipientPrivKey)
console.log('Decrypted:', decrypted.toString('utf8'))
console.log('Match:', message.equals(decrypted)) // true

// ECIES with known sender
const senderKeyPair = KeyPair.fromRandom()
const encryptedWithSender = Ecies.bitcoreEncrypt(message, recipientPubKey, senderKeyPair)
const decryptedWithSender = Ecies.bitcoreDecrypt(encryptedWithSender, recipientPrivKey)

// ECIES with custom IV
const customIv = Random.getRandomBuffer(16)
const encryptedCustomIv = Ecies.bitcoreEncrypt(message, recipientPubKey, null, customIv)
const decryptedCustomIv = Ecies.bitcoreDecrypt(encryptedCustomIv, recipientPrivKey)

// Electrum-style ECIES
const electrumEncrypted = Ecies.electrumEncrypt(message, recipientPubKey)
const electrumDecrypted = Ecies.electrumDecrypt(electrumEncrypted, recipientPrivKey)

// Electrum-style with separate sender key
const senderKeyPair2 = KeyPair.fromRandom()
const electrumEncNoKey = Ecies.electrumEncrypt(message, recipientPubKey, senderKeyPair2, true)
const electrumDecNoKey = Ecies.electrumDecrypt(electrumEncNoKey, recipientPrivKey, senderKeyPair2.pubKey)

// Async ECIES operations
const asyncEncrypted = await Ecies.asyncBitcoreEncrypt(message, recipientPubKey)
const asyncDecrypted = await Ecies.asyncBitcoreDecrypt(asyncEncrypted, recipientPrivKey)

ECIES Key Derivation

// Manual key derivation for ECIES
const aliceKeyPair = KeyPair.fromRandom()
const bobKeyPair = KeyPair.fromRandom()

// Alice derives keys using Bob's public key
const aliceKeys = Ecies.ivkEkM(aliceKeyPair.privKey, bobKeyPair.pubKey)
console.log('Alice IV:', aliceKeys.iv.toString('hex'))
console.log('Alice encryption key:', aliceKeys.kE.toString('hex'))
console.log('Alice MAC key:', aliceKeys.kM.toString('hex'))

// Bob derives the same keys using Alice's public key
const bobKeys = Ecies.ivkEkM(bobKeyPair.privKey, aliceKeyPair.pubKey)
console.log('Keys match:', 
  aliceKeys.iv.equals(bobKeys.iv) &&
  aliceKeys.kE.equals(bobKeys.kE) &&
  aliceKeys.kM.equals(bobKeys.kM)
) // true

// Use derived keys for manual encryption
const message = Buffer.from('Hello Bob!', 'utf8')
const encryptedManual = Aescbc.encrypt(message, aliceKeys.kE, aliceKeys.iv)
const decryptedManual = Aescbc.decrypt(encryptedManual, bobKeys.kE, bobKeys.iv)

Advanced Encryption Patterns

Secure Message Exchange

// Complete secure messaging system
class SecureMessaging {
  constructor(keyPair) {
    this.keyPair = keyPair
    this.pubKey = keyPair.pubKey
    this.privKey = keyPair.privKey
  }
  
  // Encrypt message for recipient
  encryptFor(message, recipientPubKey) {
    const messageBuf = Buffer.from(message, 'utf8')
    return Ecies.bitcoreEncrypt(messageBuf, recipientPubKey, this.keyPair)
  }
  
  // Decrypt message from sender
  decryptFrom(encryptedBuf) {
    const decrypted = Ecies.bitcoreDecrypt(encryptedBuf, this.privKey)
    return decrypted.toString('utf8')
  }
  
  // Get public key for sharing
  getPublicKey() {
    return this.pubKey.toHex()
  }
}

// Usage
const alice = new SecureMessaging(KeyPair.fromRandom())
const bob = new SecureMessaging(KeyPair.fromRandom())

// Alice encrypts message for Bob
const message = 'Hello Bob, this is a secret message!'
const encrypted = alice.encryptFor(message, bob.pubKey)

// Bob decrypts message from Alice  
const decrypted = bob.decryptFrom(encrypted)
console.log('Decrypted message:', decrypted)

Hybrid Encryption for Large Data

// Hybrid encryption: RSA-style key exchange + AES data encryption
function hybridEncrypt(data, recipientPubKey) {
  // Generate random AES key
  const aesKey = Random.getRandomBuffer(32) // 256-bit key
  const iv = Random.getRandomBuffer(16)     // 128-bit IV
  
  // Encrypt data with AES-CBC
  const encryptedData = Aescbc.encrypt(data, aesKey, iv)
  
  // Encrypt AES key with ECIES
  const keyBuf = Buffer.concat([aesKey, iv])
  const encryptedKey = Ecies.bitcoreEncrypt(keyBuf, recipientPubKey)
  
  // Return combined result
  return {
    encryptedKey: encryptedKey,
    encryptedData: encryptedData
  }
}

function hybridDecrypt(encryptedObj, recipientPrivKey) {
  // Decrypt AES key with ECIES
  const keyBuf = Ecies.bitcoreDecrypt(encryptedObj.encryptedKey, recipientPrivKey)
  const aesKey = keyBuf.slice(0, 32)
  const iv = keyBuf.slice(32, 48)
  
  // Decrypt data with AES-CBC
  return Aescbc.decrypt(encryptedObj.encryptedData, aesKey, iv)
}

// Usage for large data
const largeData = Buffer.alloc(1024 * 1024, 0x42) // 1MB of data
const recipientKeyPair = KeyPair.fromRandom()

const encrypted = hybridEncrypt(largeData, recipientKeyPair.pubKey)
const decrypted = hybridDecrypt(encrypted, recipientKeyPair.privKey)

console.log('Large data match:', largeData.equals(decrypted)) // true
console.log('Encrypted key size:', encrypted.encryptedKey.length)
console.log('Encrypted data size:', encrypted.encryptedData.length)

Authenticated Encryption

// Add HMAC authentication to AES encryption
function authenticatedEncrypt(message, key) {
  // Derive encryption and MAC keys
  const encKey = Hash.sha256(Buffer.concat([key, Buffer.from('encrypt')]))
  const macKey = Hash.sha256(Buffer.concat([key, Buffer.from('authenticate')]))
  
  // Encrypt with AES-CBC
  const iv = Random.getRandomBuffer(16)
  const encrypted = Aescbc.encrypt(message, encKey.slice(0, 32), iv)
  
  // Calculate HMAC over IV + ciphertext
  const toAuthenticate = Buffer.concat([iv, encrypted])
  const hmac = Hash.sha256Hmac(toAuthenticate, macKey)
  
  // Return IV + ciphertext + HMAC
  return Buffer.concat([iv, encrypted, hmac])
}

function authenticatedDecrypt(encryptedWithAuth, key) {
  // Derive encryption and MAC keys
  const encKey = Hash.sha256(Buffer.concat([key, Buffer.from('encrypt')]))
  const macKey = Hash.sha256(Buffer.concat([key, Buffer.from('authenticate')]))
  
  // Extract components
  const iv = encryptedWithAuth.slice(0, 16)
  const encrypted = encryptedWithAuth.slice(16, -32)
  const receivedHmac = encryptedWithAuth.slice(-32)
  
  // Verify HMAC
  const toAuthenticate = Buffer.concat([iv, encrypted])
  const calculatedHmac = Hash.sha256Hmac(toAuthenticate, macKey)
  
  if (!receivedHmac.equals(calculatedHmac)) {
    throw new Error('Authentication failed - message may have been tampered with')
  }
  
  // Decrypt if authentication passed
  return Aescbc.decrypt(encrypted, encKey.slice(0, 32), iv)
}

// Usage
const message = Buffer.from('Authenticated message', 'utf8')
const key = Random.getRandomBuffer(32)

const encrypted = authenticatedEncrypt(message, key)
const decrypted = authenticatedDecrypt(encrypted, key)

console.log('Authenticated decryption successful:', message.equals(decrypted))

// Try to tamper with message
const tampered = Buffer.from(encrypted)
tampered[20] ^= 0x01 // Flip one bit

try {
  authenticatedDecrypt(tampered, key)
} catch (error) {
  console.log('Tampering detected:', error.message)
}

Password-Based Encryption

// PBKDF2-based password encryption
function passwordEncrypt(message, password, iterations = 10000) {
  const passwordBuf = Buffer.from(password, 'utf8')
  const salt = Random.getRandomBuffer(16)
  
  // Derive key from password using PBKDF2
  const derivedKey = bsv.deps.pbkdf2(passwordBuf, salt, iterations, 48, 'sha256')
  const encKey = derivedKey.slice(0, 32)  // 256-bit encryption key
  const iv = derivedKey.slice(32, 48)     // 128-bit IV
  
  // Encrypt message
  const encrypted = Aescbc.encrypt(message, encKey, iv)
  
  // Return salt + encrypted data
  return {
    salt: salt,
    iterations: iterations,
    encrypted: encrypted
  }
}

function passwordDecrypt(encryptedObj, password) {
  const passwordBuf = Buffer.from(password, 'utf8')
  
  // Derive same key from password and salt
  const derivedKey = bsv.deps.pbkdf2(
    passwordBuf, 
    encryptedObj.salt, 
    encryptedObj.iterations, 
    48, 
    'sha256'
  )
  const encKey = derivedKey.slice(0, 32)
  const iv = derivedKey.slice(32, 48)
  
  // Decrypt message
  return Aescbc.decrypt(encryptedObj.encrypted, encKey, iv)
}

// Usage
const message = Buffer.from('Password-protected secret', 'utf8')
const password = 'strong-password-123'

const encrypted = passwordEncrypt(message, password, 50000) // Higher iterations
const decrypted = passwordDecrypt(encrypted, password)

console.log('Password encryption successful:', message.equals(decrypted))

Error Handling

Encryption operations include comprehensive validation:

// AES key size validation
try {
  const invalidKey = Buffer.alloc(15) // Wrong size
  Aes.encrypt(Buffer.alloc(16), invalidKey)
} catch (error) {
  console.error('Invalid key size:', error.message)
}

// Block size validation
try {
  const wrongSize = Buffer.alloc(15) // Not 16 bytes
  Aes.encrypt(wrongSize, Buffer.alloc(32))
} catch (error) {
  console.error('Invalid block size:', error.message)
}

// ECIES decryption errors
try {
  const corruptedData = Buffer.alloc(100, 0xff)
  Ecies.bitcoreDecrypt(corruptedData, recipientPrivKey)
} catch (error) {
  console.error('ECIES decryption failed:', error.message)
}

// CBC padding errors
try {
  const invalidPadding = Buffer.alloc(32, 0x42)
  Aescbc.decrypt(invalidPadding, key, iv, true) // May fail on bad padding
} catch (error) {
  console.error('Padding error:', error.message)
}

Security Considerations

IV and Key Management

// NEVER reuse IVs with the same key
const key = Random.getRandomBuffer(32)
const message1 = Buffer.from('Message 1', 'utf8')
const message2 = Buffer.from('Message 2', 'utf8')

// Good - different IVs
const iv1 = Random.getRandomBuffer(16)
const iv2 = Random.getRandomBuffer(16)
const enc1 = Aescbc.encrypt(message1, key, iv1)
const enc2 = Aescbc.encrypt(message2, key, iv2)

// Bad - same IV (DO NOT DO THIS)
// const sameIv = Random.getRandomBuffer(16)
// const badEnc1 = Aescbc.encrypt(message1, key, sameIv)
// const badEnc2 = Aescbc.encrypt(message2, key, sameIv)

// Generate new keys for each session
const sessionKey = Random.getRandomBuffer(32)
// Use sessionKey only for current session, generate new one next time

Constant-Time Comparison

// Use constant-time comparison for MACs and authentication
function constantTimeEquals(a, b) {
  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
}

// Use in authenticated decryption
function secureAuthenticatedDecrypt(encryptedWithAuth, key) {
  // ... derive keys and extract components ...
  
  // Secure HMAC comparison
  if (!constantTimeEquals(receivedHmac, calculatedHmac)) {
    throw new Error('Authentication failed')
  }
  
  // ... continue with decryption ...
}

Install with Tessl CLI

npx tessl i tessl/npm-bsv

docs

core-bitcoin.md

cryptography.md

encryption.md

hd-wallets.md

index.md

transaction-building.md

utilities.md

tile.json