meta-log-db
Version:
Native database package for Meta-Log (ProLog, DataLog, R5RS)
205 lines (173 loc) • 6.49 kB
text/typescript
/**
* BIP32 HD Key Derivation Implementation
*
* Implements Hierarchical Deterministic (HD) wallet key derivation using Web Crypto API
* Based on BIP32 specification: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
*/
/**
* Parse derivation path (e.g., "m/44'/60'/0'/0/0")
*/
function parsePath(path: string): number[] {
const parts = path.split('/');
if (parts[0] !== 'm') {
throw new Error('Path must start with "m"');
}
return parts.slice(1).map(part => {
const hardened = part.endsWith("'") || part.endsWith('h');
const index = parseInt(part.replace(/['h]/g, ''), 10);
if (isNaN(index)) {
throw new Error(`Invalid path segment: ${part}`);
}
// Hardened keys use index >= 2^31
return hardened ? index + 0x80000000 : index;
});
}
/**
* HMAC-SHA512 using Web Crypto API
*/
async function hmacSha512(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
// Ensure we have proper ArrayBuffer views
const keyBuffer = key.buffer instanceof ArrayBuffer ? key : new Uint8Array(key).buffer;
const dataBuffer = data.buffer instanceof ArrayBuffer ? data : new Uint8Array(data).buffer;
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyBuffer as ArrayBuffer,
{ name: 'HMAC', hash: 'SHA-512' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, dataBuffer as ArrayBuffer);
return new Uint8Array(signature);
}
/**
* Derive child key from parent key using BIP32
*/
async function deriveChildKey(
parentKey: Uint8Array,
parentChainCode: Uint8Array,
index: number
): Promise<{ key: Uint8Array; chainCode: Uint8Array }> {
const isHardened = index >= 0x80000000;
const indexBuffer = new ArrayBuffer(4);
const indexView = new DataView(indexBuffer);
indexView.setUint32(0, index, false);
const data = new Uint8Array(37);
if (isHardened) {
// Hardened: 0x00 || parentKey || index
data[0] = 0x00;
data.set(parentKey, 1);
data.set(new Uint8Array(indexBuffer), 33);
} else {
// Non-hardened: parentPublicKey || index
// For non-hardened, we need the public key
// This is a simplified version - full implementation would derive public key
throw new Error('Non-hardened derivation requires public key derivation (not implemented)');
}
const hmac = await hmacSha512(parentChainCode, data);
const childKey = hmac.slice(0, 32);
const chainCode = hmac.slice(32, 64);
// Add parent key to child key (modulo curve order)
// Simplified: XOR for demonstration (full implementation would use secp256k1 arithmetic)
const derivedKey = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
derivedKey[i] = (parentKey[i] + childKey[i]) % 256;
}
return { key: derivedKey, chainCode };
}
/**
* Derive key from seed using BIP32 derivation path
*/
export async function deriveKey(seed: Uint8Array, path: string): Promise<CryptoKey> {
// Master key derivation: HMAC-SHA512(Key = "Bitcoin seed", Data = seed)
const keyMaterial = new TextEncoder().encode('Bitcoin seed');
const masterHmac = await hmacSha512(keyMaterial, seed);
const masterKey = masterHmac.slice(0, 32);
const masterChainCode = masterHmac.slice(32, 64);
// Parse derivation path
const indices = parsePath(path);
// Derive through each level
let currentKey: Uint8Array = masterKey;
let currentChainCode: Uint8Array = masterChainCode;
for (const index of indices) {
const result = await deriveChildKey(currentKey, currentChainCode, index);
currentKey = new Uint8Array(result.key);
currentChainCode = new Uint8Array(result.chainCode);
}
// Import as AES-GCM key for encryption
// Ensure we have a proper ArrayBuffer
const keyBuffer = currentKey.buffer instanceof ArrayBuffer
? currentKey.buffer
: new Uint8Array(currentKey).buffer;
return await crypto.subtle.importKey(
'raw',
keyBuffer as ArrayBuffer,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
/**
* Derive public key from private key (simplified)
*
* Note: Full implementation would use secp256k1 elliptic curve operations
* This is a placeholder that derives a key suitable for encryption
*/
export async function derivePublicKey(privateKey: CryptoKey): Promise<CryptoKey> {
// Export private key material
const keyData = await crypto.subtle.exportKey('raw', privateKey);
const keyBytes = new Uint8Array(keyData);
// Simplified public key derivation (XOR with constant)
// Full implementation would use secp256k1 point multiplication
const publicKeyBytes = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
publicKeyBytes[i] = keyBytes[i] ^ 0xFF;
}
return await crypto.subtle.importKey(
'raw',
publicKeyBytes,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
}
/**
* Derive key from extended key format
*
* Extended key format: base58 encoded (version || depth || fingerprint || index || chaincode || key)
* This is a simplified version - full implementation would decode base58
*/
export async function deriveFromExtendedKey(extendedKey: string, path: string): Promise<CryptoKey> {
// For now, decode as hex (full implementation would use base58)
// Browser-compatible hex decoding
const hexMatch = extendedKey.match(/.{1,2}/g);
if (!hexMatch) {
throw new Error('Invalid hex string');
}
const keyBytes = new Uint8Array(hexMatch.map(byte => parseInt(byte, 16)));
if (keyBytes.length < 78) {
throw new Error('Invalid extended key length');
}
const chainCode = keyBytes.slice(13, 45);
const key = keyBytes.slice(45, 77);
// Parse derivation path (remove 'm' prefix if present)
const indices = parsePath(path.startsWith('m/') ? path : `m/${path}`);
// Derive through each level
let currentKey: Uint8Array = new Uint8Array(key);
let currentChainCode: Uint8Array = new Uint8Array(chainCode);
for (const index of indices) {
const result = await deriveChildKey(currentKey, currentChainCode, index);
currentKey = new Uint8Array(result.key);
currentChainCode = new Uint8Array(result.chainCode);
}
// Ensure we have a proper ArrayBuffer
const keyBuffer = currentKey.buffer instanceof ArrayBuffer
? currentKey.buffer
: new Uint8Array(currentKey).buffer;
return await crypto.subtle.importKey(
'raw',
keyBuffer as ArrayBuffer,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}