@metamask/eth-hd-keyring 7.0.0 → 7.0.2
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/CHANGELOG.md +73 -34
- package/index.js +312 -0
- package/jest.config.js +27 -21
- package/package.json +17 -34
- package/{src/HDKeyring.test.ts → test/index.js} +111 -145
- package/.eslintrc.js +0 -36
- package/.nvmrc +0 -1
- package/.prettierrc.js +0 -8
- package/.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs +0 -9
- package/src/HDKeyring.ts +0 -460
- package/src/errors.ts +0 -16
- package/src/index.ts +0 -1
- package/tsconfig.build.json +0 -13
- package/tsconfig.json +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
|
|
2
3
|
All notable changes to this project will be documented in this file.
|
|
3
4
|
|
|
4
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
@@ -6,86 +7,124 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
7
|
|
|
7
8
|
## [Unreleased]
|
|
8
9
|
|
|
9
|
-
## [7.0.
|
|
10
|
+
## [7.0.2]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Convert to monorepo
|
|
15
|
+
- Package name does not change (`@metamask/eth-hd-keyring`) and sources have been moved to: `packages/keyring-eth-hd`.
|
|
16
|
+
- You can find all the changes [here](https://github.com/MetaMask/accounts/compare/6da58b4...38794aa).
|
|
17
|
+
|
|
18
|
+
## [7.0.1]
|
|
19
|
+
|
|
10
20
|
### Changed
|
|
21
|
+
|
|
11
22
|
- **BREAKING:** Update minimum Node.js version from v14 to v16 ([#98](https://github.com/MetaMask/eth-hd-keyring/pull/98))
|
|
12
23
|
- Bump dependencies ([#99](https://github.com/MetaMask/eth-hd-keyring/pull/99))
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
- **BREAKING:** `@metamask/eth-sig-util` from `^6.0.0` to `^7.0.0`
|
|
25
|
+
- **BREAKING:** `@metamask/utils` from `^5.0.2` to `^8.1.0`
|
|
26
|
+
- `@ethereumjs/tx` from `^4.1.1` to `^4.2.0`
|
|
27
|
+
- `@ethereumjs/util` from `8.0.5` to `^8.1.0`
|
|
28
|
+
- `ethereum-cryptography` from `^1.2.0` to `^2.1.2`
|
|
18
29
|
|
|
19
|
-
## [
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
## [7.0.0] [RETRACTED]
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- This version was retracted due to a bug causing code to be missing from published package.
|
|
35
|
+
|
|
36
|
+
## [6.0.2]
|
|
22
37
|
|
|
23
38
|
### Fixed
|
|
39
|
+
|
|
24
40
|
- Bump dependencies ([#94](https://github.com/MetaMask/eth-hd-keyring/pull/94))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
- `@ethereumjs/util` from `^8.0.2` to `^8.1.0`
|
|
42
|
+
- `@metamask/eth-sig-util` from `^5.0.2` to `^6.0.0`
|
|
43
|
+
- `@metamask/scure-bip39` from `^2.0.3` to `^2.1.0`
|
|
44
|
+
- `@metamask/utils` from `^5.0.0` to `^5.0.2`
|
|
45
|
+
- `ethereum-cryptography` from `^1.1.2` to `^1.2.0`
|
|
46
|
+
|
|
47
|
+
## [6.0.1] [RETRACTED]
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- This version was retracted due to a bug causing code to be missing from published package.
|
|
30
52
|
|
|
31
53
|
## [6.0.0]
|
|
54
|
+
|
|
32
55
|
### Changed
|
|
56
|
+
|
|
33
57
|
- Revert mnemonic serialization format from `Record<number, number>` (i.e. a stringified `Uint8Array`) which was introduced in v5.0.0 back to an untyped array of utf8 encoded bytes, which was the format prior to v5.0.0 ([#81](https://github.com/MetaMask/eth-hd-keyring/pull/81))
|
|
34
58
|
|
|
35
59
|
## [5.0.1] [DEPRECATED]
|
|
60
|
+
|
|
36
61
|
### Removed
|
|
62
|
+
|
|
37
63
|
- Remove prepack script and references in order to fix publish release flow ([#77](https://github.com/MetaMask/eth-hd-keyring/pull/77))
|
|
38
64
|
|
|
39
65
|
## [5.0.0] [DEPRECATED]
|
|
66
|
+
|
|
40
67
|
### Changed
|
|
68
|
+
|
|
41
69
|
- **BREAKING**: Update minimum Node.js version from v12 to v14 ([#67](https://github.com/MetaMask/eth-hd-keyring/pull/67))
|
|
42
70
|
- **BREAKING:** Makes version-specific `signTypedData` methods private ([#71](https://github.com/MetaMask/eth-hd-keyring/pull/71))
|
|
43
|
-
|
|
71
|
+
- Consumers should use the generic `signTypedData` method and pass the version they'd like as a property in the options argument.
|
|
44
72
|
- **BREAKING:** Makes the `wallets` property private ([#71](https://github.com/MetaMask/eth-hd-keyring/pull/71))
|
|
45
|
-
|
|
73
|
+
- Consumers should not use this property as it is intended for internal use only.
|
|
46
74
|
- **BREAKING:** Makes `getPrivateKeyFor` a private method ([#71](https://github.com/MetaMask/eth-hd-keyring/pull/71))
|
|
47
|
-
|
|
75
|
+
- Consumers who wish to get the private key for a given account should use the `exportAccount` method.
|
|
48
76
|
- **BREAKING:** Bumps browser requirements to those with ES2020 support or greater ([#70](https://github.com/MetaMask/eth-hd-keyring/pull/70))
|
|
49
|
-
|
|
50
|
-
- Replaces use of `ethereumjs-wallet` implementation of hdkey with one from `ethereum-cryptography` and adapts accordingly.
|
|
77
|
+
- This change is introduced in update of `@metamask/eth-sig-util` to v5 and new direct dependency on `ethereumjs/util` v8.0.2
|
|
78
|
+
- Replaces use of `ethereumjs-wallet` implementation of hdkey with one from `ethereum-cryptography` and adapts accordingly. ([#69](https://github.com/MetaMask/eth-hd-keyring/pull/69))
|
|
51
79
|
- Replaces `@metamask/bip39` with `@metamask/scure-bip39` ([#67](https://github.com/MetaMask/eth-hd-keyring/pull/67))
|
|
52
80
|
|
|
53
81
|
### Removed
|
|
82
|
+
|
|
54
83
|
- **BREAKING:** Remove redundant `newGethSignMessage` method ([#71](https://github.com/MetaMask/eth-hd-keyring/pull/71))
|
|
55
|
-
|
|
84
|
+
- Consumers can use `signPersonalMessage` method as a replacement for newGethSignMessage.
|
|
56
85
|
- **BREAKING:** `HDKeyring` no longer extends `EventEmitter`, so no `EventEmitter` methods are available on this class ([#70](https://github.com/MetaMask/eth-hd-keyring/pull/70))
|
|
57
86
|
- Removes `ethereumjs-util` dependency. ([#67](https://github.com/MetaMask/eth-hd-keyring/pull/67))
|
|
58
87
|
|
|
59
88
|
## [4.0.2]
|
|
89
|
+
|
|
60
90
|
### Added
|
|
91
|
+
|
|
61
92
|
- Add parameter validation for constructor / `deserialize` method ([#65](https://github.com/MetaMask/eth-hd-keyring/pull/65))
|
|
62
|
-
|
|
93
|
+
- As of v4.0.0, the `deserialize` method (which is also called by the constructor) can no longer generate accounts with the `numberOfAccounts` option without a `mnemonic`. Prior to v4.0.0, a mnemonic was generated automatically if it was missing, but we now want to ensure a mnemonic is never implicitly generated without the caller knowing.
|
|
63
94
|
|
|
64
95
|
## [4.0.1]
|
|
96
|
+
|
|
65
97
|
### Added
|
|
98
|
+
|
|
66
99
|
- Add tests to get coverage to 100% ([#62](https://github.com/MetaMask/eth-hd-keyring/pull/62))
|
|
67
100
|
|
|
68
101
|
### Fixed
|
|
102
|
+
|
|
69
103
|
- Fix bug where an unexpected error would occur if the mnemonic passed to `_initFromMnemonic` was a buffer array ([#62](https://github.com/MetaMask/eth-hd-keyring/pull/62))
|
|
70
104
|
|
|
71
105
|
## [4.0.0]
|
|
106
|
+
|
|
72
107
|
### Changed
|
|
108
|
+
|
|
73
109
|
- **BREAKING**: Do not allow re-initialization of keyring instance ([#55](https://github.com/MetaMask/eth-hd-keyring/pull/55))
|
|
74
|
-
|
|
110
|
+
- Consumers are now required to call generateRandomMnemonic() after initialization for creating new SRPs.
|
|
75
111
|
- **BREAKING**: Update minimum Node.js version from v10 to v12 ([#45](https://github.com/MetaMask/eth-hd-keyring/pull/45))
|
|
76
112
|
- Add `@lavamoat/allow-scripts` ([#47](https://github.com/MetaMask/eth-hd-keyring/pull/47))
|
|
77
|
-
|
|
113
|
+
- We now have an allowlist for all post-install scripts. The standard setup script has been added, along with new contributor documentation in the README to explain this script.
|
|
78
114
|
- Obfuscate serialized mnemonic ([#59](https://github.com/MetaMask/eth-hd-keyring/pull/59))
|
|
79
|
-
|
|
80
|
-
|
|
115
|
+
- Class variable `mnemonic` on `HdKeyring` can now be either type `Buffer` or type `string`.
|
|
116
|
+
- Deserialize method (and `HdKeyring` constructor by extension) can no longer be passed an options object containing a value for `numberOfAccounts` if it is not also containing a value for `mnemonic`.
|
|
81
117
|
- Package name changed from `eth-hd-keyring` to `@metamask/eth-hd-keyring`.
|
|
82
118
|
|
|
83
|
-
[Unreleased]: https://github.com/MetaMask/eth-hd-keyring
|
|
84
|
-
[7.0.
|
|
85
|
-
[
|
|
86
|
-
[
|
|
87
|
-
[
|
|
88
|
-
[
|
|
89
|
-
[
|
|
90
|
-
[
|
|
91
|
-
[
|
|
119
|
+
[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@7.0.2...HEAD
|
|
120
|
+
[7.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@7.0.1...@metamask/eth-hd-keyring@7.0.2
|
|
121
|
+
[7.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@7.0.0...@metamask/eth-hd-keyring@7.0.1
|
|
122
|
+
[7.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@6.0.2...@metamask/eth-hd-keyring@7.0.0
|
|
123
|
+
[6.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@6.0.1...@metamask/eth-hd-keyring@6.0.2
|
|
124
|
+
[6.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@6.0.0...@metamask/eth-hd-keyring@6.0.1
|
|
125
|
+
[6.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@5.0.1...@metamask/eth-hd-keyring@6.0.0
|
|
126
|
+
[5.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@5.0.0...@metamask/eth-hd-keyring@5.0.1
|
|
127
|
+
[5.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@4.0.2...@metamask/eth-hd-keyring@5.0.0
|
|
128
|
+
[4.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@4.0.1...@metamask/eth-hd-keyring@4.0.2
|
|
129
|
+
[4.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-hd-keyring@4.0.0...@metamask/eth-hd-keyring@4.0.1
|
|
130
|
+
[4.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/eth-hd-keyring@4.0.0
|
package/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
const { HDKey } = require('ethereum-cryptography/hdkey');
|
|
2
|
+
const { keccak256 } = require('ethereum-cryptography/keccak');
|
|
3
|
+
const { bytesToHex } = require('ethereum-cryptography/utils');
|
|
4
|
+
const {
|
|
5
|
+
privateToPublic,
|
|
6
|
+
publicToAddress,
|
|
7
|
+
ecsign,
|
|
8
|
+
arrToBufArr,
|
|
9
|
+
bufferToHex,
|
|
10
|
+
} = require('@ethereumjs/util');
|
|
11
|
+
const bip39 = require('@metamask/scure-bip39');
|
|
12
|
+
const { wordlist } = require('@metamask/scure-bip39/dist/wordlists/english');
|
|
13
|
+
const {
|
|
14
|
+
concatSig,
|
|
15
|
+
decrypt,
|
|
16
|
+
getEncryptionPublicKey,
|
|
17
|
+
normalize,
|
|
18
|
+
personalSign,
|
|
19
|
+
signTypedData,
|
|
20
|
+
SignTypedDataVersion,
|
|
21
|
+
} = require('@metamask/eth-sig-util');
|
|
22
|
+
const { assertIsHexString, remove0x } = require('@metamask/utils');
|
|
23
|
+
|
|
24
|
+
// Options:
|
|
25
|
+
const hdPathString = `m/44'/60'/0'/0`;
|
|
26
|
+
const type = 'HD Key Tree';
|
|
27
|
+
|
|
28
|
+
class HdKeyring {
|
|
29
|
+
/* PUBLIC METHODS */
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
this.type = type;
|
|
32
|
+
this._wallets = [];
|
|
33
|
+
this.deserialize(opts);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
generateRandomMnemonic() {
|
|
37
|
+
this._initFromMnemonic(bip39.generateMnemonic(wordlist));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_uint8ArrayToString(mnemonic) {
|
|
41
|
+
const recoveredIndices = Array.from(
|
|
42
|
+
new Uint16Array(new Uint8Array(mnemonic).buffer),
|
|
43
|
+
);
|
|
44
|
+
return recoveredIndices.map((i) => wordlist[i]).join(' ');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_stringToUint8Array(mnemonic) {
|
|
48
|
+
const indices = mnemonic.split(' ').map((word) => wordlist.indexOf(word));
|
|
49
|
+
return new Uint8Array(new Uint16Array(indices).buffer);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_mnemonicToUint8Array(mnemonic) {
|
|
53
|
+
let mnemonicData = mnemonic;
|
|
54
|
+
// when encrypted/decrypted, buffers get cast into js object with a property type set to buffer
|
|
55
|
+
if (mnemonic && mnemonic.type && mnemonic.type === 'Buffer') {
|
|
56
|
+
mnemonicData = mnemonic.data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
// this block is for backwards compatibility with vaults that were previously stored as buffers, number arrays or plain text strings
|
|
61
|
+
typeof mnemonicData === 'string' ||
|
|
62
|
+
Buffer.isBuffer(mnemonicData) ||
|
|
63
|
+
Array.isArray(mnemonicData)
|
|
64
|
+
) {
|
|
65
|
+
let mnemonicAsString = mnemonicData;
|
|
66
|
+
if (Array.isArray(mnemonicData)) {
|
|
67
|
+
mnemonicAsString = Buffer.from(mnemonicData).toString();
|
|
68
|
+
} else if (Buffer.isBuffer(mnemonicData)) {
|
|
69
|
+
mnemonicAsString = mnemonicData.toString();
|
|
70
|
+
}
|
|
71
|
+
return this._stringToUint8Array(mnemonicAsString);
|
|
72
|
+
} else if (
|
|
73
|
+
mnemonicData instanceof Object &&
|
|
74
|
+
!(mnemonicData instanceof Uint8Array)
|
|
75
|
+
) {
|
|
76
|
+
// when encrypted/decrypted the Uint8Array becomes a js object we need to cast back to a Uint8Array
|
|
77
|
+
return Uint8Array.from(Object.values(mnemonicData));
|
|
78
|
+
}
|
|
79
|
+
return mnemonicData;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
serialize() {
|
|
83
|
+
const mnemonicAsString = this._uint8ArrayToString(this.mnemonic);
|
|
84
|
+
const uint8ArrayMnemonic = new TextEncoder('utf-8').encode(
|
|
85
|
+
mnemonicAsString,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return Promise.resolve({
|
|
89
|
+
mnemonic: Array.from(uint8ArrayMnemonic),
|
|
90
|
+
numberOfAccounts: this._wallets.length,
|
|
91
|
+
hdPath: this.hdPath,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
deserialize(opts = {}) {
|
|
96
|
+
if (opts.numberOfAccounts && !opts.mnemonic) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Eth-Hd-Keyring: Deserialize method cannot be called with an opts value for numberOfAccounts and no menmonic',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.root) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'Eth-Hd-Keyring: Secret recovery phrase already provided',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
this.opts = opts;
|
|
108
|
+
this._wallets = [];
|
|
109
|
+
this.mnemonic = null;
|
|
110
|
+
this.root = null;
|
|
111
|
+
this.hdPath = opts.hdPath || hdPathString;
|
|
112
|
+
|
|
113
|
+
if (opts.mnemonic) {
|
|
114
|
+
this._initFromMnemonic(opts.mnemonic);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (opts.numberOfAccounts) {
|
|
118
|
+
return this.addAccounts(opts.numberOfAccounts);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return Promise.resolve([]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
addAccounts(numberOfAccounts = 1) {
|
|
125
|
+
if (!this.root) {
|
|
126
|
+
throw new Error('Eth-Hd-Keyring: No secret recovery phrase provided');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const oldLen = this._wallets.length;
|
|
130
|
+
const newWallets = [];
|
|
131
|
+
for (let i = oldLen; i < numberOfAccounts + oldLen; i++) {
|
|
132
|
+
const wallet = this.root.deriveChild(i);
|
|
133
|
+
newWallets.push(wallet);
|
|
134
|
+
this._wallets.push(wallet);
|
|
135
|
+
}
|
|
136
|
+
const hexWallets = newWallets.map((w) => {
|
|
137
|
+
return this._addressfromPublicKey(w.publicKey);
|
|
138
|
+
});
|
|
139
|
+
return Promise.resolve(hexWallets);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getAccounts() {
|
|
143
|
+
return this._wallets.map((w) => this._addressfromPublicKey(w.publicKey));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* BASE KEYRING METHODS */
|
|
147
|
+
|
|
148
|
+
// returns an address specific to an app
|
|
149
|
+
async getAppKeyAddress(address, origin) {
|
|
150
|
+
if (!origin || typeof origin !== 'string') {
|
|
151
|
+
throw new Error(`'origin' must be a non-empty string`);
|
|
152
|
+
}
|
|
153
|
+
const wallet = this._getWalletForAccount(address, {
|
|
154
|
+
withAppKeyOrigin: origin,
|
|
155
|
+
});
|
|
156
|
+
const appKeyAddress = normalize(
|
|
157
|
+
publicToAddress(wallet.publicKey).toString('hex'),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return appKeyAddress;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// exportAccount should return a hex-encoded private key:
|
|
164
|
+
async exportAccount(address, opts = {}) {
|
|
165
|
+
const wallet = this._getWalletForAccount(address, opts);
|
|
166
|
+
return bytesToHex(wallet.privateKey);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// tx is an instance of the ethereumjs-transaction class.
|
|
170
|
+
async signTransaction(address, tx, opts = {}) {
|
|
171
|
+
const privKey = this._getPrivateKeyFor(address, opts);
|
|
172
|
+
const signedTx = tx.sign(privKey);
|
|
173
|
+
// Newer versions of Ethereumjs-tx are immutable and return a new tx object
|
|
174
|
+
return signedTx === undefined ? tx : signedTx;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// For eth_sign, we need to sign arbitrary data:
|
|
178
|
+
async signMessage(address, data, opts = {}) {
|
|
179
|
+
assertIsHexString(data);
|
|
180
|
+
const message = remove0x(data);
|
|
181
|
+
const privKey = this._getPrivateKeyFor(address, opts);
|
|
182
|
+
const msgSig = ecsign(Buffer.from(message, 'hex'), privKey);
|
|
183
|
+
const rawMsgSig = concatSig(msgSig.v, msgSig.r, msgSig.s);
|
|
184
|
+
return rawMsgSig;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// For personal_sign, we need to prefix the message:
|
|
188
|
+
async signPersonalMessage(address, msgHex, opts = {}) {
|
|
189
|
+
const privKey = this._getPrivateKeyFor(address, opts);
|
|
190
|
+
const privateKey = Buffer.from(privKey, 'hex');
|
|
191
|
+
const sig = personalSign({ privateKey, data: msgHex });
|
|
192
|
+
return sig;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// For eth_decryptMessage:
|
|
196
|
+
async decryptMessage(withAccount, encryptedData) {
|
|
197
|
+
const wallet = this._getWalletForAccount(withAccount);
|
|
198
|
+
const { privateKey: privateKeyAsUint8Array } = wallet;
|
|
199
|
+
const privateKeyAsHex = Buffer.from(privateKeyAsUint8Array).toString('hex');
|
|
200
|
+
const sig = decrypt({ privateKey: privateKeyAsHex, encryptedData });
|
|
201
|
+
return sig;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// personal_signTypedData, signs data along with the schema
|
|
205
|
+
async signTypedData(
|
|
206
|
+
withAccount,
|
|
207
|
+
typedData,
|
|
208
|
+
opts = { version: SignTypedDataVersion.V1 },
|
|
209
|
+
) {
|
|
210
|
+
// Treat invalid versions as "V1"
|
|
211
|
+
const version = Object.keys(SignTypedDataVersion).includes(opts.version)
|
|
212
|
+
? opts.version
|
|
213
|
+
: SignTypedDataVersion.V1;
|
|
214
|
+
|
|
215
|
+
const privateKey = this._getPrivateKeyFor(withAccount, opts);
|
|
216
|
+
return signTypedData({ privateKey, data: typedData, version });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
removeAccount(account) {
|
|
220
|
+
const address = normalize(account);
|
|
221
|
+
if (
|
|
222
|
+
!this._wallets
|
|
223
|
+
.map(({ publicKey }) => this._addressfromPublicKey(publicKey))
|
|
224
|
+
.includes(address)
|
|
225
|
+
) {
|
|
226
|
+
throw new Error(`Address ${address} not found in this keyring`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this._wallets = this._wallets.filter(
|
|
230
|
+
({ publicKey }) => this._addressfromPublicKey(publicKey) !== address,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// get public key for nacl
|
|
235
|
+
async getEncryptionPublicKey(withAccount, opts = {}) {
|
|
236
|
+
const privKey = this._getPrivateKeyFor(withAccount, opts);
|
|
237
|
+
const publicKey = getEncryptionPublicKey(privKey);
|
|
238
|
+
return publicKey;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_getPrivateKeyFor(address, opts = {}) {
|
|
242
|
+
if (!address) {
|
|
243
|
+
throw new Error('Must specify address.');
|
|
244
|
+
}
|
|
245
|
+
const wallet = this._getWalletForAccount(address, opts);
|
|
246
|
+
return wallet.privateKey;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
_getWalletForAccount(account, opts = {}) {
|
|
250
|
+
const address = normalize(account);
|
|
251
|
+
let wallet = this._wallets.find(({ publicKey }) => {
|
|
252
|
+
return this._addressfromPublicKey(publicKey) === address;
|
|
253
|
+
});
|
|
254
|
+
if (!wallet) {
|
|
255
|
+
throw new Error('HD Keyring - Unable to find matching address.');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (opts.withAppKeyOrigin) {
|
|
259
|
+
const { privateKey } = wallet;
|
|
260
|
+
const appKeyOriginBuffer = Buffer.from(opts.withAppKeyOrigin, 'utf8');
|
|
261
|
+
const appKeyBuffer = Buffer.concat([privateKey, appKeyOriginBuffer]);
|
|
262
|
+
const appKeyPrivateKey = arrToBufArr(keccak256(appKeyBuffer, 256));
|
|
263
|
+
const appKeyPublicKey = privateToPublic(appKeyPrivateKey);
|
|
264
|
+
wallet = { privateKey: appKeyPrivateKey, publicKey: appKeyPublicKey };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return wallet;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* PRIVATE / UTILITY METHODS */
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Sets appropriate properties for the keyring based on the given
|
|
274
|
+
* BIP39-compliant mnemonic.
|
|
275
|
+
*
|
|
276
|
+
* @param {string|Array<number>|Buffer} mnemonic - A seed phrase represented
|
|
277
|
+
* as a string, an array of UTF-8 bytes, or a Buffer. Mnemonic input
|
|
278
|
+
* passed as type buffer or array of UTF-8 bytes must be NFKD normalized.
|
|
279
|
+
*/
|
|
280
|
+
_initFromMnemonic(mnemonic) {
|
|
281
|
+
if (this.root) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
'Eth-Hd-Keyring: Secret recovery phrase already provided',
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.mnemonic = this._mnemonicToUint8Array(mnemonic);
|
|
288
|
+
|
|
289
|
+
// validate before initializing
|
|
290
|
+
const isValid = bip39.validateMnemonic(this.mnemonic, wordlist);
|
|
291
|
+
if (!isValid) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
'Eth-Hd-Keyring: Invalid secret recovery phrase provided',
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// eslint-disable-next-line n/no-sync
|
|
298
|
+
const seed = bip39.mnemonicToSeedSync(this.mnemonic, wordlist);
|
|
299
|
+
this.hdWallet = HDKey.fromMasterSeed(seed);
|
|
300
|
+
this.root = this.hdWallet.derive(this.hdPath);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// small helper function to convert publicKey in Uint8Array form to a publicAddress as a hex
|
|
304
|
+
_addressfromPublicKey(publicKey) {
|
|
305
|
+
return bufferToHex(
|
|
306
|
+
publicToAddress(Buffer.from(publicKey), true),
|
|
307
|
+
).toLowerCase();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
HdKeyring.type = type;
|
|
312
|
+
module.exports = HdKeyring;
|
package/jest.config.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/*
|
|
2
|
+
* For a detailed explanation regarding each configuration property and type check, visit:
|
|
3
|
+
* https://jestjs.io/docs/configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const merge = require('deepmerge');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const baseConfig = require('../../jest.config.packages');
|
|
10
|
+
|
|
11
|
+
const displayName = path.basename(__dirname);
|
|
12
|
+
|
|
13
|
+
module.exports = merge(baseConfig, {
|
|
14
|
+
// The display name when running multiple projects
|
|
15
|
+
displayName,
|
|
16
|
+
|
|
17
|
+
// An array of regexp pattern strings used to skip coverage collection
|
|
18
|
+
coveragePathIgnorePatterns: ['./test'],
|
|
19
|
+
|
|
20
|
+
// The glob patterns Jest uses to detect test files
|
|
21
|
+
testMatch: ['**/test/**/*.[jt]s?(x)'],
|
|
22
|
+
|
|
23
|
+
// An object that configures minimum threshold enforcement for coverage results
|
|
4
24
|
coverageThreshold: {
|
|
5
25
|
global: {
|
|
6
|
-
branches:
|
|
26
|
+
branches: 84,
|
|
7
27
|
functions: 100,
|
|
8
|
-
lines:
|
|
9
|
-
statements:
|
|
28
|
+
lines: 95,
|
|
29
|
+
statements: 95,
|
|
10
30
|
},
|
|
11
31
|
},
|
|
12
|
-
|
|
13
|
-
// "resetMocks" resets all mocks, including mocked modules, to jest.fn(),
|
|
14
|
-
// between each test case.
|
|
15
|
-
resetMocks: true,
|
|
16
|
-
// "restoreMocks" restores all mocks created using jest.spyOn to their
|
|
17
|
-
// original implementations, between each test. It does not affect mocked
|
|
18
|
-
// modules.
|
|
19
|
-
restoreMocks: true,
|
|
20
|
-
testEnvironment: 'node',
|
|
21
|
-
testMatch: ['./**/*.test.ts'],
|
|
22
|
-
testTimeout: 2500,
|
|
23
|
-
transform: {
|
|
24
|
-
'^.+\\.tsx?$': 'ts-jest',
|
|
25
|
-
},
|
|
26
|
-
};
|
|
32
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask/eth-hd-keyring",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"description": "A simple standard interface for a seed phrase generated set of Ethereum accounts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ethereum",
|
|
@@ -18,53 +18,36 @@
|
|
|
18
18
|
"author": "Dan Finlay",
|
|
19
19
|
"main": "index.js",
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
21
|
+
"build": "",
|
|
22
|
+
"build:clean": "",
|
|
23
|
+
"build:force": "",
|
|
24
|
+
"changelog:update": "../../scripts/update-changelog.sh @metamask/eth-hd-keyring",
|
|
25
|
+
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-hd-keyring",
|
|
26
|
+
"publish:preview": "yarn npm publish --tag preview",
|
|
27
|
+
"test": "jest",
|
|
28
|
+
"test:clean": "jest --clearCache",
|
|
29
|
+
"test:verbose": "jest --verbose"
|
|
27
30
|
},
|
|
28
31
|
"dependencies": {
|
|
29
|
-
"@ethereumjs/tx": "^4.2.0",
|
|
30
32
|
"@ethereumjs/util": "^8.1.0",
|
|
31
|
-
"@metamask/bip39": "^4.0.0",
|
|
32
33
|
"@metamask/eth-sig-util": "^7.0.0",
|
|
33
34
|
"@metamask/scure-bip39": "^2.1.0",
|
|
34
35
|
"@metamask/utils": "^8.1.0",
|
|
36
|
+
"deepmerge": "^4.2.2",
|
|
35
37
|
"ethereum-cryptography": "^2.1.2"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
|
-
"@
|
|
40
|
+
"@ethereumjs/tx": "^4.0.1",
|
|
41
|
+
"@lavamoat/allow-scripts": "^3.0.4",
|
|
39
42
|
"@lavamoat/preinstall-always-fail": "^1.0.0",
|
|
40
|
-
"@metamask/auto-changelog": "^
|
|
41
|
-
"@metamask/
|
|
42
|
-
"@metamask/eslint-config-jest": "^9.0.0",
|
|
43
|
-
"@metamask/eslint-config-nodejs": "^8.0.0",
|
|
44
|
-
"@metamask/eslint-config-typescript": "^11.1.0",
|
|
43
|
+
"@metamask/auto-changelog": "^3.4.4",
|
|
44
|
+
"@metamask/bip39": "^4.0.0",
|
|
45
45
|
"@metamask/eth-hd-keyring": "4.0.1",
|
|
46
46
|
"@types/jest": "^29.4.0",
|
|
47
|
-
"
|
|
48
|
-
"@typescript-eslint/eslint-plugin": "latest",
|
|
49
|
-
"@typescript-eslint/parser": "^5.54.1",
|
|
50
|
-
"eslint": "^7.32.0",
|
|
51
|
-
"eslint-config-prettier": "^8.3.0",
|
|
52
|
-
"eslint-plugin-import": "^2.24.2",
|
|
53
|
-
"eslint-plugin-jest": "^24.3.6",
|
|
54
|
-
"eslint-plugin-jsdoc": "latest",
|
|
55
|
-
"eslint-plugin-node": "^11.1.0",
|
|
56
|
-
"eslint-plugin-prettier": "^3.3.1",
|
|
57
|
-
"jest": "^29.4.3",
|
|
58
|
-
"prettier": "^2.4.1",
|
|
59
|
-
"prettier-plugin-packagejson": "^2.2.12",
|
|
60
|
-
"ts-jest": "^29.0.5",
|
|
61
|
-
"ts-node": "^10.9.1",
|
|
62
|
-
"typedoc": "^0.23.26",
|
|
63
|
-
"typescript": "^4.9.5"
|
|
47
|
+
"jest": "^29.4.3"
|
|
64
48
|
},
|
|
65
|
-
"packageManager": "yarn@3.3.0",
|
|
66
49
|
"engines": {
|
|
67
|
-
"node": "^
|
|
50
|
+
"node": "^18.18 || >=20"
|
|
68
51
|
},
|
|
69
52
|
"publishConfig": {
|
|
70
53
|
"access": "public",
|