@rabby-wallet/eth-hd-keyring 4.0.1 → 4.1.0-beta.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/dist/index.js +152 -49
- package/index.ts +198 -65
- package/package.json +2 -2
- package/test/index.js +154 -1
package/dist/index.js
CHANGED
|
@@ -39,92 +39,123 @@ 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.setAccountDetail = (address, accountDetail) => {
|
|
77
|
+
this.accountDetails = Object.assign(Object.assign({}, this.accountDetails), { [address.toLowerCase()]: accountDetail });
|
|
78
|
+
};
|
|
79
|
+
this.getAccountDetail = (address) => {
|
|
80
|
+
return this.accountDetails[address.toLowerCase()];
|
|
81
|
+
};
|
|
60
82
|
this.deserialize(opts);
|
|
61
83
|
}
|
|
62
84
|
serialize() {
|
|
63
85
|
return Promise.resolve({
|
|
64
86
|
mnemonic: this.mnemonic,
|
|
87
|
+
/**
|
|
88
|
+
* @deprecated
|
|
89
|
+
*/
|
|
65
90
|
activeIndexes: this.activeIndexes,
|
|
66
91
|
hdPath: this.hdPath,
|
|
67
92
|
byImport: this.byImport,
|
|
68
93
|
index: this.index,
|
|
94
|
+
needPassphrase: this.needPassphrase,
|
|
95
|
+
accounts: this.accounts,
|
|
96
|
+
accountDetails: this.accountDetails,
|
|
69
97
|
publicKey: this.publicKey,
|
|
70
98
|
});
|
|
71
99
|
}
|
|
72
100
|
deserialize(opts = {}) {
|
|
73
101
|
this.wallets = [];
|
|
74
102
|
this.mnemonic = null;
|
|
75
|
-
this.
|
|
76
|
-
this.hdPath = opts.hdPath || hdPathString;
|
|
103
|
+
this.hdPath = opts.hdPath || HD_PATH_BASE[HDPathType.BIP44];
|
|
77
104
|
this.byImport = !!opts.byImport;
|
|
78
105
|
this.index = opts.index || 0;
|
|
106
|
+
this.needPassphrase = opts.needPassphrase || !!opts.passphrase;
|
|
107
|
+
this.accounts = opts.accounts || [];
|
|
108
|
+
this.accountDetails = opts.accountDetails || {};
|
|
79
109
|
this.publicKey = opts.publicKey || '';
|
|
80
110
|
if (opts.mnemonic) {
|
|
81
|
-
this.
|
|
111
|
+
this.mnemonic = opts.mnemonic;
|
|
112
|
+
this.setPassphrase(opts.passphrase || '');
|
|
82
113
|
}
|
|
83
|
-
|
|
114
|
+
// activeIndexes is deprecated, if accounts is not empty, use accounts
|
|
115
|
+
if (!this.accounts.length && opts.activeIndexes) {
|
|
84
116
|
return this.activeAccounts(opts.activeIndexes);
|
|
85
117
|
}
|
|
86
118
|
return Promise.resolve([]);
|
|
87
119
|
}
|
|
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) {
|
|
120
|
+
initFromMnemonic(mnemonic, passphrase) {
|
|
96
121
|
this.mnemonic = mnemonic;
|
|
97
|
-
|
|
98
|
-
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
122
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
|
99
123
|
this.hdWallet = hdkey_1.HDKey.fromMasterSeed(seed);
|
|
100
|
-
this.root = this.hdWallet.derive(this.hdPath);
|
|
101
124
|
if (!this.publicKey) {
|
|
102
|
-
this.
|
|
125
|
+
this.publicKey = this.calcBasePublicKey(this.hdWallet);
|
|
103
126
|
}
|
|
104
127
|
}
|
|
128
|
+
calcBasePublicKey(hdKey) {
|
|
129
|
+
return (0, util_1.bytesToHex)(hdKey.derive(this.getHDPathBase(HDPathType.BIP44)).publicKey);
|
|
130
|
+
}
|
|
105
131
|
addAccounts(numberOfAccounts = 1) {
|
|
106
|
-
if (!this.
|
|
132
|
+
if (!this.hdWallet) {
|
|
107
133
|
this.initFromMnemonic(bip39.generateMnemonic(english_1.wordlist));
|
|
108
134
|
}
|
|
109
135
|
let count = numberOfAccounts;
|
|
110
136
|
let currentIdx = 0;
|
|
111
|
-
const
|
|
137
|
+
const addresses = [];
|
|
112
138
|
while (count) {
|
|
113
|
-
const [, wallet] = this._addressFromIndex(currentIdx);
|
|
114
|
-
if (this.wallets.
|
|
139
|
+
const [address, wallet] = this._addressFromIndex(currentIdx);
|
|
140
|
+
if (this.wallets.find((w) => (0, util_1.bytesToHex)(w.publicKey) === (0, util_1.bytesToHex)(wallet.publicKey))) {
|
|
115
141
|
currentIdx++;
|
|
116
142
|
}
|
|
117
143
|
else {
|
|
118
144
|
this.wallets.push(wallet);
|
|
119
|
-
|
|
120
|
-
this.activeIndexes.push(currentIdx);
|
|
145
|
+
addresses.push(address);
|
|
146
|
+
// this.activeIndexes.push(currentIdx);
|
|
147
|
+
this.setAccountDetail(address, {
|
|
148
|
+
hdPath: this.hdPath,
|
|
149
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
150
|
+
index: currentIdx,
|
|
151
|
+
});
|
|
121
152
|
count--;
|
|
122
153
|
}
|
|
154
|
+
if (!this.accounts.includes(address)) {
|
|
155
|
+
this.accounts.push(address);
|
|
156
|
+
}
|
|
123
157
|
}
|
|
124
|
-
|
|
125
|
-
return sigUtil.normalize(this._addressfromPublicKey(w.publicKey));
|
|
126
|
-
});
|
|
127
|
-
return Promise.resolve(hexWallets);
|
|
158
|
+
return Promise.resolve(addresses);
|
|
128
159
|
}
|
|
129
160
|
activeAccounts(indexes) {
|
|
130
161
|
const accounts = [];
|
|
@@ -133,6 +164,15 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
133
164
|
this.wallets.push(wallet);
|
|
134
165
|
this.activeIndexes.push(index);
|
|
135
166
|
accounts.push(address);
|
|
167
|
+
// hdPath is BIP44
|
|
168
|
+
this.setAccountDetail(address, {
|
|
169
|
+
hdPath: this.hdPath,
|
|
170
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
171
|
+
index: index,
|
|
172
|
+
});
|
|
173
|
+
if (!this.accounts.includes(address)) {
|
|
174
|
+
this.accounts.push(address);
|
|
175
|
+
}
|
|
136
176
|
}
|
|
137
177
|
return accounts;
|
|
138
178
|
}
|
|
@@ -160,9 +200,14 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
160
200
|
return accounts;
|
|
161
201
|
}
|
|
162
202
|
removeAccount(address) {
|
|
163
|
-
|
|
164
|
-
const index = this.
|
|
203
|
+
var _a;
|
|
204
|
+
const index = (_a = this.getInfoByAddress(address)) === null || _a === void 0 ? void 0 : _a.index;
|
|
165
205
|
this.activeIndexes = this.activeIndexes.filter((i) => i !== index);
|
|
206
|
+
delete this.accountDetails[address];
|
|
207
|
+
this.accounts = this.accounts.filter((acc) => acc !== address);
|
|
208
|
+
this.wallets = this.wallets.filter(({ publicKey }) => sigUtil
|
|
209
|
+
.normalize(this._addressFromPublicKey(publicKey))
|
|
210
|
+
.toLowerCase() !== address.toLowerCase());
|
|
166
211
|
}
|
|
167
212
|
__getPage(increment) {
|
|
168
213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -184,34 +229,92 @@ class HdKeyring extends eth_simple_keyring_1.default {
|
|
|
184
229
|
});
|
|
185
230
|
}
|
|
186
231
|
getAccounts() {
|
|
232
|
+
var _a;
|
|
233
|
+
if ((_a = this.accounts) === null || _a === void 0 ? void 0 : _a.length) {
|
|
234
|
+
return Promise.resolve(this.accounts);
|
|
235
|
+
}
|
|
187
236
|
return Promise.resolve(this.wallets.map((w) => {
|
|
188
|
-
return sigUtil.normalize(this.
|
|
237
|
+
return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
|
|
189
238
|
}));
|
|
190
239
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
240
|
+
getInfoByAddress(address) {
|
|
241
|
+
const detail = this.accountDetails[address];
|
|
242
|
+
if (detail) {
|
|
243
|
+
return detail;
|
|
244
|
+
}
|
|
245
|
+
for (const key in this.wallets) {
|
|
246
|
+
const wallet = this.wallets[key];
|
|
247
|
+
if (sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey)) ===
|
|
248
|
+
address.toLowerCase()) {
|
|
249
|
+
return {
|
|
250
|
+
index: Number(key),
|
|
251
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
252
|
+
hdPath: this.hdPath,
|
|
253
|
+
};
|
|
195
254
|
}
|
|
196
255
|
}
|
|
197
256
|
return null;
|
|
198
257
|
}
|
|
199
|
-
/* PRIVATE METHODS */
|
|
200
258
|
_addressFromIndex(i) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this._index2wallet[i] = [address, wallet];
|
|
209
|
-
}
|
|
210
|
-
return this._index2wallet[i];
|
|
259
|
+
const child = this.getChildForIndex(i);
|
|
260
|
+
const wallet = {
|
|
261
|
+
publicKey: (0, util_1.privateToPublic)(child.privateKey),
|
|
262
|
+
privateKey: child.privateKey,
|
|
263
|
+
};
|
|
264
|
+
const address = sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey));
|
|
265
|
+
return [address, wallet];
|
|
211
266
|
}
|
|
212
|
-
|
|
267
|
+
_addressFromPublicKey(publicKey) {
|
|
213
268
|
return (0, util_1.bytesToHex)((0, util_1.publicToAddress)(publicKey, true)).toLowerCase();
|
|
214
269
|
}
|
|
270
|
+
generateMnemonic() {
|
|
271
|
+
return bip39.generateMnemonic(english_1.wordlist);
|
|
272
|
+
}
|
|
273
|
+
setHdPath(hdPath = HD_PATH_BASE[HDPathType.BIP44]) {
|
|
274
|
+
this.hdPath = hdPath;
|
|
275
|
+
}
|
|
276
|
+
getChildForIndex(index) {
|
|
277
|
+
return this.hdWallet.derive(this.getPathForIndex(index));
|
|
278
|
+
}
|
|
279
|
+
isLedgerLiveHdPath() {
|
|
280
|
+
return this.hdPath === HD_PATH_BASE[HDPathType.LedgerLive];
|
|
281
|
+
}
|
|
282
|
+
getPathForIndex(index) {
|
|
283
|
+
return this.isLedgerLiveHdPath()
|
|
284
|
+
? `m/44'/60'/${index}'/0/0`
|
|
285
|
+
: `${this.hdPath}/${index}`;
|
|
286
|
+
}
|
|
287
|
+
setPassphrase(passphrase) {
|
|
288
|
+
this.initFromMnemonic(this.mnemonic, passphrase);
|
|
289
|
+
for (const acc of this.accounts) {
|
|
290
|
+
const detail = this.getAccountDetail(acc);
|
|
291
|
+
if (detail) {
|
|
292
|
+
this.setHdPath(detail.hdPath);
|
|
293
|
+
const [address, wallet] = this._addressFromIndex(detail.index);
|
|
294
|
+
if (address.toLowerCase() === acc.toLowerCase()) {
|
|
295
|
+
this.wallets.push(wallet);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* if passphrase is correct, the publicKey will be the same as the stored one
|
|
302
|
+
*/
|
|
303
|
+
checkPassphrase(passphrase) {
|
|
304
|
+
const seed = bip39.mnemonicToSeedSync(this.mnemonic, passphrase);
|
|
305
|
+
const hdWallet = hdkey_1.HDKey.fromMasterSeed(seed);
|
|
306
|
+
const publicKey = this.calcBasePublicKey(hdWallet);
|
|
307
|
+
return this.publicKey === publicKey;
|
|
308
|
+
}
|
|
309
|
+
getHDPathBase(hdPathType) {
|
|
310
|
+
return HD_PATH_BASE[hdPathType];
|
|
311
|
+
}
|
|
312
|
+
setHDPathType(hdPathType) {
|
|
313
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
314
|
+
const hdPath = this.getHDPathBase(hdPathType);
|
|
315
|
+
this.setHdPath(hdPath);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
215
318
|
}
|
|
216
319
|
HdKeyring.type = type;
|
|
217
320
|
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,43 @@ 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> = {};
|
|
47
71
|
|
|
48
72
|
/* PUBLIC METHODS */
|
|
49
|
-
constructor(opts = {}) {
|
|
73
|
+
constructor(opts: DeserializeOption = {} as any) {
|
|
50
74
|
super();
|
|
51
75
|
this.deserialize(opts);
|
|
52
76
|
}
|
|
@@ -54,81 +78,93 @@ class HdKeyring extends SimpleKeyring {
|
|
|
54
78
|
serialize() {
|
|
55
79
|
return Promise.resolve({
|
|
56
80
|
mnemonic: this.mnemonic,
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated
|
|
83
|
+
*/
|
|
57
84
|
activeIndexes: this.activeIndexes,
|
|
58
85
|
hdPath: this.hdPath,
|
|
59
86
|
byImport: this.byImport,
|
|
60
87
|
index: this.index,
|
|
88
|
+
needPassphrase: this.needPassphrase,
|
|
89
|
+
accounts: this.accounts,
|
|
90
|
+
accountDetails: this.accountDetails,
|
|
61
91
|
publicKey: this.publicKey,
|
|
62
92
|
});
|
|
63
93
|
}
|
|
64
94
|
|
|
65
|
-
deserialize(opts: DeserializeOption = {}) {
|
|
95
|
+
deserialize(opts: DeserializeOption = {} as any) {
|
|
66
96
|
this.wallets = [];
|
|
67
97
|
this.mnemonic = null;
|
|
68
|
-
this.
|
|
69
|
-
this.hdPath = opts.hdPath || hdPathString;
|
|
98
|
+
this.hdPath = opts.hdPath || HD_PATH_BASE[HDPathType.BIP44];
|
|
70
99
|
this.byImport = !!opts.byImport;
|
|
71
100
|
this.index = opts.index || 0;
|
|
101
|
+
this.needPassphrase = opts.needPassphrase || !!opts.passphrase;
|
|
102
|
+
this.accounts = opts.accounts || [];
|
|
103
|
+
this.accountDetails = opts.accountDetails || {};
|
|
72
104
|
this.publicKey = opts.publicKey || '';
|
|
73
105
|
|
|
74
106
|
if (opts.mnemonic) {
|
|
75
|
-
this.
|
|
107
|
+
this.mnemonic = opts.mnemonic;
|
|
108
|
+
this.setPassphrase(opts.passphrase || '');
|
|
76
109
|
}
|
|
77
110
|
|
|
78
|
-
|
|
111
|
+
// activeIndexes is deprecated, if accounts is not empty, use accounts
|
|
112
|
+
if (!this.accounts.length && opts.activeIndexes) {
|
|
79
113
|
return this.activeAccounts(opts.activeIndexes);
|
|
80
114
|
}
|
|
81
115
|
|
|
82
116
|
return Promise.resolve([]);
|
|
83
117
|
}
|
|
84
118
|
|
|
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) {
|
|
119
|
+
initFromMnemonic(mnemonic, passphrase?: string) {
|
|
95
120
|
this.mnemonic = mnemonic;
|
|
96
|
-
|
|
97
|
-
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
121
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
|
98
122
|
this.hdWallet = HDKey.fromMasterSeed(seed);
|
|
99
|
-
this.root = this.hdWallet!.derive(this.hdPath);
|
|
100
|
-
|
|
101
123
|
if (!this.publicKey) {
|
|
102
|
-
this.
|
|
124
|
+
this.publicKey = this.calcBasePublicKey(this.hdWallet!);
|
|
103
125
|
}
|
|
104
126
|
}
|
|
105
127
|
|
|
128
|
+
private calcBasePublicKey(hdKey: HDKey) {
|
|
129
|
+
return bytesToHex(
|
|
130
|
+
hdKey.derive(this.getHDPathBase(HDPathType.BIP44)).publicKey!,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
addAccounts(numberOfAccounts = 1) {
|
|
107
|
-
if (!this.
|
|
135
|
+
if (!this.hdWallet) {
|
|
108
136
|
this.initFromMnemonic(bip39.generateMnemonic(wordlist));
|
|
109
137
|
}
|
|
110
138
|
|
|
111
139
|
let count = numberOfAccounts;
|
|
112
140
|
let currentIdx = 0;
|
|
113
|
-
const
|
|
114
|
-
|
|
141
|
+
const addresses: string[] = [];
|
|
142
|
+
|
|
115
143
|
while (count) {
|
|
116
|
-
const [, wallet] = this._addressFromIndex(currentIdx);
|
|
117
|
-
if (
|
|
144
|
+
const [address, wallet] = this._addressFromIndex(currentIdx);
|
|
145
|
+
if (
|
|
146
|
+
this.wallets.find(
|
|
147
|
+
(w) => bytesToHex(w.publicKey) === bytesToHex(wallet.publicKey),
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
118
150
|
currentIdx++;
|
|
119
151
|
} else {
|
|
120
152
|
this.wallets.push(wallet);
|
|
121
|
-
|
|
122
|
-
this.activeIndexes.push(currentIdx);
|
|
153
|
+
addresses.push(address);
|
|
154
|
+
// this.activeIndexes.push(currentIdx);
|
|
155
|
+
this.setAccountDetail(address, {
|
|
156
|
+
hdPath: this.hdPath,
|
|
157
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
158
|
+
index: currentIdx,
|
|
159
|
+
});
|
|
123
160
|
count--;
|
|
124
161
|
}
|
|
162
|
+
if (!this.accounts.includes(address)) {
|
|
163
|
+
this.accounts.push(address);
|
|
164
|
+
}
|
|
125
165
|
}
|
|
126
166
|
|
|
127
|
-
|
|
128
|
-
return sigUtil.normalize(this._addressfromPublicKey(w.publicKey));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return Promise.resolve(hexWallets);
|
|
167
|
+
return Promise.resolve(addresses);
|
|
132
168
|
}
|
|
133
169
|
|
|
134
170
|
activeAccounts(indexes: number[]) {
|
|
@@ -139,6 +175,16 @@ class HdKeyring extends SimpleKeyring {
|
|
|
139
175
|
this.activeIndexes.push(index);
|
|
140
176
|
|
|
141
177
|
accounts.push(address);
|
|
178
|
+
// hdPath is BIP44
|
|
179
|
+
this.setAccountDetail(address, {
|
|
180
|
+
hdPath: this.hdPath,
|
|
181
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
182
|
+
index: index,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!this.accounts.includes(address)) {
|
|
186
|
+
this.accounts.push(address);
|
|
187
|
+
}
|
|
142
188
|
}
|
|
143
189
|
|
|
144
190
|
return accounts;
|
|
@@ -171,9 +217,16 @@ class HdKeyring extends SimpleKeyring {
|
|
|
171
217
|
}
|
|
172
218
|
|
|
173
219
|
removeAccount(address) {
|
|
174
|
-
|
|
175
|
-
const index = this.getIndexByAddress(address);
|
|
220
|
+
const index = this.getInfoByAddress(address)?.index;
|
|
176
221
|
this.activeIndexes = this.activeIndexes.filter((i) => i !== index);
|
|
222
|
+
delete this.accountDetails[address];
|
|
223
|
+
this.accounts = this.accounts.filter((acc) => acc !== address);
|
|
224
|
+
this.wallets = this.wallets.filter(
|
|
225
|
+
({ publicKey }) =>
|
|
226
|
+
sigUtil
|
|
227
|
+
.normalize(this._addressFromPublicKey(publicKey))
|
|
228
|
+
.toLowerCase() !== address.toLowerCase(),
|
|
229
|
+
);
|
|
177
230
|
}
|
|
178
231
|
|
|
179
232
|
async __getPage(increment: number): Promise<
|
|
@@ -205,43 +258,123 @@ class HdKeyring extends SimpleKeyring {
|
|
|
205
258
|
}
|
|
206
259
|
|
|
207
260
|
getAccounts() {
|
|
261
|
+
if (this.accounts?.length) {
|
|
262
|
+
return Promise.resolve(this.accounts);
|
|
263
|
+
}
|
|
264
|
+
|
|
208
265
|
return Promise.resolve(
|
|
209
266
|
this.wallets.map((w) => {
|
|
210
|
-
return sigUtil.normalize(this.
|
|
267
|
+
return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
|
|
211
268
|
}),
|
|
212
269
|
);
|
|
213
270
|
}
|
|
214
271
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
272
|
+
getInfoByAddress(address: string): AccountDetail | null {
|
|
273
|
+
const detail = this.accountDetails[address];
|
|
274
|
+
if (detail) {
|
|
275
|
+
return detail;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const key in this.wallets) {
|
|
279
|
+
const wallet = this.wallets[key];
|
|
280
|
+
if (
|
|
281
|
+
sigUtil.normalize(this._addressFromPublicKey(wallet.publicKey)) ===
|
|
282
|
+
address.toLowerCase()
|
|
283
|
+
) {
|
|
284
|
+
return {
|
|
285
|
+
index: Number(key),
|
|
286
|
+
hdPathType: HD_PATH_TYPE[this.hdPath],
|
|
287
|
+
hdPath: this.hdPath,
|
|
288
|
+
};
|
|
219
289
|
}
|
|
220
290
|
}
|
|
221
291
|
return null;
|
|
222
292
|
}
|
|
223
293
|
|
|
224
|
-
/* PRIVATE METHODS */
|
|
225
|
-
|
|
226
294
|
_addressFromIndex(i: number): [string, Wallet] {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
);
|
|
236
|
-
this._index2wallet[i] = [address, wallet];
|
|
237
|
-
}
|
|
295
|
+
const child = this.getChildForIndex(i);
|
|
296
|
+
const wallet = {
|
|
297
|
+
publicKey: privateToPublic(child.privateKey!),
|
|
298
|
+
privateKey: child.privateKey!,
|
|
299
|
+
};
|
|
300
|
+
const address = sigUtil.normalize(
|
|
301
|
+
this._addressFromPublicKey(wallet.publicKey),
|
|
302
|
+
);
|
|
238
303
|
|
|
239
|
-
return
|
|
304
|
+
return [address, wallet];
|
|
240
305
|
}
|
|
241
306
|
|
|
242
|
-
|
|
307
|
+
private _addressFromPublicKey(publicKey: Uint8Array) {
|
|
243
308
|
return bytesToHex(publicToAddress(publicKey, true)).toLowerCase();
|
|
244
309
|
}
|
|
310
|
+
|
|
311
|
+
generateMnemonic() {
|
|
312
|
+
return bip39.generateMnemonic(wordlist);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
setHdPath(hdPath = HD_PATH_BASE[HDPathType.BIP44]) {
|
|
316
|
+
this.hdPath = hdPath;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private getChildForIndex(index: number) {
|
|
320
|
+
return this.hdWallet!.derive(this.getPathForIndex(index));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private isLedgerLiveHdPath() {
|
|
324
|
+
return this.hdPath === HD_PATH_BASE[HDPathType.LedgerLive];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private getPathForIndex(index) {
|
|
328
|
+
return this.isLedgerLiveHdPath()
|
|
329
|
+
? `m/44'/60'/${index}'/0/0`
|
|
330
|
+
: `${this.hdPath}/${index}`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
setPassphrase(passphrase: string) {
|
|
334
|
+
this.initFromMnemonic(this.mnemonic, passphrase);
|
|
335
|
+
|
|
336
|
+
for (const acc of this.accounts) {
|
|
337
|
+
const detail = this.getAccountDetail(acc);
|
|
338
|
+
if (detail) {
|
|
339
|
+
this.setHdPath(detail.hdPath);
|
|
340
|
+
const [address, wallet] = this._addressFromIndex(detail.index);
|
|
341
|
+
if (address.toLowerCase() === acc.toLowerCase()) {
|
|
342
|
+
this.wallets.push(wallet);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* if passphrase is correct, the publicKey will be the same as the stored one
|
|
350
|
+
*/
|
|
351
|
+
checkPassphrase(passphrase: string) {
|
|
352
|
+
const seed = bip39.mnemonicToSeedSync(this.mnemonic!, passphrase);
|
|
353
|
+
const hdWallet = HDKey.fromMasterSeed(seed);
|
|
354
|
+
const publicKey = this.calcBasePublicKey(hdWallet);
|
|
355
|
+
|
|
356
|
+
return this.publicKey === publicKey;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
setAccountDetail = (address: string, accountDetail: AccountDetail) => {
|
|
360
|
+
this.accountDetails = {
|
|
361
|
+
...this.accountDetails,
|
|
362
|
+
[address.toLowerCase()]: accountDetail,
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
getAccountDetail = (address: string) => {
|
|
367
|
+
return this.accountDetails[address.toLowerCase()];
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
private getHDPathBase(hdPathType: HDPathType) {
|
|
371
|
+
return HD_PATH_BASE[hdPathType];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async setHDPathType(hdPathType: HDPathType) {
|
|
375
|
+
const hdPath = this.getHDPathBase(hdPathType);
|
|
376
|
+
this.setHdPath(hdPath);
|
|
377
|
+
}
|
|
245
378
|
}
|
|
246
379
|
|
|
247
380
|
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.
|
|
3
|
+
"version": "4.1.0-beta.0",
|
|
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
|
});
|