108 lines
4.2 KiB
JavaScript
108 lines
4.2 KiB
JavaScript
// Debug script to extract all intermediate values from nostr-tools NIP-44
|
|
const { v2 } = require('./nostr-tools/nip44.js');
|
|
const { bytesToHex, hexToBytes } = require('@noble/hashes/utils');
|
|
|
|
// Test vector 1: single char 'a'
|
|
console.log('=== NOSTR-TOOLS DEBUG: Single char "a" ===');
|
|
const sec1 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
|
const sec2 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000002');
|
|
const nonce = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
|
const plaintext = 'a';
|
|
|
|
// Step 1: Get public keys
|
|
const { schnorr } = require('@noble/curves/secp256k1');
|
|
const pub1 = bytesToHex(schnorr.getPublicKey(sec1));
|
|
const pub2 = bytesToHex(schnorr.getPublicKey(sec2));
|
|
console.log('pub1:', pub1);
|
|
console.log('pub2:', pub2);
|
|
|
|
// Step 2: Get conversation key
|
|
const conversationKey = v2.utils.getConversationKey(sec1, pub2);
|
|
console.log('conversation_key:', bytesToHex(conversationKey));
|
|
|
|
// Step 3: Get shared secret (raw ECDH)
|
|
const { secp256k1 } = require('@noble/curves/secp256k1');
|
|
const sharedPoint = secp256k1.getSharedSecret(sec1, '02' + pub2);
|
|
const sharedSecret = sharedPoint.subarray(1, 33); // X coordinate only
|
|
console.log('ecdh_shared_secret:', bytesToHex(sharedSecret));
|
|
|
|
// Step 4: Get message keys using internal function
|
|
const hkdf = require('@noble/hashes/hkdf');
|
|
const { sha256 } = require('@noble/hashes/sha256');
|
|
|
|
// HKDF Extract step
|
|
const salt = new TextEncoder().encode('nip44-v2');
|
|
const prk = hkdf.extract(sha256, sharedSecret, salt);
|
|
console.log('hkdf_extract_result:', bytesToHex(prk));
|
|
|
|
// HKDF Expand step
|
|
const messageKeys = hkdf.expand(sha256, prk, nonce, 76);
|
|
const chachaKey = messageKeys.subarray(0, 32);
|
|
const chachaNonce = messageKeys.subarray(32, 44);
|
|
const hmacKey = messageKeys.subarray(44, 76);
|
|
|
|
console.log('chacha_key:', bytesToHex(chachaKey));
|
|
console.log('chacha_nonce:', bytesToHex(chachaNonce));
|
|
console.log('hmac_key:', bytesToHex(hmacKey));
|
|
|
|
// Step 5: Pad the plaintext
|
|
function pad(plaintext) {
|
|
const utf8Encoder = new TextEncoder();
|
|
const unpadded = utf8Encoder.encode(plaintext);
|
|
const unpaddedLen = unpadded.length;
|
|
|
|
// Length prefix (big-endian u16)
|
|
const prefix = new Uint8Array(2);
|
|
new DataView(prefix.buffer).setUint16(0, unpaddedLen, false);
|
|
|
|
// Calculate padded length
|
|
function calcPaddedLen(len) {
|
|
if (len <= 32) return 32;
|
|
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
|
|
const chunk = nextPower <= 256 ? 32 : nextPower / 8;
|
|
return chunk * (Math.floor((len - 1) / chunk) + 1);
|
|
}
|
|
|
|
const paddedLen = calcPaddedLen(unpaddedLen + 2);
|
|
const suffix = new Uint8Array(paddedLen - 2 - unpaddedLen);
|
|
|
|
// Combine: prefix + plaintext + padding
|
|
const result = new Uint8Array(paddedLen);
|
|
result.set(prefix);
|
|
result.set(unpadded, 2);
|
|
result.set(suffix, 2 + unpaddedLen);
|
|
|
|
return result;
|
|
}
|
|
|
|
const paddedPlaintext = pad(plaintext);
|
|
console.log('padded_plaintext:', bytesToHex(paddedPlaintext));
|
|
console.log('padded_length:', paddedPlaintext.length);
|
|
|
|
// Step 6: ChaCha20 encrypt
|
|
const { chacha20 } = require('@noble/ciphers/chacha');
|
|
const ciphertext = chacha20(chachaKey, chachaNonce, paddedPlaintext);
|
|
console.log('ciphertext:', bytesToHex(ciphertext));
|
|
|
|
// Step 7: HMAC with AAD
|
|
const { hmac } = require('@noble/hashes/hmac');
|
|
const { concatBytes } = require('@noble/hashes/utils');
|
|
const aad = concatBytes(nonce, ciphertext);
|
|
console.log('aad_data:', bytesToHex(aad));
|
|
const mac = hmac(sha256, hmacKey, aad);
|
|
console.log('mac:', bytesToHex(mac));
|
|
|
|
// Step 8: Final payload
|
|
const { base64 } = require('@scure/base');
|
|
const payload = concatBytes(new Uint8Array([2]), nonce, ciphertext, mac);
|
|
console.log('raw_payload:', bytesToHex(payload));
|
|
const base64Payload = base64.encode(payload);
|
|
console.log('final_payload:', base64Payload);
|
|
|
|
// Expected from test vectors
|
|
console.log('expected_payload:', 'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb');
|
|
|
|
// Now let's also test the full encrypt function
|
|
const fullEncrypt = v2.encrypt(plaintext, conversationKey, nonce);
|
|
console.log('v2.encrypt_result:', fullEncrypt);
|