@rabby-wallet/eth-hd-keyring 4.0.1 → 4.1.0-beta.1
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/dist/index.js +155 -49
- package/index.ts +201 -65
- package/package.json +2 -2
- package/test/index.js +154 -1
package/dist/index.js
CHANGED
|
@@ -39,92 +39,125 @@ const english_1 = require("@scure/bip39/wordlists/english");
|
|
|
39
39
|
const sigUtil = __importStar(require("eth-sig-util"));
|
|
40
40
|
const util_1 = require("@ethereumjs/util");
|
|
41
41
|
// Options:
|
|
42
|
-
const hdPathString = "m/44'/60'/0'/0";
|
|
43
42
|
const type = 'HD Key Tree';
|
|
43
|
+
var HDPathType;
|
|
44
|
+
(function (HDPathType) {
|
|
45
|
+
HDPathType["LedgerLive"] = "LedgerLive";
|
|
46
|
+
HDPathType["Legacy"] = "Legacy";
|
|
47
|
+
HDPathType["BIP44"] = "BIP44";
|
|
48
|
+
})(HDPathType || (HDPathType = {}));
|
|
49
|
+
const HD_PATH_BASE = {
|
|
50
|
+
[HDPathType.BIP44]: "m/44'/60'/0'/0",
|
|
51
|
+
[HDPathType.Legacy]: "m/44'/60'/0'",
|
|
52
|
+
[HDPathType.LedgerLive]: "m/44'/60'/0'/0/0",
|
|
53
|
+
};
|
|
54
|
+
const HD_PATH_TYPE = {
|
|
55
|
+
[HD_PATH_BASE[HDPathType.BIP44]]: HDPathType.BIP44,
|
|
56
|
+
[HD_PATH_BASE[HDPathType.Legacy]]: HDPathType.Legacy,
|
|
57
|
+
[HD_PATH_BASE[HDPathType.LedgerLive]]: HDPathType.LedgerLive,
|
|
58
|
+
};
|
|
44
59
|
class HdKeyring extends eth_simple_keyring_1.default {
|
|
45
60
|
/* PUBLIC METHODS */
|
|
46
61
|
constructor(opts = {}) {
|
|
47
62
|
super();
|
|
48
63
|
this.type = type;
|
|
49
64
|
this.mnemonic = null;
|
|
50
|
-
this.hdPath =
|
|
51
|
-
this.root = null;
|
|
65
|
+
this.hdPath = HD_PATH_BASE[HDPathType.BIP44];
|
|
52
66
|
this.wallets = [];
|
|
53
|
-
this._index2wallet = {};
|
|
54
67
|
this.activeIndexes = [];
|
|
55
68
|
this.index = 0;
|
|
56
69
|
this.page = 0;
|
|
57
70
|
this.perPage = 5;
|
|
58
71
|
this.byImport = false;
|
|
59
72
|
this.publicKey = '';
|
|
73
|
+
this.needPassphrase = false;
|
|
74
|
+
this.accounts = [];
|
|
75
|
+
this.accountDetails = {};
|
|
76
|
+
this.passphrase = '';
|
|
77
|
+
this.setAccountDetail = (address, accountDetail) => {
|
|
78
|
+
this.accountDetails = Object.assign(Object.assign({}, this.accountDetails), { [address.toLowerCase()]: accountDetail });
|
|
79
|
+
};
|
|
80
|
+
this.getAccountDetail = (address) => {
|
|
81
|
+
return this.accountDetails[address.toLowerCase()];
|
|
82
|
+
};
|
|
60
83
|
this.deserialize(opts);
|
|
61
84
|
}
|
|
62
85
|
serialize() {
|
|
63
86
|
return Promise.resolve({
|
|
64
87
|
mnemonic: this.mnemonic,
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated
|
|
90
|
+
*/
|
|
65
91
|
activeIndexes: this.activeIndexes,
|
|
66
92
|
hdPath: this.hdPath,
|
|
67
93
|
byImport: this.byImport,
|
|
68
94
|
index: this.index,
|
|
95
|
+
needPassphrase: this.needPassphrase,
|
|
96
|
+
accounts: this.accounts,
|
|
97
|
+
accountDetails: this.accountDetails,
|
|
69
98
|
publicKey: this.publicKey,
|
|
70
99
|
});
|
|
71
100
|
}
|
|
72
101
|
deserialize(opts = {}) {
|
|
73
102
|
this.wallets = [];
|
|
74
103
|
this.mnemonic = null;
|
|
75
|
-
this.
|
|
76
|
-
this.hdPath = opts.hdPath || hdPathString;
|
|
104
|
+
this.hdPath = opts.hdPath || HD_PATH_BASE[HDPathType.BIP44];
|
|
77
105
|
this.byImport = !!opts.byImport;
|
|
78
106
|
this.index = opts.index || 0;
|
|
107
|
+
this.needPassphrase = opts.needPassphrase || !!opts.passphrase;
|
|
108
|
+
this.passphrase = opts.passphrase;
|
|
109
|
+
this.accounts = opts.accounts || [];
|
|
110
|
+
this.accountDetails = opts.accountDetails || {};
|
|
79
111
|
this.publicKey = opts.publicKey || '';
|
|
80
112
|
if (opts.mnemonic) {
|
|
81
|
-
this.
|
|
113
|
+
this.mnemonic = opts.mnemonic;
|
|
114
|
+
this.setPassphrase(opts.passphrase || '');
|
|
82
115
|
}
|
|
83
|
-
|
|
116
|
+
// activeIndexes is deprecated, if accounts is not empty, use accounts
|
|
117
|
+
if (!this.accounts.length && opts.activeIndexes) {
|
|
84
118
|
return this.activeAccounts(opts.activeIndexes);
|
|
85
119
|
}
|
|
86
120
|
return Promise.resolve([]);
|
|
87
121
|
}
|
|
88
|
-
|
|
89
|
-
this.root = this.hdWallet.derive(this.hdPath);
|
|
90
|
-
this.publicKey = (0, util_1.bytesToHex)(this.root.publicKey);
|
|
91
|
-
}
|
|
92
|
-
getPublicKey() {
|
|
93
|
-
return this.publicKey;
|
|
94
|
-
}
|
|
95
|
-
initFromMnemonic(mnemonic) {
|
|
122
|
+
initFromMnemonic(mnemonic, passphrase) {
|
|
96
123
|
this.mnemonic = mnemonic;
|
|
97
|
-
|
|
98
|
-
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
124
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
|
99
125
|
this.hdWallet = hdkey_1.HDKey.fromMasterSeed(seed);
|
|
100
|
-
this.root = this.hdWallet.derive(this.hdPath);
|
|
101
126
|
if (!this.publicKey) {
|
|
102
|
-
this.
|
|
127
|
+
this.publicKey = this.calcBasePublicKey(this.hdWallet);
|
|
103
128
|
}
|
|
104
129
|
}
|
|
130
|
+
calcBasePublicKey(hdKey) {
|
|
131
|
+
return (0, util_1.bytesToHex)(hdKey.derive(this.getHDPathBase(HDPathType.BIP44)).publicKey);
|
|
132
|
+
}
|
|
105
133
|
addAccounts(numberOfAccounts = 1) {
|
|
106
|
-
if (!this.
|
|
134
|
+
if (!this.hdWallet) {
|
|
107
135
|
this.initFromMnemonic(bip39.generateMnemonic(english_1.wordlist));
|
|
108
136
|
}
|
|
109
137
|
let count = numberOfAccounts;
|
|
110
138
|
let currentIdx = 0;
|
|
111
|
-
const
|
|
139
|
+
const addresses = [];
|
|
112
140
|
while (count) {
|
|
113
|
-
const [, wallet] = this._addressFromIndex(currentIdx);
|
|
114
|
-
if (this.wallets.
|
|
141
|
+
const [address, wallet] = this._addressFromIndex(currentIdx);
|
|
142
|
+
if (this.wallets.find((w) => (0, util_1.bytesToHex)(w.publicKey) === (0, util_1.bytesToHex)(wallet.publicKey))) {
|
|
115
143
|
currentIdx++;
|
|
116
144
|
}
|
|
117
145
|
else {
|
|
118
146
|
this.wallets.push(wallet);
|
|
119
|
-
|
|
120
|
-
this.activeIndexes.push(currentIdx);
|
|
147
|
+
addresses.push(address);
|
|
148
|
+
// this.activeIndexes.push(currentIdx);
|
|
149
|
+
this.setAccountDetail(address, {
|
|
150
|
+
hdPath: this.hdPath,
|
|
151
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
152
|
+
index: currentIdx,
|
|
153
|
+
});
|
|
121
154
|
count--;
|
|
122
155
|
}
|
|
156
|
+
if (!this.accounts.includes(address)) {
|
|
157
|
+
this.accounts.push(address);
|
|
158
|
+
}
|
|
123
159
|
}
|
|
124
|
-
|
|
125
|
-
return sigUtil.normalize(this._addressfromPublicKey(w.publicKey));
|
|
126
|
-
});
|
|
127
|
-
return Promise.resolve(hexWallets);
|
|
160
|
+
return Promise.resolve(addresses);
|
|
128
161
|
}
|
|
129
162
|
activeAccounts(indexes) {
|
|
130
163
|
const accounts = [];
|
|
@@ -133,6 +166,15 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
133
166
|
this.wallets.push(wallet);
|
|
134
167
|
this.activeIndexes.push(index);
|
|
135
168
|
accounts.push(address);
|
|
169
|
+
// hdPath is BIP44
|
|
170
|
+
this.setAccountDetail(address, {
|
|
171
|
+
hdPath: this.hdPath,
|
|
172
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
173
|
+
index: index,
|
|
174
|
+
});
|
|
175
|
+
if (!this.accounts.includes(address)) {
|
|
176
|
+
this.accounts.push(address);
|
|
177
|
+
}
|
|
136
178
|
}
|
|
137
179
|
return accounts;
|
|
138
180
|
}
|
|
@@ -160,9 +202,14 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
160
202
|
return accounts;
|
|
161
203
|
}
|
|
162
204
|
removeAccount(address) {
|
|
163
|
-
|
|
164
|
-
const index = this.
|
|
205
|
+
var _a;
|
|
206
|
+
const index = (_a = this.getInfoByAddress(address)) === null || _a === void 0 ? void 0 : _a.index;
|
|
165
207
|
this.activeIndexes = this.activeIndexes.filter((i) => i !== index);
|
|
208
|
+
delete this.accountDetails[address];
|
|
209
|
+
this.accounts = this.accounts.filter((acc) => acc !== address);
|
|
210
|
+
this.wallets = this.wallets.filter(({ publicKey }) => sigUtil
|
|
211
|
+
.normalize(this._addressFromPublicKey(publicKey))
|
|
212
|
+
.toLowerCase() !== address.toLowerCase());
|
|
166
213
|
}
|
|
167
214
|
__getPage(increment) {
|
|
168
215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -184,34 +231,93 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
184
231
|
});
|
|
185
232
|
}
|
|
186
233
|
getAccounts() {
|
|
234
|
+
var _a;
|
|
235
|
+
if ((_a = this.accounts) === null || _a === void 0 ? void 0 : _a.length) {
|
|
236
|
+
return Promise.resolve(this.accounts);
|
|
237
|
+
}
|
|
187
238
|
return Promise.resolve(this.wallets.map((w) => {
|
|
188
|
-
return sigUtil.normalize(this.
|
|
239
|
+
return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
|
|
189
240
|
}));
|
|
190
241
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
242
|
+
getInfoByAddress(address) {
|
|
243
|
+
const detail = this.accountDetails[address];
|
|
244
|
+
if (detail) {
|
|
245
|
+
return detail;
|
|
246
|
+
}
|
|
247
|
+
for (const key in this.wallets) {
|
|
248
|
+
const wallet = this.wallets[key];
|
|
249
|
+
if (sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey)) ===
|
|
250
|
+
address.toLowerCase()) {
|
|
251
|
+
return {
|
|
252
|
+
index: Number(key),
|
|
253
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
254
|
+
hdPath: this.hdPath,
|
|
255
|
+
};
|
|
195
256
|
}
|
|
196
257
|
}
|
|
197
258
|
return null;
|
|
198
259
|
}
|
|
199
|
-
/* PRIVATE METHODS */
|
|
200
260
|
_addressFromIndex(i) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this._index2wallet[i] = [address, wallet];
|
|
209
|
-
}
|
|
210
|
-
return this._index2wallet[i];
|
|
261
|
+
const child = this.getChildForIndex(i);
|
|
262
|
+
const wallet = {
|
|
263
|
+
publicKey: (0, util_1.privateToPublic)(child.privateKey),
|
|
264
|
+
privateKey: child.privateKey,
|
|
265
|
+
};
|
|
266
|
+
const address = sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey));
|
|
267
|
+
return [address, wallet];
|
|
211
268
|
}
|
|
212
|
-
|
|
269
|
+
_addressFromPublicKey(publicKey) {
|
|
213
270
|
return (0, util_1.bytesToHex)((0, util_1.publicToAddress)(publicKey, true)).toLowerCase();
|
|
214
271
|
}
|
|
272
|
+
generateMnemonic() {
|
|
273
|
+
return bip39.generateMnemonic(english_1.wordlist);
|
|
274
|
+
}
|
|
275
|
+
setHdPath(hdPath = HD_PATH_BASE[HDPathType.BIP44]) {
|
|
276
|
+
this.hdPath = hdPath;
|
|
277
|
+
}
|
|
278
|
+
getChildForIndex(index) {
|
|
279
|
+
return this.hdWallet.derive(this.getPathForIndex(index));
|
|
280
|
+
}
|
|
281
|
+
isLedgerLiveHdPath() {
|
|
282
|
+
return this.hdPath === HD_PATH_BASE[HDPathType.LedgerLive];
|
|
283
|
+
}
|
|
284
|
+
getPathForIndex(index) {
|
|
285
|
+
return this.isLedgerLiveHdPath()
|
|
286
|
+
? `m/44'/60'/${index}'/0/0`
|
|
287
|
+
: `${this.hdPath}/${index}`;
|
|
288
|
+
}
|
|
289
|
+
setPassphrase(passphrase) {
|
|
290
|
+
this.passphrase = passphrase;
|
|
291
|
+
this.initFromMnemonic(this.mnemonic, passphrase);
|
|
292
|
+
for (const acc of this.accounts) {
|
|
293
|
+
const detail = this.getAccountDetail(acc);
|
|
294
|
+
if (detail) {
|
|
295
|
+
this.setHdPath(detail.hdPath);
|
|
296
|
+
const [address, wallet] = this._addressFromIndex(detail.index);
|
|
297
|
+
if (address.toLowerCase() === acc.toLowerCase()) {
|
|
298
|
+
this.wallets.push(wallet);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* if passphrase is correct, the publicKey will be the same as the stored one
|
|
305
|
+
*/
|
|
306
|
+
checkPassphrase(passphrase) {
|
|
307
|
+
const seed = bip39.mnemonicToSeedSync(this.mnemonic, passphrase);
|
|
308
|
+
const hdWallet = hdkey_1.HDKey.fromMasterSeed(seed);
|
|
309
|
+
const publicKey = this.calcBasePublicKey(hdWallet);
|
|
310
|
+
return this.publicKey === publicKey;
|
|
311
|
+
}
|
|
312
|
+
getHDPathBase(hdPathType) {
|
|
313
|
+
return HD_PATH_BASE[hdPathType];
|
|
314
|
+
}
|
|
315
|
+
setHDPathType(hdPathType) {
|
|
316
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
const hdPath = this.getHDPathBase(hdPathType);
|
|
318
|
+
this.setHdPath(hdPath);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
215
321
|
}
|
|
216
322
|
HdKeyring.type = type;
|
|
217
323
|
exports.default = HdKeyring;
|
package/index.ts
CHANGED
|
@@ -4,16 +4,29 @@ import SimpleKeyring from '@rabby-wallet/eth-simple-keyring';
|
|
|
4
4
|
import * as bip39 from '@scure/bip39';
|
|
5
5
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
6
6
|
import * as sigUtil from 'eth-sig-util';
|
|
7
|
-
import {
|
|
8
|
-
bytesToHex,
|
|
9
|
-
publicToAddress,
|
|
10
|
-
privateToPublic,
|
|
11
|
-
} from '@ethereumjs/util';
|
|
7
|
+
import { bytesToHex, publicToAddress, privateToPublic } from '@ethereumjs/util';
|
|
12
8
|
|
|
13
9
|
// Options:
|
|
14
|
-
const hdPathString = "m/44'/60'/0'/0";
|
|
15
10
|
const type = 'HD Key Tree';
|
|
16
11
|
|
|
12
|
+
enum HDPathType {
|
|
13
|
+
LedgerLive = 'LedgerLive',
|
|
14
|
+
Legacy = 'Legacy',
|
|
15
|
+
BIP44 = 'BIP44',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HD_PATH_BASE = {
|
|
19
|
+
[HDPathType.BIP44]: "m/44'/60'/0'/0",
|
|
20
|
+
[HDPathType.Legacy]: "m/44'/60'/0'",
|
|
21
|
+
[HDPathType.LedgerLive]: "m/44'/60'/0'/0/0",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const HD_PATH_TYPE = {
|
|
25
|
+
[HD_PATH_BASE[HDPathType.BIP44]]: HDPathType.BIP44,
|
|
26
|
+
[HD_PATH_BASE[HDPathType.Legacy]]: HDPathType.Legacy,
|
|
27
|
+
[HD_PATH_BASE[HDPathType.LedgerLive]]: HDPathType.LedgerLive,
|
|
28
|
+
};
|
|
29
|
+
|
|
17
30
|
interface Wallet {
|
|
18
31
|
publicKey: Uint8Array;
|
|
19
32
|
privateKey: Uint8Array;
|
|
@@ -21,32 +34,44 @@ interface Wallet {
|
|
|
21
34
|
|
|
22
35
|
interface DeserializeOption {
|
|
23
36
|
hdPath?: string;
|
|
24
|
-
mnemonic
|
|
37
|
+
mnemonic: string;
|
|
25
38
|
activeIndexes?: number[];
|
|
26
39
|
byImport?: boolean;
|
|
27
40
|
index?: number;
|
|
41
|
+
passphrase?: string;
|
|
42
|
+
needPassphrase?: boolean;
|
|
43
|
+
accounts?: string[];
|
|
44
|
+
accountDetails?: Record<string, AccountDetail>;
|
|
28
45
|
publicKey?: string;
|
|
29
46
|
}
|
|
30
47
|
|
|
48
|
+
interface AccountDetail {
|
|
49
|
+
hdPath: string;
|
|
50
|
+
hdPathType: HDPathType;
|
|
51
|
+
index: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
class HdKeyring extends SimpleKeyring {
|
|
32
55
|
static type = type;
|
|
33
56
|
|
|
34
57
|
type = type;
|
|
35
58
|
mnemonic: string | null = null;
|
|
36
|
-
hdPath =
|
|
59
|
+
hdPath = HD_PATH_BASE[HDPathType.BIP44];
|
|
37
60
|
hdWallet?: HDKey;
|
|
38
|
-
root: HDKey | null = null;
|
|
39
61
|
wallets: Wallet[] = [];
|
|
40
|
-
_index2wallet: Record<number, [string, Wallet]> = {};
|
|
41
62
|
activeIndexes: number[] = [];
|
|
42
63
|
index = 0;
|
|
43
64
|
page = 0;
|
|
44
65
|
perPage = 5;
|
|
45
66
|
byImport = false;
|
|
46
67
|
publicKey: string = '';
|
|
68
|
+
needPassphrase = false;
|
|
69
|
+
accounts: string[] = [];
|
|
70
|
+
accountDetails: Record<string, AccountDetail> = {};
|
|
71
|
+
passphrase?: string = '';
|
|
47
72
|
|
|
48
73
|
/* PUBLIC METHODS */
|
|
49
|
-
constructor(opts = {}) {
|
|
74
|
+
constructor(opts: DeserializeOption = {} as any) {
|
|
50
75
|
super();
|
|
51
76
|
this.deserialize(opts);
|
|
52
77
|
}
|
|
@@ -54,81 +79,94 @@ class HdKeyring extends SimpleKeyring {
|
|
|
54
79
|
serialize() {
|
|
55
80
|
return Promise.resolve({
|
|
56
81
|
mnemonic: this.mnemonic,
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated
|
|
84
|
+
*/
|
|
57
85
|
activeIndexes: this.activeIndexes,
|
|
58
86
|
hdPath: this.hdPath,
|
|
59
87
|
byImport: this.byImport,
|
|
60
88
|
index: this.index,
|
|
89
|
+
needPassphrase: this.needPassphrase,
|
|
90
|
+
accounts: this.accounts,
|
|
91
|
+
accountDetails: this.accountDetails,
|
|
61
92
|
publicKey: this.publicKey,
|
|
62
93
|
});
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
deserialize(opts: DeserializeOption = {}) {
|
|
96
|
+
deserialize(opts: DeserializeOption = {} as any) {
|
|
66
97
|
this.wallets = [];
|
|
67
98
|
this.mnemonic = null;
|
|
68
|
-
this.
|
|
69
|
-
this.hdPath = opts.hdPath || hdPathString;
|
|
99
|
+
this.hdPath = opts.hdPath || HD_PATH_BASE[HDPathType.BIP44];
|
|
70
100
|
this.byImport = !!opts.byImport;
|
|
71
101
|
this.index = opts.index || 0;
|
|
102
|
+
this.needPassphrase = opts.needPassphrase || !!opts.passphrase;
|
|
103
|
+
this.passphrase = opts.passphrase;
|
|
104
|
+
this.accounts = opts.accounts || [];
|
|
105
|
+
this.accountDetails = opts.accountDetails || {};
|
|
72
106
|
this.publicKey = opts.publicKey || '';
|
|
73
107
|
|
|
74
108
|
if (opts.mnemonic) {
|
|
75
|
-
this.
|
|
109
|
+
this.mnemonic = opts.mnemonic;
|
|
110
|
+
this.setPassphrase(opts.passphrase || '');
|
|
76
111
|
}
|
|
77
112
|
|
|
78
|
-
|
|
113
|
+
// activeIndexes is deprecated, if accounts is not empty, use accounts
|
|
114
|
+
if (!this.accounts.length && opts.activeIndexes) {
|
|
79
115
|
return this.activeAccounts(opts.activeIndexes);
|
|
80
116
|
}
|
|
81
117
|
|
|
82
118
|
return Promise.resolve([]);
|
|
83
119
|
}
|
|
84
120
|
|
|
85
|
-
|
|
86
|
-
this.root = this.hdWallet!.derive(this.hdPath);
|
|
87
|
-
this.publicKey = bytesToHex(this.root.publicKey!);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
getPublicKey() {
|
|
91
|
-
return this.publicKey;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
initFromMnemonic(mnemonic) {
|
|
121
|
+
initFromMnemonic(mnemonic, passphrase?: string) {
|
|
95
122
|
this.mnemonic = mnemonic;
|
|
96
|
-
|
|
97
|
-
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
123
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
|
98
124
|
this.hdWallet = HDKey.fromMasterSeed(seed);
|
|
99
|
-
this.root = this.hdWallet!.derive(this.hdPath);
|
|
100
|
-
|
|
101
125
|
if (!this.publicKey) {
|
|
102
|
-
this.
|
|
126
|
+
this.publicKey = this.calcBasePublicKey(this.hdWallet!);
|
|
103
127
|
}
|
|
104
128
|
}
|
|
105
129
|
|
|
130
|
+
private calcBasePublicKey(hdKey: HDKey) {
|
|
131
|
+
return bytesToHex(
|
|
132
|
+
hdKey.derive(this.getHDPathBase(HDPathType.BIP44)).publicKey!,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
106
136
|
addAccounts(numberOfAccounts = 1) {
|
|
107
|
-
if (!this.
|
|
137
|
+
if (!this.hdWallet) {
|
|
108
138
|
this.initFromMnemonic(bip39.generateMnemonic(wordlist));
|
|
109
139
|
}
|
|
110
140
|
|
|
111
141
|
let count = numberOfAccounts;
|
|
112
142
|
let currentIdx = 0;
|
|
113
|
-
const
|
|
114
|
-
|
|
143
|
+
const addresses: string[] = [];
|
|
144
|
+
|
|
115
145
|
while (count) {
|
|
116
|
-
const [, wallet] = this._addressFromIndex(currentIdx);
|
|
117
|
-
if (
|
|
146
|
+
const [address, wallet] = this._addressFromIndex(currentIdx);
|
|
147
|
+
if (
|
|
148
|
+
this.wallets.find(
|
|
149
|
+
(w) => bytesToHex(w.publicKey) === bytesToHex(wallet.publicKey),
|
|
150
|
+
)
|
|
151
|
+
) {
|
|
118
152
|
currentIdx++;
|
|
119
153
|
} else {
|
|
120
154
|
this.wallets.push(wallet);
|
|
121
|
-
|
|
122
|
-
this.activeIndexes.push(currentIdx);
|
|
155
|
+
addresses.push(address);
|
|
156
|
+
// this.activeIndexes.push(currentIdx);
|
|
157
|
+
this.setAccountDetail(address, {
|
|
158
|
+
hdPath: this.hdPath,
|
|
159
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
160
|
+
index: currentIdx,
|
|
161
|
+
});
|
|
123
162
|
count--;
|
|
124
163
|
}
|
|
164
|
+
if (!this.accounts.includes(address)) {
|
|
165
|
+
this.accounts.push(address);
|
|
166
|
+
}
|
|
125
167
|
}
|
|
126
168
|
|
|
127
|
-
|
|
128
|
-
return sigUtil.normalize(this._addressfromPublicKey(w.publicKey));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return Promise.resolve(hexWallets);
|
|
169
|
+
return Promise.resolve(addresses);
|
|
132
170
|
}
|
|
133
171
|
|
|
134
172
|
activeAccounts(indexes: number[]) {
|
|
@@ -139,6 +177,16 @@ class HdKeyring extends SimpleKeyring {
|
|
|
139
177
|
this.activeIndexes.push(index);
|
|
140
178
|
|
|
141
179
|
accounts.push(address);
|
|
180
|
+
// hdPath is BIP44
|
|
181
|
+
this.setAccountDetail(address, {
|
|
182
|
+
hdPath: this.hdPath,
|
|
183
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
184
|
+
index: index,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!this.accounts.includes(address)) {
|
|
188
|
+
this.accounts.push(address);
|
|
189
|
+
}
|
|
142
190
|
}
|
|
143
191
|
|
|
144
192
|
return accounts;
|
|
@@ -171,9 +219,16 @@ class HdKeyring extends SimpleKeyring {
|
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
removeAccount(address) {
|
|
174
|
-
|
|
175
|
-
const index = this.getIndexByAddress(address);
|
|
222
|
+
const index = this.getInfoByAddress(address)?.index;
|
|
176
223
|
this.activeIndexes = this.activeIndexes.filter((i) => i !== index);
|
|
224
|
+
delete this.accountDetails[address];
|
|
225
|
+
this.accounts = this.accounts.filter((acc) => acc !== address);
|
|
226
|
+
this.wallets = this.wallets.filter(
|
|
227
|
+
({ publicKey }) =>
|
|
228
|
+
sigUtil
|
|
229
|
+
.normalize(this._addressFromPublicKey(publicKey))
|
|
230
|
+
.toLowerCase() !== address.toLowerCase(),
|
|
231
|
+
);
|
|
177
232
|
}
|
|
178
233
|
|
|
179
234
|
async __getPage(increment: number): Promise<
|
|
@@ -205,43 +260,124 @@ class HdKeyring extends SimpleKeyring {
|
|
|
205
260
|
}
|
|
206
261
|
|
|
207
262
|
getAccounts() {
|
|
263
|
+
if (this.accounts?.length) {
|
|
264
|
+
return Promise.resolve(this.accounts);
|
|
265
|
+
}
|
|
266
|
+
|
|
208
267
|
return Promise.resolve(
|
|
209
268
|
this.wallets.map((w) => {
|
|
210
|
-
return sigUtil.normalize(this.
|
|
269
|
+
return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
|
|
211
270
|
}),
|
|
212
271
|
);
|
|
213
272
|
}
|
|
214
273
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
274
|
+
getInfoByAddress(address: string): AccountDetail | null {
|
|
275
|
+
const detail = this.accountDetails[address];
|
|
276
|
+
if (detail) {
|
|
277
|
+
return detail;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const key in this.wallets) {
|
|
281
|
+
const wallet = this.wallets[key];
|
|
282
|
+
if (
|
|
283
|
+
sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey)) ===
|
|
284
|
+
address.toLowerCase()
|
|
285
|
+
) {
|
|
286
|
+
return {
|
|
287
|
+
index: Number(key),
|
|
288
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
289
|
+
hdPath: this.hdPath,
|
|
290
|
+
};
|
|
219
291
|
}
|
|
220
292
|
}
|
|
221
293
|
return null;
|
|
222
294
|
}
|
|
223
295
|
|
|
224
|
-
/* PRIVATE METHODS */
|
|
225
|
-
|
|
226
296
|
_addressFromIndex(i: number): [string, Wallet] {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
);
|
|
236
|
-
this._index2wallet[i] = [address, wallet];
|
|
237
|
-
}
|
|
297
|
+
const child = this.getChildForIndex(i);
|
|
298
|
+
const wallet = {
|
|
299
|
+
publicKey: privateToPublic(child.privateKey!),
|
|
300
|
+
privateKey: child.privateKey!,
|
|
301
|
+
};
|
|
302
|
+
const address = sigUtil.normalize(
|
|
303
|
+
this._addressFromPublicKey(wallet.publicKey),
|
|
304
|
+
);
|
|
238
305
|
|
|
239
|
-
return
|
|
306
|
+
return [address, wallet];
|
|
240
307
|
}
|
|
241
308
|
|
|
242
|
-
|
|
309
|
+
private _addressFromPublicKey(publicKey: Uint8Array) {
|
|
243
310
|
return bytesToHex(publicToAddress(publicKey, true)).toLowerCase();
|
|
244
311
|
}
|
|
312
|
+
|
|
313
|
+
generateMnemonic() {
|
|
314
|
+
return bip39.generateMnemonic(wordlist);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
setHdPath(hdPath = HD_PATH_BASE[HDPathType.BIP44]) {
|
|
318
|
+
this.hdPath = hdPath;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private getChildForIndex(index: number) {
|
|
322
|
+
return this.hdWallet!.derive(this.getPathForIndex(index));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private isLedgerLiveHdPath() {
|
|
326
|
+
return this.hdPath === HD_PATH_BASE[HDPathType.LedgerLive];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private getPathForIndex(index) {
|
|
330
|
+
return this.isLedgerLiveHdPath()
|
|
331
|
+
? `m/44'/60'/${index}'/0/0`
|
|
332
|
+
: `${this.hdPath}/${index}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
setPassphrase(passphrase: string) {
|
|
336
|
+
this.passphrase = passphrase;
|
|
337
|
+
this.initFromMnemonic(this.mnemonic, passphrase);
|
|
338
|
+
|
|
339
|
+
for (const acc of this.accounts) {
|
|
340
|
+
const detail = this.getAccountDetail(acc);
|
|
341
|
+
if (detail) {
|
|
342
|
+
this.setHdPath(detail.hdPath);
|
|
343
|
+
const [address, wallet] = this._addressFromIndex(detail.index);
|
|
344
|
+
if (address.toLowerCase() === acc.toLowerCase()) {
|
|
345
|
+
this.wallets.push(wallet);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* if passphrase is correct, the publicKey will be the same as the stored one
|
|
353
|
+
*/
|
|
354
|
+
checkPassphrase(passphrase: string) {
|
|
355
|
+
const seed = bip39.mnemonicToSeedSync(this.mnemonic!, passphrase);
|
|
356
|
+
const hdWallet = HDKey.fromMasterSeed(seed);
|
|
357
|
+
const publicKey = this.calcBasePublicKey(hdWallet);
|
|
358
|
+
|
|
359
|
+
return this.publicKey === publicKey;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
setAccountDetail = (address: string, accountDetail: AccountDetail) => {
|
|
363
|
+
this.accountDetails = {
|
|
364
|
+
...this.accountDetails,
|
|
365
|
+
[address.toLowerCase()]: accountDetail,
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
getAccountDetail = (address: string) => {
|
|
370
|
+
return this.accountDetails[address.toLowerCase()];
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
private getHDPathBase(hdPathType: HDPathType) {
|
|
374
|
+
return HD_PATH_BASE[hdPathType];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async setHDPathType(hdPathType: HDPathType) {
|
|
378
|
+
const hdPath = this.getHDPathBase(hdPathType);
|
|
379
|
+
this.setHdPath(hdPath);
|
|
380
|
+
}
|
|
245
381
|
}
|
|
246
382
|
|
|
247
383
|
export default HdKeyring;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rabby-wallet/eth-hd-keyring",
|
|
3
|
-
"version": "4.0.1",
|
|
3
|
+
"version": "4.1.0-beta.1",
|
|
4
4
|
"description": "A simple standard interface for a seed phrase generated set of Ethereum accounts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ethereum",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' --ignore-path .gitignore",
|
|
25
25
|
"lint": "yarn lint:eslint && yarn lint:misc --check",
|
|
26
26
|
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
|
|
27
|
-
"test": "mocha"
|
|
27
|
+
"test": "yarn build && mocha"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@ethereumjs/util": "^9.0.0",
|
package/test/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const firstAcct = '0x1c96099350f13d558464ec79b9be4445aa0ef579';
|
|
|
20
20
|
const secondAcct = '0x1b00aed43a693f3a957f9feb5cc08afa031e37a0';
|
|
21
21
|
|
|
22
22
|
describe('hd-keyring', function () {
|
|
23
|
-
let keyring;
|
|
23
|
+
let keyring = new HdKeyring();
|
|
24
24
|
beforeEach(function () {
|
|
25
25
|
keyring = new HdKeyring();
|
|
26
26
|
});
|
|
@@ -334,6 +334,76 @@ describe('hd-keyring', function () {
|
|
|
334
334
|
console.log('failed because', reason);
|
|
335
335
|
});
|
|
336
336
|
});
|
|
337
|
+
|
|
338
|
+
it('hdPath.Legacy', function (done) {
|
|
339
|
+
const hdPathLegacy = "m/44'/60'/0'";
|
|
340
|
+
keyring.deserialize({
|
|
341
|
+
mnemonic: sampleMnemonic,
|
|
342
|
+
activeIndexes: [0, 1],
|
|
343
|
+
hdPath: hdPathLegacy,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
keyring.getAccounts().then((addersses) => {
|
|
347
|
+
assert.deepEqual(addersses, [
|
|
348
|
+
'0x5a5a19b534db50801fb6dec48ea262ca3a0efda6',
|
|
349
|
+
'0x6729dd439a96d4a7bc6362c231d6931cfaa31088',
|
|
350
|
+
]);
|
|
351
|
+
done();
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('hdPath.LedgerLive', function (done) {
|
|
356
|
+
const hdPathLedgerLive = "m/44'/60'/0'/0/0";
|
|
357
|
+
keyring.deserialize({
|
|
358
|
+
mnemonic: sampleMnemonic,
|
|
359
|
+
activeIndexes: [0, 1],
|
|
360
|
+
hdPath: hdPathLedgerLive,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
keyring.getAccounts().then((addersses) => {
|
|
364
|
+
assert.deepEqual(addersses, [
|
|
365
|
+
firstAcct,
|
|
366
|
+
'0x0827a0c8f451b8fcca2cd4e9c23c47a92ca69a56',
|
|
367
|
+
]);
|
|
368
|
+
done();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('hdPath.bip44', function (done) {
|
|
373
|
+
const hdPathBIP44 = "m/44'/60'/0'/0";
|
|
374
|
+
keyring.deserialize({
|
|
375
|
+
mnemonic: sampleMnemonic,
|
|
376
|
+
activeIndexes: [0, 1],
|
|
377
|
+
hdPath: hdPathBIP44,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
keyring.getAccounts().then((addersses) => {
|
|
381
|
+
assert.deepEqual(addersses, [firstAcct, secondAcct]);
|
|
382
|
+
done();
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('setHdPath', function (done) {
|
|
387
|
+
const hdPathLedgerLive = "m/44'/60'/0'/0/0";
|
|
388
|
+
const hdPathBIP44 = "m/44'/60'/0'/0";
|
|
389
|
+
|
|
390
|
+
keyring.deserialize({
|
|
391
|
+
mnemonic: sampleMnemonic,
|
|
392
|
+
activeIndexes: [0, 1],
|
|
393
|
+
hdPath: hdPathBIP44,
|
|
394
|
+
});
|
|
395
|
+
keyring.setHdPath(hdPathLedgerLive);
|
|
396
|
+
keyring.activeAccounts([1]);
|
|
397
|
+
|
|
398
|
+
keyring.getAccounts().then((addersses) => {
|
|
399
|
+
assert.deepEqual(addersses, [
|
|
400
|
+
firstAcct,
|
|
401
|
+
secondAcct,
|
|
402
|
+
'0x0827a0c8f451b8fcca2cd4e9c23c47a92ca69a56',
|
|
403
|
+
]);
|
|
404
|
+
done();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
337
407
|
});
|
|
338
408
|
|
|
339
409
|
/*
|
|
@@ -525,4 +595,87 @@ describe('hd-keyring', function () {
|
|
|
525
595
|
assert.deepEqual(activeIndexes, [2, 3, 6]);
|
|
526
596
|
});
|
|
527
597
|
});
|
|
598
|
+
|
|
599
|
+
describe('accountDetails', function () {
|
|
600
|
+
it('should return correct account details', async function () {
|
|
601
|
+
const address = firstAcct;
|
|
602
|
+
|
|
603
|
+
keyring = new HdKeyring({
|
|
604
|
+
mnemonic: sampleMnemonic,
|
|
605
|
+
activeIndexes: [0, 2, 3, 6],
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const accountDetail = await keyring.getAccountDetail(address);
|
|
609
|
+
|
|
610
|
+
assert.deepEqual(accountDetail, {
|
|
611
|
+
hdPath: "m/44'/60'/0'/0",
|
|
612
|
+
hdPathType: 'BIP44',
|
|
613
|
+
index: 0,
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe('passphrase', function () {
|
|
619
|
+
it('should be able to set a passphrase', async function () {
|
|
620
|
+
await keyring.deserialize({
|
|
621
|
+
mnemonic: sampleMnemonic,
|
|
622
|
+
});
|
|
623
|
+
keyring.setPassphrase('abc123');
|
|
624
|
+
keyring.activeAccounts([0, 1]);
|
|
625
|
+
const result = await keyring.getAccounts();
|
|
626
|
+
|
|
627
|
+
assert.deepEqual(result, [
|
|
628
|
+
'0x8db9506aa1c0e2c07dc03417ded629e0cffe2412',
|
|
629
|
+
'0x805eacc9c707b94581fd2a230f437bd370fe229c',
|
|
630
|
+
]);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('needPassphrase', async function () {
|
|
634
|
+
keyring = new HdKeyring({
|
|
635
|
+
mnemonic: sampleMnemonic,
|
|
636
|
+
needPassphrase: true,
|
|
637
|
+
accounts: [
|
|
638
|
+
'0x8db9506aa1c0e2c07dc03417ded629e0cffe2412',
|
|
639
|
+
'0x805eacc9c707b94581fd2a230f437bd370fe229c',
|
|
640
|
+
],
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
assert.equal(keyring.wallets.length, 0);
|
|
644
|
+
assert.equal(keyring.accounts.length, 2);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('get wallet when set passphrase', async function () {
|
|
648
|
+
await keyring.deserialize({
|
|
649
|
+
mnemonic: sampleMnemonic,
|
|
650
|
+
needPassphrase: true,
|
|
651
|
+
accounts: [
|
|
652
|
+
'0x8db9506aa1c0e2c07dc03417ded629e0cffe2412',
|
|
653
|
+
'0x805eacc9c707b94581fd2a230f437bd370fe229c',
|
|
654
|
+
],
|
|
655
|
+
accountDetails: {
|
|
656
|
+
'0x8db9506aa1c0e2c07dc03417ded629e0cffe2412': {
|
|
657
|
+
hdPath: "m/44'/60'/0'/0",
|
|
658
|
+
hdPathType: 'BIP44',
|
|
659
|
+
index: 0,
|
|
660
|
+
},
|
|
661
|
+
'0x805eacc9c707b94581fd2a230f437bd370fe229c': {
|
|
662
|
+
hdPath: "m/44'/60'/0'/0",
|
|
663
|
+
hdPathType: 'BIP44',
|
|
664
|
+
index: 1,
|
|
665
|
+
},
|
|
666
|
+
[firstAcct]: {
|
|
667
|
+
hdPath: "m/44'/60'/0'/0",
|
|
668
|
+
hdPathType: 'BIP44',
|
|
669
|
+
index: 0,
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
assert.equal(keyring.wallets.length, 0);
|
|
675
|
+
|
|
676
|
+
keyring.setPassphrase('abc123');
|
|
677
|
+
|
|
678
|
+
assert.equal(keyring.wallets.length, 2);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
528
681
|
});
|