107 lines
3.8 KiB
JavaScript
107 lines
3.8 KiB
JavaScript
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)
|