@@ -7,160 +7,148 @@ const TEST_MNEMONIC =
77 'test test test test test test test test test test test junk' ;
88const TEST_PASSWORD = 'my-secret-password' ;
99const TEST_SALT = 'deadbeefdeadbeefdeadbeefdeadbeef' ;
10+ // Use fast PBKDF2 iterations for testing. Production uses 600,000.
11+ const TEST_PBKDF2_ITERATIONS = 1_000 ;
1012
1113describe ( 'mnemonic-crypto' , ( ) => {
1214 describe ( 'encryptMnemonic / decryptMnemonic' , ( ) => {
13- // PBKDF2 with 600k iterations is slow under coverage instrumentation.
14- const KDF_TIMEOUT = 900_000 ;
15-
16- it (
17- 'roundtrips with correct password' ,
18- ( ) => {
19- const encrypted = encryptMnemonic ( {
20- mnemonic : TEST_MNEMONIC ,
21- password : TEST_PASSWORD ,
22- } ) ;
23-
24- expect ( encrypted . encrypted ) . toBe ( true ) ;
25- expect ( encrypted . ciphertext ) . not . toBe ( '' ) ;
26- expect ( encrypted . nonce ) . not . toBe ( '' ) ;
27- expect ( encrypted . salt ) . not . toBe ( '' ) ;
28-
29- const decrypted = decryptMnemonic ( {
30- data : encrypted ,
31- password : TEST_PASSWORD ,
32- } ) ;
33-
34- expect ( decrypted ) . toBe ( TEST_MNEMONIC ) ;
35- } ,
36- KDF_TIMEOUT ,
37- ) ;
38-
39- it (
40- 'roundtrips with caller-provided salt' ,
41- ( ) => {
42- const encrypted = encryptMnemonic ( {
43- mnemonic : TEST_MNEMONIC ,
44- password : TEST_PASSWORD ,
45- salt : TEST_SALT ,
46- } ) ;
47-
48- expect ( encrypted . salt ) . toBe ( TEST_SALT ) ;
49-
50- const decrypted = decryptMnemonic ( {
15+ it ( 'roundtrips with correct password' , ( ) => {
16+ const encrypted = encryptMnemonic ( {
17+ mnemonic : TEST_MNEMONIC ,
18+ password : TEST_PASSWORD ,
19+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
20+ } ) ;
21+
22+ expect ( encrypted . encrypted ) . toBe ( true ) ;
23+ expect ( encrypted . ciphertext ) . not . toBe ( '' ) ;
24+ expect ( encrypted . nonce ) . not . toBe ( '' ) ;
25+ expect ( encrypted . salt ) . not . toBe ( '' ) ;
26+
27+ const decrypted = decryptMnemonic ( {
28+ data : encrypted ,
29+ password : TEST_PASSWORD ,
30+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
31+ } ) ;
32+
33+ expect ( decrypted ) . toBe ( TEST_MNEMONIC ) ;
34+ } ) ;
35+
36+ it ( 'roundtrips with caller-provided salt' , ( ) => {
37+ const encrypted = encryptMnemonic ( {
38+ mnemonic : TEST_MNEMONIC ,
39+ password : TEST_PASSWORD ,
40+ salt : TEST_SALT ,
41+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
42+ } ) ;
43+
44+ expect ( encrypted . salt ) . toBe ( TEST_SALT ) ;
45+
46+ const decrypted = decryptMnemonic ( {
47+ data : encrypted ,
48+ password : TEST_PASSWORD ,
49+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
50+ } ) ;
51+
52+ expect ( decrypted ) . toBe ( TEST_MNEMONIC ) ;
53+ } ) ;
54+
55+ it ( 'throws on decrypt with wrong password' , ( ) => {
56+ const encrypted = encryptMnemonic ( {
57+ mnemonic : TEST_MNEMONIC ,
58+ password : TEST_PASSWORD ,
59+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
60+ } ) ;
61+
62+ expect ( ( ) =>
63+ decryptMnemonic ( {
5164 data : encrypted ,
65+ password : 'wrong-password' ,
66+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
67+ } ) ,
68+ ) . toThrow ( / i n v a l i d .* t a g | d e c r y p t / iu) ;
69+ } ) ;
70+
71+ it ( 'produces deterministic output for same password and salt' , ( ) => {
72+ const a = encryptMnemonic ( {
73+ mnemonic : TEST_MNEMONIC ,
74+ password : TEST_PASSWORD ,
75+ salt : TEST_SALT ,
76+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
77+ } ) ;
78+ const b = encryptMnemonic ( {
79+ mnemonic : TEST_MNEMONIC ,
80+ password : TEST_PASSWORD ,
81+ salt : TEST_SALT ,
82+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
83+ } ) ;
84+
85+ expect ( a ) . toStrictEqual ( b ) ;
86+ } ) ;
87+
88+ it ( 'produces different output for different passwords with same salt' , ( ) => {
89+ const a = encryptMnemonic ( {
90+ mnemonic : TEST_MNEMONIC ,
91+ password : 'password-a' ,
92+ salt : TEST_SALT ,
93+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
94+ } ) ;
95+ const b = encryptMnemonic ( {
96+ mnemonic : TEST_MNEMONIC ,
97+ password : 'password-b' ,
98+ salt : TEST_SALT ,
99+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
100+ } ) ;
101+
102+ expect ( a . ciphertext ) . not . toBe ( b . ciphertext ) ;
103+ } ) ;
104+
105+ it ( 'stores all fields as hex strings' , ( ) => {
106+ const encrypted = encryptMnemonic ( {
107+ mnemonic : TEST_MNEMONIC ,
108+ password : TEST_PASSWORD ,
109+ salt : TEST_SALT ,
110+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
111+ } ) ;
112+
113+ expect ( encrypted . ciphertext ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
114+ expect ( encrypted . nonce ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
115+ expect ( encrypted . salt ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
116+ expect ( encrypted . nonce ) . toHaveLength ( 24 ) ; // 12 bytes = 24 hex chars
117+ } ) ;
118+
119+ it ( 'handles empty mnemonic' , ( ) => {
120+ const encrypted = encryptMnemonic ( {
121+ mnemonic : '' ,
122+ password : TEST_PASSWORD ,
123+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
124+ } ) ;
125+ const decrypted = decryptMnemonic ( {
126+ data : encrypted ,
127+ password : TEST_PASSWORD ,
128+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
129+ } ) ;
130+ expect ( decrypted ) . toBe ( '' ) ;
131+ } ) ;
132+
133+ it ( 'rejects tampered ciphertext' , ( ) => {
134+ const encrypted = encryptMnemonic ( {
135+ mnemonic : TEST_MNEMONIC ,
136+ password : TEST_PASSWORD ,
137+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
138+ } ) ;
139+
140+ const tampered : EncryptedMnemonicData = {
141+ ...encrypted ,
142+ ciphertext : encrypted . ciphertext . replace ( / ^ .{ 2 } / u, 'ff' ) ,
143+ } ;
144+
145+ expect ( ( ) =>
146+ decryptMnemonic ( {
147+ data : tampered ,
52148 password : TEST_PASSWORD ,
53- } ) ;
54-
55- expect ( decrypted ) . toBe ( TEST_MNEMONIC ) ;
56- } ,
57- KDF_TIMEOUT ,
58- ) ;
59-
60- it (
61- 'throws on decrypt with wrong password' ,
62- ( ) => {
63- const encrypted = encryptMnemonic ( {
64- mnemonic : TEST_MNEMONIC ,
65- password : TEST_PASSWORD ,
66- } ) ;
67-
68- expect ( ( ) =>
69- decryptMnemonic ( { data : encrypted , password : 'wrong-password' } ) ,
70- ) . toThrow ( / i n v a l i d .* t a g | d e c r y p t / iu) ;
71- } ,
72- KDF_TIMEOUT ,
73- ) ;
74-
75- it (
76- 'produces deterministic output for same password and salt' ,
77- ( ) => {
78- const a = encryptMnemonic ( {
79- mnemonic : TEST_MNEMONIC ,
80- password : TEST_PASSWORD ,
81- salt : TEST_SALT ,
82- } ) ;
83- const b = encryptMnemonic ( {
84- mnemonic : TEST_MNEMONIC ,
85- password : TEST_PASSWORD ,
86- salt : TEST_SALT ,
87- } ) ;
88-
89- expect ( a ) . toStrictEqual ( b ) ;
90- } ,
91- KDF_TIMEOUT ,
92- ) ;
93-
94- it (
95- 'produces different output for different passwords with same salt' ,
96- ( ) => {
97- const a = encryptMnemonic ( {
98- mnemonic : TEST_MNEMONIC ,
99- password : 'password-a' ,
100- salt : TEST_SALT ,
101- } ) ;
102- const b = encryptMnemonic ( {
103- mnemonic : TEST_MNEMONIC ,
104- password : 'password-b' ,
105- salt : TEST_SALT ,
106- } ) ;
107-
108- expect ( a . ciphertext ) . not . toBe ( b . ciphertext ) ;
109- } ,
110- KDF_TIMEOUT ,
111- ) ;
112-
113- it (
114- 'stores all fields as hex strings' ,
115- ( ) => {
116- const encrypted = encryptMnemonic ( {
117- mnemonic : TEST_MNEMONIC ,
118- password : TEST_PASSWORD ,
119- salt : TEST_SALT ,
120- } ) ;
121-
122- expect ( encrypted . ciphertext ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
123- expect ( encrypted . nonce ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
124- expect ( encrypted . salt ) . toMatch ( / ^ [ \d a - f ] + $ / u) ;
125- expect ( encrypted . nonce ) . toHaveLength ( 24 ) ; // 12 bytes = 24 hex chars
126- } ,
127- KDF_TIMEOUT ,
128- ) ;
129-
130- it (
131- 'handles empty mnemonic' ,
132- ( ) => {
133- const encrypted = encryptMnemonic ( {
134- mnemonic : '' ,
135- password : TEST_PASSWORD ,
136- } ) ;
137- const decrypted = decryptMnemonic ( {
138- data : encrypted ,
139- password : TEST_PASSWORD ,
140- } ) ;
141- expect ( decrypted ) . toBe ( '' ) ;
142- } ,
143- KDF_TIMEOUT ,
144- ) ;
145-
146- it (
147- 'rejects tampered ciphertext' ,
148- ( ) => {
149- const encrypted = encryptMnemonic ( {
150- mnemonic : TEST_MNEMONIC ,
151- password : TEST_PASSWORD ,
152- } ) ;
153-
154- const tampered : EncryptedMnemonicData = {
155- ...encrypted ,
156- ciphertext : encrypted . ciphertext . replace ( / ^ .{ 2 } / u, 'ff' ) ,
157- } ;
158-
159- expect ( ( ) =>
160- decryptMnemonic ( { data : tampered , password : TEST_PASSWORD } ) ,
161- ) . toThrow ( / i n v a l i d .* t a g | d e c r y p t / iu) ;
162- } ,
163- KDF_TIMEOUT ,
164- ) ;
149+ pbkdf2Iterations : TEST_PBKDF2_ITERATIONS ,
150+ } ) ,
151+ ) . toThrow ( / i n v a l i d .* t a g | d e c r y p t / iu) ;
152+ } ) ;
165153 } ) ;
166154} ) ;
0 commit comments