Skip to content

Commit aee8940

Browse files
authored
test: optimize NIP-44 unit tests (#624)
Signed-off-by: ABHAY PANDEY <pandeyabhay967@gmail.com>
1 parent 54139ed commit aee8940

2 files changed

Lines changed: 41 additions & 77 deletions

File tree

.changeset/vast-signs-melt.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

test/unit/utils/nip44.spec.ts

Lines changed: 39 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,155 +18,121 @@ function pubkeyFromPrivkey(secHex: string): string {
1818

1919
const SEC1 = '0000000000000000000000000000000000000000000000000000000000000001'
2020
const SEC2 = '0000000000000000000000000000000000000000000000000000000000000002'
21+
const SEC3 = '0000000000000000000000000000000000000000000000000000000000000003'
2122
const KNOWN_CONVERSATION_KEY = 'c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d'
2223
const KNOWN_NONCE = '0000000000000000000000000000000000000000000000000000000000000001'
2324
const KNOWN_PLAINTEXT = 'a'
2425
const KNOWN_PAYLOAD =
2526
'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb'
2627

28+
let PUB1: string
29+
let PUB2: string
30+
let PUB3: string
31+
let CONVERSATION_KEY: Buffer
32+
let RECIPIENT_CONVERSATION_KEY: Buffer
33+
let DIFFERENT_CONVERSATION_KEY: Buffer
34+
2735
// ---------------------------------------------------------------------------
2836

2937
describe('NIP-44', () => {
38+
before(() => {
39+
PUB1 = pubkeyFromPrivkey(SEC1)
40+
PUB2 = pubkeyFromPrivkey(SEC2)
41+
PUB3 = pubkeyFromPrivkey(SEC3)
42+
CONVERSATION_KEY = getConversationKey(SEC1, PUB2)
43+
RECIPIENT_CONVERSATION_KEY = getConversationKey(SEC2, PUB1)
44+
DIFFERENT_CONVERSATION_KEY = getConversationKey(SEC1, PUB3)
45+
})
46+
3047
describe('getConversationKey', () => {
3148
it('derives the correct conversation key from sec1 and pub2', () => {
32-
const pub2 = pubkeyFromPrivkey(SEC2)
33-
const key = getConversationKey(SEC1, pub2)
34-
expect(key.toString('hex')).to.equal(KNOWN_CONVERSATION_KEY)
49+
expect(CONVERSATION_KEY.toString('hex')).to.equal(KNOWN_CONVERSATION_KEY)
3550
})
3651

3752
it('is symmetric: conv(a, B) == conv(b, A)', () => {
38-
const pub1 = pubkeyFromPrivkey(SEC1)
39-
const pub2 = pubkeyFromPrivkey(SEC2)
40-
const keyAB = getConversationKey(SEC1, pub2)
41-
const keyBA = getConversationKey(SEC2, pub1)
42-
expect(keyAB.toString('hex')).to.equal(keyBA.toString('hex'))
53+
expect(CONVERSATION_KEY.toString('hex')).to.equal(RECIPIENT_CONVERSATION_KEY.toString('hex'))
4354
})
4455

4556
it('produces different keys for different key pairs', () => {
46-
const sec3 = '0000000000000000000000000000000000000000000000000000000000000003'
47-
const pub2 = pubkeyFromPrivkey(SEC2)
48-
const pub3 = pubkeyFromPrivkey(sec3)
49-
const key12 = getConversationKey(SEC1, pub2)
50-
const key13 = getConversationKey(SEC1, pub3)
51-
expect(key12.toString('hex')).to.not.equal(key13.toString('hex'))
57+
expect(CONVERSATION_KEY.toString('hex')).to.not.equal(DIFFERENT_CONVERSATION_KEY.toString('hex'))
5258
})
5359
})
5460

5561
describe('nip44Encrypt', () => {
5662
it('produces the canonical payload from the NIP-44 spec test vector', () => {
57-
const pub2 = pubkeyFromPrivkey(SEC2)
58-
const conversationKey = getConversationKey(SEC1, pub2)
5963
const nonce = Buffer.from(KNOWN_NONCE, 'hex')
6064

61-
const payload = nip44Encrypt(KNOWN_PLAINTEXT, conversationKey, nonce)
65+
const payload = nip44Encrypt(KNOWN_PLAINTEXT, CONVERSATION_KEY, nonce)
6266
expect(payload).to.equal(KNOWN_PAYLOAD)
6367
})
6468

6569
it('produces a valid base64 string starting with version byte 0x02', () => {
66-
const pub2 = pubkeyFromPrivkey(SEC2)
67-
const conversationKey = getConversationKey(SEC1, pub2)
68-
69-
const payload = nip44Encrypt('hello', conversationKey)
70+
const payload = nip44Encrypt('hello', CONVERSATION_KEY)
7071
const decoded = Buffer.from(payload, 'base64')
7172

7273
expect(decoded[0]).to.equal(2) // version byte
7374
expect(payload.length).to.be.within(132, 87472)
7475
})
7576

7677
it('produces different ciphertexts for the same plaintext (random nonce)', () => {
77-
const pub2 = pubkeyFromPrivkey(SEC2)
78-
const conversationKey = getConversationKey(SEC1, pub2)
79-
80-
const payload1 = nip44Encrypt('same message', conversationKey)
81-
const payload2 = nip44Encrypt('same message', conversationKey)
78+
const payload1 = nip44Encrypt('same message', CONVERSATION_KEY)
79+
const payload2 = nip44Encrypt('same message', CONVERSATION_KEY)
8280

8381
expect(payload1).to.not.equal(payload2)
8482
})
8583

8684
it('throws for empty plaintext', () => {
87-
const pub2 = pubkeyFromPrivkey(SEC2)
88-
const conversationKey = getConversationKey(SEC1, pub2)
89-
90-
expect(() => nip44Encrypt('', conversationKey)).to.throw('invalid plaintext length')
85+
expect(() => nip44Encrypt('', CONVERSATION_KEY)).to.throw('invalid plaintext length')
9186
})
9287

9388
it('throws for plaintext exceeding 65535 bytes', () => {
94-
const pub2 = pubkeyFromPrivkey(SEC2)
95-
const conversationKey = getConversationKey(SEC1, pub2)
96-
97-
expect(() => nip44Encrypt('x'.repeat(65536), conversationKey)).to.throw('invalid plaintext length')
89+
expect(() => nip44Encrypt('x'.repeat(65536), CONVERSATION_KEY)).to.throw('invalid plaintext length')
9890
})
9991
})
10092

10193
describe('nip44Decrypt', () => {
10294
it('decrypts the canonical NIP-44 spec test vector', () => {
103-
const pub2 = pubkeyFromPrivkey(SEC2)
104-
const conversationKey = getConversationKey(SEC1, pub2)
105-
106-
const plaintext = nip44Decrypt(KNOWN_PAYLOAD, conversationKey)
95+
const plaintext = nip44Decrypt(KNOWN_PAYLOAD, CONVERSATION_KEY)
10796
expect(plaintext).to.equal(KNOWN_PLAINTEXT)
10897
})
10998

11099
it('round-trips any plaintext through encrypt then decrypt', () => {
111-
const pub2 = pubkeyFromPrivkey(SEC2)
112-
const conversationKey = getConversationKey(SEC1, pub2)
113100
const original = 'Hola, que tal? 🌍'
114101

115-
const payload = nip44Encrypt(original, conversationKey)
116-
const recovered = nip44Decrypt(payload, conversationKey)
102+
const payload = nip44Encrypt(original, CONVERSATION_KEY)
103+
const recovered = nip44Decrypt(payload, CONVERSATION_KEY)
117104

118105
expect(recovered).to.equal(original)
119106
})
120107

121108
it('works with the symmetric key (recipient decrypts sender message)', () => {
122-
const pub1 = pubkeyFromPrivkey(SEC1)
123-
const pub2 = pubkeyFromPrivkey(SEC2)
124-
125-
const senderKey = getConversationKey(SEC1, pub2)
126-
const recipientKey = getConversationKey(SEC2, pub1)
127-
128-
const payload = nip44Encrypt('secret message', senderKey)
129-
const plaintext = nip44Decrypt(payload, recipientKey)
109+
const payload = nip44Encrypt('secret message', CONVERSATION_KEY)
110+
const plaintext = nip44Decrypt(payload, RECIPIENT_CONVERSATION_KEY)
130111

131112
expect(plaintext).to.equal('secret message')
132113
})
133114

134115
it('throws when MAC is tampered', () => {
135-
const pub2 = pubkeyFromPrivkey(SEC2)
136-
const conversationKey = getConversationKey(SEC1, pub2)
137-
const payload = nip44Encrypt('tamper me', conversationKey)
116+
const payload = nip44Encrypt('tamper me', CONVERSATION_KEY)
138117

139118
// Flip the last character of the base64 payload to corrupt the MAC
140119
const tampered = payload.slice(0, -4) + 'AAAA'
141120

142-
expect(() => nip44Decrypt(tampered, conversationKey)).to.throw()
121+
expect(() => nip44Decrypt(tampered, CONVERSATION_KEY)).to.throw()
143122
})
144123

145124
it('throws for payload starting with # (unsupported future version)', () => {
146-
const pub2 = pubkeyFromPrivkey(SEC2)
147-
const conversationKey = getConversationKey(SEC1, pub2)
148-
149-
expect(() => nip44Decrypt('#not-base64', conversationKey)).to.throw('unknown version')
125+
expect(() => nip44Decrypt('#not-base64', CONVERSATION_KEY)).to.throw('unknown version')
150126
})
151127

152128
it('throws for payload that is too short', () => {
153-
const pub2 = pubkeyFromPrivkey(SEC2)
154-
const conversationKey = getConversationKey(SEC1, pub2)
155-
156-
expect(() => nip44Decrypt('dG9vc2hvcnQ=', conversationKey)).to.throw('invalid payload size')
129+
expect(() => nip44Decrypt('dG9vc2hvcnQ=', CONVERSATION_KEY)).to.throw('invalid payload size')
157130
})
158131

159132
it('throws for wrong conversation key', () => {
160-
const sec3 = '0000000000000000000000000000000000000000000000000000000000000003'
161-
const pub2 = pubkeyFromPrivkey(SEC2)
162-
const pub3 = pubkeyFromPrivkey(sec3)
163-
164-
const senderKey = getConversationKey(SEC1, pub2)
165-
const wrongKey = getConversationKey(SEC1, pub3)
166-
167-
const payload = nip44Encrypt('private', senderKey)
133+
const payload = nip44Encrypt('private', CONVERSATION_KEY)
168134

169-
expect(() => nip44Decrypt(payload, wrongKey)).to.throw()
135+
expect(() => nip44Decrypt(payload, DIFFERENT_CONVERSATION_KEY)).to.throw()
170136
})
171137
})
172138

@@ -176,9 +142,7 @@ describe('NIP-44', () => {
176142
})
177143

178144
it('returns undefined for a freshly encrypted payload', () => {
179-
const pub2 = pubkeyFromPrivkey(SEC2)
180-
const conversationKey = getConversationKey(SEC1, pub2)
181-
const payload = nip44Encrypt('hello', conversationKey)
145+
const payload = nip44Encrypt('hello', CONVERSATION_KEY)
182146

183147
expect(validateNip44Payload(payload)).to.be.undefined
184148
})
@@ -228,11 +192,9 @@ describe('NIP-44', () => {
228192

229193
for (const [unpaddedLen, expectedPaddedLen] of cases) {
230194
it(`pads ${unpaddedLen} bytes to ${expectedPaddedLen} bytes`, () => {
231-
const pub2 = pubkeyFromPrivkey(SEC2)
232-
const conversationKey = getConversationKey(SEC1, pub2)
233195
const plaintext = 'a'.repeat(unpaddedLen)
234196

235-
const payload = nip44Encrypt(plaintext, conversationKey)
197+
const payload = nip44Encrypt(plaintext, CONVERSATION_KEY)
236198
const decoded = Buffer.from(payload, 'base64')
237199

238200
// Layout: 1 (version) + 32 (nonce) + paddedLen + 2 (length prefix) + 32 (mac)

0 commit comments

Comments
 (0)