Implement NIP-44: secure versioned replacement for NIP4 (#221)

This commit is contained in:
Paul Miller
2023-09-30 01:43:48 +02:00
committed by GitHub
parent c73268c4e2
commit eb0a9093f2
6 changed files with 424 additions and 51 deletions

View File

@@ -1,21 +1,75 @@
import crypto from 'node:crypto'
import { hexToBytes } from '@noble/hashes/utils'
import { encrypt, decrypt, utils } from './nip44.ts'
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
import { v2 as vectors } from './nip44.vectors.json'
import { getPublicKey } from './keys.ts'
import { encrypt, decrypt, getSharedSecret } from './nip44.ts'
import { getPublicKey, generatePrivateKey } from './keys.ts'
// @ts-ignore
// eslint-disable-next-line no-undef
globalThis.crypto = crypto
test('encrypt and decrypt message', async () => {
let sk1 = generatePrivateKey()
let sk2 = generatePrivateKey()
let pk1 = getPublicKey(sk1)
let pk2 = getPublicKey(sk2)
let sharedKey1 = getSharedSecret(sk1, pk2)
let sharedKey2 = getSharedSecret(sk2, pk1)
expect(decrypt(hexToBytes(sk1), encrypt(hexToBytes(sk1), 'hello'))).toEqual('hello')
expect(decrypt(sharedKey2, encrypt(sharedKey1, 'hello'))).toEqual('hello')
test('NIP44: valid_sec', async () => {
for (const v of vectors.valid_sec) {
const pub2 = getPublicKey(v.sec2)
const key = utils.v2.getConversationKey(v.sec1, pub2)
expect(bytesToHex(key)).toEqual(v.shared)
const ciphertext = encrypt(key, v.plaintext, { salt: hexToBytes(v.salt) })
expect(ciphertext).toEqual(v.ciphertext)
const decrypted = decrypt(key, ciphertext)
expect(decrypted).toEqual(v.plaintext)
}
})
test('NIP44: valid_pub', async () => {
for (const v of vectors.valid_pub) {
const key = utils.v2.getConversationKey(v.sec1, v.pub2)
expect(bytesToHex(key)).toEqual(v.shared)
const ciphertext = encrypt(key, v.plaintext, { salt: hexToBytes(v.salt) })
expect(ciphertext).toEqual(v.ciphertext)
const decrypted = decrypt(key, ciphertext)
expect(decrypted).toEqual(v.plaintext)
}
})
test('NIP44: invalid', async () => {
for (const v of vectors.invalid) {
expect(() => {
const key = utils.v2.getConversationKey(v.sec1, v.pub2)
const ciphertext = decrypt(key, v.plaintext)
}).toThrowError()
}
})
test('NIP44: invalid_conversation_key', async () => {
for (const v of vectors.invalid_conversation_key) {
expect(() => {
const key = utils.v2.getConversationKey(v.sec1, v.pub2)
const ciphertext = encrypt(key, v.plaintext)
}).toThrowError()
}
})
test('NIP44: v1 calcPadding', () => {
for (const [len, shouldBePaddedTo] of vectors.padding) {
const actual = utils.v2.calcPadding(len)
expect(actual).toEqual(shouldBePaddedTo)
}
})
// To re-generate vectors and produce new ones:
// Create regen.mjs with this content:
// import {getPublicKey, nip44} from './lib/esm/nostr.mjs'
// import {bytesToHex, hexToBytes} from '@noble/hashes/utils'
// import vectors from './nip44.vectors.json' assert { type: "json" };
// function genVectors(v) {
// const pub2 = v.pub2 ?? getPublicKey(v.sec2);
// let sharedKey = nip44.utils.v2.getConversationKey(v.sec1, pub2)
// let ciphertext = nip44.encrypt(sharedKey, v.plaintext, { salt: hexToBytes(v.salt) })
// console.log({
// sec1: v.sec1,
// pub2: pub2,
// sharedKey: bytesToHex(sharedKey),
// salt: v.salt,
// plaintext: v.plaintext,
// ciphertext
// })
// }
// for (let v of vectors.valid_sec) genVectors(v);
// for (let v of vectors.valid_pub) genVectors(v);
// const padded = concatBytes(utils.v2.pad(plaintext), new Uint8Array(250))
// const mac = randomBytes(32)