@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/.eslintrc.js +3 -0
- package/.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs +9 -0
- package/CHANGELOG.md +32 -1
- package/README.md +2 -3
- package/index.js +216 -36
- package/jest.config.js +2 -2
- package/package.json +16 -14
- package/test/index.js +521 -114
- package/.github/CODEOWNERS +0 -4
- package/.github/workflows/build-test.yml +0 -48
- package/.github/workflows/create-release-pr.yml +0 -50
- package/.github/workflows/publish-release.yml +0 -29
- package/.github/workflows/require-additional-reviewer.yml +0 -29
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
40
|
-
keyring = new HdKeyring({
|
|
41
|
-
mnemonic:
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
136
|
-
keyring
|
|
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
|
-
|
|
139
|
-
|
|
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
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
227
|
+
const accountsFirstCheck = await keyring.getAccounts();
|
|
228
|
+
|
|
229
|
+
expect(accountsFirstCheck).toHaveLength(1);
|
|
157
230
|
await keyring.addAccounts(1);
|
|
158
|
-
const
|
|
159
|
-
expect(
|
|
160
|
-
expect(
|
|
161
|
-
expect(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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('
|
|
427
|
-
it('should
|
|
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
|
-
|
|
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
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
440
|
-
|
|
541
|
+
await keyring.deserialize({
|
|
542
|
+
mnemonic: sampleMnemonic,
|
|
543
|
+
numberOfAccounts: 1,
|
|
544
|
+
});
|
|
441
545
|
|
|
442
|
-
const
|
|
443
|
-
|
|
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
|
-
|
|
447
|
-
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
462
|
-
address,
|
|
463
|
-
'anotherapp.origin.io',
|
|
622
|
+
await expect(keyring.signMessage('', message)).rejects.toThrow(
|
|
623
|
+
'Must specify address.',
|
|
464
624
|
);
|
|
625
|
+
});
|
|
465
626
|
|
|
466
|
-
|
|
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(
|
|
634
|
+
await expect(
|
|
635
|
+
keyring.signMessage(notKeyringAddress, message),
|
|
636
|
+
).rejects.toThrow('HD Keyring - Unable to find matching address.');
|
|
469
637
|
});
|
|
638
|
+
});
|
|
470
639
|
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
690
|
+
firstAcct,
|
|
481
691
|
'someapp.origin.io',
|
|
482
692
|
);
|
|
483
693
|
|
|
484
|
-
expect(
|
|
694
|
+
expect(isValidAddress(appKeyAddress1)).toBe(true);
|
|
485
695
|
|
|
486
696
|
const appKeyAddress2 = await keyring.getAppKeyAddress(
|
|
487
|
-
|
|
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(
|
|
711
|
+
expect(isValidAddress(appKeyAddress1)).toBe(true);
|
|
492
712
|
|
|
493
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
'
|
|
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
|
-
|
|
732
|
+
});
|
|
733
|
+
});
|
|
507
734
|
|
|
508
|
-
|
|
735
|
+
describe('exportAccount', function () {
|
|
736
|
+
let keyring;
|
|
737
|
+
beforeEach(() => {
|
|
738
|
+
keyring = new HdKeyring({
|
|
509
739
|
mnemonic: sampleMnemonic,
|
|
510
740
|
numberOfAccounts: 1,
|
|
511
741
|
});
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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('
|
|
520
|
-
|
|
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
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
862
|
+
signature,
|
|
863
|
+
version: SignTypedDataVersion.V4,
|
|
538
864
|
});
|
|
865
|
+
expect(restored).toBe(address);
|
|
866
|
+
});
|
|
867
|
+
});
|
|
539
868
|
|
|
540
|
-
|
|
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
|
|
546
|
-
|
|
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
|
-
|
|
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
|
});
|