import * as bip39 from "bip39"; // Bun implements the Web Crypto API globally as `crypto` const BASE43_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:"; // --- Helper: Base43 Decode --- function base43Decode(str: string): Uint8Array { let value = 0n; const base = 43n; for (const char of str) { const index = BASE43_ALPHABET.indexOf(char); if (index === -1) throw new Error(`Invalid Base43 char: ${char}`); value = value * base + BigInt(index); } // Convert BigInt to Buffer/Uint8Array let hex = value.toString(16); if (hex.length % 2 !== 0) hex = '0' + hex; return new Uint8Array(Buffer.from(hex, 'hex')); } // --- Main Decryption Function --- async function decryptKruxKEF(kefData: string, passphrase: string) { // 1. Decode Base43 const rawBytes = base43Decode(kefData); // 2. Parse Envelope let offset = 0; // ID Length (1 byte) const idLen = rawBytes[offset++]; // ID / Salt const salt = rawBytes.slice(offset, offset + idLen); offset += idLen; // Version (1 byte) const version = rawBytes[offset++]; // Iterations (3 bytes, Big-Endian) const iterBytes = rawBytes.slice(offset, offset + 3); const iterations = (iterBytes[0] << 16) | (iterBytes[1] << 8) | iterBytes[2]; offset += 3; // Payload: [IV (12 bytes)] [Ciphertext (N)] [Tag (4 bytes)] const payload = rawBytes.slice(offset); const iv = payload.slice(0, 12); const tagLength = 4; const ciphertextWithTag = payload.slice(12); console.log("--- Parsed KEF Data ---"); console.log(`Version: ${version}`); console.log(`Iterations: ${iterations}`); console.log(`Salt (hex): ${Buffer.from(salt).toString('hex')}`); if (version !== 20) { throw new Error("Only KEF Version 20 (AES-GCM) is supported."); } // 3. Derive Key (PBKDF2-HMAC-SHA256) const keyMaterial = await crypto.subtle.importKey( "raw", new TextEncoder().encode(passphrase), { name: "PBKDF2" }, false, ["deriveKey"] ); const key = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: salt, iterations: iterations, hash: "SHA-256", }, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["decrypt"] ); // 4. Decrypt (AES-GCM) try { const decryptedBuffer = await crypto.subtle.decrypt( { name: "AES-GCM", iv: iv, tagLength: 32, // 4 bytes * 8 }, key, ciphertextWithTag ); return new Uint8Array(decryptedBuffer); } catch (error) { throw new Error(`Decryption failed: ${error}`); } } // --- Run Test --- const kefString = "1334+HGXM$F8PPOIRNHX0.R*:SBMHK$X88LX$*/Y417R/6S1ZQOB2LHM-L+4T1YQVU:B*CKGXONP7:Y/R-B*:$R8FK"; const passphrase = "aaa"; const expectedMnemonic = "differ release beauty fresh tortoise usage curtain spoil october town embrace ridge rough reject cabin snap glimpse enter book coach green lonely hundred mercy"; console.log(`\nDecrypting KEF String...`); try { const entropy = await decryptKruxKEF(kefString, passphrase); const mnemonic = bip39.entropyToMnemonic(Buffer.from(entropy)); console.log("\n--- Result ---"); console.log(`Mnemonic: ${mnemonic}`); if (mnemonic === expectedMnemonic) { console.log("\n✅ SUCCESS: Mnemonic matches expected output."); } else { console.log("\n❌ FAIL: Mnemonic does not match."); } } catch (e) { console.error(e); }