Duration: 75 minutes
Prerequisites: Basic TypeScript knowledge, Elliptic Curve Fundamentals tutorial completed, ECDH Key Exchange tutorial completed
Type-42 is a key derivation protocol that enables two parties with master keys to derive child keys from one another using a specific string called an “invoice number.” This creates a shared key universe that only these two parties can access, enabling secure communication and transactions without revealing their master keys.
The protocol gets its name from its historical use in Bitcoin payment systems, where invoice numbers were used to generate unique keys for each transaction. However, Type-42 has broader applications in secure messaging, authentication, and any scenario requiring shared key derivation.
import { PrivateKey, PublicKey, Utils } from '@bsv/sdk'
Type-42 key derivation follows these steps:
// Alice generates her master key pair
const alice = PrivateKey.fromRandom()
const alicePub = alice.toPublicKey()
// Bob generates his master key pair
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
console.log('Alice master public key:', alicePub.toString())
console.log('Bob master public key:', bobPub.toString())
// Both parties agree on an invoice number to use
// This could be a payment ID, message ID, or any unique identifier
const invoiceNumber = '2-simple signing protocol-1'
console.log('Using invoice number:', invoiceNumber)
// Alice derives a child private key for signing
const aliceSigningChild = alice.deriveChild(bobPub, invoiceNumber)
// Bob derives Alice's corresponding public key
const aliceSigningPub = alicePub.deriveChild(bob, invoiceNumber)
// Verify the keys match
const derivedPubFromPriv = aliceSigningChild.toPublicKey()
const keysMatch = derivedPubFromPriv.toString() === aliceSigningPub.toString()
console.log('Keys match:', keysMatch)
// true
Let’s implement a complete example where Alice signs a message for Bob using Type-42 derived keys:
import { PrivateKey, Utils } from '@bsv/sdk'
async function demonstrateType42Signing() {
// Step 1: Generate master keys
const alice = PrivateKey.fromRandom()
const alicePub = alice.toPublicKey()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
// Step 2: Agree on invoice number
const invoiceNumber = '2-secure-message-001'
// Step 3: Alice derives her signing key
const aliceSigningChild = alice.deriveChild(bobPub, invoiceNumber)
// Step 4: Alice signs a message
const message = Utils.toArray('Hello Bob, this is a secure message!', 'utf8')
const signature = aliceSigningChild.sign(message)
console.log('Message signed by Alice')
console.log('Signature:', signature.toDER('hex'))
// Step 5: Bob derives Alice's public key and verifies
const aliceSigningPub = alicePub.deriveChild(bob, invoiceNumber)
const verified = aliceSigningPub.verify(message, signature)
console.log('Signature verified by Bob:', verified)
// true
return {
alice,
bob,
aliceSigningChild,
aliceSigningPub,
message,
signature,
verified
}
}
// Run the demonstration
demonstrateType42Signing()
Type-42 enables both parties to derive keys for each other. Here’s how Bob can also sign messages for Alice:
async function bidirectionalType42() {
const alice = PrivateKey.fromRandom()
const alicePub = alice.toPublicKey()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
const invoiceNumber = '2-bidirectional-chat-001'
// Alice signs a message for Bob
const aliceMessage = Utils.toArray('Hi Bob!', 'utf8')
const aliceSigningKey = alice.deriveChild(bobPub, invoiceNumber)
const aliceSignature = aliceSigningKey.sign(aliceMessage)
// Bob signs a reply for Alice
const bobMessage = Utils.toArray('Hi Alice!', 'utf8')
const bobSigningKey = bob.deriveChild(alicePub, invoiceNumber)
const bobSignature = bobSigningKey.sign(bobMessage)
// Cross-verification
const aliceVerifyKey = alicePub.deriveChild(bob, invoiceNumber)
const bobVerifyKey = bobPub.deriveChild(alice, invoiceNumber)
const aliceVerified = aliceVerifyKey.verify(aliceMessage, aliceSignature)
const bobVerified = bobVerifyKey.verify(bobMessage, bobSignature)
console.log('Alice message verified:', aliceVerified)
console.log('Bob message verified:', bobVerified)
return { aliceVerified, bobVerified }
}
The SDK supports a special “anyone” key concept for scenarios where one party wants to create publicly verifiable signatures. The “anyone” key is simply the private key with value 1:
// The "anyone" private key
const anyonePrivateKey = new PrivateKey(1)
const anyonePublicKey = anyonePrivateKey.toPublicKey()
console.log('Anyone public key:', anyonePublicKey.toString())
// Using "anyone" key for public verification
function createPubliclyVerifiableSignature() {
const signer = PrivateKey.fromRandom()
const invoiceNumber = '2-public-announcement-001'
// Derive key using "anyone" as counterparty
const signingKey = signer.deriveChild(anyonePublicKey, invoiceNumber)
// Sign message
const message = Utils.toArray('This is a public announcement', 'utf8')
const signature = signingKey.sign(message)
// Anyone can verify using the signer's public key
const signerPub = signer.toPublicKey()
const verifyKey = signerPub.deriveChild(anyonePrivateKey, invoiceNumber)
const verified = verifyKey.verify(message, signature)
console.log('Public signature verified:', verified)
return {
signerPublicKey: signerPub.toString(),
signature: signature.toDER('hex'),
verified
}
}
createPubliclyVerifiableSignature()
Use different invoice numbers for different purposes:
function multiPurposeKeyDerivation() {
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
// Different keys for different purposes
const signingKey = alice.deriveChild(bobPub, '2-signing-001')
const encryptionKey = alice.deriveChild(bobPub, '2-encryption-001')
const authKey = alice.deriveChild(bobPub, '2-auth-001')
console.log('Signing key:', signingKey.toHex().substring(0, 16) + '...')
console.log('Encryption key:', encryptionKey.toHex().substring(0, 16) + '...')
console.log('Auth key:', authKey.toHex().substring(0, 16) + '...')
// Each key is unique and serves a different purpose
return { signingKey, encryptionKey, authKey }
}
Create time-based or session-based keys:
function sessionBasedKeys() {
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
// Create session-specific keys
const sessionId = Date.now().toString()
const sessionInvoice = `2-session-${sessionId}`
const sessionKey = alice.deriveChild(bobPub, sessionInvoice)
console.log('Session ID:', sessionId)
console.log('Session key created:', sessionKey.toHex().substring(0, 16) + '...')
return { sessionId, sessionKey }
}
Type-42 derived keys can be used in Bitcoin transactions:
import { Transaction, P2PKH } from '@bsv/sdk'
async function type42Transaction() {
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
const invoiceNumber = '2-payment-001'
// Alice derives a key for this specific payment
const paymentKey = alice.deriveChild(bobPub, invoiceNumber)
const paymentAddress = paymentKey.toAddress()
console.log('Payment address:', paymentAddress)
// Bob can derive the same public key to verify ownership
const alicePub = alice.toPublicKey()
const verifyKey = alicePub.deriveChild(bob, invoiceNumber)
const verifyAddress = verifyKey.toAddress()
console.log('Addresses match:', paymentAddress === verifyAddress)
return { paymentKey, paymentAddress, verifyAddress }
}
Implement robust error handling for Type-42 operations:
function safeType42Derivation(
privateKey: PrivateKey,
counterpartyPublicKey: PublicKey,
invoiceNumber: string
): PrivateKey | null {
try {
// Validate inputs
if (!privateKey || !counterpartyPublicKey || !invoiceNumber) {
throw new Error('Missing required parameters')
}
if (invoiceNumber.length === 0) {
throw new Error('Invoice number cannot be empty')
}
// Perform derivation
const derivedKey = privateKey.deriveChild(counterpartyPublicKey, invoiceNumber)
// Validate result
if (!derivedKey) {
throw new Error('Key derivation failed')
}
return derivedKey
} catch (error: any) {
console.error('Type-42 derivation error:', error.message)
return null
}
}
// Usage example
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
const derivedKey = safeType42Derivation(alice, bobPub, '2-safe-derivation-001')
if (derivedKey) {
console.log('Derivation successful')
} else {
console.log('Derivation failed')
}
For applications that frequently use the same invoice numbers:
class Type42KeyCache {
private cache = new Map<string, PrivateKey>()
constructor(private masterKey: PrivateKey) {}
deriveKey(counterpartyPub: PublicKey, invoiceNumber: string): PrivateKey {
const cacheKey = `${counterpartyPub.toString()}-${invoiceNumber}`
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!
}
const derivedKey = this.masterKey.deriveChild(counterpartyPub, invoiceNumber)
this.cache.set(cacheKey, derivedKey)
return derivedKey
}
clearCache(): void {
this.cache.clear()
}
getCacheSize(): number {
return this.cache.size
}
}
// Usage
const alice = PrivateKey.fromRandom()
const keyCache = new Type42KeyCache(alice)
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
// First call performs derivation
const key1 = keyCache.deriveKey(bobPub, '2-cached-001')
// Second call uses cache
const key2 = keyCache.deriveKey(bobPub, '2-cached-001')
console.log('Keys are identical:', key1.toHex() === key2.toHex())
console.log('Cache size:', keyCache.getCacheSize())
// Good: Structured, unique invoice numbers
const goodInvoiceNumbers = [
'2-payment-20241210-001',
'2-message-session-abc123',
'2-auth-token-xyz789'
]
// Avoid: Predictable or reused invoice numbers
const badInvoiceNumbers = [
'1', // Too simple
'payment', // Not unique
'2-payment-001' // Reused across different contexts
]
function secureMasterKeyUsage() {
// Generate master key securely
const masterKey = PrivateKey.fromRandom()
// Never log or expose master keys
// console.log('Master key:', masterKey.toHex()) // DON'T DO THIS
// Use derived keys for operations
const counterparty = PrivateKey.fromRandom().toPublicKey()
const derivedKey = masterKey.deriveChild(counterparty, '2-secure-operation-001')
// Log derived keys if needed (they don't reveal master key)
console.log('Derived key (safe to log):', derivedKey.toHex().substring(0, 16) + '...')
return derivedKey
}
function validateCounterparty(publicKey: PublicKey): boolean {
try {
// Ensure the public key is valid by checking its coordinates
const x = publicKey.x
const y = publicKey.y
if (!x || !y) {
return false
}
// Additional validation can be added here
return true
} catch (error: any) {
return false
}
}
function testType42Implementation() {
console.log('Testing Type-42 key derivation...')
// Test 1: Basic derivation
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
const bobPub = bob.toPublicKey()
const alicePub = alice.toPublicKey()
const invoiceNumber = '2-test-001'
const aliceChild = alice.deriveChild(bobPub, invoiceNumber)
const aliceChildPub = alicePub.deriveChild(bob, invoiceNumber)
const test1 = aliceChild.toPublicKey().toString() === aliceChildPub.toString()
console.log('Test 1 - Key consistency:', test1 ? 'PASS' : 'FAIL')
// Test 2: Message signing and verification
const message = Utils.toArray('Test message', 'utf8')
const signature = aliceChild.sign(message)
const verified = aliceChildPub.verify(message, signature)
console.log('Test 2 - Sign/verify:', verified ? 'PASS' : 'FAIL')
// Test 3: Different invoice numbers produce different keys
const key1 = alice.deriveChild(bobPub, '2-test-001')
const key2 = alice.deriveChild(bobPub, '2-test-002')
const test3 = key1.toHex() !== key2.toHex()
console.log('Test 3 - Unique keys:', test3 ? 'PASS' : 'FAIL')
// Test 4: Bidirectional derivation
const bobChild = bob.deriveChild(alicePub, invoiceNumber)
const bobChildPub = bobPub.deriveChild(alice, invoiceNumber)
const test4 = bobChild.toPublicKey().toString() === bobChildPub.toString()
console.log('Test 4 - Bidirectional:', test4 ? 'PASS' : 'FAIL')
return test1 && verified && test3 && test4
}
// Run tests
const allTestsPassed = testType42Implementation()
console.log('All tests passed:', allTestsPassed)
// Problem: Derived keys don't match
// Solution: Ensure consistent invoice numbers and key order
function debugKeyMismatch() {
const alice = PrivateKey.fromRandom()
const bob = PrivateKey.fromRandom()
// Wrong: Different invoice numbers
const key1 = alice.deriveChild(bob.toPublicKey(), '2-test-001')
const key2 = alice.toPublicKey().deriveChild(bob, '2-test-002') // Different number
// Correct: Same invoice number
const key3 = alice.deriveChild(bob.toPublicKey(), '2-test-001')
const key4 = alice.toPublicKey().deriveChild(bob, '2-test-001') // Same number
console.log('Wrong approach - keys match:',
key1.toPublicKey().toString() === key2.toString())
console.log('Correct approach - keys match:',
key3.toPublicKey().toString() === key4.toString())
}
function handleInvalidPublicKey() {
try {
const alice = PrivateKey.fromRandom()
// This would cause an error if publicKey is invalid
const invalidPub = null as any
const derivedKey = alice.deriveChild(invalidPub, '2-test-001')
} catch (error: any) {
console.log('Caught invalid public key error:', error.message)
// Handle the error appropriately
}
}
Type-42 key derivation provides a powerful mechanism for creating shared key universes between two parties. You’ve learned how to:
Type-42 enables sophisticated cryptographic protocols while maintaining the security properties of elliptic curve cryptography. The derived keys are unlinkable to master keys by outside parties, providing privacy and security for Bitcoin applications.
WalletClient
For production applications, the WalletClient
provides secure key management for Type-42 operations:
```typescript // Usage example const walletClient = new WalletClient(‘https://api.bsvwallet.com/v1’) const alice = walletClient.getPrivateKey(‘alice’) const bob = walletClient.getPrivateKey(‘bob’) const invoiceNumber = ‘2-secure-operation-001’
const derivedKey = alice.deriveChild(bob.toPublicKey(), invoiceNumber) console.log(‘Derived key:’, derivedKey.toHex().substring(0, 16) + ‘…’)