import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils' import { secp256k1 } from '@noble/curves/secp256k1' import { cbc } from '@noble/ciphers/aes' import { base64 } from '@scure/base' // UTF-8 encoder/decoder const utf8Encoder = new TextEncoder() const utf8Decoder = new TextDecoder() function getNormalizedX(key) { return key.slice(1, 33) } function encrypt(secretKey, pubkey, text) { console.log(`[JS] Encrypting "${text}" using sk1 -> pk2`) console.log(`[JS] Private Key: ${secretKey}`) console.log(`[JS] Public Key: ${pubkey}`) // Step 1: Get shared secret const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey) console.log(`[JS] Shared Secret: ${bytesToHex(key)}`) // Step 2: Normalize key (remove first byte, keep next 32) const normalizedKey = getNormalizedX(key) console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`) // Step 3: Generate random IV const iv = randomBytes(16) console.log(`[JS] IV: ${bytesToHex(iv)}`) // Step 4: Encode plaintext to UTF-8 const plaintext = utf8Encoder.encode(text) console.log(`[JS] UTF-8 Plaintext: ${bytesToHex(plaintext)}`) // Step 5: AES-CBC encryption const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext) console.log(`[JS] Raw Ciphertext: ${bytesToHex(new Uint8Array(ciphertext))}`) // Step 6: Base64 encoding const ctb64 = base64.encode(new Uint8Array(ciphertext)) const ivb64 = base64.encode(new Uint8Array(iv.buffer)) const result = `${ctb64}?iv=${ivb64}` console.log(`[JS] Encrypted Result: ${result}`) return result } function decrypt(secretKey, pubkey, data) { console.log(`[JS] Decrypting "${data}" using sk2 + pk1`) console.log(`[JS] Private Key: ${secretKey}`) console.log(`[JS] Public Key: ${pubkey}`) // Step 1: Parse format const [ctb64, ivb64] = data.split('?iv=') // Step 2: Get shared secret const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey) console.log(`[JS] Shared Secret: ${bytesToHex(key)}`) // Step 3: Normalize key const normalizedKey = getNormalizedX(key) console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`) // Step 4: Base64 decode const iv = base64.decode(ivb64) const ciphertext = base64.decode(ctb64) console.log(`[JS] IV: ${bytesToHex(iv)}`) console.log(`[JS] Raw Ciphertext: ${bytesToHex(ciphertext)}`) // Step 5: AES-CBC decryption const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext) console.log(`[JS] Decrypted Plaintext: ${bytesToHex(plaintext)}`) // Step 6: UTF-8 decode const result = utf8Decoder.decode(plaintext) console.log(`[JS] UTF-8 Decoded: "${result}"`) return result } // Test with exact vectors async function main() { console.log("=== NIP-04 DEBUG COMPARISON (JavaScript) ===") const sk1 = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe" const pk1 = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1" const sk2 = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220" const pk2 = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3" const plaintext = "nanana" const expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA==" console.log("\n--- ENCRYPTION TEST ---") const encrypted = encrypt(sk1, pk2, plaintext) console.log("\n--- DECRYPTION TEST ---") const decrypted = decrypt(sk2, pk1, expectedCiphertext) console.log("\n--- RESULTS ---") console.log(`Encryption Success: Generated ciphertext`) console.log(`Decryption Success: ${decrypted === plaintext}`) console.log(`Expected: "${plaintext}"`) console.log(`Got: "${decrypted}"`) } main().catch(console.error)