@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 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 = hdPathString;
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.root = null;
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.initFromMnemonic(opts.mnemonic);
111
+ this.mnemonic = opts.mnemonic;
112
+ this.setPassphrase(opts.passphrase || '');
82
113
  }
83
- if (opts.activeIndexes) {
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
- initPublicKey() {
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
- this._index2wallet = {};
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.initPublicKey();
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.root) {
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 newWallets = [];
137
+ const addresses = [];
112
138
  while (count) {
113
- const [, wallet] = this._addressFromIndex(currentIdx);
114
- if (this.wallets.includes(wallet)) {
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
- newWallets.push(wallet);
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
- const hexWallets = newWallets.map((w) => {
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
- super.removeAccount(address);
164
- const index = this.getIndexByAddress(address);
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._addressfromPublicKey(w.publicKey));
237
+ return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
189
238
  }));
190
239
  }
191
- getIndexByAddress(address) {
192
- for (const key in this._index2wallet) {
193
- if (this._index2wallet[key][0].toLowerCase() === address.toLowerCase()) {
194
- return Number(key);
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
- if (!this._index2wallet[i]) {
202
- const child = this.root.deriveChild(i);
203
- const wallet = {
204
- publicKey: (0, util_1.privateToPublic)(child.privateKey),
205
- privateKey: child.privateKey,
206
- };
207
- const address = sigUtil.normalize(this._addressfromPublicKey(wallet.publicKey));
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
- _addressfromPublicKey(publicKey) {
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?: string;
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 = hdPathString;
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.root = null;
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.initFromMnemonic(opts.mnemonic);
107
+ this.mnemonic = opts.mnemonic;
108
+ this.setPassphrase(opts.passphrase || '');
76
109
  }
77
110
 
78
- if (opts.activeIndexes) {
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
- private initPublicKey() {
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
- this._index2wallet = {};
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.initPublicKey();
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.root) {
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 newWallets: Wallet[] = [];
114
-
141
+ const addresses: string[] = [];
142
+
115
143
  while (count) {
116
- const [, wallet] = this._addressFromIndex(currentIdx);
117
- if (this.wallets.includes(wallet)) {
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
- newWallets.push(wallet);
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
- const hexWallets = newWallets.map((w) => {
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
- super.removeAccount(address);
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._addressfromPublicKey(w.publicKey));
267
+ return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
211
268
  }),
212
269
  );
213
270
  }
214
271
 
215
- getIndexByAddress(address: string): number | null {
216
- for (const key in this._index2wallet) {
217
- if (this._index2wallet[key][0].toLowerCase() === address.toLowerCase()) {
218
- return Number(key);
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
- if (!this._index2wallet[i]) {
228
- const child = this.root!.deriveChild(i);
229
- const wallet = {
230
- publicKey: privateToPublic(child.privateKey!),
231
- privateKey: child.privateKey!,
232
- };
233
- const address = sigUtil.normalize(
234
- this._addressfromPublicKey(wallet.publicKey),
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 this._index2wallet[i];
304
+ return [address, wallet];
240
305
  }
241
306
 
242
- _addressfromPublicKey(publicKey: Uint8Array) {
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.1",
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
  });