@@ -18,155 +18,121 @@ function pubkeyFromPrivkey(secHex: string): string {
1818
1919const SEC1 = '0000000000000000000000000000000000000000000000000000000000000001'
2020const SEC2 = '0000000000000000000000000000000000000000000000000000000000000002'
21+ const SEC3 = '0000000000000000000000000000000000000000000000000000000000000003'
2122const KNOWN_CONVERSATION_KEY = 'c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d'
2223const KNOWN_NONCE = '0000000000000000000000000000000000000000000000000000000000000001'
2324const KNOWN_PLAINTEXT = 'a'
2425const 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
2937describe ( '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