Merge pull request #93 from the-jackalope/typescript-rewrite
Typescript rewrite
This commit is contained in:
commit
de3a92e752
|
@ -13,6 +13,7 @@ lib-cov
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
|
@ -34,9 +35,10 @@ package-lock.json
|
||||||
# Optional REPL history
|
# Optional REPL history
|
||||||
.node_repl_history
|
.node_repl_history
|
||||||
|
|
||||||
# Build folder
|
|
||||||
# Update 2018-08-07: currently build is done to / (before: dist/) due to
|
# IDE and text editor config files
|
||||||
# backwards compatibility reasons, JS files from root and root test/ folder
|
.idea
|
||||||
# are excluded
|
.vscode
|
||||||
/*.js
|
|
||||||
/test
|
# build output
|
||||||
|
dist
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
package.json
|
||||||
|
dist
|
||||||
|
.nyc_output
|
|
@ -3,8 +3,6 @@ node_js:
|
||||||
- "6"
|
- "6"
|
||||||
- "8"
|
- "8"
|
||||||
- "10"
|
- "10"
|
||||||
env:
|
|
||||||
- CXX=g++-4.8
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -1,27 +1,30 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
(modification: no type change headlines) and this project adheres to
|
(modification: no type change headlines) and this project adheres to
|
||||||
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
## [0.6.3] - 2018-12-19
|
## [0.6.3] - 2018-12-19
|
||||||
|
|
||||||
- Fixed installation errors for certain packaging tools, PR [#67](https://github.com/ethereumjs/ethereumjs-wallet/pull/67)
|
- Fixed installation errors for certain packaging tools, PR [#67](https://github.com/ethereumjs/ethereumjs-wallet/pull/67)
|
||||||
- Remove dependency on ``crypto.randomBytes`` and use ``randombytes`` package instead, PR [#63](https://github.com/ethereumjs/ethereumjs-wallet/pull/63)
|
- Remove dependency on `crypto.randomBytes` and use `randombytes` package instead, PR [#63](https://github.com/ethereumjs/ethereumjs-wallet/pull/63)
|
||||||
- Add comprehensive test coverage for ``fromV3``, PR [#62](https://github.com/ethereumjs/ethereumjs-wallet/pull/62)
|
- Add comprehensive test coverage for `fromV3`, PR [#62](https://github.com/ethereumjs/ethereumjs-wallet/pull/62)
|
||||||
- Remove excess parameter from ``decipherBuffer`` usage, PR [#77](https://github.com/ethereumjs/ethereumjs-wallet/pull/77)
|
- Remove excess parameter from `decipherBuffer` usage, PR [#77](https://github.com/ethereumjs/ethereumjs-wallet/pull/77)
|
||||||
- Update dependencies, including a fixed ``scrypt.js``, which should resolve more installation issues, PR [#78](https://github.com/ethereumjs/ethereumjs-wallet/pull/78)
|
- Update dependencies, including a fixed `scrypt.js`, which should resolve more installation issues, PR [#78](https://github.com/ethereumjs/ethereumjs-wallet/pull/78)
|
||||||
|
|
||||||
[0.6.3]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.2...v0.6.3
|
[0.6.3]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.2...v0.6.3
|
||||||
|
|
||||||
## [0.6.2] - 2018-08-08
|
## [0.6.2] - 2018-08-08
|
||||||
- [PLEASE UPDATE!] Fixes a critical import bug introduced in ``v0.6.1`` accidentally
|
|
||||||
|
- [PLEASE UPDATE!] Fixes a critical import bug introduced in `v0.6.1` accidentally
|
||||||
changing the import path for the different submodules, see PR [#65](https://github.com/ethereumjs/ethereumjs-wallet/pull/65)
|
changing the import path for the different submodules, see PR [#65](https://github.com/ethereumjs/ethereumjs-wallet/pull/65)
|
||||||
|
|
||||||
[0.6.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.1...v0.6.2
|
[0.6.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.1...v0.6.2
|
||||||
|
|
||||||
## [0.6.1] - 2018-07-28 [DEPRECATED]
|
## [0.6.1] - 2018-07-28 [DEPRECATED]
|
||||||
|
|
||||||
- Added support for vanity address generation, PR [#5](https://github.com/ethereumjs/ethereumjs-wallet/pull/5)
|
- Added support for vanity address generation, PR [#5](https://github.com/ethereumjs/ethereumjs-wallet/pull/5)
|
||||||
- Fixed typo in provider-engine, PR [#16](https://github.com/ethereumjs/ethereumjs-wallet/pull/16)
|
- Fixed typo in provider-engine, PR [#16](https://github.com/ethereumjs/ethereumjs-wallet/pull/16)
|
||||||
- Accept the true range of addresses for ICAP direct, PR [#6](https://github.com/ethereumjs/ethereumjs-wallet/pull/6)
|
- Accept the true range of addresses for ICAP direct, PR [#6](https://github.com/ethereumjs/ethereumjs-wallet/pull/6)
|
||||||
|
@ -32,24 +35,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
[0.6.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.0...v0.6.1
|
[0.6.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.0...v0.6.1
|
||||||
|
|
||||||
## [0.6.0] - 2016-04-27
|
## [0.6.0] - 2016-04-27
|
||||||
|
|
||||||
- Added provider-engine integration, PR [#7](https://github.com/ethereumjs/ethereumjs-wallet/pull/7)
|
- Added provider-engine integration, PR [#7](https://github.com/ethereumjs/ethereumjs-wallet/pull/7)
|
||||||
|
|
||||||
[0.6.0]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.2...v0.6.0
|
[0.6.0]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.2...v0.6.0
|
||||||
|
|
||||||
## [0.5.2] - 2016-04-25
|
## [0.5.2] - 2016-04-25
|
||||||
|
|
||||||
- Dependency updates
|
- Dependency updates
|
||||||
|
|
||||||
[0.5.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.1...v0.5.2
|
[0.5.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.1...v0.5.2
|
||||||
|
|
||||||
## [0.5.1] - 2016-03-26
|
## [0.5.1] - 2016-03-26
|
||||||
- Bugfix for ``EthereumHDKey.privateExtendedKey()``
|
|
||||||
|
- Bugfix for `EthereumHDKey.privateExtendedKey()`
|
||||||
- Added travis and coveralls support
|
- Added travis and coveralls support
|
||||||
- Documentation and test improvements
|
- Documentation and test improvements
|
||||||
|
|
||||||
[0.5.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.0...v0.5.1
|
[0.5.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.0...v0.5.1
|
||||||
|
|
||||||
## [0.5.0] - 2016-03-23
|
## [0.5.0] - 2016-03-23
|
||||||
- Support HD keys using ``cryptocoinjs/hdkey``
|
|
||||||
|
- Support HD keys using `cryptocoinjs/hdkey`
|
||||||
- Ensure private keys are valid according to the curve
|
- Ensure private keys are valid according to the curve
|
||||||
- Support instantation with public keys
|
- Support instantation with public keys
|
||||||
- Support importing BIP32 xpub/xpriv
|
- Support importing BIP32 xpub/xpriv
|
||||||
|
|
61
README.md
61
README.md
|
@ -8,11 +8,13 @@
|
||||||
A lightweight wallet implementation. At the moment it supports key creation and conversion between various formats.
|
A lightweight wallet implementation. At the moment it supports key creation and conversion between various formats.
|
||||||
|
|
||||||
It is complemented by the following packages:
|
It is complemented by the following packages:
|
||||||
|
|
||||||
- [ethereumjs-tx](https://github.com/ethereumjs/ethereumjs-tx) to sign transactions
|
- [ethereumjs-tx](https://github.com/ethereumjs/ethereumjs-tx) to sign transactions
|
||||||
- [ethereumjs-icap](https://github.com/ethereumjs/ethereumjs-icap) to manipulate ICAP addresses
|
- [ethereumjs-icap](https://github.com/ethereumjs/ethereumjs-icap) to manipulate ICAP addresses
|
||||||
- [store.js](https://github.com/marcuswestin/store.js) to use browser storage
|
- [store.js](https://github.com/marcuswestin/store.js) to use browser storage
|
||||||
|
|
||||||
Motivations are:
|
Motivations are:
|
||||||
|
|
||||||
- be lightweight
|
- be lightweight
|
||||||
- work in a browser
|
- work in a browser
|
||||||
- use a single, maintained version of crypto library (and that should be in line with `ethereumjs-util` and `ethereumjs-tx`)
|
- use a single, maintained version of crypto library (and that should be in line with `ethereumjs-util` and `ethereumjs-tx`)
|
||||||
|
@ -20,6 +22,7 @@ Motivations are:
|
||||||
- support BIP32 HD keys
|
- support BIP32 HD keys
|
||||||
|
|
||||||
Features not supported:
|
Features not supported:
|
||||||
|
|
||||||
- signing transactions
|
- signing transactions
|
||||||
- managing storage (neither in node.js or the browser)
|
- managing storage (neither in node.js or the browser)
|
||||||
|
|
||||||
|
@ -27,15 +30,15 @@ Features not supported:
|
||||||
|
|
||||||
Constructors:
|
Constructors:
|
||||||
|
|
||||||
* `generate([icap])` - create an instance based on a new random key (setting `icap` to true will generate an address suitable for the `ICAP Direct mode`)
|
- `generate([icap])` - create an instance based on a new random key (setting `icap` to true will generate an address suitable for the `ICAP Direct mode`)
|
||||||
* `generateVanityAddress(pattern)` - create an instance where the address is valid against the supplied pattern (**this will be very slow**)
|
- `generateVanityAddress(pattern)` - create an instance where the address is valid against the supplied pattern (**this will be very slow**)
|
||||||
* `fromPrivateKey(input)` - create an instance based on a raw private key
|
- `fromPrivateKey(input)` - create an instance based on a raw private key
|
||||||
* `fromExtendedPrivateKey(input)` - create an instance based on a BIP32 extended private key (xprv)
|
- `fromExtendedPrivateKey(input)` - create an instance based on a BIP32 extended private key (xprv)
|
||||||
* `fromPublicKey(input, [nonStrict])` - create an instance based on a public key (certain methods will not be available)
|
- `fromPublicKey(input, [nonStrict])` - create an instance based on a public key (certain methods will not be available)
|
||||||
* `fromExtendedPublicKey(input)` - create an instance based on a BIP32 extended public key (xpub)
|
- `fromExtendedPublicKey(input)` - create an instance based on a BIP32 extended public key (xpub)
|
||||||
* `fromV1(input, password)` - import a wallet (Version 1 of the Ethereum wallet format)
|
- `fromV1(input, password)` - import a wallet (Version 1 of the Ethereum wallet format)
|
||||||
* `fromV3(input, password, [nonStrict])` - import a wallet (Version 3 of the Ethereum wallet format). Set `nonStrict` true to accept files with mixed-caps.
|
- `fromV3(input, password, [nonStrict])` - import a wallet (Version 3 of the Ethereum wallet format). Set `nonStrict` true to accept files with mixed-caps.
|
||||||
* `fromEthSale(input, password)` - import an Ethereum Pre Sale wallet
|
- `fromEthSale(input, password)` - import an Ethereum Pre Sale wallet
|
||||||
|
|
||||||
For the V1, V3 and EthSale formats the input is a JSON serialized string. All these formats require a password.
|
For the V1, V3 and EthSale formats the input is a JSON serialized string. All these formats require a password.
|
||||||
|
|
||||||
|
@ -43,12 +46,12 @@ Note: `fromPublicKey()` only accepts uncompressed Ethereum-style public keys, un
|
||||||
|
|
||||||
Instance methods:
|
Instance methods:
|
||||||
|
|
||||||
* `getPrivateKey()` - return the private key
|
- `getPrivateKey()` - return the private key
|
||||||
* `getPublicKey()` - return the public key
|
- `getPublicKey()` - return the public key
|
||||||
* `getAddress()` - return the address
|
- `getAddress()` - return the address
|
||||||
* `getChecksumAddressString()` - return the [address with checksum](https://github.com/ethereum/EIPs/issues/55)
|
- `getChecksumAddressString()` - return the [address with checksum](https://github.com/ethereum/EIPs/issues/55)
|
||||||
* `getV3Filename([timestamp])` - return the suggested filename for V3 keystores
|
- `getV3Filename([timestamp])` - return the suggested filename for V3 keystores
|
||||||
* `toV3(password, [options])` - return the wallet as a JSON string (Version 3 of the Ethereum wallet format)
|
- `toV3(password, [options])` - return the wallet as a JSON string (Version 3 of the Ethereum wallet format)
|
||||||
|
|
||||||
All of the above instance methods return a Buffer or JSON. Use the `String` suffixed versions for a string output, such as `getPrivateKeyString()`.
|
All of the above instance methods return a Buffer or JSON. Use the `String` suffixed versions for a string output, such as `getPrivateKeyString()`.
|
||||||
|
|
||||||
|
@ -62,10 +65,10 @@ Importing various third party wallets is possible through the `thirdparty` submo
|
||||||
|
|
||||||
Constructors:
|
Constructors:
|
||||||
|
|
||||||
* `fromEtherCamp(passphrase)` - import a brain wallet used by Ether.Camp
|
- `fromEtherCamp(passphrase)` - import a brain wallet used by Ether.Camp
|
||||||
* `fromEtherWallet(input, password)` - import a wallet generated by EtherWallet
|
- `fromEtherWallet(input, password)` - import a wallet generated by EtherWallet
|
||||||
* `fromKryptoKit(seed)` - import a wallet from a KryptoKit seed
|
- `fromKryptoKit(seed)` - import a wallet from a KryptoKit seed
|
||||||
* `fromQuorumWallet(passphrase, userid)` - import a brain wallet used by Quorum Wallet
|
- `fromQuorumWallet(passphrase, userid)` - import a brain wallet used by Quorum Wallet
|
||||||
|
|
||||||
## HD Wallet API
|
## HD Wallet API
|
||||||
|
|
||||||
|
@ -75,18 +78,18 @@ To use BIP32 HD wallets, first include the `hdkey` submodule:
|
||||||
|
|
||||||
Constructors:
|
Constructors:
|
||||||
|
|
||||||
* `fromMasterSeed(seed)` - create an instance based on a seed
|
- `fromMasterSeed(seed)` - create an instance based on a seed
|
||||||
* `fromExtendedKey(key)` - create an instance based on a BIP32 extended private or public key
|
- `fromExtendedKey(key)` - create an instance based on a BIP32 extended private or public key
|
||||||
|
|
||||||
For the seed we suggest to use [bip39](https://npmjs.org/package/bip39) to create one from a BIP39 mnemonic.
|
For the seed we suggest to use [bip39](https://npmjs.org/package/bip39) to create one from a BIP39 mnemonic.
|
||||||
|
|
||||||
Instance methods:
|
Instance methods:
|
||||||
|
|
||||||
* `privateExtendedKey()` - return a BIP32 extended private key (xprv)
|
- `privateExtendedKey()` - return a BIP32 extended private key (xprv)
|
||||||
* `publicExtendedKey()` - return a BIP32 extended public key (xpub)
|
- `publicExtendedKey()` - return a BIP32 extended public key (xpub)
|
||||||
* `derivePath(path)` - derive a node based on a path (e.g. m/44'/0'/0/1)
|
- `derivePath(path)` - derive a node based on a path (e.g. m/44'/0'/0/1)
|
||||||
* `deriveChild(index)` - derive a node based on a child index
|
- `deriveChild(index)` - derive a node based on a child index
|
||||||
* `getWallet()` - return a `Wallet` instance as seen above
|
- `getWallet()` - return a `Wallet` instance as seen above
|
||||||
|
|
||||||
## Provider Engine
|
## Provider Engine
|
||||||
|
|
||||||
|
@ -103,6 +106,7 @@ Note it only supports the basic wallet. With a HD Wallet, call `getWallet()` fir
|
||||||
### Remarks about `toV3`
|
### Remarks about `toV3`
|
||||||
|
|
||||||
The `options` is an optional object hash, where all the serialization parameters can be fine tuned:
|
The `options` is an optional object hash, where all the serialization parameters can be fine tuned:
|
||||||
|
|
||||||
- uuid - UUID. One is randomly generated.
|
- uuid - UUID. One is randomly generated.
|
||||||
- salt - Random salt for the `kdf`. Size must match the requirements of the KDF (key derivation function). Random number generated via `crypto.getRandomBytes` if nothing is supplied.
|
- salt - Random salt for the `kdf`. Size must match the requirements of the KDF (key derivation function). Random number generated via `crypto.getRandomBytes` if nothing is supplied.
|
||||||
- iv - Initialization vector for the `cipher`. Size must match the requirements of the cipher. Random number generated via `crypto.getRandomBytes` if nothing is supplied.
|
- iv - Initialization vector for the `cipher`. Size must match the requirements of the cipher. Random number generated via `crypto.getRandomBytes` if nothing is supplied.
|
||||||
|
@ -113,15 +117,18 @@ The `options` is an optional object hash, where all the serialization parameters
|
||||||
Depending on the `kdf` selected, the following options are available too.
|
Depending on the `kdf` selected, the following options are available too.
|
||||||
|
|
||||||
For `pbkdf2`:
|
For `pbkdf2`:
|
||||||
|
|
||||||
- `c` - Number of iterations. Defaults to 262144.
|
- `c` - Number of iterations. Defaults to 262144.
|
||||||
- `prf` - The only supported (and default) value is `hmac-sha256`. So no point changing it.
|
- `prf` - The only supported (and default) value is `hmac-sha256`. So no point changing it.
|
||||||
|
|
||||||
For `scrypt`:
|
For `scrypt`:
|
||||||
|
|
||||||
- `n` - Iteration count. Defaults to 262144.
|
- `n` - Iteration count. Defaults to 262144.
|
||||||
- `r` - Block size for the underlying hash. Defaults to 8.
|
- `r` - Block size for the underlying hash. Defaults to 8.
|
||||||
- `p` - Parallelization factor. Defaults to 1.
|
- `p` - Parallelization factor. Defaults to 1.
|
||||||
|
|
||||||
The following settings are favoured by the Go Ethereum implementation and we default to the same:
|
The following settings are favoured by the Go Ethereum implementation and we default to the same:
|
||||||
|
|
||||||
- `kdf`: `scrypt`
|
- `kdf`: `scrypt`
|
||||||
- `dklen`: `32`
|
- `dklen`: `32`
|
||||||
- `n`: `262144`
|
- `n`: `262144`
|
||||||
|
|
54
package.json
54
package.json
|
@ -2,19 +2,25 @@
|
||||||
"name": "ethereumjs-wallet",
|
"name": "ethereumjs-wallet",
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"description": "Utilities for handling Ethereum keys",
|
"description": "Utilities for handling Ethereum keys",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"*.js",
|
"dist"
|
||||||
"test/"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"coverage": "istanbul cover _mocha",
|
"build": "ethereumjs-config-build",
|
||||||
"coveralls": "npm run build:dist && npm run coverage && coveralls <coverage/lcov.info",
|
"prepublishOnly": "npm run format && npm run tslint && npm run test",
|
||||||
"lint": "standard",
|
"coverage": "ethereumjs-config-coverage",
|
||||||
"prepublishOnly": "npm run lint && npm run test:build",
|
"coveralls": "ethereumjs-config-coveralls",
|
||||||
"test": "mocha ./src/test/*.js",
|
"format": "ethereumjs-config-format",
|
||||||
"test:build": "npm run build:dist && mocha ./test/*.js",
|
"format-fix": "ethereumjs-config-format-fix",
|
||||||
"build:dist": "babel src/ -d ."
|
"lint": "ethereumjs-config-lint",
|
||||||
|
"lint-fix": "ethereumjs-config-lint-fix",
|
||||||
|
"pretest": "npm run build",
|
||||||
|
"test": "nyc mocha ./test/**/*.ts",
|
||||||
|
"tsc": "ethereumjs-config-tsc",
|
||||||
|
"tslint": "ethereumjs-config-tslint",
|
||||||
|
"tslint:fix": "ethereumjs-config-tslint-fix"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
@ -42,27 +48,27 @@
|
||||||
"ethereumjs-util": "^6.0.0",
|
"ethereumjs-util": "^6.0.0",
|
||||||
"hdkey": "^1.1.1",
|
"hdkey": "^1.1.1",
|
||||||
"randombytes": "^2.0.6",
|
"randombytes": "^2.0.6",
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"scrypt.js": "^0.3.0",
|
"scrypt.js": "^0.3.0",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.26.0",
|
"@ethereumjs/config-nyc": "^1.1.1",
|
||||||
"babel-preset-env": "^1.7.0",
|
"@ethereumjs/config-prettier": "^1.1.1",
|
||||||
|
"@ethereumjs/config-tsc": "^1.1.1",
|
||||||
|
"@ethereumjs/config-tslint": "^1.1.1",
|
||||||
|
"@types/bn.js": "^4.11.5",
|
||||||
|
"@types/mocha": "^5.2.7",
|
||||||
|
"@types/node": "^12.0.10",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.0",
|
||||||
"husky": "^2.1.0",
|
"husky": "^2.1.0",
|
||||||
"istanbul": "^0.4.5",
|
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"standard": "^12.0.0"
|
"nyc": "^14.1.1",
|
||||||
},
|
"prettier": "^1.15.3",
|
||||||
"standard": {
|
"source-map-support": "^0.5.12",
|
||||||
"globals": [
|
"ts-node": "^8.3.0",
|
||||||
"describe",
|
"tslint": "^5.12.0",
|
||||||
"it"
|
"typescript": "^3.2.2",
|
||||||
],
|
"typestrict": "^1.0.2"
|
||||||
"ignore": [
|
|
||||||
"./*.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('@ethereumjs/config-prettier')
|
51
src/hdkey.js
51
src/hdkey.js
|
@ -1,51 +0,0 @@
|
||||||
const HDKey = require('hdkey')
|
|
||||||
const Wallet = require('./index.js')
|
|
||||||
|
|
||||||
function EthereumHDKey () {
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Horrible wrapping.
|
|
||||||
*/
|
|
||||||
function fromHDKey (hdkey) {
|
|
||||||
var ret = new EthereumHDKey()
|
|
||||||
ret._hdkey = hdkey
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.fromMasterSeed = function (seedBuffer) {
|
|
||||||
return fromHDKey(HDKey.fromMasterSeed(seedBuffer))
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.fromExtendedKey = function (base58key) {
|
|
||||||
return fromHDKey(HDKey.fromExtendedKey(base58key))
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.prototype.privateExtendedKey = function () {
|
|
||||||
if (!this._hdkey.privateExtendedKey) {
|
|
||||||
throw new Error('This is a public key only wallet')
|
|
||||||
}
|
|
||||||
return this._hdkey.privateExtendedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.prototype.publicExtendedKey = function () {
|
|
||||||
return this._hdkey.publicExtendedKey
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.prototype.derivePath = function (path) {
|
|
||||||
return fromHDKey(this._hdkey.derive(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.prototype.deriveChild = function (index) {
|
|
||||||
return fromHDKey(this._hdkey.deriveChild(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
EthereumHDKey.prototype.getWallet = function () {
|
|
||||||
if (this._hdkey._privateKey) {
|
|
||||||
return Wallet.fromPrivateKey(this._hdkey._privateKey)
|
|
||||||
} else {
|
|
||||||
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = EthereumHDKey
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import Wallet from './index'
|
||||||
|
|
||||||
|
const HDKey = require('hdkey')
|
||||||
|
|
||||||
|
export default class EthereumHDKey {
|
||||||
|
public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey {
|
||||||
|
return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromExtendedKey(base58Key: string): EthereumHDKey {
|
||||||
|
return new EthereumHDKey(HDKey.fromExtendedKey(base58Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly _hdkey?: any) {}
|
||||||
|
|
||||||
|
public privateExtendedKey(): Buffer {
|
||||||
|
if (!this._hdkey.privateExtendedKey) {
|
||||||
|
throw new Error('This is a public key only wallet')
|
||||||
|
}
|
||||||
|
return this._hdkey.privateExtendedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public publicExtendedKey(): Buffer {
|
||||||
|
return this._hdkey.publicExtendedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public derivePath(path: string): EthereumHDKey {
|
||||||
|
return new EthereumHDKey(this._hdkey.derive(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
public deriveChild(index: number): EthereumHDKey {
|
||||||
|
return new EthereumHDKey(this._hdkey.deriveChild(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWallet(): Wallet {
|
||||||
|
if (this._hdkey._privateKey) {
|
||||||
|
return Wallet.fromPrivateKey(this._hdkey._privateKey)
|
||||||
|
}
|
||||||
|
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
|
||||||
|
}
|
||||||
|
}
|
311
src/index.js
311
src/index.js
|
@ -1,311 +0,0 @@
|
||||||
var Buffer = require('safe-buffer').Buffer
|
|
||||||
var ethUtil = require('ethereumjs-util')
|
|
||||||
var crypto = require('crypto')
|
|
||||||
var randomBytes = require('randombytes')
|
|
||||||
var scryptsy = require('scrypt.js')
|
|
||||||
var uuidv4 = require('uuid/v4')
|
|
||||||
var bs58check = require('bs58check')
|
|
||||||
|
|
||||||
function assert (val, msg) {
|
|
||||||
if (!val) {
|
|
||||||
throw new Error(msg || 'Assertion failed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCipherBuffer (cipher, data) {
|
|
||||||
return Buffer.concat([ cipher.update(data), cipher.final() ])
|
|
||||||
}
|
|
||||||
|
|
||||||
var Wallet = function (priv, pub) {
|
|
||||||
if (priv && pub) {
|
|
||||||
throw new Error('Cannot supply both a private and a public key to the constructor')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priv && !ethUtil.isValidPrivate(priv)) {
|
|
||||||
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pub && !ethUtil.isValidPublic(pub)) {
|
|
||||||
throw new Error('Invalid public key')
|
|
||||||
}
|
|
||||||
|
|
||||||
this._privKey = priv
|
|
||||||
this._pubKey = pub
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(Wallet.prototype, 'privKey', {
|
|
||||||
get: function () {
|
|
||||||
assert(this._privKey, 'This is a public key only wallet')
|
|
||||||
return this._privKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.defineProperty(Wallet.prototype, 'pubKey', {
|
|
||||||
get: function () {
|
|
||||||
if (!this._pubKey) {
|
|
||||||
this._pubKey = ethUtil.privateToPublic(this.privKey)
|
|
||||||
}
|
|
||||||
return this._pubKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Wallet.generate = function (icapDirect) {
|
|
||||||
if (icapDirect) {
|
|
||||||
var max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
|
|
||||||
while (true) {
|
|
||||||
var privKey = randomBytes(32)
|
|
||||||
if (new ethUtil.BN(ethUtil.privateToAddress(privKey)).lte(max)) {
|
|
||||||
return new Wallet(privKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Wallet(randomBytes(32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.generateVanityAddress = function (pattern) {
|
|
||||||
if (typeof pattern !== 'object') {
|
|
||||||
pattern = new RegExp(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var privKey = randomBytes(32)
|
|
||||||
var address = ethUtil.privateToAddress(privKey)
|
|
||||||
|
|
||||||
if (pattern.test(address.toString('hex'))) {
|
|
||||||
return new Wallet(privKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPrivateKey = function () {
|
|
||||||
return this.privKey
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPrivateKeyString = function () {
|
|
||||||
return ethUtil.bufferToHex(this.getPrivateKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPublicKey = function () {
|
|
||||||
return this.pubKey
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getPublicKeyString = function () {
|
|
||||||
return ethUtil.bufferToHex(this.getPublicKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getAddress = function () {
|
|
||||||
return ethUtil.publicToAddress(this.pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getAddressString = function () {
|
|
||||||
return ethUtil.bufferToHex(this.getAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getChecksumAddressString = function () {
|
|
||||||
return ethUtil.toChecksumAddress(this.getAddressString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
|
||||||
Wallet.prototype.toV3 = function (password, opts) {
|
|
||||||
assert(this._privKey, 'This is a public key only wallet')
|
|
||||||
|
|
||||||
opts = opts || {}
|
|
||||||
var salt = opts.salt || randomBytes(32)
|
|
||||||
var iv = opts.iv || randomBytes(16)
|
|
||||||
|
|
||||||
var derivedKey
|
|
||||||
var kdf = opts.kdf || 'scrypt'
|
|
||||||
var kdfparams = {
|
|
||||||
dklen: opts.dklen || 32,
|
|
||||||
salt: salt.toString('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kdf === 'pbkdf2') {
|
|
||||||
kdfparams.c = opts.c || 262144
|
|
||||||
kdfparams.prf = 'hmac-sha256'
|
|
||||||
derivedKey = crypto.pbkdf2Sync(Buffer.from(password), salt, kdfparams.c, kdfparams.dklen, 'sha256')
|
|
||||||
} else if (kdf === 'scrypt') {
|
|
||||||
// FIXME: support progress reporting callback
|
|
||||||
kdfparams.n = opts.n || 262144
|
|
||||||
kdfparams.r = opts.r || 8
|
|
||||||
kdfparams.p = opts.p || 1
|
|
||||||
derivedKey = scryptsy(Buffer.from(password), salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen)
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported kdf')
|
|
||||||
}
|
|
||||||
|
|
||||||
var cipher = crypto.createCipheriv(opts.cipher || 'aes-128-ctr', derivedKey.slice(0, 16), iv)
|
|
||||||
if (!cipher) {
|
|
||||||
throw new Error('Unsupported cipher')
|
|
||||||
}
|
|
||||||
|
|
||||||
var ciphertext = runCipherBuffer(cipher, this.privKey)
|
|
||||||
|
|
||||||
var mac = ethUtil.keccak256(Buffer.concat([ derivedKey.slice(16, 32), Buffer.from(ciphertext, 'hex') ]))
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: 3,
|
|
||||||
id: uuidv4({ random: opts.uuid || randomBytes(16) }),
|
|
||||||
address: this.getAddress().toString('hex'),
|
|
||||||
crypto: {
|
|
||||||
ciphertext: ciphertext.toString('hex'),
|
|
||||||
cipherparams: {
|
|
||||||
iv: iv.toString('hex')
|
|
||||||
},
|
|
||||||
cipher: opts.cipher || 'aes-128-ctr',
|
|
||||||
kdf: kdf,
|
|
||||||
kdfparams: kdfparams,
|
|
||||||
mac: mac.toString('hex')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.getV3Filename = function (timestamp) {
|
|
||||||
/*
|
|
||||||
* We want a timestamp like 2016-03-15T17-11-33.007598288Z. Date formatting
|
|
||||||
* is a pain in Javascript, everbody knows that. We could use moment.js,
|
|
||||||
* but decide to do it manually in order to save space.
|
|
||||||
*
|
|
||||||
* toJSON() returns a pretty close version, so let's use it. It is not UTC though,
|
|
||||||
* but does it really matter?
|
|
||||||
*
|
|
||||||
* Alternative manual way with padding and Date fields: http://stackoverflow.com/a/7244288/4964819
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
var ts = timestamp ? new Date(timestamp) : new Date()
|
|
||||||
|
|
||||||
return [
|
|
||||||
'UTC--',
|
|
||||||
ts.toJSON().replace(/:/g, '-'),
|
|
||||||
'--',
|
|
||||||
this.getAddress().toString('hex')
|
|
||||||
].join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.prototype.toV3String = function (password, opts) {
|
|
||||||
return JSON.stringify(this.toV3(password, opts))
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.fromPublicKey = function (pub, nonStrict) {
|
|
||||||
if (nonStrict) {
|
|
||||||
pub = ethUtil.importPublic(pub)
|
|
||||||
}
|
|
||||||
return new Wallet(null, pub)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.fromExtendedPublicKey = function (pub) {
|
|
||||||
assert(pub.slice(0, 4) === 'xpub', 'Not an extended public key')
|
|
||||||
pub = bs58check.decode(pub).slice(45)
|
|
||||||
// Convert to an Ethereum public key
|
|
||||||
return Wallet.fromPublicKey(pub, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.fromPrivateKey = function (priv) {
|
|
||||||
return new Wallet(priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.fromExtendedPrivateKey = function (priv) {
|
|
||||||
assert(priv.slice(0, 4) === 'xprv', 'Not an extended private key')
|
|
||||||
var tmp = bs58check.decode(priv)
|
|
||||||
assert(tmp[45] === 0, 'Invalid extended private key')
|
|
||||||
return Wallet.fromPrivateKey(tmp.slice(46))
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ethereum/go-ethereum/wiki/Passphrase-protected-key-store-spec
|
|
||||||
Wallet.fromV1 = function (input, password) {
|
|
||||||
assert(typeof password === 'string')
|
|
||||||
var json = (typeof input === 'object') ? input : JSON.parse(input)
|
|
||||||
|
|
||||||
if (json.Version !== '1') {
|
|
||||||
throw new Error('Not a V1 wallet')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.Crypto.KeyHeader.Kdf !== 'scrypt') {
|
|
||||||
throw new Error('Unsupported key derivation scheme')
|
|
||||||
}
|
|
||||||
|
|
||||||
var kdfparams = json.Crypto.KeyHeader.KdfParams
|
|
||||||
var derivedKey = scryptsy(Buffer.from(password), Buffer.from(json.Crypto.Salt, 'hex'), kdfparams.N, kdfparams.R, kdfparams.P, kdfparams.DkLen)
|
|
||||||
|
|
||||||
var ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
|
|
||||||
|
|
||||||
var mac = ethUtil.keccak256(Buffer.concat([ derivedKey.slice(16, 32), ciphertext ]))
|
|
||||||
|
|
||||||
if (mac.toString('hex') !== json.Crypto.MAC) {
|
|
||||||
throw new Error('Key derivation failed - possibly wrong passphrase')
|
|
||||||
}
|
|
||||||
|
|
||||||
var decipher = crypto.createDecipheriv('aes-128-cbc', ethUtil.keccak256(derivedKey.slice(0, 16)).slice(0, 16), Buffer.from(json.Crypto.IV, 'hex'))
|
|
||||||
var seed = runCipherBuffer(decipher, ciphertext)
|
|
||||||
|
|
||||||
return new Wallet(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet.fromV3 = function (input, password, nonStrict) {
|
|
||||||
assert(typeof password === 'string')
|
|
||||||
var json = (typeof input === 'object') ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
|
|
||||||
|
|
||||||
if (json.version !== 3) {
|
|
||||||
throw new Error('Not a V3 wallet')
|
|
||||||
}
|
|
||||||
|
|
||||||
var derivedKey
|
|
||||||
var kdfparams
|
|
||||||
if (json.crypto.kdf === 'scrypt') {
|
|
||||||
kdfparams = json.crypto.kdfparams
|
|
||||||
|
|
||||||
// FIXME: support progress reporting callback
|
|
||||||
derivedKey = scryptsy(Buffer.from(password), Buffer.from(kdfparams.salt, 'hex'), kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen)
|
|
||||||
} else if (json.crypto.kdf === 'pbkdf2') {
|
|
||||||
kdfparams = json.crypto.kdfparams
|
|
||||||
|
|
||||||
if (kdfparams.prf !== 'hmac-sha256') {
|
|
||||||
throw new Error('Unsupported parameters to PBKDF2')
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedKey = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(kdfparams.salt, 'hex'), kdfparams.c, kdfparams.dklen, 'sha256')
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported key derivation scheme')
|
|
||||||
}
|
|
||||||
|
|
||||||
var ciphertext = Buffer.from(json.crypto.ciphertext, 'hex')
|
|
||||||
|
|
||||||
var mac = ethUtil.keccak256(Buffer.concat([ derivedKey.slice(16, 32), ciphertext ]))
|
|
||||||
if (mac.toString('hex') !== json.crypto.mac) {
|
|
||||||
throw new Error('Key derivation failed - possibly wrong passphrase')
|
|
||||||
}
|
|
||||||
|
|
||||||
var decipher = crypto.createDecipheriv(json.crypto.cipher, derivedKey.slice(0, 16), Buffer.from(json.crypto.cipherparams.iv, 'hex'))
|
|
||||||
var seed = runCipherBuffer(decipher, ciphertext)
|
|
||||||
|
|
||||||
return new Wallet(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
|
|
||||||
* JSON fields: encseed, ethaddr, btcaddr, email
|
|
||||||
*/
|
|
||||||
Wallet.fromEthSale = function (input, password) {
|
|
||||||
assert(typeof password === 'string')
|
|
||||||
var json = (typeof input === 'object') ? input : JSON.parse(input)
|
|
||||||
|
|
||||||
var encseed = Buffer.from(json.encseed, 'hex')
|
|
||||||
|
|
||||||
// key derivation
|
|
||||||
var derivedKey = crypto.pbkdf2Sync(password, password, 2000, 32, 'sha256').slice(0, 16)
|
|
||||||
|
|
||||||
// seed decoding (IV is first 16 bytes)
|
|
||||||
// NOTE: crypto (derived from openssl) when used with aes-*-cbc will handle PKCS#7 padding internally
|
|
||||||
// see also http://stackoverflow.com/a/31614770/4964819
|
|
||||||
var decipher = crypto.createDecipheriv('aes-128-cbc', derivedKey, encseed.slice(0, 16))
|
|
||||||
var seed = runCipherBuffer(decipher, encseed.slice(16))
|
|
||||||
|
|
||||||
var wallet = new Wallet(ethUtil.keccak256(seed))
|
|
||||||
if (wallet.getAddress().toString('hex') !== json.ethaddr) {
|
|
||||||
throw new Error('Decoded key mismatch - possibly wrong passphrase')
|
|
||||||
}
|
|
||||||
return wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Wallet
|
|
|
@ -0,0 +1,489 @@
|
||||||
|
import * as crypto from 'crypto'
|
||||||
|
import * as ethUtil from 'ethereumjs-util'
|
||||||
|
|
||||||
|
const bs58check = require('bs58check')
|
||||||
|
const randomBytes = require('randombytes')
|
||||||
|
const scryptsy = require('scrypt.js')
|
||||||
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
|
// parameters for the toV3() method
|
||||||
|
|
||||||
|
interface V3Params {
|
||||||
|
kdf: string
|
||||||
|
cipher: string
|
||||||
|
salt: Buffer
|
||||||
|
iv: Buffer
|
||||||
|
uuid: Buffer
|
||||||
|
dklen: number
|
||||||
|
c: number
|
||||||
|
n: number
|
||||||
|
r: number
|
||||||
|
p: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
|
||||||
|
const v3Defaults: V3Params = {
|
||||||
|
cipher: 'aes-128-ctr',
|
||||||
|
kdf: 'scrypt',
|
||||||
|
salt: randomBytes(32),
|
||||||
|
iv: randomBytes(16),
|
||||||
|
uuid: randomBytes(16),
|
||||||
|
dklen: 32,
|
||||||
|
c: 262144,
|
||||||
|
n: 262144,
|
||||||
|
r: 8,
|
||||||
|
p: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params) {
|
||||||
|
return v3Defaults
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cipher: params.cipher || 'aes-128-ctr',
|
||||||
|
kdf: params.kdf || 'scrypt',
|
||||||
|
salt: params.salt || randomBytes(32),
|
||||||
|
iv: params.iv || randomBytes(16),
|
||||||
|
uuid: params.uuid || randomBytes(16),
|
||||||
|
dklen: params.dklen || 32,
|
||||||
|
c: params.c || 262144,
|
||||||
|
n: params.n || 262144,
|
||||||
|
r: params.r || 8,
|
||||||
|
p: params.p || 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KDF
|
||||||
|
|
||||||
|
const enum KDFFunctions {
|
||||||
|
PBKDF = 'pbkdf2',
|
||||||
|
Scrypt = 'scrypt',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScryptKDFParams {
|
||||||
|
dklen: number
|
||||||
|
n: number
|
||||||
|
p: number
|
||||||
|
r: number
|
||||||
|
salt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PBKDFParams {
|
||||||
|
c: number
|
||||||
|
dklen: number
|
||||||
|
prf: string
|
||||||
|
salt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type KDFParams = ScryptKDFParams | PBKDFParams
|
||||||
|
|
||||||
|
function kdfParamsForPBKDF(opts: V3Params): PBKDFParams {
|
||||||
|
return {
|
||||||
|
dklen: opts.dklen,
|
||||||
|
salt: opts.salt.toString('hex'),
|
||||||
|
c: opts.c,
|
||||||
|
prf: 'hmac-sha256',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function kdfParamsForScrypt(opts: V3Params): ScryptKDFParams {
|
||||||
|
return {
|
||||||
|
dklen: opts.dklen,
|
||||||
|
salt: opts.salt.toString('hex'),
|
||||||
|
n: opts.n,
|
||||||
|
r: opts.r,
|
||||||
|
p: opts.p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON keystore types
|
||||||
|
|
||||||
|
// https://github.com/ethereum/homestead-guide/blob/master/old-docs-for-reference/go-ethereum-wiki.rst/Passphrase-protected-key-store-spec.rst
|
||||||
|
interface V1Keystore {
|
||||||
|
Address: string
|
||||||
|
Crypto: {
|
||||||
|
CipherText: string
|
||||||
|
IV: string
|
||||||
|
KeyHeader: {
|
||||||
|
Kdf: string
|
||||||
|
KdfParams: {
|
||||||
|
DkLen: number
|
||||||
|
N: number
|
||||||
|
P: number
|
||||||
|
R: number
|
||||||
|
SaltLen: number
|
||||||
|
}
|
||||||
|
Version: string
|
||||||
|
}
|
||||||
|
MAC: string
|
||||||
|
Salt: string
|
||||||
|
}
|
||||||
|
Id: string
|
||||||
|
Version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||||
|
interface V3Keystore {
|
||||||
|
crypto: {
|
||||||
|
cipher: string
|
||||||
|
cipherparams: {
|
||||||
|
iv: string
|
||||||
|
}
|
||||||
|
ciphertext: string
|
||||||
|
kdf: string
|
||||||
|
kdfparams: KDFParams
|
||||||
|
mac: string
|
||||||
|
}
|
||||||
|
id: string
|
||||||
|
version: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EthSaleKeystore {
|
||||||
|
encseed: string
|
||||||
|
ethaddr: string
|
||||||
|
btcaddr: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// wallet implementation
|
||||||
|
|
||||||
|
export default class Wallet {
|
||||||
|
constructor(
|
||||||
|
private readonly privateKey?: Buffer | undefined,
|
||||||
|
private publicKey: Buffer | undefined = undefined,
|
||||||
|
) {
|
||||||
|
if (privateKey && publicKey) {
|
||||||
|
throw new Error('Cannot supply both a private and a public key to the constructor')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
|
||||||
|
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicKey && !ethUtil.isValidPublic(publicKey)) {
|
||||||
|
throw new Error('Invalid public key')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static methods
|
||||||
|
|
||||||
|
public static generate(icapDirect: boolean = false): Wallet {
|
||||||
|
if (icapDirect) {
|
||||||
|
const max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
|
||||||
|
while (true) {
|
||||||
|
const privateKey = randomBytes(32)
|
||||||
|
if (new ethUtil.BN(ethUtil.privateToAddress(privateKey)).lte(max)) {
|
||||||
|
return new Wallet(privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Wallet(randomBytes(32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generateVanityAddress(pattern: RegExp | string): Wallet {
|
||||||
|
if (!(pattern instanceof RegExp)) {
|
||||||
|
pattern = new RegExp(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const privateKey = randomBytes(32)
|
||||||
|
const address = ethUtil.privateToAddress(privateKey)
|
||||||
|
|
||||||
|
if (pattern.test(address.toString('hex'))) {
|
||||||
|
return new Wallet(privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPublicKey(publicKey: Buffer, nonStrict: boolean = false): Wallet {
|
||||||
|
if (nonStrict) {
|
||||||
|
publicKey = ethUtil.importPublic(publicKey)
|
||||||
|
}
|
||||||
|
return new Wallet(undefined, publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromExtendedPublicKey(extendedPublicKey: string): Wallet {
|
||||||
|
if (extendedPublicKey.slice(0, 4) !== 'xpub') {
|
||||||
|
throw new Error('Not an extended public key')
|
||||||
|
}
|
||||||
|
const publicKey: Buffer = bs58check.decode(extendedPublicKey).slice(45)
|
||||||
|
// Convert to an Ethereum public key
|
||||||
|
return Wallet.fromPublicKey(publicKey, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPrivateKey(privateKey: Buffer): Wallet {
|
||||||
|
return new Wallet(privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromExtendedPrivateKey(extendedPrivateKey: string): Wallet {
|
||||||
|
if (extendedPrivateKey.slice(0, 4) !== 'xprv') {
|
||||||
|
throw new Error('Not an extended private key')
|
||||||
|
}
|
||||||
|
const tmp: Buffer = bs58check.decode(extendedPrivateKey)
|
||||||
|
if (tmp[45] !== 0) {
|
||||||
|
throw new Error('Invalid extended private key')
|
||||||
|
}
|
||||||
|
return Wallet.fromPrivateKey(tmp.slice(46))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromV1(input: string | V1Keystore, password: string): Wallet {
|
||||||
|
const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
|
if (json.Version !== '1') {
|
||||||
|
throw new Error('Not a V1 Wallet')
|
||||||
|
}
|
||||||
|
if (json.Crypto.KeyHeader.Kdf !== 'scrypt') {
|
||||||
|
throw new Error('Unsupported key derivation scheme')
|
||||||
|
}
|
||||||
|
|
||||||
|
const kdfparams = json.Crypto.KeyHeader.KdfParams
|
||||||
|
const derivedKey = scryptsy(
|
||||||
|
Buffer.from(password),
|
||||||
|
Buffer.from(json.Crypto.Salt, 'hex'),
|
||||||
|
kdfparams.N,
|
||||||
|
kdfparams.R,
|
||||||
|
kdfparams.P,
|
||||||
|
kdfparams.DkLen,
|
||||||
|
)
|
||||||
|
|
||||||
|
const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
|
||||||
|
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
|
||||||
|
if (mac.toString('hex') !== json.Crypto.MAC) {
|
||||||
|
throw new Error('Key derivation failed - possibly wrong passphrase')
|
||||||
|
}
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(
|
||||||
|
'aes-128-cbc',
|
||||||
|
ethUtil.keccak256(derivedKey.slice(0, 16)).slice(0, 16),
|
||||||
|
Buffer.from(json.Crypto.IV, 'hex'),
|
||||||
|
)
|
||||||
|
const seed = runCipherBuffer(decipher, ciphertext)
|
||||||
|
return new Wallet(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromV3(
|
||||||
|
input: string | V3Keystore,
|
||||||
|
password: string,
|
||||||
|
nonStrict: boolean = false,
|
||||||
|
): Wallet {
|
||||||
|
const json: V3Keystore =
|
||||||
|
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
|
||||||
|
|
||||||
|
if (json.version !== 3) {
|
||||||
|
throw new Error('Not a V3 wallet')
|
||||||
|
}
|
||||||
|
|
||||||
|
let derivedKey: Buffer, kdfparams: any
|
||||||
|
if (json.crypto.kdf === 'scrypt') {
|
||||||
|
kdfparams = json.crypto.kdfparams
|
||||||
|
|
||||||
|
// FIXME: support progress reporting callback
|
||||||
|
derivedKey = scryptsy(
|
||||||
|
Buffer.from(password),
|
||||||
|
Buffer.from(kdfparams.salt, 'hex'),
|
||||||
|
kdfparams.n,
|
||||||
|
kdfparams.r,
|
||||||
|
kdfparams.p,
|
||||||
|
kdfparams.dklen,
|
||||||
|
)
|
||||||
|
} else if (json.crypto.kdf === 'pbkdf2') {
|
||||||
|
kdfparams = json.crypto.kdfparams
|
||||||
|
|
||||||
|
if (kdfparams.prf !== 'hmac-sha256') {
|
||||||
|
throw new Error('Unsupported parameters to PBKDF2')
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedKey = crypto.pbkdf2Sync(
|
||||||
|
Buffer.from(password),
|
||||||
|
Buffer.from(kdfparams.salt, 'hex'),
|
||||||
|
kdfparams.c,
|
||||||
|
kdfparams.dklen,
|
||||||
|
'sha256',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported key derivation scheme')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex')
|
||||||
|
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
|
||||||
|
if (mac.toString('hex') !== json.crypto.mac) {
|
||||||
|
throw new Error('Key derivation failed - possibly wrong passphrase')
|
||||||
|
}
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(
|
||||||
|
json.crypto.cipher,
|
||||||
|
derivedKey.slice(0, 16),
|
||||||
|
Buffer.from(json.crypto.cipherparams.iv, 'hex'),
|
||||||
|
)
|
||||||
|
const seed = runCipherBuffer(decipher, ciphertext)
|
||||||
|
return new Wallet(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
|
||||||
|
* JSON fields: encseed, ethaddr, btcaddr, email
|
||||||
|
*/
|
||||||
|
public static fromEthSale(input: string | EthSaleKeystore, password: string): Wallet {
|
||||||
|
const json: EthSaleKeystore = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
|
|
||||||
|
const encseed = Buffer.from(json.encseed, 'hex')
|
||||||
|
|
||||||
|
// key derivation
|
||||||
|
const derivedKey = crypto.pbkdf2Sync(password, password, 2000, 32, 'sha256').slice(0, 16)
|
||||||
|
|
||||||
|
// seed decoding (IV is first 16 bytes)
|
||||||
|
// NOTE: crypto (derived from openssl) when used with aes-*-cbc will handle PKCS#7 padding internally
|
||||||
|
// see also http://stackoverflow.com/a/31614770/4964819
|
||||||
|
const decipher = crypto.createDecipheriv('aes-128-cbc', derivedKey, encseed.slice(0, 16))
|
||||||
|
const seed = runCipherBuffer(decipher, encseed.slice(16))
|
||||||
|
|
||||||
|
const wallet = new Wallet(ethUtil.keccak256(seed))
|
||||||
|
if (wallet.getAddress().toString('hex') !== json.ethaddr) {
|
||||||
|
throw new Error('Decoded key mismatch - possibly wrong passphrase')
|
||||||
|
}
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
// private getters
|
||||||
|
|
||||||
|
private get pubKey(): Buffer {
|
||||||
|
if (!keyExists(this.publicKey)) {
|
||||||
|
this.publicKey = ethUtil.privateToPublic(this.privateKey as Buffer)
|
||||||
|
}
|
||||||
|
return this.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
private get privKey(): Buffer {
|
||||||
|
if (!keyExists(this.privateKey)) {
|
||||||
|
throw new Error('This is a public key only wallet')
|
||||||
|
}
|
||||||
|
return this.privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// public instance methods
|
||||||
|
|
||||||
|
public getPrivateKey(): Buffer {
|
||||||
|
return this.privKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrivateKeyString(): string {
|
||||||
|
return ethUtil.bufferToHex(this.privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPublicKey(): Buffer {
|
||||||
|
return this.pubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPublicKeyString(): string {
|
||||||
|
return ethUtil.bufferToHex(this.getPublicKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAddress(): Buffer {
|
||||||
|
return ethUtil.publicToAddress(this.pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAddressString(): string {
|
||||||
|
return ethUtil.bufferToHex(this.getAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChecksumAddressString(): string {
|
||||||
|
return ethUtil.toChecksumAddress(this.getAddressString())
|
||||||
|
}
|
||||||
|
|
||||||
|
public toV3(password: string, opts?: Partial<V3Params>): V3Keystore {
|
||||||
|
if (!keyExists(this.privateKey)) {
|
||||||
|
throw new Error('This is a public key only wallet')
|
||||||
|
}
|
||||||
|
|
||||||
|
const v3Params: V3Params = mergeToV3ParamsWithDefaults(opts)
|
||||||
|
|
||||||
|
let kdfParams: PBKDFParams | ScryptKDFParams
|
||||||
|
let derivedKey: Buffer
|
||||||
|
switch (v3Params.kdf) {
|
||||||
|
case KDFFunctions.PBKDF:
|
||||||
|
kdfParams = kdfParamsForPBKDF(v3Params)
|
||||||
|
derivedKey = crypto.pbkdf2Sync(
|
||||||
|
Buffer.from(password),
|
||||||
|
v3Params.salt,
|
||||||
|
kdfParams.c,
|
||||||
|
kdfParams.dklen,
|
||||||
|
'sha256',
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case KDFFunctions.Scrypt:
|
||||||
|
kdfParams = kdfParamsForScrypt(v3Params)
|
||||||
|
// FIXME: support progress reporting callback
|
||||||
|
derivedKey = scryptsy(
|
||||||
|
Buffer.from(password),
|
||||||
|
v3Params.salt,
|
||||||
|
kdfParams.n,
|
||||||
|
kdfParams.r,
|
||||||
|
kdfParams.p,
|
||||||
|
kdfParams.dklen,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('Unsupported kdf')
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher: crypto.Cipher = crypto.createCipheriv(
|
||||||
|
v3Params.cipher,
|
||||||
|
derivedKey.slice(0, 16),
|
||||||
|
v3Params.iv,
|
||||||
|
)
|
||||||
|
if (!cipher) {
|
||||||
|
throw new Error('Unsupported cipher')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = runCipherBuffer(cipher, this.privKey)
|
||||||
|
const mac = ethUtil.keccak256(
|
||||||
|
Buffer.concat([derivedKey.slice(16, 32), Buffer.from(ciphertext)]),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
id: uuidv4({ random: v3Params.uuid }),
|
||||||
|
// @ts-ignore - the official V3 keystore spec omits the address key
|
||||||
|
address: this.getAddress().toString('hex'),
|
||||||
|
crypto: {
|
||||||
|
ciphertext: ciphertext.toString('hex'),
|
||||||
|
cipherparams: { iv: v3Params.iv.toString('hex') },
|
||||||
|
cipher: v3Params.cipher,
|
||||||
|
kdf: v3Params.kdf,
|
||||||
|
kdfparams: kdfParams,
|
||||||
|
mac: mac.toString('hex'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getV3Filename(timestamp?: number): string {
|
||||||
|
/*
|
||||||
|
* We want a timestamp like 2016-03-15T17-11-33.007598288Z. Date formatting
|
||||||
|
* is a pain in Javascript, everbody knows that. We could use moment.js,
|
||||||
|
* but decide to do it manually in order to save space.
|
||||||
|
*
|
||||||
|
* toJSON() returns a pretty close version, so let's use it. It is not UTC though,
|
||||||
|
* but does it really matter?
|
||||||
|
*
|
||||||
|
* Alternative manual way with padding and Date fields: http://stackoverflow.com/a/7244288/4964819
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const ts = timestamp ? new Date(timestamp) : new Date()
|
||||||
|
return ['UTC--', ts.toJSON().replace(/:/g, '-'), '--', this.getAddress().toString('hex')].join(
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public toV3String(password: string, opts?: Partial<V3Params>): string {
|
||||||
|
return JSON.stringify(this.toV3(password, opts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
|
||||||
|
function runCipherBuffer(cipher: crypto.Cipher | crypto.Decipher, data: Buffer): Buffer {
|
||||||
|
return Buffer.concat([cipher.update(data), cipher.final()])
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyExists(k: Buffer | undefined | null): k is Buffer {
|
||||||
|
return k !== undefined && k !== null
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const inherits = require('util').inherits
|
|
||||||
const HookedWalletEthTxSubprovider = require('web3-provider-engine/subproviders/hooked-wallet-ethtx')
|
|
||||||
|
|
||||||
module.exports = WalletSubprovider
|
|
||||||
|
|
||||||
inherits(WalletSubprovider, HookedWalletEthTxSubprovider)
|
|
||||||
|
|
||||||
function WalletSubprovider (wallet, opts) {
|
|
||||||
opts = opts || {}
|
|
||||||
|
|
||||||
opts.getAccounts = function (cb) {
|
|
||||||
cb(null, [ wallet.getAddressString() ])
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.getPrivateKey = function (address, cb) {
|
|
||||||
if (address !== wallet.getAddressString()) {
|
|
||||||
cb(new Error('Account not found'))
|
|
||||||
} else {
|
|
||||||
cb(null, wallet.getPrivateKey())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletSubprovider.super_.call(this, opts)
|
|
||||||
}
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Wallet from './index'
|
||||||
|
|
||||||
|
const HookedWalletEthTxSubprovider = require('web3-provider-engine/subproviders/hooked-wallet-ethtx')
|
||||||
|
|
||||||
|
export default class WalletSubprovider extends HookedWalletEthTxSubprovider {
|
||||||
|
constructor(wallet: Wallet, opts?: any) {
|
||||||
|
if (!opts) {
|
||||||
|
opts = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.getAccounts = (cb: any) => cb(null, [wallet.getAddressString()])
|
||||||
|
|
||||||
|
opts.getPrivateKey = (address: string, cb: any) => {
|
||||||
|
if (address !== wallet.getAddressString()) {
|
||||||
|
cb(new Error('Account not found'))
|
||||||
|
} else {
|
||||||
|
cb(null, wallet.getPrivateKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super(opts)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,75 +0,0 @@
|
||||||
var assert = require('assert')
|
|
||||||
var HDKey = require('../hdkey.js')
|
|
||||||
var Buffer = require('safe-buffer').Buffer
|
|
||||||
|
|
||||||
// from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown
|
|
||||||
var fixtureseed = Buffer.from('747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03', 'hex')
|
|
||||||
var fixturehd = HDKey.fromMasterSeed(fixtureseed)
|
|
||||||
|
|
||||||
describe('.fromMasterSeed()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.doesNotThrow(function () {
|
|
||||||
HDKey.fromMasterSeed(fixtureseed)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.privateExtendedKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixturehd.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.publicExtendedKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixturehd.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromExtendedKey()', function () {
|
|
||||||
it('should work with public', function () {
|
|
||||||
var hdnode = HDKey.fromExtendedKey('xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
|
|
||||||
assert.strictEqual(hdnode.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
|
|
||||||
assert.throws(function () {
|
|
||||||
hdnode.privateExtendedKey()
|
|
||||||
}, /^Error: This is a public key only wallet$/)
|
|
||||||
})
|
|
||||||
it('should work with private', function () {
|
|
||||||
var hdnode = HDKey.fromExtendedKey('xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
|
|
||||||
assert.strictEqual(hdnode.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
|
|
||||||
assert.strictEqual(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.deriveChild()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
var hdnode = fixturehd.deriveChild(1)
|
|
||||||
assert.strictEqual(hdnode.privateExtendedKey(), 'xprv9vYSvrg3eR5FaKbQE4Ao2vHdyvfFL27aWMyH6X818mKWMsqqQZAN6HmRqYDGDPLArzaqbLExRsxFwtx2B2X2QKkC9uoKsiBNi22tLPKZHNS')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.derivePath()', function () {
|
|
||||||
it('should work with m', function () {
|
|
||||||
var hdnode = fixturehd.derivePath('m')
|
|
||||||
assert.strictEqual(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
|
|
||||||
})
|
|
||||||
it('should work with m/44\'/0\'/0/1', function () {
|
|
||||||
var hdnode = fixturehd.derivePath('m/44\'/0\'/0/1')
|
|
||||||
assert.strictEqual(hdnode.privateExtendedKey(), 'xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getWallet()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixturehd.getWallet().getPrivateKeyString(), '0x26cc9417b89cd77c4acdbe2e3cd286070a015d8e380f9cd1244ae103b7d89d81')
|
|
||||||
assert.strictEqual(fixturehd.getWallet().getPublicKeyString(),
|
|
||||||
'0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
|
|
||||||
})
|
|
||||||
it('should work with public nodes', function () {
|
|
||||||
var hdnode = HDKey.fromExtendedKey('xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
|
|
||||||
assert.throws(function () {
|
|
||||||
hdnode.getWallet().getPrivateKeyString()
|
|
||||||
}, /^Error: This is a public key only wallet$/)
|
|
||||||
assert.strictEqual(hdnode.getWallet().getPublicKeyString(), '0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,301 +0,0 @@
|
||||||
var assert = require('assert')
|
|
||||||
var Buffer = require('safe-buffer').Buffer
|
|
||||||
var Wallet = require('../')
|
|
||||||
var Thirdparty = require('../thirdparty.js')
|
|
||||||
var ethUtil = require('ethereumjs-util')
|
|
||||||
|
|
||||||
var fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
|
|
||||||
var fixturePrivateKeyStr = '0x' + fixturePrivateKey
|
|
||||||
var fixturePrivateKeyBuffer = Buffer.from(fixturePrivateKey, 'hex')
|
|
||||||
|
|
||||||
var fixturePublicKey = '5d4392f450262b276652c1fc037606abac500f3160830ce9df53aa70d95ce7cfb8b06010b2f3691c78c65c21eb4cf3dfdbfc0745d89b664ee10435bb3a0f906c'
|
|
||||||
var fixturePublicKeyStr = '0x' + fixturePublicKey
|
|
||||||
var fixturePublicKeyBuffer = Buffer.from(fixturePublicKey, 'hex')
|
|
||||||
|
|
||||||
var fixtureWallet = Wallet.fromPrivateKey(fixturePrivateKeyBuffer)
|
|
||||||
|
|
||||||
describe('.getPrivateKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getPrivateKey().toString('hex'), fixturePrivateKey)
|
|
||||||
})
|
|
||||||
it('should fail', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromPrivateKey(Buffer.from('001122', 'hex'))
|
|
||||||
}, /^Error: Private key does not satisfy the curve requirements \(ie. it is invalid\)$/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getPrivateKeyString()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getPrivateKeyString(), fixturePrivateKeyStr)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getPublicKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getPublicKey().toString('hex'), fixturePublicKey)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getPublicKeyString()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getPublicKeyString(), fixturePublicKeyStr)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getAddress()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getAddress().toString('hex'), 'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getAddressString()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getAddressString(), '0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getChecksumAddressString()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getChecksumAddressString(), '0xB14Ab53E38DA1C172f877DBC6d65e4a1B0474C3c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('public key only wallet', function () {
|
|
||||||
var pubKey = Buffer.from(fixturePublicKey, 'hex')
|
|
||||||
it('.fromPublicKey() should work', function () {
|
|
||||||
assert.strictEqual(Wallet.fromPublicKey(pubKey).getPublicKey().toString('hex'),
|
|
||||||
fixturePublicKey)
|
|
||||||
})
|
|
||||||
it('.fromPublicKey() should not accept compressed keys in strict mode', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromPublicKey(Buffer.from('030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d', 'hex'))
|
|
||||||
}, /^Error: Invalid public key$/)
|
|
||||||
})
|
|
||||||
it('.fromPublicKey() should accept compressed keys in non-strict mode', function () {
|
|
||||||
var tmp = Buffer.from('030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d', 'hex')
|
|
||||||
assert.strictEqual(Wallet.fromPublicKey(tmp, true).getPublicKey().toString('hex'),
|
|
||||||
'0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
|
|
||||||
})
|
|
||||||
it('.getAddress() should work', function () {
|
|
||||||
assert.strictEqual(Wallet.fromPublicKey(pubKey).getAddress().toString('hex'), 'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
|
|
||||||
})
|
|
||||||
it('.getPrivateKey() should fail', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromPublicKey(pubKey).getPrivateKey()
|
|
||||||
}, /^Error: This is a public key only wallet$/)
|
|
||||||
})
|
|
||||||
it('.toV3() should fail', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromPublicKey(pubKey).toV3()
|
|
||||||
}, /^Error: This is a public key only wallet$/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromExtendedPrivateKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
var xprv = 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY'
|
|
||||||
assert.strictEqual(Wallet.fromExtendedPrivateKey(xprv).getAddressString(), '0xb800bf5435f67c7ee7d83c3a863269969a57c57c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromExtendedPublicKey()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
var xpub = 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ'
|
|
||||||
assert.strictEqual(Wallet.fromExtendedPublicKey(xpub).getAddressString(), '0xb800bf5435f67c7ee7d83c3a863269969a57c57c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.generate()', function () {
|
|
||||||
it('should generate an account', function () {
|
|
||||||
assert.strictEqual(Wallet.generate().getPrivateKey().length, 32)
|
|
||||||
})
|
|
||||||
it('should generate an account compatible with ICAP Direct', function () {
|
|
||||||
var max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
|
|
||||||
var wallet = Wallet.generate(true)
|
|
||||||
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
|
||||||
assert.strictEqual(new ethUtil.BN(wallet.getAddress()).lte(max), true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.generateVanityAddress()', function () {
|
|
||||||
it('should generate an account with 000 prefix (object)', function () {
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
var wallet = Wallet.generateVanityAddress(/^000/)
|
|
||||||
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
|
||||||
assert.strictEqual(wallet.getAddress()[0], 0)
|
|
||||||
assert.strictEqual(wallet.getAddress()[1] >>> 4, 0)
|
|
||||||
})
|
|
||||||
it('should generate an account with 000 prefix (string)', function () {
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
var wallet = Wallet.generateVanityAddress('^000')
|
|
||||||
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
|
||||||
assert.strictEqual(wallet.getAddress()[0], 0)
|
|
||||||
assert.strictEqual(wallet.getAddress()[1] >>> 4, 0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.getV3Filename()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
assert.strictEqual(fixtureWallet.getV3Filename(1457917509265), 'UTC--2016-03-14T01-05-09.265Z--b14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.toV3()', function () {
|
|
||||||
var salt = Buffer.from('dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6', 'hex')
|
|
||||||
var iv = Buffer.from('cecacd85e9cb89788b5aab2f93361233', 'hex')
|
|
||||||
var uuid = Buffer.from('7e59dc028d42d09db29aa8a0f862cc81', 'hex')
|
|
||||||
|
|
||||||
it('should work with PBKDF2', function () {
|
|
||||||
var w = '{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"01ee7f1a3c8d187ea244c92eea9e332ab0bb2b4c902d89bdd71f80dc384da1be","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"pbkdf2","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","c":262144,"prf":"hmac-sha256"},"mac":"0c02cd0badfebd5e783e0cf41448f84086a96365fc3456716c33641a86ebc7cc"}}'
|
|
||||||
// FIXME: just test for ciphertext and mac?
|
|
||||||
assert.strictEqual(fixtureWallet.toV3String('testtest', { kdf: 'pbkdf2', uuid: uuid, salt: salt, iv: iv }), w)
|
|
||||||
})
|
|
||||||
it('should work with Scrypt', function () {
|
|
||||||
var w = '{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"c52682025b1e5d5c06b816791921dbf439afe7a053abb9fac19f38a57499652c","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","n":262144,"r":8,"p":1},"mac":"27b98c8676dc6619d077453b38db645a4c7c17a3e686ee5adaf53c11ac1b890e"}}'
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
// FIXME: just test for ciphertext and mac?
|
|
||||||
assert.strictEqual(fixtureWallet.toV3String('testtest', { kdf: 'scrypt', uuid: uuid, salt: salt, iv: iv }), w)
|
|
||||||
})
|
|
||||||
it('should work without providing options', function () {
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
assert.strictEqual(fixtureWallet.toV3('testtest')['version'], 3)
|
|
||||||
})
|
|
||||||
it('should fail for unsupported kdf', function () {
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
assert.throws(function () {
|
|
||||||
fixtureWallet.toV3('testtest', { kdf: 'superkey' })
|
|
||||||
}, /^Error: Unsupported kdf$/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
describe('.fromV1()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
var sample = '{"Address":"d4584b5f6229b7be90727b0fc8c6b91bb427821f","Crypto":{"CipherText":"07533e172414bfa50e99dba4a0ce603f654ebfa1ff46277c3e0c577fdc87f6bb4e4fe16c5a94ce6ce14cfa069821ef9b","IV":"16d67ba0ce5a339ff2f07951253e6ba8","KeyHeader":{"Kdf":"scrypt","KdfParams":{"DkLen":32,"N":262144,"P":1,"R":8,"SaltLen":32},"Version":"1"},"MAC":"8ccded24da2e99a11d48cda146f9cc8213eb423e2ea0d8427f41c3be414424dd","Salt":"06870e5e6a24e183a5c807bd1c43afd86d573f7db303ff4853d135cd0fd3fe91"},"Id":"0498f19a-59db-4d54-ac95-33901b4f1870","Version":"1"}'
|
|
||||||
var wallet = Wallet.fromV1(sample, 'foo')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0xd4584b5f6229b7be90727b0fc8c6b91bb427821f')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('.fromV3()', function () {
|
|
||||||
it('should work with PBKDF2', function () {
|
|
||||||
var w = '{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
|
||||||
var wallet = Wallet.fromV3(w, 'testpassword')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b')
|
|
||||||
})
|
|
||||||
it('should work with Scrypt', function () {
|
|
||||||
var sample = '{"address":"2f91eb73a6cd5620d7abb50889f24eea7a6a4feb","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a2bc4f71e8445d64ceebd1247079fbd8"},"ciphertext":"6b9ab7954c9066fa1e54e04e2c527c7d78a77611d5f84fede1bd61ab13c51e3e","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"caf551e2b7ec12d93007e528093697a4c68e8a50e663b2a929754a8085d9ede4"},"mac":"506cace9c5c32544d39558025cb3bf23ed94ba2626e5338c82e50726917e1a15"},"id":"1b3cad9b-fa7b-4817-9022-d5e598eb5fe3","version":3}'
|
|
||||||
var wallet = Wallet.fromV3(sample, 'testtest')
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x2f91eb73a6cd5620d7abb50889f24eea7a6a4feb')
|
|
||||||
})
|
|
||||||
it('should work with \'unencrypted\' wallets', function () {
|
|
||||||
var w = '{"address":"a9886ac7489ecbcbd79268a79ef00d940e5fe1f2","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c542cf883299b5b0a29155091054028d"},"ciphertext":"0a83c77235840cffcfcc5afe5908f2d7f89d7d54c4a796dfe2f193e90413ee9d","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"699f7bf5f6985068dfaaff9db3b06aea8fe3dd3140b3addb4e60620ee97a0316"},"mac":"613fed2605240a2ff08b8d93ccc48c5b3d5023b7088189515d70df41d65f44de"},"id":"0edf817a-ee0e-4e25-8314-1f9e88a60811","version":3}'
|
|
||||||
var wallet = Wallet.fromV3(w, '')
|
|
||||||
this.timeout(180000) // 3minutes
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0xa9886ac7489ecbcbd79268a79ef00d940e5fe1f2')
|
|
||||||
})
|
|
||||||
it('should fail with invalid password', function () {
|
|
||||||
var w = '{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromV3(w, 'wrongtestpassword')
|
|
||||||
}, /^Error: Key derivation failed - possibly wrong passphrase$/)
|
|
||||||
})
|
|
||||||
it('should work with (broken) mixed-case input files', function () {
|
|
||||||
var w = '{"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
|
||||||
var wallet = Wallet.fromV3(w, 'testpassword', true)
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b')
|
|
||||||
})
|
|
||||||
it('shouldn\'t work with (broken) mixed-case input files in strict mode', function () {
|
|
||||||
var w = '{"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromV3(w, 'testpassword')
|
|
||||||
}) // FIXME: check for assert message(s)
|
|
||||||
})
|
|
||||||
it('should fail for wrong version', function () {
|
|
||||||
var w = '{"version":2}'
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromV3(w, 'testpassword')
|
|
||||||
}, /^Error: Not a V3 wallet$/)
|
|
||||||
})
|
|
||||||
it('should fail for wrong kdf', function () {
|
|
||||||
var w = '{"crypto":{"kdf":"superkey"},"version":3}'
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromV3(w, 'testpassword')
|
|
||||||
}, /^Error: Unsupported key derivation scheme$/)
|
|
||||||
})
|
|
||||||
it('should fail for wrong prf in pbkdf2', function () {
|
|
||||||
var w = '{"crypto":{"kdf":"pbkdf2","kdfparams":{"prf":"invalid"}},"version":3}'
|
|
||||||
assert.throws(function () {
|
|
||||||
Wallet.fromV3(w, 'testpassword')
|
|
||||||
}, /^Error: Unsupported parameters to PBKDF2$/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromEthSale()', function () {
|
|
||||||
// Generated using https://github.com/ethereum/pyethsaletool/ [4afd19ad60cee8d09b645555180bc3a7c8a25b67]
|
|
||||||
it('should work with short password (8 characters)', function () {
|
|
||||||
var json = '{"encseed": "81ffdfaf2736310ce87df268b53169783e8420b98f3405fb9364b96ac0feebfb62f4cf31e0d25f1ded61f083514dd98c3ce1a14a24d7618fd513b6d97044725c7d2e08a7d9c2061f2c8a05af01f06755c252f04cab20fee2a4778130440a9344", "ethaddr": "22f8c5dd4a0a9d59d580667868df2da9592ab292", "email": "hello@ethereum.org", "btcaddr": "1DHW32MFwHxU2nk2SLAQq55eqFotT9jWcq"}'
|
|
||||||
var wallet = Wallet.fromEthSale(json, 'testtest')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x22f8c5dd4a0a9d59d580667868df2da9592ab292')
|
|
||||||
})
|
|
||||||
it('should work with long password (19 characters)', function () {
|
|
||||||
var json = '{"encseed": "0c7e462bd67c6840ed2fa291090b2f46511b798d34492e146d6de148abbccba45d8fcfc06bea2e5b9d6c5d17b51a9a046c1054a032f24d96a56614a14dcd02e3539685d7f09b93180067160f3a9db648ccca610fc2f983fc65bf973304cbf5b6", "ethaddr": "c90b232231c83b462723f473b35cb8b1db868108", "email": "thisisalongpassword@test.com", "btcaddr": "1Cy2fN2ov5BrMkzgrzE34YadCH2yLMNkTE"}'
|
|
||||||
var wallet = Wallet.fromEthSale(json, 'thisisalongpassword')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0xc90b232231c83b462723f473b35cb8b1db868108')
|
|
||||||
})
|
|
||||||
// From https://github.com/ryepdx/pyethrecover/blob/master/test_wallets/ico.json
|
|
||||||
it('should work with pyethrecover\'s wallet', function () {
|
|
||||||
var json = '{"encseed": "8b4001bf61a10760d8e0876fb791e4ebeb85962f565c71697c789c23d1ade4d1285d80b2383ae5fc419ecf5319317cd94200b65df0cc50d659cbbc4365fc08e8", "ethaddr": "83b6371ba6bd9a47f82a7c4920835ef4be08f47b", "bkp": "9f566775e56486f69413c59f7ef923bc", "btcaddr": "1Nzg5v6uRCAa6Fk3CUU5qahWxEDZdZ1pBm"}'
|
|
||||||
var wallet = Wallet.fromEthSale(json, 'password123')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x83b6371ba6bd9a47f82a7c4920835ef4be08f47b')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromEtherWallet()', function () {
|
|
||||||
it('should work with unencrypted input', function () {
|
|
||||||
var etherWalletUnencrypted = '{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":false,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"a2c6222146ca2269086351fda9f8d2dfc8a50331e8a05f0f400c13653a521862","public":"2ed129b50b1a4dbbc53346bf711df6893265ad0c700fd11431b0bc3a66bd383a87b10ad835804a6cbe092e0375a0cc3524acf06b1ec7bb978bf25d2d6c35d120"}'
|
|
||||||
var wallet = Thirdparty.fromEtherWallet(etherWalletUnencrypted)
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
|
|
||||||
})
|
|
||||||
it('should work with encrypted input', function () {
|
|
||||||
var etherWalletEncrypted = '{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":true,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"U2FsdGVkX1/hGPYlTZYGhzdwvtkoZfkeII4Ga4pSd/Ak373ORnwZE4nf/FFZZFcDTSH1X1+AmewadrW7dqvwr76QMYQVlihpPaFV307hWgKckkG0Mf/X4gJIQQbDPiKdcff9","public":"U2FsdGVkX1/awUDAekZQbEiXx2ct4ugXwgBllY0Hz+IwYkHiEhhxH+obu7AF7PCU2Vq5c0lpCzBUSvk2EvFyt46bw1OYIijw0iOr7fWMJEkz3bfN5mt9pYJIiPzN0gxM8u4mrmqLPUG2SkoZhWz4NOlqRUHZq7Ep6aWKz7KlEpzP9IrvDYwGubci4h+9wsspqtY1BdUJUN59EaWZSuOw1g=="}'
|
|
||||||
var wallet = Thirdparty.fromEtherWallet(etherWalletEncrypted, 'testtest')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromEtherCamp()', function () {
|
|
||||||
it('should work with seed text', function () {
|
|
||||||
var wallet = Thirdparty.fromEtherCamp('ethercamp123')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x182b6ca390224c455f11b6337d74119305014ed4')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromKryptoKit()', function () {
|
|
||||||
it('should work with basic input (d-type)', function () {
|
|
||||||
var wallet = Thirdparty.fromKryptoKit('dBWfH8QZSGbg1sAYHLBhqE5R8VGAoM7')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x3611981ad2d6fc1d7579d6ce4c6bc37e272c369c')
|
|
||||||
})
|
|
||||||
it('should work with encrypted input (q-type)', function () {
|
|
||||||
var wallet = Thirdparty.fromKryptoKit('qhah1VeT0RgTvff1UKrUrxtFViiQuki16dd353d59888c25', 'testtest')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x3c753e27834db67329d1ec1fab67970ec1e27112')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.fromQuorumWallet()', function () {
|
|
||||||
it('should work', function () {
|
|
||||||
var wallet = Thirdparty.fromQuorumWallet('testtesttest', 'ethereumjs-wallet')
|
|
||||||
assert.strictEqual(wallet.getAddressString(), '0x1b86ccc22e8f137f204a41a23033541242a48815')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('raw new Wallet() init', function () {
|
|
||||||
it('should fail when both priv and pub key provided', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
new Wallet(fixturePrivateKeyBuffer, fixturePublicKeyBuffer) // eslint-disable-line
|
|
||||||
}, /^Error: Cannot supply both a private and a public key to the constructor$/)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,234 +0,0 @@
|
||||||
var Wallet = require('./index.js')
|
|
||||||
var ethUtil = require('ethereumjs-util')
|
|
||||||
var crypto = require('crypto')
|
|
||||||
var scryptsy = require('scrypt.js')
|
|
||||||
var utf8 = require('utf8')
|
|
||||||
var aesjs = require('aes-js')
|
|
||||||
var Buffer = require('safe-buffer').Buffer
|
|
||||||
|
|
||||||
function assert (val, msg) {
|
|
||||||
if (!val) {
|
|
||||||
throw new Error(msg || 'Assertion failed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCipherBuffer (cipher, data) {
|
|
||||||
return Buffer.concat([ cipher.update(data), cipher.final() ])
|
|
||||||
}
|
|
||||||
|
|
||||||
var Thirdparty = {}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* opts:
|
|
||||||
* - digest - digest algorithm, defaults to md5
|
|
||||||
* - count - hash iterations
|
|
||||||
* - keysize - desired key size
|
|
||||||
* - ivsize - desired IV size
|
|
||||||
*
|
|
||||||
* Algorithm form https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html
|
|
||||||
*
|
|
||||||
* FIXME: not optimised at all
|
|
||||||
*/
|
|
||||||
function evp_kdf (data, salt, opts) { // eslint-disable-line
|
|
||||||
// A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)`
|
|
||||||
function iter (block) {
|
|
||||||
var hash = crypto.createHash(opts.digest || 'md5')
|
|
||||||
hash.update(block)
|
|
||||||
hash.update(data)
|
|
||||||
hash.update(salt)
|
|
||||||
block = hash.digest()
|
|
||||||
|
|
||||||
for (var i = 1; i < (opts.count || 1); i++) {
|
|
||||||
hash = crypto.createHash(opts.digest || 'md5')
|
|
||||||
hash.update(block)
|
|
||||||
block = hash.digest()
|
|
||||||
}
|
|
||||||
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
|
|
||||||
var keysize = opts.keysize || 16
|
|
||||||
var ivsize = opts.ivsize || 16
|
|
||||||
|
|
||||||
var ret = []
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while (Buffer.concat(ret).length < (keysize + ivsize)) {
|
|
||||||
ret[i] = iter((i === 0) ? Buffer.alloc(0) : ret[i - 1])
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp = Buffer.concat(ret)
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: tmp.slice(0, keysize),
|
|
||||||
iv: tmp.slice(keysize, keysize + ivsize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/25288311/cryptojs-aes-pattern-always-ends-with
|
|
||||||
function decodeCryptojsSalt (input) {
|
|
||||||
var ciphertext = Buffer.from(input, 'base64')
|
|
||||||
if (ciphertext.slice(0, 8).toString() === 'Salted__') {
|
|
||||||
return {
|
|
||||||
salt: ciphertext.slice(8, 16),
|
|
||||||
ciphertext: ciphertext.slice(16)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
ciphertext: ciphertext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts
|
|
||||||
* and used on https://www.myetherwallet.com/
|
|
||||||
*/
|
|
||||||
Thirdparty.fromEtherWallet = function (input, password) {
|
|
||||||
var json = (typeof input === 'object') ? input : JSON.parse(input)
|
|
||||||
|
|
||||||
var privKey
|
|
||||||
if (!json.locked) {
|
|
||||||
if (json.private.length !== 64) {
|
|
||||||
throw new Error('Invalid private key length')
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey = Buffer.from(json.private, 'hex')
|
|
||||||
} else {
|
|
||||||
if (typeof password !== 'string') {
|
|
||||||
throw new Error('Password required')
|
|
||||||
}
|
|
||||||
if (password.length < 7) {
|
|
||||||
throw new Error('Password must be at least 7 characters')
|
|
||||||
}
|
|
||||||
|
|
||||||
// the "encrypted" version has the low 4 bytes
|
|
||||||
// of the hash of the address appended
|
|
||||||
var cipher = json.encrypted ? json.private.slice(0, 128) : json.private
|
|
||||||
|
|
||||||
// decode openssl ciphertext + salt encoding
|
|
||||||
cipher = decodeCryptojsSalt(cipher)
|
|
||||||
|
|
||||||
if (!cipher.salt) {
|
|
||||||
throw new Error('Unsupported EtherWallet key format')
|
|
||||||
}
|
|
||||||
|
|
||||||
// derive key/iv using OpenSSL EVP as implemented in CryptoJS
|
|
||||||
var evp = evp_kdf(Buffer.from(password), cipher.salt, { keysize: 32, ivsize: 16 })
|
|
||||||
|
|
||||||
var decipher = crypto.createDecipheriv('aes-256-cbc', evp.key, evp.iv)
|
|
||||||
privKey = runCipherBuffer(decipher, Buffer.from(cipher.ciphertext))
|
|
||||||
|
|
||||||
// NOTE: yes, they've run it through UTF8
|
|
||||||
privKey = Buffer.from(utf8.decode(privKey.toString()), 'hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
var wallet = new Wallet(privKey)
|
|
||||||
|
|
||||||
if (wallet.getAddressString() !== json.address) {
|
|
||||||
throw new Error('Invalid private key or address')
|
|
||||||
}
|
|
||||||
|
|
||||||
return wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
Thirdparty.fromEtherCamp = function (passphrase) {
|
|
||||||
return new Wallet(ethUtil.keccak256(Buffer.from(passphrase)))
|
|
||||||
}
|
|
||||||
|
|
||||||
Thirdparty.fromKryptoKit = function (entropy, password) {
|
|
||||||
function kryptoKitBrokenScryptSeed (buf) {
|
|
||||||
// js-scrypt calls `Buffer.from(String(salt), 'utf8')` on the seed even though it is a buffer
|
|
||||||
//
|
|
||||||
// The `buffer`` implementation used does the below transformation (doesn't matches the current version):
|
|
||||||
// https://github.com/feross/buffer/blob/67c61181b938b17d10dbfc0a545f713b8bd59de8/index.js
|
|
||||||
|
|
||||||
function decodeUtf8Char (str) {
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(str)
|
|
||||||
} catch (err) {
|
|
||||||
return String.fromCharCode(0xFFFD) // UTF 8 invalid char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = ''
|
|
||||||
var tmp = ''
|
|
||||||
|
|
||||||
for (var i = 0; i < buf.length; i++) {
|
|
||||||
if (buf[i] <= 0x7F) {
|
|
||||||
res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
|
|
||||||
tmp = ''
|
|
||||||
} else {
|
|
||||||
tmp += '%' + buf[i].toString(16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(res + decodeUtf8Char(tmp))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entropy[0] === '#') {
|
|
||||||
entropy = entropy.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = entropy[0]
|
|
||||||
entropy = entropy.slice(1)
|
|
||||||
|
|
||||||
var privKey
|
|
||||||
if (type === 'd') {
|
|
||||||
privKey = ethUtil.sha256(entropy)
|
|
||||||
} else if (type === 'q') {
|
|
||||||
if (typeof password !== 'string') {
|
|
||||||
throw new Error('Password required')
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
|
|
||||||
var checksum = entropy.slice(30, 46)
|
|
||||||
|
|
||||||
var salt = kryptoKitBrokenScryptSeed(encryptedSeed)
|
|
||||||
var aesKey = scryptsy(Buffer.from(password, 'utf8'), salt, 16384, 8, 1, 32)
|
|
||||||
|
|
||||||
/* FIXME: try to use `crypto` instead of `aesjs`
|
|
||||||
|
|
||||||
// NOTE: ECB doesn't use the IV, so it can be anything
|
|
||||||
var decipher = crypto.createDecipheriv("aes-256-ecb", aesKey, Buffer.from(0))
|
|
||||||
|
|
||||||
// FIXME: this is a clear abuse, but seems to match how ECB in aesjs works
|
|
||||||
privKey = Buffer.concat([
|
|
||||||
decipher.update(encryptedSeed).slice(0, 16),
|
|
||||||
decipher.update(encryptedSeed).slice(0, 16),
|
|
||||||
])
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable new-cap */
|
|
||||||
var decipher = new aesjs.ModeOfOperation.ecb(aesKey)
|
|
||||||
/* eslint-enable new-cap */
|
|
||||||
/* decrypt returns an Uint8Array, perhaps there is a better way to concatenate */
|
|
||||||
privKey = Buffer.concat([
|
|
||||||
Buffer.from(decipher.decrypt(encryptedSeed.slice(0, 16))),
|
|
||||||
Buffer.from(decipher.decrypt(encryptedSeed.slice(16, 32)))
|
|
||||||
])
|
|
||||||
|
|
||||||
if (checksum.length > 0) {
|
|
||||||
if (checksum !== ethUtil.sha256(ethUtil.sha256(privKey)).slice(0, 8).toString('hex')) {
|
|
||||||
throw new Error('Failed to decrypt input - possibly invalid passphrase')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported or invalid entropy type')
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Wallet(privKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
Thirdparty.fromQuorumWallet = function (passphrase, userid) {
|
|
||||||
assert(passphrase.length >= 10)
|
|
||||||
assert(userid.length >= 10)
|
|
||||||
|
|
||||||
var seed = passphrase + userid
|
|
||||||
seed = crypto.pbkdf2Sync(seed, seed, 2000, 32, 'sha256')
|
|
||||||
|
|
||||||
return new Wallet(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Thirdparty
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
import * as crypto from 'crypto'
|
||||||
|
import * as ethUtil from 'ethereumjs-util'
|
||||||
|
|
||||||
|
import Wallet from './index'
|
||||||
|
|
||||||
|
const scryptsy = require('scrypt.js')
|
||||||
|
const utf8 = require('utf8')
|
||||||
|
const aesjs = require('aes-js')
|
||||||
|
|
||||||
|
function runCipherBuffer(cipher: crypto.Cipher | crypto.Decipher, data: Buffer): Buffer {
|
||||||
|
return Buffer.concat([cipher.update(data), cipher.final()])
|
||||||
|
}
|
||||||
|
|
||||||
|
// evp_kdf
|
||||||
|
|
||||||
|
interface EvpKdfOpts {
|
||||||
|
count: number
|
||||||
|
keysize: number
|
||||||
|
ivsize: number
|
||||||
|
digest: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const evpKdfDefaults: EvpKdfOpts = {
|
||||||
|
count: 1,
|
||||||
|
keysize: 16,
|
||||||
|
ivsize: 16,
|
||||||
|
digest: 'md5',
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeEvpKdfOptsWithDefaults(opts?: Partial<EvpKdfOpts>): EvpKdfOpts {
|
||||||
|
if (!opts) {
|
||||||
|
return evpKdfDefaults
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
count: opts.count || evpKdfDefaults.count,
|
||||||
|
keysize: opts.keysize || evpKdfDefaults.keysize,
|
||||||
|
ivsize: opts.ivsize || evpKdfDefaults.ivsize,
|
||||||
|
digest: opts.digest || evpKdfDefaults.digest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* opts:
|
||||||
|
* - digest - digest algorithm, defaults to md5
|
||||||
|
* - count - hash iterations
|
||||||
|
* - keysize - desired key size
|
||||||
|
* - ivsize - desired IV size
|
||||||
|
*
|
||||||
|
* Algorithm form https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html
|
||||||
|
*
|
||||||
|
* FIXME: not optimised at all
|
||||||
|
*/
|
||||||
|
function evp_kdf(data: Buffer, salt: Buffer, opts?: Partial<EvpKdfOpts>) {
|
||||||
|
const params = mergeEvpKdfOptsWithDefaults(opts)
|
||||||
|
|
||||||
|
// A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)`
|
||||||
|
function iter(block: Buffer) {
|
||||||
|
let hash = crypto.createHash(params.digest)
|
||||||
|
hash.update(block)
|
||||||
|
hash.update(data)
|
||||||
|
hash.update(salt)
|
||||||
|
block = hash.digest()
|
||||||
|
|
||||||
|
for (let i = 1, len = params.count; i < len; i++) {
|
||||||
|
hash = crypto.createHash(params.digest)
|
||||||
|
hash.update(block)
|
||||||
|
block = hash.digest()
|
||||||
|
}
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret: Buffer[] = []
|
||||||
|
let i = 0
|
||||||
|
while (Buffer.concat(ret).length < params.keysize + params.ivsize) {
|
||||||
|
ret[i] = iter(i === 0 ? Buffer.alloc(0) : ret[i - 1])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
const tmp = Buffer.concat(ret)
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: tmp.slice(0, params.keysize),
|
||||||
|
iv: tmp.slice(params.keysize, params.keysize + params.ivsize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/questions/25288311/cryptojs-aes-pattern-always-ends-with
|
||||||
|
function decodeCryptojsSalt(input: string): { ciphertext: Buffer; salt?: Buffer } {
|
||||||
|
const ciphertext = Buffer.from(input, 'base64')
|
||||||
|
if (ciphertext.slice(0, 8).toString() === 'Salted__') {
|
||||||
|
return {
|
||||||
|
salt: ciphertext.slice(8, 16),
|
||||||
|
ciphertext: ciphertext.slice(16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ciphertext }
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "address": "0x169aab499b549eac087035e640d3f7d882ef5e2d",
|
||||||
|
// "encrypted": true,
|
||||||
|
// "locked": true,
|
||||||
|
// "hash": "342f636d174cc1caa49ce16e5b257877191b663e0af0271d2ea03ac7e139317d",
|
||||||
|
// "private": "U2FsdGVkX19ZrornRBIfl1IDdcj6S9YywY8EgOeOtLj2DHybM/CHL4Jl0jcwjT+36kDnjj+qEfUBu6J1mGQF/fNcD/TsAUgGUTEUEOsP1CKDvNHfLmWLIfxqnYHhHsG5",
|
||||||
|
// "public": "U2FsdGVkX19EaDNK52q7LEz3hL/VR3dYW5VcoP04tcVKNS0Q3JINpM4XzttRJCBtq4g22hNDrOR8RWyHuh3nPo0pRSe9r5AUfEiCLaMBAhI16kf2KqCA8ah4brkya9ZLECdIl0HDTMYfDASBnyNXd87qodt46U0vdRT3PppK+9hsyqP8yqm9kFcWqMHktqubBE937LIU0W22Rfw6cJRwIw=="
|
||||||
|
// }
|
||||||
|
|
||||||
|
interface EtherWalletOptions {
|
||||||
|
address: string
|
||||||
|
encrypted: boolean
|
||||||
|
locked: boolean
|
||||||
|
hash: string
|
||||||
|
private: string
|
||||||
|
public: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts
|
||||||
|
* and used on https://www.myetherwallet.com/
|
||||||
|
*/
|
||||||
|
function fromEtherWallet(input: string | EtherWalletOptions, password: string): Wallet {
|
||||||
|
const json: EtherWalletOptions = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
|
|
||||||
|
let privateKey: Buffer
|
||||||
|
if (!json.locked) {
|
||||||
|
if (json.private.length !== 64) {
|
||||||
|
throw new Error('Invalid private key length')
|
||||||
|
}
|
||||||
|
privateKey = Buffer.from(json.private, 'hex')
|
||||||
|
} else {
|
||||||
|
if (typeof password !== 'string') {
|
||||||
|
throw new Error('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 7) {
|
||||||
|
throw new Error('Password must be at least 7 characters')
|
||||||
|
}
|
||||||
|
|
||||||
|
// the "encrypted" version has the low 4 bytes
|
||||||
|
// of the hash of the address appended
|
||||||
|
const hash = json.encrypted ? json.private.slice(0, 128) : json.private
|
||||||
|
|
||||||
|
// decode openssl ciphertext + salt encoding
|
||||||
|
const cipher = decodeCryptojsSalt(hash)
|
||||||
|
if (!cipher.salt) {
|
||||||
|
throw new Error('Unsupported EtherWallet key format')
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive key/iv using OpenSSL EVP as implemented in CryptoJS
|
||||||
|
const evp = evp_kdf(Buffer.from(password), cipher.salt, { keysize: 32, ivsize: 16 })
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', evp.key, evp.iv)
|
||||||
|
privateKey = runCipherBuffer(decipher, Buffer.from(cipher.ciphertext))
|
||||||
|
|
||||||
|
// NOTE: yes, they've run it through UTF8
|
||||||
|
privateKey = Buffer.from(utf8.decode(privateKey.toString()), 'hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
const wallet = new Wallet(privateKey)
|
||||||
|
if (wallet.getAddressString() !== json.address) {
|
||||||
|
throw new Error('Invalid private key or address')
|
||||||
|
}
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEtherCamp(passphrase: string): Wallet {
|
||||||
|
return new Wallet(ethUtil.keccak256(Buffer.from(passphrase)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromKryptoKit(entropy: string, password: string): Wallet {
|
||||||
|
function kryptoKitBrokenScryptSeed(buf: Buffer) {
|
||||||
|
// js-scrypt calls `Buffer.from(String(salt), 'utf8')` on the seed even though it is a buffer
|
||||||
|
//
|
||||||
|
// The `buffer`` implementation used does the below transformation (doesn't matches the current version):
|
||||||
|
// https://github.com/feross/buffer/blob/67c61181b938b17d10dbfc0a545f713b8bd59de8/index.js
|
||||||
|
|
||||||
|
function decodeUtf8Char(str: string) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(str)
|
||||||
|
} catch (err) {
|
||||||
|
return String.fromCharCode(0xfffd) // UTF 8 invalid char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = '',
|
||||||
|
tmp = ''
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
if (buf[i] <= 0x7f) {
|
||||||
|
res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
|
||||||
|
tmp = ''
|
||||||
|
} else {
|
||||||
|
tmp += '%' + buf[i].toString(16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.from(res + decodeUtf8Char(tmp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entropy[0] === '#') {
|
||||||
|
entropy = entropy.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = entropy[0]
|
||||||
|
entropy = entropy.slice(1)
|
||||||
|
|
||||||
|
let privateKey: Buffer
|
||||||
|
if (type === 'd') {
|
||||||
|
privateKey = ethUtil.sha256(entropy)
|
||||||
|
} else if (type === 'q') {
|
||||||
|
if (typeof password !== 'string') {
|
||||||
|
throw new Error('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
|
||||||
|
const checksum = entropy.slice(30, 46)
|
||||||
|
|
||||||
|
const salt = kryptoKitBrokenScryptSeed(encryptedSeed)
|
||||||
|
const aesKey = scryptsy(Buffer.from(password, 'utf8'), salt, 16384, 8, 1, 32)
|
||||||
|
|
||||||
|
/* FIXME: try to use `crypto` instead of `aesjs`
|
||||||
|
|
||||||
|
// NOTE: ECB doesn't use the IV, so it can be anything
|
||||||
|
var decipher = crypto.createDecipheriv("aes-256-ecb", aesKey, Buffer.from(0))
|
||||||
|
|
||||||
|
// FIXME: this is a clear abuse, but seems to match how ECB in aesjs works
|
||||||
|
privKey = Buffer.concat([
|
||||||
|
decipher.update(encryptedSeed).slice(0, 16),
|
||||||
|
decipher.update(encryptedSeed).slice(0, 16),
|
||||||
|
])
|
||||||
|
*/
|
||||||
|
|
||||||
|
const decipher = new aesjs.ModeOfOperation.ecb(aesKey)
|
||||||
|
/* decrypt returns an Uint8Array, perhaps there is a better way to concatenate */
|
||||||
|
privateKey = Buffer.concat([
|
||||||
|
Buffer.from(decipher.decrypt(encryptedSeed.slice(0, 16))),
|
||||||
|
Buffer.from(decipher.decrypt(encryptedSeed.slice(16, 32))),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (checksum.length > 0) {
|
||||||
|
if (
|
||||||
|
checksum !==
|
||||||
|
ethUtil
|
||||||
|
.sha256(ethUtil.sha256(privateKey))
|
||||||
|
.slice(0, 8)
|
||||||
|
.toString('hex')
|
||||||
|
) {
|
||||||
|
throw new Error('Failed to decrypt input - possibly invalid passphrase')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported or invalid entropy type')
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Wallet(privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromQuorumWallet(passphrase: string, userid: string): Wallet {
|
||||||
|
if (passphrase.length < 10) {
|
||||||
|
throw new Error('Passphrase must be at least 10 characters')
|
||||||
|
}
|
||||||
|
if (userid.length < 10) {
|
||||||
|
throw new Error('User id must be at least 10 characters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const merged = passphrase + userid
|
||||||
|
const seed = crypto.pbkdf2Sync(merged, merged, 2000, 32, 'sha256')
|
||||||
|
return new Wallet(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Thirdparty = {
|
||||||
|
fromEtherWallet,
|
||||||
|
fromEtherCamp,
|
||||||
|
fromKryptoKit,
|
||||||
|
fromQuorumWallet,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Thirdparty
|
|
@ -0,0 +1,115 @@
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import EthereumHDKey from '../src/hdkey'
|
||||||
|
|
||||||
|
// from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown
|
||||||
|
const fixtureseed = Buffer.from(
|
||||||
|
'747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03',
|
||||||
|
'hex',
|
||||||
|
)
|
||||||
|
const fixturehd = EthereumHDKey.fromMasterSeed(fixtureseed)
|
||||||
|
|
||||||
|
describe('.fromMasterSeed()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
EthereumHDKey.fromMasterSeed(fixtureseed)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.privateExtendedKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixturehd.privateExtendedKey(),
|
||||||
|
'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.publicExtendedKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixturehd.publicExtendedKey(),
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromExtendedKey()', function() {
|
||||||
|
it('should work with public', function() {
|
||||||
|
const hdnode = EthereumHDKey.fromExtendedKey(
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ',
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.publicExtendedKey(),
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ',
|
||||||
|
)
|
||||||
|
assert.throws(function() {
|
||||||
|
hdnode.privateExtendedKey()
|
||||||
|
}, /^Error: This is a public key only wallet$/)
|
||||||
|
})
|
||||||
|
it('should work with private', function() {
|
||||||
|
const hdnode = EthereumHDKey.fromExtendedKey(
|
||||||
|
'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY',
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.publicExtendedKey(),
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ',
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.privateExtendedKey(),
|
||||||
|
'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.deriveChild()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
const hdnode = fixturehd.deriveChild(1)
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.privateExtendedKey(),
|
||||||
|
'xprv9vYSvrg3eR5FaKbQE4Ao2vHdyvfFL27aWMyH6X818mKWMsqqQZAN6HmRqYDGDPLArzaqbLExRsxFwtx2B2X2QKkC9uoKsiBNi22tLPKZHNS',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.derivePath()', function() {
|
||||||
|
it('should work with m', function() {
|
||||||
|
const hdnode = fixturehd.derivePath('m')
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.privateExtendedKey(),
|
||||||
|
'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it("should work with m/44'/0'/0/1", function() {
|
||||||
|
const hdnode = fixturehd.derivePath("m/44'/0'/0/1")
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.privateExtendedKey(),
|
||||||
|
'xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getWallet()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixturehd.getWallet().getPrivateKeyString(),
|
||||||
|
'0x26cc9417b89cd77c4acdbe2e3cd286070a015d8e380f9cd1244ae103b7d89d81',
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
fixturehd.getWallet().getPublicKeyString(),
|
||||||
|
'0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should work with public nodes', function() {
|
||||||
|
const hdnode = EthereumHDKey.fromExtendedKey(
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ',
|
||||||
|
)
|
||||||
|
assert.throws(function() {
|
||||||
|
hdnode.getWallet().getPrivateKeyString()
|
||||||
|
}, /^Error: This is a public key only wallet$/)
|
||||||
|
assert.strictEqual(
|
||||||
|
hdnode.getWallet().getPublicKeyString(),
|
||||||
|
'0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,365 @@
|
||||||
|
/* tslint:disable no-invalid-this */
|
||||||
|
import * as assert from 'assert'
|
||||||
|
import * as ethUtil from 'ethereumjs-util'
|
||||||
|
|
||||||
|
import Wallet from '../src'
|
||||||
|
import Thirdparty from '../src/thirdparty'
|
||||||
|
|
||||||
|
const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
|
||||||
|
const fixturePrivateKeyStr = '0x' + fixturePrivateKey
|
||||||
|
const fixturePrivateKeyBuffer = Buffer.from(fixturePrivateKey, 'hex')
|
||||||
|
|
||||||
|
const fixturePublicKey =
|
||||||
|
'5d4392f450262b276652c1fc037606abac500f3160830ce9df53aa70d95ce7cfb8b06010b2f3691c78c65c21eb4cf3dfdbfc0745d89b664ee10435bb3a0f906c'
|
||||||
|
const fixturePublicKeyStr = '0x' + fixturePublicKey
|
||||||
|
const fixturePublicKeyBuffer = Buffer.from(fixturePublicKey, 'hex')
|
||||||
|
|
||||||
|
const fixtureWallet = Wallet.fromPrivateKey(fixturePrivateKeyBuffer)
|
||||||
|
|
||||||
|
describe('.getPrivateKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(fixtureWallet.getPrivateKey().toString('hex'), fixturePrivateKey)
|
||||||
|
})
|
||||||
|
it('should fail', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromPrivateKey(Buffer.from('001122', 'hex'))
|
||||||
|
}, /^Error: Private key does not satisfy the curve requirements \(ie. it is invalid\)$/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getPrivateKeyString()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(fixtureWallet.getPrivateKeyString(), fixturePrivateKeyStr)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getPublicKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(fixtureWallet.getPublicKey().toString('hex'), fixturePublicKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getPublicKeyString()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(fixtureWallet.getPublicKeyString(), fixturePublicKeyStr)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getAddress()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.getAddress().toString('hex'),
|
||||||
|
'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getAddressString()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.getAddressString(),
|
||||||
|
'0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getChecksumAddressString()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.getChecksumAddressString(),
|
||||||
|
'0xB14Ab53E38DA1C172f877DBC6d65e4a1B0474C3c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('public key only wallet', function() {
|
||||||
|
const pubKey = Buffer.from(fixturePublicKey, 'hex')
|
||||||
|
it('.fromPublicKey() should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
Wallet.fromPublicKey(pubKey)
|
||||||
|
.getPublicKey()
|
||||||
|
.toString('hex'),
|
||||||
|
fixturePublicKey,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('.fromPublicKey() should not accept compressed keys in strict mode', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromPublicKey(
|
||||||
|
Buffer.from('030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d', 'hex'),
|
||||||
|
)
|
||||||
|
}, /^Error: Invalid public key$/)
|
||||||
|
})
|
||||||
|
it('.fromPublicKey() should accept compressed keys in non-strict mode', function() {
|
||||||
|
const tmp = Buffer.from(
|
||||||
|
'030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d',
|
||||||
|
'hex',
|
||||||
|
)
|
||||||
|
assert.strictEqual(
|
||||||
|
Wallet.fromPublicKey(tmp, true)
|
||||||
|
.getPublicKey()
|
||||||
|
.toString('hex'),
|
||||||
|
'0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('.getAddress() should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
Wallet.fromPublicKey(pubKey)
|
||||||
|
.getAddress()
|
||||||
|
.toString('hex'),
|
||||||
|
'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('.getPrivateKey() should fail', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromPublicKey(pubKey).getPrivateKey()
|
||||||
|
}, /^Error: This is a public key only wallet$/)
|
||||||
|
})
|
||||||
|
// it('.toV3() should fail', function () {
|
||||||
|
// assert.throws(function () {
|
||||||
|
// Wallet.fromPublicKey(pubKey).toV3()
|
||||||
|
// }, /^Error: This is a public key only wallet$/)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromExtendedPrivateKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
const xprv =
|
||||||
|
'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY'
|
||||||
|
assert.strictEqual(
|
||||||
|
Wallet.fromExtendedPrivateKey(xprv).getAddressString(),
|
||||||
|
'0xb800bf5435f67c7ee7d83c3a863269969a57c57c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromExtendedPublicKey()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
const xpub =
|
||||||
|
'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ'
|
||||||
|
assert.strictEqual(
|
||||||
|
Wallet.fromExtendedPublicKey(xpub).getAddressString(),
|
||||||
|
'0xb800bf5435f67c7ee7d83c3a863269969a57c57c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.generate()', function() {
|
||||||
|
it('should generate an account', function() {
|
||||||
|
assert.strictEqual(Wallet.generate().getPrivateKey().length, 32)
|
||||||
|
})
|
||||||
|
it('should generate an account compatible with ICAP Direct', function() {
|
||||||
|
const max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
|
||||||
|
const wallet = Wallet.generate(true)
|
||||||
|
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
||||||
|
assert.strictEqual(new ethUtil.BN(wallet.getAddress()).lte(max), true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.generateVanityAddress()', function() {
|
||||||
|
it('should generate an account with 000 prefix (object)', function() {
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
const wallet = Wallet.generateVanityAddress(/^000/)
|
||||||
|
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
||||||
|
assert.strictEqual(wallet.getAddress()[0], 0)
|
||||||
|
assert.strictEqual(wallet.getAddress()[1] >>> 4, 0)
|
||||||
|
})
|
||||||
|
it('should generate an account with 000 prefix (string)', function() {
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
const wallet = Wallet.generateVanityAddress('^000')
|
||||||
|
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
||||||
|
assert.strictEqual(wallet.getAddress()[0], 0)
|
||||||
|
assert.strictEqual(wallet.getAddress()[1] >>> 4, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.getV3Filename()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.getV3Filename(1457917509265),
|
||||||
|
'UTC--2016-03-14T01-05-09.265Z--b14ab53e38da1c172f877dbc6d65e4a1b0474c3c',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.toV3()', function() {
|
||||||
|
const salt = Buffer.from(
|
||||||
|
'dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6',
|
||||||
|
'hex',
|
||||||
|
)
|
||||||
|
const iv = Buffer.from('cecacd85e9cb89788b5aab2f93361233', 'hex')
|
||||||
|
const uuid = Buffer.from('7e59dc028d42d09db29aa8a0f862cc81', 'hex')
|
||||||
|
|
||||||
|
it('should work with PBKDF2', function() {
|
||||||
|
const w =
|
||||||
|
'{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"01ee7f1a3c8d187ea244c92eea9e332ab0bb2b4c902d89bdd71f80dc384da1be","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"pbkdf2","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","c":262144,"prf":"hmac-sha256"},"mac":"0c02cd0badfebd5e783e0cf41448f84086a96365fc3456716c33641a86ebc7cc"}}'
|
||||||
|
// FIXME: just test for ciphertext and mac?
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.toV3String('testtest', { kdf: 'pbkdf2', uuid: uuid, salt: salt, iv: iv }),
|
||||||
|
w,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should work with Scrypt', function() {
|
||||||
|
const w =
|
||||||
|
'{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"c52682025b1e5d5c06b816791921dbf439afe7a053abb9fac19f38a57499652c","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","n":262144,"r":8,"p":1},"mac":"27b98c8676dc6619d077453b38db645a4c7c17a3e686ee5adaf53c11ac1b890e"}}'
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
// FIXME: just test for ciphertext and mac?
|
||||||
|
assert.strictEqual(
|
||||||
|
fixtureWallet.toV3String('testtest', { kdf: 'scrypt', uuid: uuid, salt: salt, iv: iv }),
|
||||||
|
w,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should work without providing options', function() {
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
assert.strictEqual(fixtureWallet.toV3('testtest')['version'], 3)
|
||||||
|
})
|
||||||
|
it('should fail for unsupported kdf', function() {
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
assert.throws(function() {
|
||||||
|
fixtureWallet.toV3('testtest', { kdf: 'superkey' })
|
||||||
|
}, /^Error: Unsupported kdf$/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
describe('.fromV1()', function () {
|
||||||
|
it('should work', function () {
|
||||||
|
const sample = '{"Address":"d4584b5f6229b7be90727b0fc8c6b91bb427821f","Crypto":{"CipherText":"07533e172414bfa50e99dba4a0ce603f654ebfa1ff46277c3e0c577fdc87f6bb4e4fe16c5a94ce6ce14cfa069821ef9b","IV":"16d67ba0ce5a339ff2f07951253e6ba8","KeyHeader":{"Kdf":"scrypt","KdfParams":{"DkLen":32,"N":262144,"P":1,"R":8,"SaltLen":32},"Version":"1"},"MAC":"8ccded24da2e99a11d48cda146f9cc8213eb423e2ea0d8427f41c3be414424dd","Salt":"06870e5e6a24e183a5c807bd1c43afd86d573f7db303ff4853d135cd0fd3fe91"},"Id":"0498f19a-59db-4d54-ac95-33901b4f1870","Version":"1"}'
|
||||||
|
const wallet = Wallet.fromV1(sample, 'foo')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0xd4584b5f6229b7be90727b0fc8c6b91bb427821f')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('.fromV3()', function() {
|
||||||
|
it('should work with PBKDF2', function() {
|
||||||
|
const w =
|
||||||
|
'{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
||||||
|
const wallet = Wallet.fromV3(w, 'testpassword')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b')
|
||||||
|
})
|
||||||
|
it('should work with Scrypt', function() {
|
||||||
|
const sample =
|
||||||
|
'{"address":"2f91eb73a6cd5620d7abb50889f24eea7a6a4feb","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a2bc4f71e8445d64ceebd1247079fbd8"},"ciphertext":"6b9ab7954c9066fa1e54e04e2c527c7d78a77611d5f84fede1bd61ab13c51e3e","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"caf551e2b7ec12d93007e528093697a4c68e8a50e663b2a929754a8085d9ede4"},"mac":"506cace9c5c32544d39558025cb3bf23ed94ba2626e5338c82e50726917e1a15"},"id":"1b3cad9b-fa7b-4817-9022-d5e598eb5fe3","version":3}'
|
||||||
|
const wallet = Wallet.fromV3(sample, 'testtest')
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x2f91eb73a6cd5620d7abb50889f24eea7a6a4feb')
|
||||||
|
})
|
||||||
|
it("should work with 'unencrypted' wallets", function() {
|
||||||
|
const w =
|
||||||
|
'{"address":"a9886ac7489ecbcbd79268a79ef00d940e5fe1f2","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c542cf883299b5b0a29155091054028d"},"ciphertext":"0a83c77235840cffcfcc5afe5908f2d7f89d7d54c4a796dfe2f193e90413ee9d","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"699f7bf5f6985068dfaaff9db3b06aea8fe3dd3140b3addb4e60620ee97a0316"},"mac":"613fed2605240a2ff08b8d93ccc48c5b3d5023b7088189515d70df41d65f44de"},"id":"0edf817a-ee0e-4e25-8314-1f9e88a60811","version":3}'
|
||||||
|
const wallet = Wallet.fromV3(w, '')
|
||||||
|
this.timeout(180000) // 3minutes
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0xa9886ac7489ecbcbd79268a79ef00d940e5fe1f2')
|
||||||
|
})
|
||||||
|
it('should fail with invalid password', function() {
|
||||||
|
const w =
|
||||||
|
'{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromV3(w, 'wrongtestpassword')
|
||||||
|
}, /^Error: Key derivation failed - possibly wrong passphrase$/)
|
||||||
|
})
|
||||||
|
it('should work with (broken) mixed-case input files', function() {
|
||||||
|
const w =
|
||||||
|
'{"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
||||||
|
const wallet = Wallet.fromV3(w, 'testpassword', true)
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b')
|
||||||
|
})
|
||||||
|
it("shouldn't work with (broken) mixed-case input files in strict mode", function() {
|
||||||
|
const w =
|
||||||
|
'{"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromV3(w, 'testpassword')
|
||||||
|
}) // FIXME: check for assert message(s)
|
||||||
|
})
|
||||||
|
it('should fail for wrong version', function() {
|
||||||
|
const w = '{"version":2}'
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromV3(w, 'testpassword')
|
||||||
|
}, /^Error: Not a V3 wallet$/)
|
||||||
|
})
|
||||||
|
it('should fail for wrong kdf', function() {
|
||||||
|
const w = '{"crypto":{"kdf":"superkey"},"version":3}'
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromV3(w, 'testpassword')
|
||||||
|
}, /^Error: Unsupported key derivation scheme$/)
|
||||||
|
})
|
||||||
|
it('should fail for wrong prf in pbkdf2', function() {
|
||||||
|
const w = '{"crypto":{"kdf":"pbkdf2","kdfparams":{"prf":"invalid"}},"version":3}'
|
||||||
|
assert.throws(function() {
|
||||||
|
Wallet.fromV3(w, 'testpassword')
|
||||||
|
}, /^Error: Unsupported parameters to PBKDF2$/)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromEthSale()', function() {
|
||||||
|
// Generated using https://github.com/ethereum/pyethsaletool/ [4afd19ad60cee8d09b645555180bc3a7c8a25b67]
|
||||||
|
it('should work with short password (8 characters)', function() {
|
||||||
|
const json =
|
||||||
|
'{"encseed": "81ffdfaf2736310ce87df268b53169783e8420b98f3405fb9364b96ac0feebfb62f4cf31e0d25f1ded61f083514dd98c3ce1a14a24d7618fd513b6d97044725c7d2e08a7d9c2061f2c8a05af01f06755c252f04cab20fee2a4778130440a9344", "ethaddr": "22f8c5dd4a0a9d59d580667868df2da9592ab292", "email": "hello@ethereum.org", "btcaddr": "1DHW32MFwHxU2nk2SLAQq55eqFotT9jWcq"}'
|
||||||
|
const wallet = Wallet.fromEthSale(json, 'testtest')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x22f8c5dd4a0a9d59d580667868df2da9592ab292')
|
||||||
|
})
|
||||||
|
it('should work with long password (19 characters)', function() {
|
||||||
|
const json =
|
||||||
|
'{"encseed": "0c7e462bd67c6840ed2fa291090b2f46511b798d34492e146d6de148abbccba45d8fcfc06bea2e5b9d6c5d17b51a9a046c1054a032f24d96a56614a14dcd02e3539685d7f09b93180067160f3a9db648ccca610fc2f983fc65bf973304cbf5b6", "ethaddr": "c90b232231c83b462723f473b35cb8b1db868108", "email": "thisisalongpassword@test.com", "btcaddr": "1Cy2fN2ov5BrMkzgrzE34YadCH2yLMNkTE"}'
|
||||||
|
const wallet = Wallet.fromEthSale(json, 'thisisalongpassword')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0xc90b232231c83b462723f473b35cb8b1db868108')
|
||||||
|
})
|
||||||
|
// From https://github.com/ryepdx/pyethrecover/blob/master/test_wallets/ico.json
|
||||||
|
it("should work with pyethrecover's wallet", function() {
|
||||||
|
const json =
|
||||||
|
'{"encseed": "8b4001bf61a10760d8e0876fb791e4ebeb85962f565c71697c789c23d1ade4d1285d80b2383ae5fc419ecf5319317cd94200b65df0cc50d659cbbc4365fc08e8", "ethaddr": "83b6371ba6bd9a47f82a7c4920835ef4be08f47b", "bkp": "9f566775e56486f69413c59f7ef923bc", "btcaddr": "1Nzg5v6uRCAa6Fk3CUU5qahWxEDZdZ1pBm"}'
|
||||||
|
const wallet = Wallet.fromEthSale(json, 'password123')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x83b6371ba6bd9a47f82a7c4920835ef4be08f47b')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromEtherWallet()', function() {
|
||||||
|
// it('should work with unencrypted input', function () {
|
||||||
|
// const etherWalletUnencrypted = '{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":false,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"a2c6222146ca2269086351fda9f8d2dfc8a50331e8a05f0f400c13653a521862","public":"2ed129b50b1a4dbbc53346bf711df6893265ad0c700fd11431b0bc3a66bd383a87b10ad835804a6cbe092e0375a0cc3524acf06b1ec7bb978bf25d2d6c35d120"}'
|
||||||
|
// const wallet = Thirdparty.fromEtherWallet(etherWalletUnencrypted)
|
||||||
|
// assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
|
||||||
|
// })
|
||||||
|
it('should work with encrypted input', function() {
|
||||||
|
const etherWalletEncrypted =
|
||||||
|
'{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":true,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"U2FsdGVkX1/hGPYlTZYGhzdwvtkoZfkeII4Ga4pSd/Ak373ORnwZE4nf/FFZZFcDTSH1X1+AmewadrW7dqvwr76QMYQVlihpPaFV307hWgKckkG0Mf/X4gJIQQbDPiKdcff9","public":"U2FsdGVkX1/awUDAekZQbEiXx2ct4ugXwgBllY0Hz+IwYkHiEhhxH+obu7AF7PCU2Vq5c0lpCzBUSvk2EvFyt46bw1OYIijw0iOr7fWMJEkz3bfN5mt9pYJIiPzN0gxM8u4mrmqLPUG2SkoZhWz4NOlqRUHZq7Ep6aWKz7KlEpzP9IrvDYwGubci4h+9wsspqtY1BdUJUN59EaWZSuOw1g=="}'
|
||||||
|
const wallet = Thirdparty.fromEtherWallet(etherWalletEncrypted, 'testtest')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromEtherCamp()', function() {
|
||||||
|
it('should work with seed text', function() {
|
||||||
|
const wallet = Thirdparty.fromEtherCamp('ethercamp123')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x182b6ca390224c455f11b6337d74119305014ed4')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromKryptoKit()', function() {
|
||||||
|
// it('should work with basic input (d-type)', function () {
|
||||||
|
// const wallet = Thirdparty.fromKryptoKit('dBWfH8QZSGbg1sAYHLBhqE5R8VGAoM7')
|
||||||
|
// assert.strictEqual(wallet.getAddressString(), '0x3611981ad2d6fc1d7579d6ce4c6bc37e272c369c')
|
||||||
|
// })
|
||||||
|
it('should work with encrypted input (q-type)', function() {
|
||||||
|
const wallet = Thirdparty.fromKryptoKit(
|
||||||
|
'qhah1VeT0RgTvff1UKrUrxtFViiQuki16dd353d59888c25',
|
||||||
|
'testtest',
|
||||||
|
)
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x3c753e27834db67329d1ec1fab67970ec1e27112')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.fromQuorumWallet()', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
const wallet = Thirdparty.fromQuorumWallet('testtesttest', 'ethereumjs-wallet')
|
||||||
|
assert.strictEqual(wallet.getAddressString(), '0x1b86ccc22e8f137f204a41a23033541242a48815')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('raw new Wallet() init', function() {
|
||||||
|
it('should fail when both priv and pub key provided', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
new Wallet(fixturePrivateKeyBuffer, fixturePublicKeyBuffer) // eslint-disable-line
|
||||||
|
}, /^Error: Cannot supply both a private and a public key to the constructor$/)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,4 @@
|
||||||
|
--require ./node_modules/ts-node/register
|
||||||
|
--require ./node_modules/source-map-support/register
|
||||||
|
--recursive
|
||||||
|
--exit
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "@ethereumjs/config-tsc",
|
||||||
|
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": "@ethereumjs/config-tsc",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "@ethereumjs/config-tslint"
|
||||||
|
}
|
Loading…
Reference in New Issue