@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 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 = 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.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.root = null;
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.initFromMnemonic(opts.mnemonic);
113
+ this.mnemonic = opts.mnemonic;
114
+ this.setPassphrase(opts.passphrase || '');
82
115
  }
83
- if (opts.activeIndexes) {
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
- 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) {
122
+ initFromMnemonic(mnemonic, passphrase) {
96
123
  this.mnemonic = mnemonic;
97
- this._index2wallet = {};
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.initPublicKey();
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.root) {
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 newWallets = [];
139
+ const addresses = [];
112
140
  while (count) {
113
- const [, wallet] = this._addressFromIndex(currentIdx);
114
- if (this.wallets.includes(wallet)) {
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
- newWallets.push(wallet);
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
- const hexWallets = newWallets.map((w) => {
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
- super.removeAccount(address);
164
- const index = this.getIndexByAddress(address);
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._addressfromPublicKey(w.publicKey));
239
+ return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
189
240
  }));
190
241
  }
191
- getIndexByAddress(address) {
192
- for (const key in this._index2wallet) {
193
- if (this._index2wallet[key][0].toLowerCase() === address.toLowerCase()) {
194
- return Number(key);
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
- 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];
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
- _addressfromPublicKey(publicKey) {
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?: 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> = {};
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.root = null;
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.initFromMnemonic(opts.mnemonic);
109
+ this.mnemonic = opts.mnemonic;
110
+ this.setPassphrase(opts.passphrase || '');
76
111
  }
77
112
 
78
- if (opts.activeIndexes) {
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
- 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) {
121
+ initFromMnemonic(mnemonic, passphrase?: string) {
95
122
  this.mnemonic = mnemonic;
96
- this._index2wallet = {};
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.initPublicKey();
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.root) {
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 newWallets: Wallet[] = [];
114
-
143
+ const addresses: string[] = [];
144
+
115
145
  while (count) {
116
- const [, wallet] = this._addressFromIndex(currentIdx);
117
- if (this.wallets.includes(wallet)) {
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
- newWallets.push(wallet);
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
- const hexWallets = newWallets.map((w) => {
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
- super.removeAccount(address);
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._addressfromPublicKey(w.publicKey));
269
+ return sigUtil.normalize(this._addressFromPublicKey(w.publicKey));
211
270
  }),
212
271
  );
213
272
  }
214
273
 
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);
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
- 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
- }
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 this._index2wallet[i];
306
+ return [address, wallet];
240
307
  }
241
308
 
242
- _addressfromPublicKey(publicKey: Uint8Array) {
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
  });