@metamask/eth-hd-keyring 4.0.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/test/index.js CHANGED
@@ -5,8 +5,24 @@ const {
5
5
  recoverTypedSignature,
6
6
  signTypedData,
7
7
  SignTypedDataVersion,
8
+ encrypt,
8
9
  } = require('@metamask/eth-sig-util');
9
- const ethUtil = require('ethereumjs-util');
10
+ const { wordlist } = require('@metamask/scure-bip39/dist/wordlists/english');
11
+ const oldMMForkBIP39 = require('@metamask/bip39');
12
+ const {
13
+ isValidAddress,
14
+ bufferToHex,
15
+ toBuffer,
16
+ ecrecover,
17
+ pubToAddress,
18
+ } = require('@ethereumjs/util');
19
+ const {
20
+ TransactionFactory,
21
+ Transaction: EthereumTx,
22
+ } = require('@ethereumjs/tx');
23
+
24
+ const OldHdKeyring = require('@metamask/eth-hd-keyring');
25
+ const { keccak256 } = require('ethereum-cryptography/keccak');
10
26
  const HdKeyring = require('..');
11
27
 
12
28
  // Sample account:
@@ -18,15 +34,38 @@ const sampleMnemonic =
18
34
  const firstAcct = '0x1c96099350f13d558464ec79b9be4445aa0ef579';
19
35
  const secondAcct = '0x1b00aed43a693f3a957f9feb5cc08afa031e37a0';
20
36
 
37
+ const notKeyringAddress = '0xbD20F6F5F1616947a39E11926E78ec94817B3931';
38
+
21
39
  describe('hd-keyring', () => {
22
- let keyring;
23
- beforeEach(() => {
24
- keyring = new HdKeyring();
40
+ describe('compare old bip39 implementation with new', () => {
41
+ it('should derive the same accounts from the same mnemonics', async () => {
42
+ const mnemonics = [];
43
+ for (let i = 0; i < 99; i++) {
44
+ mnemonics.push(oldMMForkBIP39.generateMnemonic());
45
+ }
46
+
47
+ await Promise.all(
48
+ mnemonics.map(async (mnemonic) => {
49
+ const newHDKeyring = new HdKeyring({ mnemonic, numberOfAccounts: 3 });
50
+ const oldHDKeyring = new OldHdKeyring({
51
+ mnemonic,
52
+ numberOfAccounts: 3,
53
+ });
54
+ const newAccounts = await newHDKeyring.getAccounts();
55
+ const oldAccounts = await oldHDKeyring.getAccounts();
56
+ await expect(newAccounts[0]).toStrictEqual(oldAccounts[0]);
57
+
58
+ await expect(newAccounts[1]).toStrictEqual(oldAccounts[1]);
59
+
60
+ await expect(newAccounts[2]).toStrictEqual(oldAccounts[2]);
61
+ }),
62
+ );
63
+ });
25
64
  });
26
65
 
27
66
  describe('constructor', () => {
28
67
  it('constructs with a typeof string mnemonic', async () => {
29
- keyring = new HdKeyring({
68
+ const keyring = new HdKeyring({
30
69
  mnemonic: sampleMnemonic,
31
70
  numberOfAccounts: 2,
32
71
  });
@@ -36,9 +75,9 @@ describe('hd-keyring', () => {
36
75
  expect(accounts[1]).toStrictEqual(secondAcct);
37
76
  });
38
77
 
39
- it('constructs with a typeof array mnemonic', async () => {
40
- keyring = new HdKeyring({
41
- mnemonic: Array.from(Buffer.from(sampleMnemonic, 'utf8').values()),
78
+ it('constructs with a typeof buffer mnemonic', async () => {
79
+ const keyring = new HdKeyring({
80
+ mnemonic: Buffer.from(sampleMnemonic, 'utf8'),
42
81
  numberOfAccounts: 2,
43
82
  });
44
83
 
@@ -47,9 +86,15 @@ describe('hd-keyring', () => {
47
86
  expect(accounts[1]).toStrictEqual(secondAcct);
48
87
  });
49
88
 
50
- it('constructs with a typeof buffer mnemonic', async () => {
51
- keyring = new HdKeyring({
52
- mnemonic: Buffer.from(sampleMnemonic, 'utf8'),
89
+ it('constructs with a typeof Uint8Array mnemonic', async () => {
90
+ const indices = sampleMnemonic
91
+ .split(' ')
92
+ .map((word) => wordlist.indexOf(word));
93
+ const uInt8ArrayOfMnemonic = new Uint8Array(
94
+ new Uint16Array(indices).buffer,
95
+ );
96
+ const keyring = new HdKeyring({
97
+ mnemonic: uInt8ArrayOfMnemonic,
53
98
  numberOfAccounts: 2,
54
99
  });
55
100
 
@@ -84,6 +129,7 @@ describe('hd-keyring', () => {
84
129
  const alreadyProvidedError =
85
130
  'Eth-Hd-Keyring: Secret recovery phrase already provided';
86
131
  it('double generateRandomMnemonic', () => {
132
+ const keyring = new HdKeyring();
87
133
  keyring.generateRandomMnemonic();
88
134
  expect(() => {
89
135
  keyring.generateRandomMnemonic();
@@ -91,7 +137,7 @@ describe('hd-keyring', () => {
91
137
  });
92
138
 
93
139
  it('constructor + generateRandomMnemonic', () => {
94
- keyring = new HdKeyring({
140
+ const keyring = new HdKeyring({
95
141
  mnemonic: sampleMnemonic,
96
142
  numberOfAccounts: 2,
97
143
  });
@@ -102,7 +148,7 @@ describe('hd-keyring', () => {
102
148
  });
103
149
 
104
150
  it('constructor + deserialize', () => {
105
- keyring = new HdKeyring({
151
+ const keyring = new HdKeyring({
106
152
  mnemonic: sampleMnemonic,
107
153
  numberOfAccounts: 2,
108
154
  });
@@ -125,6 +171,8 @@ describe('hd-keyring', () => {
125
171
 
126
172
  describe('#type', () => {
127
173
  it('returns the correct value', () => {
174
+ const keyring = new HdKeyring();
175
+
128
176
  const { type } = keyring;
129
177
  const correct = HdKeyring.type;
130
178
  expect(type).toStrictEqual(correct);
@@ -132,33 +180,58 @@ describe('hd-keyring', () => {
132
180
  });
133
181
 
134
182
  describe('#serialize mnemonic.', () => {
135
- it('serializes mnemonic stored as a buffer in a class variable into a buffer array and does not add accounts', async () => {
136
- keyring.generateRandomMnemonic();
183
+ it('serializes the mnemonic in the same format as previous version (an array of utf8 encoded bytes)', async () => {
184
+ const keyring = new HdKeyring({
185
+ mnemonic: sampleMnemonic,
186
+ });
187
+ // uses previous version of eth-hd-keyring to ensure backwards compatibility
188
+ const oldHDKeyring = new OldHdKeyring({ mnemonic: sampleMnemonic });
189
+ const { mnemonic: oldKeyringSerializedMnemonic } =
190
+ await oldHDKeyring.serialize();
191
+
192
+ const output = await keyring.serialize();
193
+ expect(output.mnemonic).toStrictEqual(oldKeyringSerializedMnemonic);
194
+ });
195
+
196
+ it('serializes mnemonic passed in as a string to an array of utf8 encoded bytes', async () => {
197
+ const keyring = new HdKeyring({
198
+ mnemonic: sampleMnemonic,
199
+ });
137
200
  const output = await keyring.serialize();
138
- expect(output.numberOfAccounts).toBe(0);
139
- expect(Array.isArray(output.mnemonic)).toBe(true);
201
+ // this Buffer.from(...).toString() is the method of converting from an array of utf8 encoded bytes back to a string
202
+ const mnemonicAsString = Buffer.from(output.mnemonic).toString();
203
+ expect(mnemonicAsString).toStrictEqual(sampleMnemonic);
140
204
  });
141
205
 
142
- it('serializes mnemonic stored as a string in a class variable into a buffer array and does not add accounts', async () => {
143
- keyring.mnemonic = sampleMnemonic;
206
+ it('serializes mnemonic passed in as a an array of utf8 encoded bytes in the same format', async () => {
207
+ const uint8Array = new TextEncoder('utf-8').encode(sampleMnemonic);
208
+ const mnemonicAsArrayOfUtf8EncodedBytes = Array.from(uint8Array);
209
+ const keyring = new HdKeyring({
210
+ mnemonic: mnemonicAsArrayOfUtf8EncodedBytes,
211
+ });
212
+
144
213
  const output = await keyring.serialize();
145
- expect(output.numberOfAccounts).toBe(0);
146
- expect(Array.isArray(output.mnemonic)).toBe(true);
214
+ // this Buffer.from(...).toString() is the method of converting from an array of utf8 encoded bytes back to a string
215
+ const mnemonicAsString = Buffer.from(output.mnemonic).toString();
216
+ expect(mnemonicAsString).toStrictEqual(sampleMnemonic);
147
217
  });
148
218
  });
149
219
 
150
220
  describe('#deserialize a private key', () => {
151
221
  it('serializes what it deserializes', async () => {
222
+ const keyring = new HdKeyring();
152
223
  await keyring.deserialize({
153
224
  mnemonic: sampleMnemonic,
154
225
  numberOfAccounts: 1,
155
226
  });
156
- expect(keyring.wallets).toHaveLength(1);
227
+ const accountsFirstCheck = await keyring.getAccounts();
228
+
229
+ expect(accountsFirstCheck).toHaveLength(1);
157
230
  await keyring.addAccounts(1);
158
- const accounts = await keyring.getAccounts();
159
- expect(accounts[0]).toStrictEqual(firstAcct);
160
- expect(accounts[1]).toStrictEqual(secondAcct);
161
- expect(accounts).toHaveLength(2);
231
+ const accountsSecondCheck = await keyring.getAccounts();
232
+ expect(accountsSecondCheck[0]).toStrictEqual(firstAcct);
233
+ expect(accountsSecondCheck[1]).toStrictEqual(secondAcct);
234
+ expect(accountsSecondCheck).toHaveLength(2);
162
235
  const serialized = await keyring.serialize();
163
236
  expect(Buffer.from(serialized.mnemonic).toString()).toStrictEqual(
164
237
  sampleMnemonic,
@@ -169,12 +242,15 @@ describe('hd-keyring', () => {
169
242
  describe('#addAccounts', () => {
170
243
  describe('with no arguments', () => {
171
244
  it('creates a single wallet', async () => {
245
+ const keyring = new HdKeyring();
172
246
  keyring.generateRandomMnemonic();
173
247
  await keyring.addAccounts();
174
- expect(keyring.wallets).toHaveLength(1);
248
+ const accounts = await keyring.getAccounts();
249
+ expect(accounts).toHaveLength(1);
175
250
  });
176
251
 
177
252
  it('throws an error when no SRP has been generated yet', async () => {
253
+ const keyring = new HdKeyring();
178
254
  expect(() => keyring.addAccounts()).toThrow(
179
255
  'Eth-Hd-Keyring: No secret recovery phrase provided',
180
256
  );
@@ -183,35 +259,19 @@ describe('hd-keyring', () => {
183
259
 
184
260
  describe('with a numeric argument', () => {
185
261
  it('creates that number of wallets', async () => {
262
+ const keyring = new HdKeyring();
186
263
  keyring.generateRandomMnemonic();
187
264
  await keyring.addAccounts(3);
188
- expect(keyring.wallets).toHaveLength(3);
189
- });
190
- });
191
- });
192
-
193
- describe('#getAccounts', () => {
194
- it('calls getAddress on each wallet', async () => {
195
- // Push a mock wallet
196
- const desiredOutput = 'foo';
197
- keyring.wallets.push({
198
- getAddress() {
199
- return {
200
- toString() {
201
- return desiredOutput;
202
- },
203
- };
204
- },
265
+ const accounts = await keyring.getAccounts();
266
+ expect(accounts).toHaveLength(3);
205
267
  });
206
-
207
- const output = await keyring.getAccounts();
208
- expect(output[0]).toBe(`0x${desiredOutput}`);
209
- expect(output).toHaveLength(1);
210
268
  });
211
269
  });
212
270
 
213
271
  describe('#signPersonalMessage', () => {
214
272
  it('returns the expected value', async () => {
273
+ const keyring = new HdKeyring();
274
+
215
275
  const address = firstAcct;
216
276
  const message = '0x68656c6c6f20776f726c64';
217
277
 
@@ -233,8 +293,8 @@ describe('hd-keyring', () => {
233
293
 
234
294
  describe('#signTypedData', () => {
235
295
  it('can recover a basic signature', async () => {
296
+ const keyring = new HdKeyring();
236
297
  Buffer.from(privKeyHex, 'hex');
237
-
238
298
  const typedData = [
239
299
  {
240
300
  type: 'string',
@@ -266,11 +326,14 @@ describe('hd-keyring', () => {
266
326
  ];
267
327
 
268
328
  it('signs in a compliant and recoverable way', async () => {
329
+ const keyring = new HdKeyring();
269
330
  keyring.generateRandomMnemonic();
270
331
  await keyring.addAccounts(1);
271
332
  const addresses = await keyring.getAccounts();
272
333
  const address = addresses[0];
273
- const signature = await keyring.signTypedData_v1(address, typedData);
334
+ const signature = await keyring.signTypedData(address, typedData, {
335
+ version: SignTypedDataVersion.V1,
336
+ });
274
337
  const restored = recoverTypedSignature({
275
338
  data: typedData,
276
339
  signature,
@@ -282,6 +345,7 @@ describe('hd-keyring', () => {
282
345
 
283
346
  describe('#signTypedData_v3', () => {
284
347
  it('signs in a compliant and recoverable way', async () => {
348
+ const keyring = new HdKeyring();
285
349
  const typedData = {
286
350
  types: {
287
351
  EIP712Domain: [],
@@ -297,7 +361,9 @@ describe('hd-keyring', () => {
297
361
  });
298
362
  const addresses = await keyring.getAccounts();
299
363
  const address = addresses[0];
300
- const signature = await keyring.signTypedData_v3(address, typedData);
364
+ const signature = await keyring.signTypedData(address, typedData, {
365
+ version: SignTypedDataVersion.V3,
366
+ });
301
367
  const restored = recoverTypedSignature({
302
368
  data: typedData,
303
369
  signature,
@@ -309,6 +375,7 @@ describe('hd-keyring', () => {
309
375
 
310
376
  describe('#signTypedData_v3 signature verification', () => {
311
377
  it('signs in a recoverable way.', async () => {
378
+ const keyring = new HdKeyring();
312
379
  const typedData = {
313
380
  types: {
314
381
  EIP712Domain: [
@@ -351,7 +418,9 @@ describe('hd-keyring', () => {
351
418
  await keyring.addAccounts(1);
352
419
  const addresses = await keyring.getAccounts();
353
420
  const address = addresses[0];
354
- const signature = await keyring.signTypedData_v3(address, typedData);
421
+ const signature = await keyring.signTypedData(address, typedData, {
422
+ version: SignTypedDataVersion.V3,
423
+ });
355
424
  const restored = recoverTypedSignature({
356
425
  data: typedData,
357
426
  signature,
@@ -363,6 +432,7 @@ describe('hd-keyring', () => {
363
432
 
364
433
  describe('custom hd paths', () => {
365
434
  it('can deserialize with an hdPath param and generate the same accounts.', async () => {
435
+ const keyring = new HdKeyring();
366
436
  const hdPathString = `m/44'/60'/0'/0`;
367
437
  keyring.deserialize({
368
438
  mnemonic: sampleMnemonic,
@@ -376,8 +446,8 @@ describe('hd-keyring', () => {
376
446
  });
377
447
 
378
448
  it('can deserialize with an hdPath param and generate different accounts.', async () => {
449
+ const keyring = new HdKeyring();
379
450
  const hdPathString = `m/44'/60'/0'/1`;
380
-
381
451
  keyring.deserialize({
382
452
  mnemonic: sampleMnemonic,
383
453
  numberOfAccounts: 1,
@@ -398,14 +468,14 @@ describe('hd-keyring', () => {
398
468
 
399
469
  for (let i = 0; i < 1e3; i++) {
400
470
 
401
- keyring = new HdKeyring({
471
+ const keyring = new HdKeyring({
402
472
  numberOfAccounts: 1,
403
473
  })
404
474
  const originalAccounts = await keyring.getAccounts()
405
475
  const serialized = await keyring.serialize()
406
476
  const mnemonic = serialized.mnemonic
407
477
 
408
- keyring = new HdKeyring({
478
+ const keyring = new HdKeyring({
409
479
  numberOfAccounts: 1,
410
480
  mnemonic,
411
481
  })
@@ -423,129 +493,466 @@ describe('hd-keyring', () => {
423
493
  })
424
494
  */
425
495
 
426
- describe('getAppKeyAddress', () => {
427
- it('should return a public address custom to the provided app key origin', async () => {
496
+ describe('signing methods withAppKeyOrigin option', () => {
497
+ it('should signPersonalMessage with the expected key when passed a withAppKeyOrigin', async () => {
498
+ const keyring = new HdKeyring();
428
499
  const address = firstAcct;
500
+ const message = '0x68656c6c6f20776f726c64';
429
501
 
430
- keyring = new HdKeyring({
502
+ const privateKey = Buffer.from(
503
+ '8e82d2d74c50e5c8460f771d38a560ebe1151a9134c65a7e92b28ad0cfae7151',
504
+ 'hex',
505
+ );
506
+ const expectedSig = personalSign({ privateKey, data: message });
507
+
508
+ await keyring.deserialize({
431
509
  mnemonic: sampleMnemonic,
432
510
  numberOfAccounts: 1,
433
511
  });
434
- const appKeyAddress = await keyring.getAppKeyAddress(
435
- address,
436
- 'someapp.origin.io',
512
+ const sig = await keyring.signPersonalMessage(address, message, {
513
+ withAppKeyOrigin: 'someapp.origin.io',
514
+ });
515
+
516
+ expect(sig).toStrictEqual(expectedSig);
517
+ });
518
+
519
+ it('should signTypedData with the expected key when passed a withAppKeyOrigin', async () => {
520
+ const keyring = new HdKeyring();
521
+ const address = firstAcct;
522
+ const typedData = {
523
+ types: {
524
+ EIP712Domain: [],
525
+ },
526
+ domain: {},
527
+ primaryType: 'EIP712Domain',
528
+ message: {},
529
+ };
530
+
531
+ const privateKey = Buffer.from(
532
+ '8e82d2d74c50e5c8460f771d38a560ebe1151a9134c65a7e92b28ad0cfae7151',
533
+ 'hex',
437
534
  );
535
+ const expectedSig = signTypedData({
536
+ privateKey,
537
+ data: typedData,
538
+ version: SignTypedDataVersion.V3,
539
+ });
438
540
 
439
- expect(address).not.toBe(appKeyAddress);
440
- expect(ethUtil.isValidAddress(appKeyAddress)).toBe(true);
541
+ await keyring.deserialize({
542
+ mnemonic: sampleMnemonic,
543
+ numberOfAccounts: 1,
544
+ });
441
545
 
442
- const accounts = await keyring.getAccounts();
443
- expect(accounts[0]).toStrictEqual(firstAcct);
546
+ const sig = await keyring.signTypedData(address, typedData, {
547
+ withAppKeyOrigin: 'someapp.origin.io',
548
+ version: SignTypedDataVersion.V3,
549
+ });
550
+ expect(sig).toStrictEqual(expectedSig);
444
551
  });
552
+ });
445
553
 
446
- it('should return different addresses when provided different app key origins', async () => {
447
- keyring = new HdKeyring({
554
+ // /
555
+ /* TESTS FOR BASE-KEYRING METHODS */
556
+ // /
557
+
558
+ describe('#signMessage', function () {
559
+ const message =
560
+ '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0';
561
+ const expectedResult =
562
+ '0xb21867b2221db0172e970b7370825b71c57823ff8714168ce9748f32f450e2c43d0fe396eb5b5f59284b7fd108c8cf61a6180a6756bdd3d4b7b9ccc4ac6d51611b';
563
+
564
+ it('passes the dennis test', async function () {
565
+ const keyring = new HdKeyring();
566
+ await keyring.deserialize({
448
567
  mnemonic: sampleMnemonic,
449
568
  numberOfAccounts: 1,
450
569
  });
570
+ const result = await keyring.signMessage(firstAcct, message);
571
+ expect(result).toBe(expectedResult);
572
+ });
451
573
 
452
- const address = firstAcct;
574
+ it('reliably can decode messages it signs', async function () {
575
+ const keyring = new HdKeyring();
576
+ await keyring.deserialize({
577
+ mnemonic: sampleMnemonic,
578
+ numberOfAccounts: 1,
579
+ });
580
+ const localMessage = 'hello there!';
581
+ const msgHashHex = bufferToHex(keccak256(Buffer.from(localMessage)));
582
+ await keyring.addAccounts(9);
583
+ const addresses = await keyring.getAccounts();
584
+ const signatures = await Promise.all(
585
+ addresses.map(async (accountAddress) => {
586
+ return await keyring.signMessage(accountAddress, msgHashHex);
587
+ }),
588
+ );
589
+ signatures.forEach((sgn, index) => {
590
+ const accountAddress = addresses[index];
453
591
 
454
- const appKeyAddress1 = await keyring.getAppKeyAddress(
455
- address,
456
- 'someapp.origin.io',
592
+ const r = toBuffer(sgn.slice(0, 66));
593
+ const s = toBuffer(`0x${sgn.slice(66, 130)}`);
594
+ const v = BigInt(`0x${sgn.slice(130, 132)}`);
595
+ const m = toBuffer(msgHashHex);
596
+ const pub = ecrecover(m, v, r, s);
597
+ const adr = `0x${pubToAddress(pub).toString('hex')}`;
598
+
599
+ expect(adr).toBe(accountAddress);
600
+ });
601
+ });
602
+
603
+ it('throw error for invalid message', async function () {
604
+ const keyring = new HdKeyring();
605
+ await keyring.deserialize({
606
+ mnemonic: sampleMnemonic,
607
+ numberOfAccounts: 1,
608
+ });
609
+
610
+ await expect(keyring.signMessage(firstAcct, '')).rejects.toThrow(
611
+ 'Cannot convert 0x to a BigInt',
457
612
  );
613
+ });
458
614
 
459
- expect(ethUtil.isValidAddress(appKeyAddress1)).toBe(true);
615
+ it('throw error if empty address is passed', async function () {
616
+ const keyring = new HdKeyring();
617
+ await keyring.deserialize({
618
+ mnemonic: sampleMnemonic,
619
+ numberOfAccounts: 1,
620
+ });
460
621
 
461
- const appKeyAddress2 = await keyring.getAppKeyAddress(
462
- address,
463
- 'anotherapp.origin.io',
622
+ await expect(keyring.signMessage('', message)).rejects.toThrow(
623
+ 'Must specify address.',
464
624
  );
625
+ });
465
626
 
466
- expect(ethUtil.isValidAddress(appKeyAddress2)).toBe(true);
627
+ it('throw error if address not associated with the current keyring is passed', async function () {
628
+ const keyring = new HdKeyring();
629
+ await keyring.deserialize({
630
+ mnemonic: sampleMnemonic,
631
+ numberOfAccounts: 1,
632
+ });
467
633
 
468
- expect(appKeyAddress1).not.toBe(appKeyAddress2);
634
+ await expect(
635
+ keyring.signMessage(notKeyringAddress, message),
636
+ ).rejects.toThrow('HD Keyring - Unable to find matching address.');
469
637
  });
638
+ });
470
639
 
471
- it('should return the same address when called multiple times with the same params', async () => {
640
+ describe('#removeAccount', function () {
641
+ let keyring;
642
+ beforeEach(() => {
472
643
  keyring = new HdKeyring({
473
644
  mnemonic: sampleMnemonic,
474
645
  numberOfAccounts: 1,
475
646
  });
647
+ });
476
648
 
477
- const address = firstAcct;
649
+ describe('if the account exists', function () {
650
+ it('should remove that account', async function () {
651
+ const addresses = await keyring.getAccounts();
652
+ expect(addresses).toHaveLength(1);
653
+ keyring.removeAccount(addresses[0]);
654
+ const addressesAfterRemoval = await keyring.getAccounts();
655
+ expect(addressesAfterRemoval).toHaveLength(0);
656
+ });
657
+ });
658
+
659
+ describe('if the account does not exist', function () {
660
+ it('should throw an error', function () {
661
+ const unexistingAccount = '0x0000000000000000000000000000000000000000';
662
+ expect(() => keyring.removeAccount(unexistingAccount)).toThrow(
663
+ `Address ${unexistingAccount} not found in this keyring`,
664
+ );
665
+ });
666
+ });
667
+ });
668
+
669
+ describe('getAppKeyAddress', function () {
670
+ let keyring;
671
+ beforeEach(() => {
672
+ keyring = new HdKeyring({
673
+ mnemonic: sampleMnemonic,
674
+ numberOfAccounts: 1,
675
+ });
676
+ });
677
+
678
+ it('should return a public address custom to the provided app key origin', async function () {
679
+ const appKeyAddress = await keyring.getAppKeyAddress(
680
+ firstAcct,
681
+ 'someapp.origin.io',
682
+ );
683
+
684
+ expect(firstAcct).not.toBe(appKeyAddress);
685
+ expect(isValidAddress(appKeyAddress)).toBe(true);
686
+ });
478
687
 
688
+ it('should return different addresses when provided different app key origins', async function () {
479
689
  const appKeyAddress1 = await keyring.getAppKeyAddress(
480
- address,
690
+ firstAcct,
481
691
  'someapp.origin.io',
482
692
  );
483
693
 
484
- expect(ethUtil.isValidAddress(appKeyAddress1)).toBe(true);
694
+ expect(isValidAddress(appKeyAddress1)).toBe(true);
485
695
 
486
696
  const appKeyAddress2 = await keyring.getAppKeyAddress(
487
- address,
697
+ firstAcct,
698
+ 'anotherapp.origin.io',
699
+ );
700
+
701
+ expect(isValidAddress(appKeyAddress2)).toBe(true);
702
+ expect(appKeyAddress1).not.toBe(appKeyAddress2);
703
+ });
704
+
705
+ it('should return the same address when called multiple times with the same params', async function () {
706
+ const appKeyAddress1 = await keyring.getAppKeyAddress(
707
+ firstAcct,
488
708
  'someapp.origin.io',
489
709
  );
490
710
 
491
- expect(ethUtil.isValidAddress(appKeyAddress2)).toBe(true);
711
+ expect(isValidAddress(appKeyAddress1)).toBe(true);
492
712
 
493
- expect(appKeyAddress1).toStrictEqual(appKeyAddress2);
713
+ const appKeyAddress2 = await keyring.getAppKeyAddress(
714
+ firstAcct,
715
+ 'someapp.origin.io',
716
+ );
717
+
718
+ expect(isValidAddress(appKeyAddress2)).toBe(true);
719
+ expect(appKeyAddress1).toBe(appKeyAddress2);
494
720
  });
495
- });
496
721
 
497
- describe('signing methods withAppKeyOrigin option', () => {
498
- it('should signPersonalMessage with the expected key when passed a withAppKeyOrigin', async () => {
499
- const address = firstAcct;
500
- const message = '0x68656c6c6f20776f726c64';
722
+ it('should throw error if the provided origin is not a string', async function () {
723
+ await expect(keyring.getAppKeyAddress(firstAcct, [])).rejects.toThrow(
724
+ `'origin' must be a non-empty string`,
725
+ );
726
+ });
501
727
 
502
- const privateKey = Buffer.from(
503
- '8e82d2d74c50e5c8460f771d38a560ebe1151a9134c65a7e92b28ad0cfae7151',
504
- 'hex',
728
+ it('should throw error if the provided origin is an empty string', async function () {
729
+ await expect(keyring.getAppKeyAddress(firstAcct, '')).rejects.toThrow(
730
+ `'origin' must be a non-empty string`,
505
731
  );
506
- const expectedSig = personalSign({ privateKey, data: message });
732
+ });
733
+ });
507
734
 
508
- await keyring.deserialize({
735
+ describe('exportAccount', function () {
736
+ let keyring;
737
+ beforeEach(() => {
738
+ keyring = new HdKeyring({
509
739
  mnemonic: sampleMnemonic,
510
740
  numberOfAccounts: 1,
511
741
  });
512
- const sig = await keyring.signPersonalMessage(address, message, {
513
- withAppKeyOrigin: 'someapp.origin.io',
742
+ });
743
+
744
+ it('should return a hex-encoded private key', async function () {
745
+ const expectedPrivateKeyResult =
746
+ '0xd3cc16948a02a91b9fcf83735653bf3dfd82c86543fdd1e9a828bd25e8a7b68d';
747
+ const privKeyHexValue = await keyring.exportAccount(firstAcct);
748
+
749
+ expect(expectedPrivateKeyResult).toBe(`0x${privKeyHexValue}`);
750
+ });
751
+
752
+ it('throw error if account is not present', async function () {
753
+ await expect(keyring.exportAccount(notKeyringAddress)).rejects.toThrow(
754
+ 'HD Keyring - Unable to find matching address.',
755
+ );
756
+ });
757
+ });
758
+
759
+ describe('#encryptionPublicKey', function () {
760
+ const publicKey = 'LV7lWhd0mUDcvxkMU2o6uKXftu25zq4bMYdmMqppXic=';
761
+ let keyring;
762
+ beforeEach(() => {
763
+ keyring = new HdKeyring({
764
+ mnemonic: sampleMnemonic,
765
+ numberOfAccounts: 1,
514
766
  });
767
+ });
515
768
 
516
- expect(sig).toStrictEqual(expectedSig);
769
+ it('returns the expected value', async function () {
770
+ const encryptionPublicKey = await keyring.getEncryptionPublicKey(
771
+ firstAcct,
772
+ );
773
+ expect(publicKey).toBe(encryptionPublicKey);
517
774
  });
518
775
 
519
- it('should signTypedData with the expected key when passed a withAppKeyOrigin', async () => {
520
- const address = firstAcct;
776
+ it('throw error if address is blank', async function () {
777
+ await expect(keyring.getEncryptionPublicKey('')).rejects.toThrow(
778
+ 'Must specify address.',
779
+ );
780
+ });
781
+
782
+ it('throw error if address is not present in the keyring', async function () {
783
+ await expect(
784
+ keyring.getEncryptionPublicKey(notKeyringAddress),
785
+ ).rejects.toThrow('HD Keyring - Unable to find matching address.');
786
+ });
787
+ });
788
+
789
+ describe('#signTypedData V4 signature verification', function () {
790
+ let keyring;
791
+ beforeEach(() => {
792
+ keyring = new HdKeyring({
793
+ mnemonic: sampleMnemonic,
794
+ numberOfAccounts: 1,
795
+ });
796
+ });
797
+
798
+ const expectedSignature =
799
+ '0x220917664ef676d592bd709a5bffedaf69c5f6c72f13c6c4547a41d211f0923c3180893b1dec023433f11b664fabda22b74b57d21094f7798fc85b7650f8edbb1b';
800
+
801
+ it('returns the expected value', async function () {
521
802
  const typedData = {
522
803
  types: {
523
- EIP712Domain: [],
804
+ EIP712Domain: [
805
+ { name: 'name', type: 'string' },
806
+ { name: 'version', type: 'string' },
807
+ { name: 'chainId', type: 'uint256' },
808
+ { name: 'verifyingContract', type: 'address' },
809
+ ],
810
+ Person: [
811
+ { name: 'name', type: 'string' },
812
+ { name: 'wallets', type: 'address[]' },
813
+ ],
814
+ Mail: [
815
+ { name: 'from', type: 'Person' },
816
+ { name: 'to', type: 'Person[]' },
817
+ { name: 'contents', type: 'string' },
818
+ ],
819
+ Group: [
820
+ { name: 'name', type: 'string' },
821
+ { name: 'members', type: 'Person[]' },
822
+ ],
823
+ },
824
+ domain: {
825
+ name: 'Ether Mail',
826
+ version: '1',
827
+ chainId: 1,
828
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
829
+ },
830
+ primaryType: 'Mail',
831
+ message: {
832
+ from: {
833
+ name: 'Cow',
834
+ wallets: [
835
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
836
+ '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
837
+ ],
838
+ },
839
+ to: [
840
+ {
841
+ name: 'Bob',
842
+ wallets: [
843
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
844
+ '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
845
+ '0xB0B0b0b0b0b0B000000000000000000000000000',
846
+ ],
847
+ },
848
+ ],
849
+ contents: 'Hello, Bob!',
524
850
  },
525
- domain: {},
526
- primaryType: 'EIP712Domain',
527
- message: {},
528
851
  };
529
852
 
530
- const privateKey = Buffer.from(
531
- '8e82d2d74c50e5c8460f771d38a560ebe1151a9134c65a7e92b28ad0cfae7151',
532
- 'hex',
533
- );
534
- const expectedSig = signTypedData({
535
- privateKey,
853
+ const addresses = await keyring.getAccounts();
854
+ const [address] = addresses;
855
+
856
+ const signature = await keyring.signTypedData(address, typedData, {
857
+ version: 'V4',
858
+ });
859
+ expect(signature).toBe(expectedSignature);
860
+ const restored = recoverTypedSignature({
536
861
  data: typedData,
537
- version: SignTypedDataVersion.V3,
862
+ signature,
863
+ version: SignTypedDataVersion.V4,
538
864
  });
865
+ expect(restored).toBe(address);
866
+ });
867
+ });
539
868
 
540
- await keyring.deserialize({
869
+ describe('#decryptMessage', function () {
870
+ const message = 'Hello world!';
871
+ let encryptedMessage, keyring;
872
+
873
+ beforeEach(async () => {
874
+ keyring = new HdKeyring({
541
875
  mnemonic: sampleMnemonic,
542
876
  numberOfAccounts: 1,
543
877
  });
544
878
 
545
- const sig = await keyring.signTypedData_v3(address, typedData, {
546
- withAppKeyOrigin: 'someapp.origin.io',
879
+ const encryptionPublicKey = await keyring.getEncryptionPublicKey(
880
+ firstAcct,
881
+ );
882
+ encryptedMessage = encrypt({
883
+ publicKey: encryptionPublicKey,
884
+ data: message,
885
+ version: 'x25519-xsalsa20-poly1305',
547
886
  });
548
- expect(sig).toStrictEqual(expectedSig);
887
+ });
888
+
889
+ it('returns the expected value', async function () {
890
+ const decryptedMessage = await keyring.decryptMessage(
891
+ firstAcct,
892
+ encryptedMessage,
893
+ );
894
+ expect(message).toBe(decryptedMessage);
895
+ });
896
+
897
+ it('throw error if address passed is not present in the keyring', async function () {
898
+ await expect(
899
+ keyring.decryptMessage(notKeyringAddress, encryptedMessage),
900
+ ).rejects.toThrow('HD Keyring - Unable to find matching address.');
901
+ });
902
+
903
+ it('throw error if wrong encrypted data object is passed', async function () {
904
+ await expect(keyring.decryptMessage(firstAcct, {})).rejects.toThrow(
905
+ 'Encryption type/version not supported.',
906
+ );
907
+ });
908
+ });
909
+
910
+ describe('#signTransaction', function () {
911
+ let keyring;
912
+ beforeEach(() => {
913
+ keyring = new HdKeyring({
914
+ mnemonic: sampleMnemonic,
915
+ numberOfAccounts: 1,
916
+ });
917
+ });
918
+
919
+ const txParams = {
920
+ from: firstAcct,
921
+ nonce: '0x00',
922
+ gasPrice: '0x09184e72a000',
923
+ gasLimit: '0x2710',
924
+ to: firstAcct,
925
+ value: '0x1000',
926
+ };
927
+
928
+ it('returns a signed legacy tx object', async function () {
929
+ const tx = new EthereumTx(txParams);
930
+ expect(tx.isSigned()).toBe(false);
931
+
932
+ const signed = await keyring.signTransaction(firstAcct, tx);
933
+ expect(signed.isSigned()).toBe(true);
934
+ });
935
+
936
+ it('returns a signed tx object', async function () {
937
+ const tx = TransactionFactory.fromTxData(txParams);
938
+ expect(tx.isSigned()).toBe(false);
939
+
940
+ const signed = await keyring.signTransaction(firstAcct, tx);
941
+ expect(signed.isSigned()).toBe(true);
942
+ });
943
+
944
+ it('returns rejected promise if empty address is passed', async function () {
945
+ const tx = TransactionFactory.fromTxData(txParams);
946
+ await expect(keyring.signTransaction('', tx)).rejects.toThrow(
947
+ 'Must specify address.',
948
+ );
949
+ });
950
+
951
+ it('throw error if wrong address is passed', async function () {
952
+ const tx = TransactionFactory.fromTxData(txParams);
953
+ await expect(
954
+ keyring.signTransaction(notKeyringAddress, tx),
955
+ ).rejects.toThrow('HD Keyring - Unable to find matching address.');
549
956
  });
550
957
  });
551
958
  });