or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-bitcoin.mdcryptography.mdencryption.mdhd-wallets.mdindex.mdtransaction-building.mdutilities.md
tile.json

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 ...
}