├── .eslintrc.js ├── .github ├── contributing.md └── workflows │ └── build.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── README.md ├── classes │ ├── ethereumhdkey.md │ └── wallet.md └── interfaces │ ├── etherwalletoptions.md │ └── evpkdfopts.md ├── karma.conf.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── hdkey.ts ├── index.ts └── thirdparty.ts ├── test ├── hdkey.spec.ts └── index.spec.ts ├── tsconfig.json └── tsconfig.prod.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint', 'implicit-dependencies', 'import', 'prettier'], 4 | env: { 5 | es2020: true, 6 | node: true, 7 | mocha: true 8 | }, 9 | ignorePatterns: [ 10 | 'node_modules/', 11 | 'dist/', 12 | 'coverage/', 13 | 'prettier.config.js', 14 | 'typedoc.js', 15 | 'karma.conf.js', 16 | ], 17 | extends: ['typestrict', 'eslint:recommended'], 18 | rules: { 19 | 'no-console': 'warn', 20 | 'no-debugger': 'error', 21 | 'prefer-const': 'error', 22 | 'no-var': 'error', 23 | 'no-constant-condition': 'warn', 24 | 'implicit-dependencies/no-implicit': ['error', { peer: true, dev: true, optional: true }], 25 | '@typescript-eslint/prefer-nullish-coalescing': 'error', 26 | '@typescript-eslint/no-use-before-define': 'warn', 27 | '@typescript-eslint/naming-convention': [ 28 | 'error', 29 | { 30 | selector: 'interface', 31 | format: ['PascalCase', 'camelCase'], 32 | custom: { 33 | regex: '^I[A-Z]', 34 | match: false, 35 | }, 36 | }, 37 | ], 38 | 'no-unused-vars': 'off', 39 | '@typescript-eslint/no-unused-vars': [ 40 | 'error', 41 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, 42 | ], 43 | '@typescript-eslint/no-unnecessary-condition': 'off', 44 | 'no-dupe-class-members': 'off', 45 | 'no-extra-semi': 'off', 46 | 'prettier/prettier': 'error', 47 | 'no-redeclare': 'off', 48 | '@typescript-eslint/no-redeclare': ['error'], 49 | '@typescript-eslint/restrict-plus-operands': 'off', 50 | 'import/no-default-export' : 'off' 51 | }, 52 | overrides: [ 53 | { 54 | files: ['test/index.spec.ts'], 55 | rules: { 56 | 'no-invalid-this': 'off', 57 | 'no-prototype-builtins': 'off', 58 | '@typescript-eslint/no-floating-promises': 'off', 59 | }, 60 | }, 61 | ], 62 | parserOptions: { 63 | sourceType: 'module', 64 | project: './tsconfig.json', 65 | }, 66 | } 67 | 68 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Great that you want to contribute to the `EthereumJS` [ecosystem](https://ethereumjs.readthedocs.io/en/latest/introduction.html). `EthereumJS` is managed by the Ethereum Foundation and largely driven by the wider community. Everyone is welcome to join the effort and help to improve on the libraries (see our [Code of Conduct](https://ethereumjs.readthedocs.io/en/latest/code_of_conduct.html) 🌷). 4 | 5 | We have written up some [Contribution Guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html#how-to-start) to help you getting started. 6 | 7 | These include information on how we work with **Git** and how our **general workflow** and **technical setup** looks like (stuff like language, tooling, code quality and style). 8 | 9 | Happy Coding! 👾 😀 💻 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - '*' 8 | pull_request: 9 | types: [opened, reopened, synchronize] 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [14, 16, 18] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | cache: 'npm' 23 | - run: npm i 24 | - run: npm run lint 25 | - run: npm run coverage 26 | test-browser: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Use Node.js 14 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: 14 34 | cache: 'npm' 35 | - run: npm i 36 | - run: npm run test:browser 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | .nyc_output 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 29 | node_modules 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional REPL history 35 | .node_repl_history 36 | 37 | 38 | # IDE and text editor config files 39 | .idea 40 | .vscode 41 | 42 | # build output 43 | dist 44 | dist.browser 45 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | package.json 4 | dist 5 | dist.browser 6 | .nyc_output 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | (modification: no type change headlines) and this project adheres to 7 | [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 8 | 9 | ## [1.0.2] - 2021-10-08 10 | 11 | - Updated dependencies to latest, added browser build, PR [#157](https://github.com/ethereumjs/ethereumjs-wallet/pull/157) 12 | 13 | #### Included Source Files 14 | 15 | Source files from the `src` folder are now included in the distribution build. This allows for a better debugging experience in debug tools like Chrome DevTools by having working source map references to the original sources available for inspection. 16 | 17 | [1.0.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v1.0.1...v1.0.2 18 | 19 | ## [1.0.1] - 2020-09-25 20 | 21 | - Fixed a browser issue in `Wallet.fromV3()` and `Wallet.toV3()` triggered when using web bundlers using Buffer v4 shim (Webpack 4), 22 | see PR [#135](https://github.com/ethereumjs/ethereumjs-wallet/pull/135) 23 | 24 | [1.0.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v1.0.0...v1.0.1 25 | 26 | ## [1.0.0] - 2020-06-23 27 | 28 | This is the first `TypeScript` release on the library (thanks @the-jackalope for the rewrite! ❤️), see PR [#93](https://github.com/ethereumjs/ethereumjs-wallet/pull/93) for the main PR here. The release comes with various breaking changes. 29 | 30 | ### Libray Import / API Documentation 31 | 32 | The way submodules are exposed has been changed along the `TypeScript` rewrite and you will likely have to update your imports. Here is an example for the `hdkey` submodule: 33 | 34 | Node.js / ES5: 35 | 36 | ```js 37 | const { hdkey } = require('ethereumjs-wallet') 38 | ``` 39 | 40 | ESM / TypeScript: 41 | 42 | ```js 43 | import { hdkey } from 'ethereumjs-wallet' 44 | ``` 45 | 46 | See [README](https://github.com/ethereumjs/ethereumjs-wallet#wallet-api) for examples on the other submodules. 47 | 48 | Together with the switch to `TypeScript` the previously static documentation has been automated to now being generated with `TypeDoc` to reflect all latest changes, see PR [#98](https://github.com/ethereumjs/ethereumjs-wallet/pull/98). See the new [docs](https://github.com/ethereumjs/ethereumjs-wallet/blob/master/docs/README.md) for an overview on the `TypeScript` based API. 49 | 50 | ### API Changes 51 | 52 | The API of the library hasn't been changed intentionally but has become more strict on type input by the explcit type definitions from the `TypeScript` code in function signatures together with the introduction of the `ethereumjs-util` [v7](https://github.com/ethereumjs/ethereumjs-util/releases) library within the `Wallet` library, which behaves more strict on type input on the various utility functions. 53 | 54 | This leads to cases where some input - while not having been the intended way to use the library - might have been worked before through implicit type conversion and is now not possible any more. 55 | 56 | One example for this is the `Wallet.fromPublicKey()` function, here is the old code of the function: 57 | 58 | ```js 59 | Wallet.fromPublicKey = function (pub, nonStrict) { 60 | if (nonStrict) { 61 | pub = ethUtil.importPublic(pub) 62 | } 63 | return new Wallet(null, pub) 64 | } 65 | ``` 66 | 67 | and here the new `TypeScript` code: 68 | 69 | ```typescript 70 | public static fromPublicKey(publicKey: Buffer, nonStrict: boolean = false): Wallet { 71 | if (nonStrict) { 72 | publicKey = importPublic(publicKey) 73 | } 74 | return new Wallet(undefined, publicKey) 75 | } 76 | ``` 77 | 78 | This function worked in the `v0.6.x` version also with passing in a string, since the `ethereumjs-util` `v6` `importPublic` method converted the input implicitly to a `Buffer`, the `v1.0.0` version now directly enforces the `fromPublicKey` input to be a `Buffer` first hand. 79 | 80 | There will likely be more cases like this in the code since the type input of the library hasn't been documented in the older version. So we recommend here to go through all your function signature usages and see if you uses the correct input types. While a bit annoying this is a one-time task you will never have to do again since you can now profit from the clear `TypeScript` input types being both documented and enforced by the `TypeScript` compiler. 81 | 82 | ### Pure JS Crypto Dependencies 83 | 84 | This library now uses pure JS crypto dependencies which doesn't bring in the need for native compilation on installation. For `scrypt` key derivation [scrypt-js](https://github.com/ricmoo/scrypt-js) from @ricmoo is used (see PR [#125](https://github.com/ethereumjs/ethereumjs-wallet/pull/125)). 85 | 86 | For BIP-32 key derivation the new [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography) library is used which is a new Ethereum Foundation backed and formally audited libray to provide pure JS cryptographic primitives within the Ethereum ecosystem (see PR [#128](https://github.com/ethereumjs/ethereumjs-wallet/pull/128)). 87 | 88 | ### Removed ProviderEngine 89 | 90 | Support for Provider Engine has been removed for security reasons, since the package is not very actively maintained and superseded by [`json-rpc-engine`](https://github.com/MetaMask/web3-provider-engine#web3-providerengine). 91 | 92 | If you need the removed functionality, it should be relatively easily possible to do this integration by adopting the code from [provider-engine.ts](https://github.com/ethereumjs/ethereumjs-wallet/blob/v0.6.x/src/provider-engine.js). 93 | 94 | See also: PR [#117](https://github.com/ethereumjs/ethereumjs-wallet/pull/117) 95 | 96 | ### Other Changes 97 | 98 | #### Bug Fixes 99 | 100 | - Fixes a bug where `salt`, `iv` and/or `uuid` options - being supplied as strings to `Wallet.toV3()` - could lead to errors during encryption and/or output that could not be decrypted, 101 | PR [#95](https://github.com/ethereumjs/ethereumjs-wallet/pull/95) 102 | 103 | #### Refactoring & Maintenance 104 | 105 | - `ES6` class rewrite, 106 | PR [#93](https://github.com/ethereumjs/ethereumjs-wallet/pull/93) (`TypeScript` PR) 107 | - Added support for Node 12, 13, and 14, upgraded CI provider to use GH Actions in place of Travis, 108 | PR [#120](https://github.com/ethereumjs/ethereumjs-wallet/pull/120) 109 | - Updated `ethereumjs-util` dependency from `v6` to 110 | [v7.0.2](https://github.com/ethereumjs/ethereumjs-util/releases/tag/v7.0.2 (stricter types), 111 | PR [#126](https://github.com/ethereumjs/ethereumjs-wallet/pull/126) 112 | - Refactored `Wallet.deciperBuffer()`, 113 | PR [#82](https://github.com/ethereumjs/ethereumjs-wallet/pull/82) 114 | 115 | #### Development & CI 116 | 117 | - Integrated the `ethereumjs-config` EthereumJS developer configuration standards, 118 | PR [#93](https://github.com/ethereumjs/ethereumjs-wallet/pull/93) (`TypeScript` PR) 119 | - Added org links and Git hooks, 120 | PR [#88](https://github.com/ethereumjs/ethereumjs-wallet/pull/88) 121 | 122 | [0.6.4]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.3...v1.0.0 123 | 124 | ## [0.6.4] - 2020-05-01 125 | 126 | This is the last release from the `v0.6.x` release series. It adds Node 12 compatibility while maintaining compatibility 127 | down to Node 6. To be able to do so the `scrypt.js` key derivation library is exchanged with `scryptsy`. While this solution is backwards-compatible the changed library only provides a pure JS implementation and no native bindings. If you need native performance pin your dependency to `v0.6.3` or update to the `v1.0.0` library version to be released shortly after this release. 128 | 129 | Change Summary: 130 | 131 | - v0.6.x back patch: added node v12 support, switched to `scryptsy` key derivation library (pure JS implementation), 132 | PR [#114](https://github.com/ethereumjs/ethereumjs-wallet/pull/114) 133 | - Updated `hdkey` to `v1.1.1`, 134 | PR [#87](https://github.com/ethereumjs/ethereumjs-wallet/pull/87) 135 | - Refactored `decipherBuffer()`, 136 | PR [#82](https://github.com/ethereumjs/ethereumjs-wallet/pull/82) 137 | - Added more tests for `Wallet.fromEthSale()`, 138 | PR [#80](https://github.com/ethereumjs/ethereumjs-wallet/pull/80) 139 | 140 | [0.6.4]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.3...v0.6.4 141 | 142 | ## [0.6.3] - 2018-12-19 143 | 144 | - Fixed installation errors for certain packaging tools, PR [#67](https://github.com/ethereumjs/ethereumjs-wallet/pull/67) 145 | - Remove dependency on `crypto.randomBytes` and use `randombytes` package instead, PR [#63](https://github.com/ethereumjs/ethereumjs-wallet/pull/63) 146 | - Add comprehensive test coverage for `fromV3`, PR [#62](https://github.com/ethereumjs/ethereumjs-wallet/pull/62) 147 | - Remove excess parameter from `decipherBuffer` usage, PR [#77](https://github.com/ethereumjs/ethereumjs-wallet/pull/77) 148 | - Update dependencies, including a fixed `scrypt.js`, which should resolve more installation issues, PR [#78](https://github.com/ethereumjs/ethereumjs-wallet/pull/78) 149 | 150 | [0.6.3]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.2...v0.6.3 151 | 152 | ## [0.6.2] - 2018-08-08 153 | 154 | - [PLEASE UPDATE!] Fixes a critical import bug introduced in `v0.6.1` accidentally 155 | changing the import path for the different submodules, see PR [#65](https://github.com/ethereumjs/ethereumjs-wallet/pull/65) 156 | 157 | [0.6.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.1...v0.6.2 158 | 159 | ## [0.6.1] - 2018-07-28 [DEPRECATED] 160 | 161 | - Added support for vanity address generation, PR [#5](https://github.com/ethereumjs/ethereumjs-wallet/pull/5) 162 | - Fixed typo in provider-engine, PR [#16](https://github.com/ethereumjs/ethereumjs-wallet/pull/16) 163 | - Accept the true range of addresses for ICAP direct, PR [#6](https://github.com/ethereumjs/ethereumjs-wallet/pull/6) 164 | - Switched to babel ES5 build, PR [#37](https://github.com/ethereumjs/ethereumjs-wallet/pull/37) 165 | - Improve test coverage (at 88% now), PR [#27](https://github.com/ethereumjs/ethereumjs-wallet/pull/27) 166 | - Various dependency updates, PR [#25](https://github.com/ethereumjs/ethereumjs-wallet/pull/25) 167 | 168 | [0.6.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.6.0...v0.6.1 169 | 170 | ## [0.6.0] - 2016-04-27 171 | 172 | - Added provider-engine integration, PR [#7](https://github.com/ethereumjs/ethereumjs-wallet/pull/7) 173 | 174 | [0.6.0]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.2...v0.6.0 175 | 176 | ## [0.5.2] - 2016-04-25 177 | 178 | - Dependency updates 179 | 180 | [0.5.2]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.1...v0.5.2 181 | 182 | ## [0.5.1] - 2016-03-26 183 | 184 | - Bugfix for `EthereumHDKey.privateExtendedKey()` 185 | - Added travis and coveralls support 186 | - Documentation and test improvements 187 | 188 | [0.5.1]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.5.0...v0.5.1 189 | 190 | ## [0.5.0] - 2016-03-23 191 | 192 | - Support HD keys using `cryptocoinjs/hdkey` 193 | - Ensure private keys are valid according to the curve 194 | - Support instantation with public keys 195 | - Support importing BIP32 xpub/xpriv 196 | - Only support Ethereum keys internally, non-strict mode for importing compressed ones 197 | - Thirdparty API doc improvements 198 | 199 | [0.5.0]: https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.4.0...v0.5.0 200 | 201 | ## Older releases: 202 | 203 | - [0.4.0](https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.3.0...v0.4.0) - 2016-03-16 204 | - [0.3.0](https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.2.1...v0.3.0) - 2016-03-09 205 | - [0.2.1](https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.2.0...v0.2.1) - 2016-03-07 206 | - [0.2.0](https://github.com/ethereumjs/ethereumjs-wallet/compare/v0.1.0...v0.2.0) - 2016-03-07 207 | - 0.1.0 - 2016-02-23 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Beregszaszi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ethereumjs-wallet 2 | 3 | > [!WARNING] 4 | > The repository has been merged into [ethereumjs-monorepo](https://github.com/ethereumjs/ethereumjs-monorepo). Please head to the new repo for updates. 5 | 6 | --- 7 | 8 | 9 | A lightweight wallet implementation. At the moment it supports key creation and conversion between various formats. 10 | 11 | It is complemented by the following packages: 12 | 13 | - [@ethereumjs/tx](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/tx) to sign transactions 14 | - [ethereumjs-icap](https://github.com/ethereumjs/ethereumjs-icap) to manipulate ICAP addresses 15 | - [store.js](https://github.com/marcuswestin/store.js) to use browser storage 16 | 17 | Motivations are: 18 | 19 | - be lightweight 20 | - work in a browser 21 | - use a single, maintained version of crypto library (and that should be in line with [`@ethereumjs/util`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) and `@ethereumjs/tx`) 22 | - support import/export between various wallet formats 23 | - support BIP32 HD keys 24 | 25 | Features not supported: 26 | 27 | - signing transactions 28 | - managing storage (neither in node.js or the browser) 29 | 30 | ## Wallet API 31 | 32 | For information about the Wallet's API, please go to [./docs/classes/wallet.md](./docs/classes/wallet.md). 33 | 34 | You can import the `Wallet` class like this 35 | 36 | Node.js / ES6: 37 | 38 | ```js 39 | const Wallet = require('ethereumjs-wallet').default 40 | ``` 41 | 42 | ESM / TypeScript: 43 | 44 | ```js 45 | import Wallet from 'ethereumjs-wallet' 46 | ``` 47 | 48 | ## Thirdparty API 49 | 50 | Importing various third party wallets is possible through the `thirdparty` submodule: 51 | 52 | Node.js / ES5: 53 | 54 | ```js 55 | const { thirdparty } = require('ethereumjs-wallet') 56 | ``` 57 | 58 | ESM / TypeScript: 59 | 60 | ```js 61 | import { thirdparty } from 'ethereumjs-wallet' 62 | ``` 63 | 64 | Please go to [./docs/README.md](./docs/README.md) for more info. 65 | 66 | ## HD Wallet API 67 | 68 | To use BIP32 HD wallets, first include the `hdkey` submodule: 69 | 70 | Node.js / ES5: 71 | 72 | ```js 73 | const { hdkey } = require('ethereumjs-wallet') 74 | ``` 75 | 76 | ESM / TypeScript: 77 | 78 | ```js 79 | import { hdkey } from 'ethereumjs-wallet' 80 | ``` 81 | 82 | Please go to [./docs/classes/ethereumhdkey.md](./docs/classes/ethereumhdkey.md) for more info. 83 | 84 | ## Provider Engine 85 | 86 | Provider Engine is 87 | [not very actively maintained](https://github.com/MetaMask/web3-provider-engine#web3-providerengine) 88 | and support has been removed along `v1.0.0` release, see 89 | issue [#115](https://github.com/ethereumjs/ethereumjs-wallet/issues/115) for context. 90 | 91 | You can use the the old `src/provider-engine.ts` code (see associated PR) as some boilerplate 92 | for your own integration if needed. 93 | 94 | ## Remarks about `toV3` 95 | 96 | The `options` is an optional object hash, where all the serialization parameters can be fine tuned: 97 | 98 | - uuid - UUID. One is randomly generated. 99 | - 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. 100 | - iv - Initialization vector for the `cipher`. Size must match the requirements of the cipher. Random number generated via `crypto.getRandomBytes` if nothing is supplied. 101 | - kdf - The key derivation function, see below. 102 | - dklen - Derived key length. For certain `cipher` settings, this must match the block sizes of those. 103 | - cipher - The cipher to use. Names must match those of supported by `OpenSSL`, e.g. `aes-128-ctr` or `aes-128-cbc`. 104 | 105 | Depending on the `kdf` selected, the following options are available too. 106 | 107 | For `pbkdf2`: 108 | 109 | - `c` - Number of iterations. Defaults to 262144. 110 | - `prf` - The only supported (and default) value is `hmac-sha256`. So no point changing it. 111 | 112 | For `scrypt`: 113 | 114 | - `n` - Iteration count. Defaults to 262144. 115 | - `r` - Block size for the underlying hash. Defaults to 8. 116 | - `p` - Parallelization factor. Defaults to 1. 117 | 118 | The following settings are favoured by the Go Ethereum implementation and we default to the same: 119 | 120 | - `kdf`: `scrypt` 121 | - `dklen`: `32` 122 | - `n`: `262144` 123 | - `r`: `8` 124 | - `p`: `1` 125 | - `cipher`: `aes-128-ctr` 126 | 127 | # EthereumJS 128 | 129 | See our organizational [documentation](https://ethereumjs.readthedocs.io) for an introduction to `EthereumJS` as well as information on current standards and best practices. 130 | 131 | If you want to join for work or do improvements on the libraries have a look at our [contribution guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html). 132 | 133 | ## License 134 | 135 | MIT License 136 | 137 | Copyright (C) 2016 Alex Beregszaszi 138 | 139 | [actions-badge]: https://github.com/ethereumjs/ethereumjs-wallet/workflows/Build/badge.svg 140 | [actions-link]: https://github.com/ethereumjs/ethereumjs-wallet/actions 141 | [coverage-badge]: https://img.shields.io/coveralls/ethereumjs/ethereumjs-wallet.svg 142 | [coverage-link]: https://coveralls.io/r/ethereumjs/ethereumjs-wallet 143 | [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue 144 | [discord-link]: https://discord.gg/TNwARpR 145 | [npm-badge]: https://img.shields.io/npm/v/ethereumjs-wallet.svg 146 | [npm-link]: https://www.npmjs.org/package/ethereumjs-wallet 147 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ethereumjs-wallet 2 | 3 | ## Index 4 | 5 | ### Classes 6 | 7 | - [EthereumHDKey](classes/ethereumhdkey.md) 8 | - [Wallet](classes/wallet.md) 9 | 10 | ### Interfaces 11 | 12 | - [EtherWalletOptions](interfaces/etherwalletoptions.md) 13 | - [EvpKdfOpts](interfaces/evpkdfopts.md) 14 | 15 | ### Functions 16 | 17 | - [fromEtherCamp](#fromethercamp) 18 | - [fromEtherWallet](#frometherwallet) 19 | - [fromQuorumWallet](#fromquorumwallet) 20 | 21 | --- 22 | 23 | ## Functions 24 | 25 | 26 | 27 | ### fromEtherCamp 28 | 29 | ▸ **fromEtherCamp**(passphrase: _`string`_): [Wallet](classes/wallet.md) 30 | 31 | _Defined in [thirdparty.ts:169](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L169)_ 32 | 33 | **Parameters:** 34 | 35 | | Name | Type | 36 | | ---------- | -------- | 37 | | passphrase | `string` | 38 | 39 | **Returns:** [Wallet](classes/wallet.md) 40 | 41 | --- 42 | 43 | 44 | 45 | ### fromEtherWallet 46 | 47 | ▸ **fromEtherWallet**(input: _`string` \| [EtherWalletOptions](interfaces/etherwalletoptions.md)_, password: _`string`_): [Wallet](classes/wallet.md) 48 | 49 | _Defined in [thirdparty.ts:121](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L121)_ 50 | 51 | **Parameters:** 52 | 53 | | Name | Type | 54 | | -------- | ------------------------------------------------------------------ | 55 | | input | `string` \| [EtherWalletOptions](interfaces/etherwalletoptions.md) | 56 | | password | `string` | 57 | 58 | **Returns:** [Wallet](classes/wallet.md) 59 | 60 | --- 61 | 62 | 63 | 64 | ### fromQuorumWallet 65 | 66 | ▸ **fromQuorumWallet**(passphrase: _`string`_, userid: _`string`_): [Wallet](classes/wallet.md) 67 | 68 | _Defined in [thirdparty.ts:265](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L265)_ 69 | 70 | **Parameters:** 71 | 72 | | Name | Type | 73 | | ---------- | -------- | 74 | | passphrase | `string` | 75 | | userid | `string` | 76 | 77 | **Returns:** [Wallet](classes/wallet.md) 78 | 79 | --- 80 | -------------------------------------------------------------------------------- /docs/classes/ethereumhdkey.md: -------------------------------------------------------------------------------- 1 | [ethereumjs-wallet](../README.md) > [EthereumHDKey](../classes/ethereumhdkey.md) 2 | 3 | # Class: EthereumHDKey 4 | 5 | ## Hierarchy 6 | 7 | **EthereumHDKey** 8 | 9 | ## Index 10 | 11 | ### Constructors 12 | 13 | - [constructor](ethereumhdkey.md#constructor) 14 | 15 | ### Properties 16 | 17 | - [\_hdkey](ethereumhdkey.md#_hdkey) 18 | 19 | ### Methods 20 | 21 | - [deriveChild](ethereumhdkey.md#derivechild) 22 | - [derivePath](ethereumhdkey.md#derivepath) 23 | - [getWallet](ethereumhdkey.md#getwallet) 24 | - [privateExtendedKey](ethereumhdkey.md#privateextendedkey) 25 | - [publicExtendedKey](ethereumhdkey.md#publicextendedkey) 26 | - [fromExtendedKey](ethereumhdkey.md#fromextendedkey) 27 | - [fromMasterSeed](ethereumhdkey.md#frommasterseed) 28 | 29 | --- 30 | 31 | ## Constructors 32 | 33 | 34 | 35 | ### constructor 36 | 37 | ⊕ **new EthereumHDKey**(\_hdkey: _`any`_): [EthereumHDKey](ethereumhdkey.md) 38 | 39 | _Defined in [hdkey.ts:21](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L21)_ 40 | 41 | **Parameters:** 42 | 43 | | Name | Type | 44 | | ------------------ | ----- | 45 | | `Optional` \_hdkey | `any` | 46 | 47 | **Returns:** [EthereumHDKey](ethereumhdkey.md) 48 | 49 | --- 50 | 51 | ## Properties 52 | 53 | 54 | 55 | ### ` `` ` \_hdkey 56 | 57 | **● \_hdkey**: _`any`_ 58 | 59 | _Defined in [hdkey.ts:23](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L23)_ 60 | 61 | --- 62 | 63 | ## Methods 64 | 65 | 66 | 67 | ### deriveChild 68 | 69 | ▸ **deriveChild**(index: _`number`_): [EthereumHDKey](ethereumhdkey.md) 70 | 71 | _Defined in [hdkey.ts:52](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L52)_ 72 | 73 | **Parameters:** 74 | 75 | | Name | Type | 76 | | ----- | -------- | 77 | | index | `number` | 78 | 79 | **Returns:** [EthereumHDKey](ethereumhdkey.md) 80 | 81 | --- 82 | 83 | 84 | 85 | ### derivePath 86 | 87 | ▸ **derivePath**(path: _`string`_): [EthereumHDKey](ethereumhdkey.md) 88 | 89 | _Defined in [hdkey.ts:45](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L45)_ 90 | 91 | **Parameters:** 92 | 93 | | Name | Type | 94 | | ---- | -------- | 95 | | path | `string` | 96 | 97 | **Returns:** [EthereumHDKey](ethereumhdkey.md) 98 | 99 | --- 100 | 101 | 102 | 103 | ### getWallet 104 | 105 | ▸ **getWallet**(): [Wallet](wallet.md) 106 | 107 | _Defined in [hdkey.ts:59](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L59)_ 108 | 109 | **Returns:** [Wallet](wallet.md) 110 | 111 | --- 112 | 113 | 114 | 115 | ### privateExtendedKey 116 | 117 | ▸ **privateExtendedKey**(): `Buffer` 118 | 119 | _Defined in [hdkey.ts:28](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L28)_ 120 | 121 | **Returns:** `Buffer` 122 | 123 | --- 124 | 125 | 126 | 127 | ### publicExtendedKey 128 | 129 | ▸ **publicExtendedKey**(): `Buffer` 130 | 131 | _Defined in [hdkey.ts:38](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L38)_ 132 | 133 | **Returns:** `Buffer` 134 | 135 | --- 136 | 137 | 138 | 139 | ### `` fromExtendedKey 140 | 141 | ▸ **fromExtendedKey**(base58Key: _`string`_): [EthereumHDKey](ethereumhdkey.md) 142 | 143 | _Defined in [hdkey.ts:19](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L19)_ 144 | 145 | **Parameters:** 146 | 147 | | Name | Type | 148 | | --------- | -------- | 149 | | base58Key | `string` | 150 | 151 | **Returns:** [EthereumHDKey](ethereumhdkey.md) 152 | 153 | --- 154 | 155 | 156 | 157 | ### `` fromMasterSeed 158 | 159 | ▸ **fromMasterSeed**(seedBuffer: _`Buffer`_): [EthereumHDKey](ethereumhdkey.md) 160 | 161 | _Defined in [hdkey.ts:12](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/hdkey.ts#L12)_ 162 | 163 | **Parameters:** 164 | 165 | | Name | Type | 166 | | ---------- | -------- | 167 | | seedBuffer | `Buffer` | 168 | 169 | **Returns:** [EthereumHDKey](ethereumhdkey.md) 170 | 171 | --- 172 | -------------------------------------------------------------------------------- /docs/classes/wallet.md: -------------------------------------------------------------------------------- 1 | [ethereumjs-wallet](../README.md) > [Wallet](../classes/wallet.md) 2 | 3 | # Class: Wallet 4 | 5 | ## Hierarchy 6 | 7 | **Wallet** 8 | 9 | ## Index 10 | 11 | ### Constructors 12 | 13 | - [constructor](wallet.md#constructor) 14 | 15 | ### Properties 16 | 17 | - [privateKey](wallet.md#privatekey) 18 | - [publicKey](wallet.md#publickey) 19 | 20 | ### Accessors 21 | 22 | - [privKey](wallet.md#privkey) 23 | - [pubKey](wallet.md#pubkey) 24 | 25 | ### Methods 26 | 27 | - [getAddress](wallet.md#getaddress) 28 | - [getAddressString](wallet.md#getaddressstring) 29 | - [getChecksumAddressString](wallet.md#getchecksumaddressstring) 30 | - [getPrivateKey](wallet.md#getprivatekey) 31 | - [getPrivateKeyString](wallet.md#getprivatekeystring) 32 | - [getPublicKey](wallet.md#getpublickey) 33 | - [getPublicKeyString](wallet.md#getpublickeystring) 34 | - [getV3Filename](wallet.md#getv3filename) 35 | - [toV3](wallet.md#tov3) 36 | - [toV3String](wallet.md#tov3string) 37 | - [fromEthSale](wallet.md#fromethsale) 38 | - [fromExtendedPrivateKey](wallet.md#fromextendedprivatekey) 39 | - [fromExtendedPublicKey](wallet.md#fromextendedpublickey) 40 | - [fromPrivateKey](wallet.md#fromprivatekey) 41 | - [fromPublicKey](wallet.md#frompublickey) 42 | - [fromV1](wallet.md#fromv1) 43 | - [fromV3](wallet.md#fromv3) 44 | - [generate](wallet.md#generate) 45 | - [generateVanityAddress](wallet.md#generatevanityaddress) 46 | 47 | --- 48 | 49 | ## Constructors 50 | 51 | 52 | 53 | ### constructor 54 | 55 | ⊕ **new Wallet**(privateKey: _`Buffer` \| `undefined`_, publicKey?: _`Buffer` \| `undefined`_): [Wallet](wallet.md) 56 | 57 | _Defined in [index.ts:229](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L229)_ 58 | 59 | **Parameters:** 60 | 61 | | Name | Type | Default value | 62 | | ------------------------- | ----------------------- | ------------- | 63 | | `Optional` privateKey | `Buffer` \| `undefined` | - | 64 | | `Default value` publicKey | `Buffer` \| `undefined` | undefined | 65 | 66 | **Returns:** [Wallet](wallet.md) 67 | 68 | --- 69 | 70 | ## Properties 71 | 72 | 73 | 74 | ### ` `` ` privateKey 75 | 76 | **● privateKey**: _`Buffer` \| `undefined`_ 77 | 78 | _Defined in [index.ts:231](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L231)_ 79 | 80 | --- 81 | 82 | 83 | 84 | ### `` publicKey 85 | 86 | **● publicKey**: _`Buffer` \| `undefined`_ 87 | 88 | _Defined in [index.ts:232](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L232)_ 89 | 90 | --- 91 | 92 | ## Accessors 93 | 94 | 95 | 96 | ### `` privKey 97 | 98 | **privKey**: 99 | 100 | _Defined in [index.ts:480](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L480)_ 101 | 102 | --- 103 | 104 | 105 | 106 | ### `` pubKey 107 | 108 | **pubKey**: 109 | 110 | _Defined in [index.ts:470](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L470)_ 111 | 112 | --- 113 | 114 | ## Methods 115 | 116 | 117 | 118 | ### getAddress 119 | 120 | ▸ **getAddress**(): `Buffer` 121 | 122 | _Defined in [index.ts:520](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L520)_ 123 | 124 | **Returns:** `Buffer` 125 | 126 | --- 127 | 128 | 129 | 130 | ### getAddressString 131 | 132 | ▸ **getAddressString**(): `string` 133 | 134 | _Defined in [index.ts:527](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L527)_ 135 | 136 | **Returns:** `string` 137 | 138 | --- 139 | 140 | 141 | 142 | ### getChecksumAddressString 143 | 144 | ▸ **getChecksumAddressString**(): `string` 145 | 146 | _Defined in [index.ts:535](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L535)_ 147 | 148 | **Returns:** `string` 149 | 150 | --- 151 | 152 | 153 | 154 | ### getPrivateKey 155 | 156 | ▸ **getPrivateKey**(): `Buffer` 157 | 158 | _Defined in [index.ts:494](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L494)_ 159 | 160 | **Returns:** `Buffer` 161 | 162 | --- 163 | 164 | 165 | 166 | ### getPrivateKeyString 167 | 168 | ▸ **getPrivateKeyString**(): `string` 169 | 170 | _Defined in [index.ts:498](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L498)_ 171 | 172 | **Returns:** `string` 173 | 174 | --- 175 | 176 | 177 | 178 | ### getPublicKey 179 | 180 | ▸ **getPublicKey**(): `Buffer` 181 | 182 | _Defined in [index.ts:506](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L506)_ 183 | 184 | **Returns:** `Buffer` 185 | 186 | --- 187 | 188 | 189 | 190 | ### getPublicKeyString 191 | 192 | ▸ **getPublicKeyString**(): `string` 193 | 194 | _Defined in [index.ts:513](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L513)_ 195 | 196 | **Returns:** `string` 197 | 198 | --- 199 | 200 | 201 | 202 | ### getV3Filename 203 | 204 | ▸ **getV3Filename**(timestamp: _`undefined` \| `number`_): `string` 205 | 206 | _Defined in [index.ts:617](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L617)_ 207 | 208 | **Parameters:** 209 | 210 | | Name | Type | 211 | | -------------------- | ----------------------- | 212 | | `Optional` timestamp | `undefined` \| `number` | 213 | 214 | **Returns:** `string` 215 | 216 | --- 217 | 218 | 219 | 220 | ### toV3 221 | 222 | ▸ **toV3**(password: _`string`_, opts: _`Partial`<`V3Params`>_): `V3Keystore` 223 | 224 | _Defined in [index.ts:545](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L545)_ 225 | 226 | **Parameters:** 227 | 228 | | Name | Type | Description | 229 | | --------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | 230 | | password | `string` | The password used to encrypt the Keystore. | 231 | | `Optional` opts | `Partial`<`V3Params`> | The options for the keystore. See [its spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for more info. | 232 | 233 | **Returns:** `V3Keystore` 234 | 235 | --- 236 | 237 | 238 | 239 | ### toV3String 240 | 241 | ▸ **toV3String**(password: _`string`_, opts: _`Partial`<`V3Params`>_): `string` 242 | 243 | _Defined in [index.ts:635](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L635)_ 244 | 245 | **Parameters:** 246 | 247 | | Name | Type | 248 | | --------------- | --------------------- | 249 | | password | `string` | 250 | | `Optional` opts | `Partial`<`V3Params`> | 251 | 252 | **Returns:** `string` 253 | 254 | --- 255 | 256 | 257 | 258 | ### `` fromEthSale 259 | 260 | ▸ **fromEthSale**(input: _`string` \| `EthSaleKeystore`_, password: _`string`_): [Wallet](wallet.md) 261 | 262 | _Defined in [index.ts:444](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L444)_ 263 | 264 | **Parameters:** 265 | 266 | | Name | Type | 267 | | -------- | ----------------------------- | 268 | | input | `string` \| `EthSaleKeystore` | 269 | | password | `string` | 270 | 271 | **Returns:** [Wallet](wallet.md) 272 | 273 | --- 274 | 275 | 276 | 277 | ### `` fromExtendedPrivateKey 278 | 279 | ▸ **fromExtendedPrivateKey**(extendedPrivateKey: _`string`_): [Wallet](wallet.md) 280 | 281 | _Defined in [index.ts:321](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L321)_ 282 | 283 | **Parameters:** 284 | 285 | | Name | Type | 286 | | ------------------ | -------- | 287 | | extendedPrivateKey | `string` | 288 | 289 | **Returns:** [Wallet](wallet.md) 290 | 291 | --- 292 | 293 | 294 | 295 | ### `` fromExtendedPublicKey 296 | 297 | ▸ **fromExtendedPublicKey**(extendedPublicKey: _`string`_): [Wallet](wallet.md) 298 | 299 | _Defined in [index.ts:302](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L302)_ 300 | 301 | **Parameters:** 302 | 303 | | Name | Type | 304 | | ----------------- | -------- | 305 | | extendedPublicKey | `string` | 306 | 307 | **Returns:** [Wallet](wallet.md) 308 | 309 | --- 310 | 311 | 312 | 313 | ### `` fromPrivateKey 314 | 315 | ▸ **fromPrivateKey**(privateKey: _`Buffer`_): [Wallet](wallet.md) 316 | 317 | _Defined in [index.ts:314](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L314)_ 318 | 319 | **Parameters:** 320 | 321 | | Name | Type | 322 | | ---------- | -------- | 323 | | privateKey | `Buffer` | 324 | 325 | **Returns:** [Wallet](wallet.md) 326 | 327 | --- 328 | 329 | 330 | 331 | ### `` fromPublicKey 332 | 333 | ▸ **fromPublicKey**(publicKey: _`Buffer`_, nonStrict?: _`boolean`_): [Wallet](wallet.md) 334 | 335 | _Defined in [index.ts:292](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L292)_ 336 | 337 | **Parameters:** 338 | 339 | | Name | Type | Default value | 340 | | ------------------------- | --------- | ------------- | 341 | | publicKey | `Buffer` | - | 342 | | `Default value` nonStrict | `boolean` | false | 343 | 344 | **Returns:** [Wallet](wallet.md) 345 | 346 | --- 347 | 348 | 349 | 350 | ### `` fromV1 351 | 352 | ▸ **fromV1**(input: _`string` \| `V1Keystore`_, password: _`string`_): [Wallet](wallet.md) 353 | 354 | _Defined in [index.ts:338](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L338)_ 355 | 356 | **Parameters:** 357 | 358 | | Name | Type | Description | 359 | | -------- | ------------------------ | ---------------------------------------------------------------- | 360 | | input | `string` \| `V1Keystore` | A JSON serialized string, or an object representing V1 Keystore. | 361 | | password | `string` | The keystore password. | 362 | 363 | **Returns:** [Wallet](wallet.md) 364 | 365 | --- 366 | 367 | 368 | 369 | ### `` fromV3 370 | 371 | ▸ **fromV3**(input: _`string` \| `V3Keystore`_, password: _`string`_, nonStrict?: _`boolean`_): [Wallet](wallet.md) 372 | 373 | _Defined in [index.ts:378](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L378)_ 374 | 375 | **Parameters:** 376 | 377 | | Name | Type | Default value | Description | 378 | | ------------------------- | ------------------------ | ------------- | ---------------------------------------------------------------- | 379 | | input | `string` \| `V3Keystore` | - | A JSON serialized string, or an object representing V3 Keystore. | 380 | | password | `string` | - | The keystore password. | 381 | | `Default value` nonStrict | `boolean` | false | 382 | 383 | **Returns:** [Wallet](wallet.md) 384 | 385 | --- 386 | 387 | 388 | 389 | ### `` generate 390 | 391 | ▸ **generate**(icapDirect?: _`boolean`_): [Wallet](wallet.md) 392 | 393 | _Defined in [index.ts:254](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L254)_ 394 | 395 | **Parameters:** 396 | 397 | | Name | Type | Default value | Description | 398 | | -------------------------- | --------- | ------------- | --------------------------------------------------------------------------------------- | 399 | | `Default value` icapDirect | `boolean` | false | setting this to \`true\` will generate an address suitable for the \`ICAP Direct mode\` | 400 | 401 | **Returns:** [Wallet](wallet.md) 402 | 403 | --- 404 | 405 | 406 | 407 | ### `` generateVanityAddress 408 | 409 | ▸ **generateVanityAddress**(pattern: _`RegExp` \| `string`_): [Wallet](wallet.md) 410 | 411 | _Defined in [index.ts:271](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/index.ts#L271)_ 412 | 413 | **Parameters:** 414 | 415 | | Name | Type | 416 | | ------- | -------------------- | 417 | | pattern | `RegExp` \| `string` | 418 | 419 | **Returns:** [Wallet](wallet.md) 420 | 421 | --- 422 | -------------------------------------------------------------------------------- /docs/interfaces/etherwalletoptions.md: -------------------------------------------------------------------------------- 1 | [ethereumjs-wallet](../README.md) > [EtherWalletOptions](../interfaces/etherwalletoptions.md) 2 | 3 | # Interface: EtherWalletOptions 4 | 5 | ## Hierarchy 6 | 7 | **EtherWalletOptions** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | - [address](etherwalletoptions.md#address) 14 | - [encrypted](etherwalletoptions.md#encrypted) 15 | - [hash](etherwalletoptions.md#hash) 16 | - [locked](etherwalletoptions.md#locked) 17 | - [private](etherwalletoptions.md#private) 18 | - [public](etherwalletoptions.md#public) 19 | 20 | --- 21 | 22 | ## Properties 23 | 24 | 25 | 26 | ### address 27 | 28 | **● address**: _`string`_ 29 | 30 | _Defined in [thirdparty.ts:108](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L108)_ 31 | 32 | --- 33 | 34 | 35 | 36 | ### encrypted 37 | 38 | **● encrypted**: _`boolean`_ 39 | 40 | _Defined in [thirdparty.ts:109](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L109)_ 41 | 42 | --- 43 | 44 | 45 | 46 | ### hash 47 | 48 | **● hash**: _`string`_ 49 | 50 | _Defined in [thirdparty.ts:111](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L111)_ 51 | 52 | --- 53 | 54 | 55 | 56 | ### locked 57 | 58 | **● locked**: _`boolean`_ 59 | 60 | _Defined in [thirdparty.ts:110](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L110)_ 61 | 62 | --- 63 | 64 | 65 | 66 | ### private 67 | 68 | **● private**: _`string`_ 69 | 70 | _Defined in [thirdparty.ts:112](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L112)_ 71 | 72 | --- 73 | 74 | 75 | 76 | ### public 77 | 78 | **● public**: _`string`_ 79 | 80 | _Defined in [thirdparty.ts:113](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L113)_ 81 | 82 | --- 83 | -------------------------------------------------------------------------------- /docs/interfaces/evpkdfopts.md: -------------------------------------------------------------------------------- 1 | [ethereumjs-wallet](../README.md) > [EvpKdfOpts](../interfaces/evpkdfopts.md) 2 | 3 | # Interface: EvpKdfOpts 4 | 5 | ## Hierarchy 6 | 7 | **EvpKdfOpts** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | - [count](evpkdfopts.md#count) 14 | - [digest](evpkdfopts.md#digest) 15 | - [ivsize](evpkdfopts.md#ivsize) 16 | - [keysize](evpkdfopts.md#keysize) 17 | 18 | --- 19 | 20 | ## Properties 21 | 22 | 23 | 24 | ### count 25 | 26 | **● count**: _`number`_ 27 | 28 | _Defined in [thirdparty.ts:17](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L17)_ 29 | 30 | --- 31 | 32 | 33 | 34 | ### digest 35 | 36 | **● digest**: _`string`_ 37 | 38 | _Defined in [thirdparty.ts:20](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L20)_ 39 | 40 | --- 41 | 42 | 43 | 44 | ### ivsize 45 | 46 | **● ivsize**: _`number`_ 47 | 48 | _Defined in [thirdparty.ts:19](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L19)_ 49 | 50 | --- 51 | 52 | 53 | 54 | ### keysize 55 | 56 | **● keysize**: _`number`_ 57 | 58 | _Defined in [thirdparty.ts:18](https://github.com/ethereumjs/ethereumjs-wallet/blob/13fb20d/src/thirdparty.ts#L18)_ 59 | 60 | --- 61 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | frameworks: ['mocha', 'karma-typescript'], 4 | files: ['src/**/*.ts', 'test/**/*.spec.ts'], 5 | preprocessors: { 6 | '**/*.ts': ['karma-typescript'], 7 | }, 8 | plugins: ['karma-mocha', 'karma-typescript', 'karma-chrome-launcher', 'karma-firefox-launcher'], 9 | karmaTypescriptConfig: { 10 | tsconfig: './tsconfig.json', 11 | bundlerOptions: { 12 | entrypoints: /\.spec\.ts$/, 13 | acornOptions: { 14 | ecmaVersion: 11 15 | } 16 | }, 17 | }, 18 | colors: true, 19 | reporters: ['progress', 'karma-typescript'], 20 | browsers: ['FirefoxHeadless', 'ChromeHeadless'], 21 | singleRun: true, 22 | concurrency: 1, 23 | // Extend timeouts for long tests 24 | browserDisconnectTimeout: 1000000, 25 | browserNoActivityTimeout: 1000000, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereumjs-wallet", 3 | "version": "1.0.2", 4 | "description": "Utilities for handling Ethereum keys", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "browser": "dist.browser/index.js", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "dist.browser" 12 | ], 13 | "homepage": "https://github.com/ethereumjs/ethereumjs-wallet", 14 | "dependencies": { 15 | "@ethereumjs/util": "^8.0.0", 16 | "@scure/base": "1.1.1", 17 | "ethereum-cryptography": "1.2.0", 18 | "js-md5": "0.7.3", 19 | "uuid": "8.3.2" 20 | }, 21 | "devDependencies": { 22 | "eslint": "6.8.0", 23 | "@typescript-eslint/eslint-plugin": "4.27.0", 24 | "@typescript-eslint/parser": "4.27.0", 25 | "eslint-config-prettier": "6.11.0", 26 | "eslint-config-typestrict": "1.0.3", 27 | "eslint-plugin-implicit-dependencies": "1.0.4", 28 | "eslint-plugin-import": "2.26.0", 29 | "eslint-plugin-prettier": "3.1.3", 30 | "eslint-plugin-sonarjs": "0.5.0", 31 | "@types/js-md5": "0.4.3", 32 | "@types/lodash.zip": "4.2.7", 33 | "@types/mocha": "9.1.1", 34 | "@types/node": "18.0.0", 35 | "ethers": "5.6.9", 36 | "husky": "4.2.5", 37 | "karma": "6.4.0", 38 | "karma-chrome-launcher": "3.1.1", 39 | "karma-firefox-launcher": "2.1.2", 40 | "karma-mocha": "2.0.1", 41 | "karma-typescript": "5.5.3", 42 | "lodash.zip": "4.2.0", 43 | "mocha": "10.0.0", 44 | "nyc": "15.1.0", 45 | "prettier": "2.6.2", 46 | "ts-node": "10.9.1", 47 | "typedoc": "0.23.9", 48 | "typedoc-plugin-markdown": "3.13.4", 49 | "typescript": "4.7.3" 50 | }, 51 | "scripts": { 52 | "postinstall": "npm run build", 53 | "build": "tsc -p tsconfig.prod.json", 54 | "prepublishOnly": "npm run lint && npm run build && npm run test", 55 | "docs:build": "typedoc --out docs --mode file --readme none --theme markdown --mdEngine github --excludeNotExported src", 56 | "coverage": "npm run build && nyc --reporter=lcov npm run test:unit", 57 | "tsc": "tsc -p tsconfig.prod.json --noEmit", 58 | "lint": "eslint --format codeframe --config ./.eslintrc.js . --ext .js,.jsx,.ts,.tsx", 59 | "lint:fix": "eslint --fix --format codeframe --config ./.eslintrc.js . --ext .js,.jsx,.ts,.tsx", 60 | "test": "npm run test:unit && npm run test:browser", 61 | "test:unit": "mocha --require ts-node/register ./test/**/*.spec.ts", 62 | "test:browser": "karma start karma.conf.js" 63 | }, 64 | "husky": { 65 | "hooks": { 66 | "pre-push": "npm run lint" 67 | } 68 | }, 69 | "repository": { 70 | "type": "git", 71 | "url": "https://github.com/ethereumjs/ethereumjs-wallet.git" 72 | }, 73 | "keywords": [ 74 | "ethereum", 75 | "wallets", 76 | "keys" 77 | ], 78 | "author": "Alex Beregszaszi ", 79 | "license": "MIT", 80 | "bugs": { 81 | "url": "https://github.com/ethereumjs/ethereumjs-wallet/issues" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 100, 5 | } 6 | -------------------------------------------------------------------------------- /src/hdkey.ts: -------------------------------------------------------------------------------- 1 | import { HDKey } from 'ethereum-cryptography/hdkey' 2 | import Wallet from './index' 3 | 4 | export default class EthereumHDKey { 5 | /** 6 | * Creates an instance based on a seed. 7 | * 8 | * For the seed we suggest to use [bip39](https://npmjs.org/package/bip39) to 9 | * create one from a BIP39 mnemonic. 10 | */ 11 | public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey { 12 | return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer)) 13 | } 14 | 15 | /** 16 | * Create an instance based on a BIP32 extended private or public key. 17 | */ 18 | public static fromExtendedKey(base58Key: string): EthereumHDKey { 19 | return new EthereumHDKey(HDKey.fromExtendedKey(base58Key)) 20 | } 21 | 22 | constructor(private readonly _hdkey: HDKey) {} 23 | 24 | /** 25 | * Returns a BIP32 extended private key (xprv) 26 | */ 27 | public privateExtendedKey(): string { 28 | if (!this._hdkey.privateExtendedKey) { 29 | throw new Error('This is a public key only wallet') 30 | } 31 | return this._hdkey.privateExtendedKey 32 | } 33 | 34 | /** 35 | * Return a BIP32 extended public key (xpub) 36 | */ 37 | public publicExtendedKey(): string { 38 | return this._hdkey.publicExtendedKey 39 | } 40 | 41 | /** 42 | * Derives a node based on a path (e.g. m/44'/0'/0/1) 43 | */ 44 | public derivePath(path: string): EthereumHDKey { 45 | return new EthereumHDKey(this._hdkey.derive(path)) 46 | } 47 | 48 | /** 49 | * Derive a node based on a child index 50 | */ 51 | public deriveChild(index: number): EthereumHDKey { 52 | return new EthereumHDKey(this._hdkey.deriveChild(index)) 53 | } 54 | 55 | /** 56 | * Return a `Wallet` instance as seen above 57 | */ 58 | public getWallet(): Wallet { 59 | if (this._hdkey.privateKey) { 60 | return Wallet.fromPrivateKey(Buffer.from(this._hdkey.privateKey)) 61 | } 62 | if (!this._hdkey.publicKey) throw new Error('No hdkey') 63 | return Wallet.fromPublicKey(Buffer.from(this._hdkey.publicKey), true) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bufferToHex, 3 | privateToAddress, 4 | publicToAddress, 5 | toChecksumAddress, 6 | privateToPublic, 7 | importPublic, 8 | isValidPrivate, 9 | isValidPublic, 10 | } from '@ethereumjs/util' 11 | import { base58check } from '@scure/base' 12 | import * as aes from 'ethereum-cryptography/aes' 13 | import { keccak256 } from 'ethereum-cryptography/keccak' 14 | import { getRandomBytesSync } from 'ethereum-cryptography/random' 15 | import { pbkdf2 } from 'ethereum-cryptography/pbkdf2' 16 | import { scrypt } from 'ethereum-cryptography/scrypt' 17 | import { sha256 } from 'ethereum-cryptography/sha256' 18 | 19 | export { default as hdkey } from './hdkey' 20 | export { default as thirdparty } from './thirdparty' 21 | const uuidv4 = require('uuid').v4 22 | 23 | const bs58check = base58check(sha256) 24 | function randomBytes(num: number) { 25 | return Buffer.from(getRandomBytesSync(num)) 26 | } 27 | interface KDFParamsV1 { 28 | N: number 29 | P: number 30 | R: number 31 | DkLen: number 32 | } 33 | function scryptV1(password: Buffer, salt: Buffer, kdfparams: KDFParamsV1) { 34 | const { N, P, R, DkLen } = kdfparams 35 | return scrypt(password, salt, N, P, R, DkLen) 36 | } 37 | function scryptV3(password: string, kdfparams: ScryptKDFParams) { 38 | const { salt, n, p, r, dklen } = kdfparams 39 | return scrypt(Buffer.from(password), salt, n, p, r, dklen) 40 | } 41 | function scryptV3Out(password: string, kdfparams: ScryptKDFParamsOut) { 42 | const { salt, n, p, r, dklen } = kdfparams 43 | return scrypt(Buffer.from(password), Buffer.from(salt, 'hex'), n, p, r, dklen) 44 | } 45 | 46 | // parameters for the toV3() method 47 | 48 | interface V3Params { 49 | kdf: string 50 | cipher: string 51 | salt: string | Buffer 52 | iv: string | Buffer 53 | uuid: string | Buffer 54 | dklen: number 55 | c: number 56 | n: number 57 | r: number 58 | p: number 59 | } 60 | 61 | interface V3ParamsStrict { 62 | kdf: string 63 | cipher: string 64 | salt: Buffer 65 | iv: Buffer 66 | uuid: Buffer 67 | dklen: number 68 | c: number 69 | n: number 70 | r: number 71 | p: number 72 | } 73 | 74 | // helpers 75 | function keyExists(k: Buffer | undefined | null): k is Buffer { 76 | return k !== undefined && k !== null 77 | } 78 | 79 | function validateHexString(paramName: string, str: string, length?: number) { 80 | if (str.toLowerCase().startsWith('0x')) { 81 | str = str.slice(2) 82 | } 83 | if (!str && !length) { 84 | return str 85 | } 86 | if ((length as number) % 2) { 87 | throw new Error(`Invalid length argument, must be an even number`) 88 | } 89 | if (typeof length === 'number' && str.length !== length) { 90 | throw new Error(`Invalid ${paramName}, string must be ${length} hex characters`) 91 | } 92 | if (!/^([0-9a-f]{2})+$/i.test(str)) { 93 | const howMany = typeof length === 'number' ? length : 'empty or a non-zero even number of' 94 | throw new Error(`Invalid ${paramName}, string must be ${howMany} hex characters`) 95 | } 96 | return str 97 | } 98 | 99 | function validateBuffer(paramName: string, buff: Buffer, length?: number) { 100 | if (!Buffer.isBuffer(buff)) { 101 | const howManyHex = 102 | typeof length === 'number' ? `${length * 2}` : 'empty or a non-zero even number of' 103 | const howManyBytes = typeof length === 'number' ? ` (${length} bytes)` : '' 104 | throw new Error( 105 | `Invalid ${paramName}, must be a string (${howManyHex} hex characters) or buffer${howManyBytes}` 106 | ) 107 | } 108 | if (typeof length === 'number' && buff.length !== length) { 109 | throw new Error(`Invalid ${paramName}, buffer must be ${length} bytes`) 110 | } 111 | return buff 112 | } 113 | 114 | function mergeToV3ParamsWithDefaults(params?: Partial): V3ParamsStrict { 115 | const v3Defaults: V3ParamsStrict = { 116 | cipher: 'aes-128-ctr', 117 | kdf: 'scrypt', 118 | salt: randomBytes(32), 119 | iv: randomBytes(16), 120 | uuid: randomBytes(16), 121 | dklen: 32, 122 | c: 262144, 123 | n: 262144, 124 | r: 8, 125 | p: 1, 126 | } 127 | 128 | if (!params) { 129 | return v3Defaults 130 | } 131 | 132 | if (typeof params.salt === 'string') { 133 | params.salt = Buffer.from(validateHexString('salt', params.salt), 'hex') 134 | } 135 | if (typeof params.iv === 'string') { 136 | params.iv = Buffer.from(validateHexString('iv', params.iv, 32), 'hex') 137 | } 138 | if (typeof params.uuid === 'string') { 139 | params.uuid = Buffer.from(validateHexString('uuid', params.uuid, 32), 'hex') 140 | } 141 | 142 | if (params.salt) { 143 | validateBuffer('salt', params.salt) 144 | } 145 | if (params.iv) { 146 | validateBuffer('iv', params.iv, 16) 147 | } 148 | if (params.uuid) { 149 | validateBuffer('uuid', params.uuid, 16) 150 | } 151 | 152 | return { 153 | ...v3Defaults, 154 | ...(params as V3ParamsStrict), 155 | } 156 | } 157 | 158 | // KDF 159 | 160 | const enum KDFFunctions { 161 | PBKDF = 'pbkdf2', 162 | Scrypt = 'scrypt', 163 | } 164 | 165 | interface ScryptKDFParams { 166 | dklen: number 167 | n: number 168 | p: number 169 | r: number 170 | salt: Buffer 171 | } 172 | 173 | interface ScryptKDFParamsOut { 174 | dklen: number 175 | n: number 176 | p: number 177 | r: number 178 | salt: string 179 | } 180 | 181 | interface PBKDFParams { 182 | c: number 183 | dklen: number 184 | prf: string 185 | salt: Buffer 186 | } 187 | 188 | interface PBKDFParamsOut { 189 | c: number 190 | dklen: number 191 | prf: string 192 | salt: string 193 | } 194 | 195 | type KDFParams = ScryptKDFParams | PBKDFParams 196 | type KDFParamsOut = ScryptKDFParamsOut | PBKDFParamsOut 197 | 198 | function kdfParamsForPBKDF(opts: V3ParamsStrict): PBKDFParams { 199 | return { 200 | dklen: opts.dklen, 201 | salt: opts.salt, 202 | c: opts.c, 203 | prf: 'hmac-sha256', 204 | } 205 | } 206 | 207 | function kdfParamsForScrypt(opts: V3ParamsStrict): ScryptKDFParams { 208 | return { 209 | dklen: opts.dklen, 210 | salt: opts.salt, 211 | n: opts.n, 212 | p: opts.p, 213 | r: opts.r, 214 | } 215 | } 216 | 217 | // JSON keystore types 218 | 219 | // https://github.com/ethereum/homestead-guide/blob/master/old-docs-for-reference/go-ethereum-wiki.rst/Passphrase-protected-key-store-spec.rst 220 | interface V1Keystore { 221 | Address: string 222 | Crypto: { 223 | CipherText: string 224 | IV: string 225 | KeyHeader: { 226 | Kdf: string 227 | KdfParams: { 228 | DkLen: number 229 | N: number 230 | P: number 231 | R: number 232 | SaltLen: number 233 | } 234 | Version: string 235 | } 236 | MAC: string 237 | Salt: string 238 | } 239 | Id: string 240 | Version: string 241 | } 242 | 243 | // https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition 244 | interface V3Keystore { 245 | crypto: { 246 | cipher: string 247 | cipherparams: { 248 | iv: string 249 | } 250 | ciphertext: string 251 | kdf: string 252 | kdfparams: KDFParamsOut 253 | mac: string 254 | } 255 | id: string 256 | version: number 257 | } 258 | 259 | interface EthSaleKeystore { 260 | encseed: string 261 | ethaddr: string 262 | btcaddr: string 263 | email: string 264 | } 265 | 266 | // wallet implementation 267 | 268 | export default class Wallet { 269 | constructor( 270 | private readonly privateKey?: Buffer | undefined, 271 | private publicKey: Buffer | undefined = undefined 272 | ) { 273 | if (privateKey && publicKey) { 274 | throw new Error('Cannot supply both a private and a public key to the constructor') 275 | } 276 | 277 | if (privateKey && !isValidPrivate(privateKey)) { 278 | throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)') 279 | } 280 | 281 | if (publicKey && !isValidPublic(publicKey)) { 282 | throw new Error('Invalid public key') 283 | } 284 | } 285 | 286 | // static methods 287 | 288 | /** 289 | * Create an instance based on a new random key. 290 | * 291 | * @param icapDirect setting this to `true` will generate an address suitable for the `ICAP Direct mode` 292 | */ 293 | public static generate(icapDirect = false): Wallet { 294 | if (icapDirect) { 295 | const max = BigInt('0x088f924eeceeda7fe92e1f5b0fffffffffffffff') 296 | for (;;) { 297 | const privateKey = randomBytes(32) as Buffer 298 | const hex = privateToAddress(privateKey).toString('hex') 299 | if (BigInt('0x' + hex) <= max) { 300 | return new Wallet(privateKey) 301 | } 302 | } 303 | } else { 304 | return new Wallet(randomBytes(32)) 305 | } 306 | } 307 | 308 | /** 309 | * Create an instance where the address is valid against the supplied pattern (**this will be very slow**) 310 | */ 311 | public static generateVanityAddress(pattern: RegExp | string): Wallet { 312 | if (!(pattern instanceof RegExp)) { 313 | pattern = new RegExp(pattern) 314 | } 315 | 316 | for (;;) { 317 | const privateKey = randomBytes(32) as Buffer 318 | const address = privateToAddress(privateKey) 319 | 320 | if (pattern.test(address.toString('hex'))) { 321 | return new Wallet(privateKey) 322 | } 323 | } 324 | } 325 | 326 | /** 327 | * Create an instance based on a public key (certain methods will not be available) 328 | * 329 | * This method only accepts uncompressed Ethereum-style public keys, unless 330 | * the `nonStrict` flag is set to true. 331 | */ 332 | public static fromPublicKey(publicKey: Buffer, nonStrict = false): Wallet { 333 | if (nonStrict) { 334 | publicKey = importPublic(publicKey) 335 | } 336 | return new Wallet(undefined, publicKey) 337 | } 338 | 339 | /** 340 | * Create an instance based on a BIP32 extended public key (xpub) 341 | */ 342 | public static fromExtendedPublicKey(extendedPublicKey: string): Wallet { 343 | if (extendedPublicKey.slice(0, 4) !== 'xpub') { 344 | throw new Error('Not an extended public key') 345 | } 346 | const publicKey: Uint8Array = bs58check.decode(extendedPublicKey).slice(45) 347 | // Convert to an Ethereum public key 348 | return Wallet.fromPublicKey(Buffer.from(publicKey), true) 349 | } 350 | 351 | /** 352 | * Create an instance based on a raw private key 353 | */ 354 | public static fromPrivateKey(privateKey: Buffer): Wallet { 355 | return new Wallet(privateKey) 356 | } 357 | 358 | /** 359 | * Create an instance based on a BIP32 extended private key (xprv) 360 | */ 361 | public static fromExtendedPrivateKey(extendedPrivateKey: string): Wallet { 362 | if (extendedPrivateKey.slice(0, 4) !== 'xprv') { 363 | throw new Error('Not an extended private key') 364 | } 365 | const tmp: Uint8Array = bs58check.decode(extendedPrivateKey) 366 | if (tmp[45] !== 0) { 367 | throw new Error('Invalid extended private key') 368 | } 369 | return Wallet.fromPrivateKey(Buffer.from(tmp.slice(46))) 370 | } 371 | 372 | /** 373 | * Import a wallet (Version 1 of the Ethereum wallet format). 374 | * 375 | * @param input A JSON serialized string, or an object representing V1 Keystore. 376 | * @param password The keystore password. 377 | */ 378 | public static async fromV1(input: string | V1Keystore, password: string): Promise { 379 | const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input) 380 | if (json.Version !== '1') { 381 | throw new Error('Not a V1 Wallet') 382 | } 383 | if (json.Crypto.KeyHeader.Kdf !== 'scrypt') { 384 | throw new Error('Unsupported key derivation scheme') 385 | } 386 | 387 | const kdfparams = json.Crypto.KeyHeader.KdfParams 388 | const salt = Buffer.from(json.Crypto.Salt, 'hex') 389 | const derivedKey = await scryptV1(Buffer.from(password), salt, kdfparams) 390 | const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex') 391 | const mac = keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) 392 | if (Buffer.from(mac).toString('hex') !== json.Crypto.MAC) { 393 | throw new Error('Key derivation failed - possibly wrong passphrase') 394 | } 395 | 396 | const seed = await aes.decrypt( 397 | ciphertext, 398 | keccak256(derivedKey.slice(0, 16) as Buffer).slice(0, 16), 399 | Buffer.from(json.Crypto.IV, 'hex'), 400 | 'aes-128-cbc' 401 | ) 402 | return new Wallet(Buffer.from(seed)) 403 | } 404 | 405 | /** 406 | * Import a wallet (Version 3 of the Ethereum wallet format). Set `nonStrict` true to accept files with mixed-caps. 407 | * 408 | * @param input A JSON serialized string, or an object representing V3 Keystore. 409 | * @param password The keystore password. 410 | */ 411 | public static async fromV3( 412 | input: string | V3Keystore, 413 | password: string, 414 | nonStrict = false 415 | ): Promise { 416 | const json: V3Keystore = 417 | typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input) 418 | 419 | if (json.version !== 3) { 420 | throw new Error('Not a V3 wallet') 421 | } 422 | 423 | let derivedKey: Uint8Array, kdfparams: any 424 | if (json.crypto.kdf === 'scrypt') { 425 | kdfparams = json.crypto.kdfparams 426 | // FIXME: support progress reporting callback 427 | derivedKey = await scryptV3Out(password, kdfparams) 428 | } else if (json.crypto.kdf === 'pbkdf2') { 429 | kdfparams = json.crypto.kdfparams 430 | 431 | if (kdfparams.prf !== 'hmac-sha256') { 432 | throw new Error('Unsupported parameters to PBKDF2') 433 | } 434 | 435 | derivedKey = await pbkdf2( 436 | Buffer.from(password), 437 | Buffer.from(kdfparams.salt, 'hex'), 438 | kdfparams.c, 439 | kdfparams.dklen, 440 | 'sha256' 441 | ) 442 | } else { 443 | throw new Error('Unsupported key derivation scheme') 444 | } 445 | 446 | const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex') 447 | const mac = keccak256(Buffer.concat([Buffer.from(derivedKey.slice(16, 32)), ciphertext])) 448 | if (Buffer.from(mac).toString('hex') !== json.crypto.mac) { 449 | throw new Error('Key derivation failed - possibly wrong passphrase') 450 | } 451 | 452 | const seed = await aes.decrypt( 453 | ciphertext, 454 | derivedKey.slice(0, 16), 455 | Buffer.from(json.crypto.cipherparams.iv, 'hex'), 456 | json.crypto.cipher 457 | ) 458 | return new Wallet(Buffer.from(seed)) 459 | } 460 | 461 | /* 462 | * Import an Ethereum Pre Sale wallet. 463 | * Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py 464 | * JSON fields: encseed, ethaddr, btcaddr, email 465 | * 466 | * @param input A JSON serialized string, or an object representing EthSale Keystore. 467 | * @param password The keystore password. 468 | */ 469 | public static async fromEthSale( 470 | input: string | EthSaleKeystore, 471 | password: string 472 | ): Promise { 473 | const json: EthSaleKeystore = typeof input === 'object' ? input : JSON.parse(input) 474 | 475 | const encseed = Uint8Array.from(Buffer.from(json.encseed, 'hex')) 476 | 477 | // key derivation 478 | const pass = Buffer.from(password, 'utf8') 479 | const derivedKey = (await pbkdf2(pass, pass, 2000, 32, 'sha256')).slice(0, 16) 480 | 481 | // seed decoding (IV is first 16 bytes) 482 | // NOTE: crypto (derived from openssl) when used with aes-*-cbc will handle PKCS#7 padding internally 483 | // see also http://stackoverflow.com/a/31614770/4964819 484 | const seed = await aes.decrypt( 485 | encseed.slice(16), 486 | derivedKey, 487 | encseed.slice(0, 16), 488 | 'aes-128-cbc', 489 | true 490 | ) 491 | 492 | const wallet = new Wallet(Buffer.from(keccak256(seed))) 493 | if (wallet.getAddress().toString('hex') !== json.ethaddr) { 494 | throw new Error('Decoded key mismatch - possibly wrong passphrase') 495 | } 496 | return wallet 497 | } 498 | 499 | // private getters 500 | 501 | /** 502 | * Returns the wallet's public key. 503 | */ 504 | private get pubKey(): Buffer { 505 | if (!keyExists(this.publicKey)) { 506 | this.publicKey = privateToPublic(this.privateKey as Buffer) 507 | } 508 | return this.publicKey 509 | } 510 | 511 | /** 512 | * Returns the wallet's private key. 513 | */ 514 | private get privKey(): Buffer { 515 | if (!keyExists(this.privateKey)) { 516 | throw new Error('This is a public key only wallet') 517 | } 518 | return this.privateKey 519 | } 520 | 521 | // public instance methods 522 | 523 | /** 524 | * Returns the wallet's private key. 525 | * 526 | */ 527 | // tslint:disable-next-line 528 | public getPrivateKey(): Buffer { 529 | return this.privKey 530 | } 531 | 532 | public getPrivateKeyString(): string { 533 | return bufferToHex(this.privKey) 534 | } 535 | 536 | /** 537 | * Returns the wallet's public key. 538 | */ 539 | // tslint:disable-next-line 540 | public getPublicKey(): Buffer { 541 | return this.pubKey 542 | } 543 | 544 | /** 545 | * Returns the wallet's public key as a "0x" prefixed hex string 546 | */ 547 | public getPublicKeyString(): string { 548 | return bufferToHex(this.getPublicKey()) 549 | } 550 | 551 | /** 552 | * Returns the wallet's address. 553 | */ 554 | public getAddress(): Buffer { 555 | return publicToAddress(this.pubKey) 556 | } 557 | 558 | /** 559 | * Returns the wallet's address as a "0x" prefixed hex string 560 | */ 561 | public getAddressString(): string { 562 | return bufferToHex(this.getAddress()) 563 | } 564 | 565 | /** 566 | * Returns the wallet's private key as a "0x" prefixed hex string checksummed 567 | * according to [EIP 55](https://github.com/ethereum/EIPs/issues/55). 568 | */ 569 | public getChecksumAddressString(): string { 570 | return toChecksumAddress(this.getAddressString()) 571 | } 572 | 573 | /** 574 | * Returns an Etherem Version 3 Keystore Format object representing the wallet 575 | * 576 | * @param password The password used to encrypt the Keystore. 577 | * @param opts The options for the keystore. See [its spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for more info. 578 | */ 579 | public async toV3(password: string, opts?: Partial): Promise { 580 | if (!keyExists(this.privateKey)) { 581 | throw new Error('This is a public key only wallet') 582 | } 583 | 584 | const v3Params: V3ParamsStrict = mergeToV3ParamsWithDefaults(opts) 585 | 586 | let kdfParams: KDFParams 587 | let derivedKey: Uint8Array 588 | switch (v3Params.kdf) { 589 | case KDFFunctions.PBKDF: 590 | kdfParams = kdfParamsForPBKDF(v3Params) 591 | derivedKey = await pbkdf2( 592 | Buffer.from(password), 593 | kdfParams.salt, 594 | kdfParams.c, 595 | kdfParams.dklen, 596 | 'sha256' 597 | ) 598 | break 599 | case KDFFunctions.Scrypt: 600 | kdfParams = kdfParamsForScrypt(v3Params) 601 | // FIXME: support progress reporting callback 602 | derivedKey = await scryptV3(password, kdfParams) 603 | break 604 | default: 605 | throw new Error('Unsupported kdf') 606 | } 607 | 608 | const ciphertext = await aes.encrypt( 609 | this.privKey, 610 | derivedKey.slice(0, 16), 611 | v3Params.iv, 612 | v3Params.cipher, 613 | false 614 | ) 615 | const mac = keccak256( 616 | Buffer.concat([Buffer.from(derivedKey.slice(16, 32)), Buffer.from(ciphertext)]) 617 | ) 618 | 619 | return { 620 | version: 3, 621 | id: uuidv4({ random: v3Params.uuid }), 622 | // @ts-ignore - the official V3 keystore spec omits the address key 623 | address: this.getAddress().toString('hex'), 624 | crypto: { 625 | ciphertext: Buffer.from(ciphertext).toString('hex'), 626 | cipherparams: { iv: v3Params.iv.toString('hex') }, 627 | cipher: v3Params.cipher, 628 | kdf: v3Params.kdf, 629 | kdfparams: { 630 | ...kdfParams, 631 | salt: kdfParams.salt.toString('hex'), 632 | }, 633 | mac: Buffer.from(mac).toString('hex'), 634 | }, 635 | } 636 | } 637 | 638 | /** 639 | * Return the suggested filename for V3 keystores. 640 | */ 641 | public getV3Filename(timestamp?: number): string { 642 | /* 643 | * We want a timestamp like 2016-03-15T17-11-33.007598288Z. Date formatting 644 | * is a pain in Javascript, everbody knows that. We could use moment.js, 645 | * but decide to do it manually in order to save space. 646 | * 647 | * toJSON() returns a pretty close version, so let's use it. It is not UTC though, 648 | * but does it really matter? 649 | * 650 | * Alternative manual way with padding and Date fields: http://stackoverflow.com/a/7244288/4964819 651 | * 652 | */ 653 | const ts = timestamp ? new Date(timestamp) : new Date() 654 | return ['UTC--', ts.toJSON().replace(/:/g, '-'), '--', this.getAddress().toString('hex')].join( 655 | '' 656 | ) 657 | } 658 | 659 | public async toV3String(password: string, opts?: Partial): Promise { 660 | return JSON.stringify(await this.toV3(password, opts)) 661 | } 662 | 663 | /** 664 | * Verify the publicKey, privateKey pair 665 | * 666 | * @param publicKey the public key to verify against the private key of the wallet 667 | */ 668 | public verifyPublicKey(publicKey: Buffer): boolean { 669 | return privateToPublic(this.privateKey as Buffer).equals(publicKey) 670 | } 671 | } 672 | -------------------------------------------------------------------------------- /src/thirdparty.ts: -------------------------------------------------------------------------------- 1 | import { decrypt } from 'ethereum-cryptography/aes' 2 | import { keccak256 } from 'ethereum-cryptography/keccak' 3 | import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2' 4 | import { bytesToUtf8, utf8ToBytes } from 'ethereum-cryptography/utils' 5 | import * as md5 from 'js-md5' 6 | import Wallet from './index' 7 | 8 | // evp_kdf 9 | 10 | export interface EvpKdfOpts { 11 | count: number 12 | keysize: number 13 | ivsize: number 14 | digest: string 15 | } 16 | 17 | const evpKdfDefaults: EvpKdfOpts = { 18 | count: 1, 19 | keysize: 16, 20 | ivsize: 16, 21 | digest: 'md5', 22 | } 23 | 24 | function mergeEvpKdfOptsWithDefaults(opts?: Partial): EvpKdfOpts { 25 | if (!opts) { 26 | return evpKdfDefaults 27 | } 28 | return { 29 | count: opts.count ?? evpKdfDefaults.count, 30 | keysize: opts.keysize ?? evpKdfDefaults.keysize, 31 | ivsize: opts.ivsize ?? evpKdfDefaults.ivsize, 32 | digest: opts.digest ?? evpKdfDefaults.digest, 33 | } 34 | } 35 | 36 | /* 37 | * opts: 38 | * - digest - digest algorithm, defaults to md5 39 | * - count - hash iterations 40 | * - keysize - desired key size 41 | * - ivsize - desired IV size 42 | * 43 | * Algorithm form https://www.openssl.org/docs/manmaster/crypto/EVP_BytesToKey.html 44 | * 45 | * FIXME: not optimised at all 46 | */ 47 | function evp_kdf(data: Buffer, salt: Buffer, opts?: Partial) { 48 | const params = mergeEvpKdfOptsWithDefaults(opts) 49 | 50 | // A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)` 51 | function iter(block: Buffer) { 52 | if (params.digest !== 'md5') throw new Error('Only md5 is supported in evp_kdf') 53 | let hash = md5.create() 54 | hash.update(block) 55 | hash.update(data) 56 | hash.update(salt) 57 | block = Buffer.from(hash.arrayBuffer()) 58 | 59 | for (let i = 1, len = params.count; i < len; i++) { 60 | hash = md5.create() 61 | hash.update(block) 62 | block = Buffer.from(hash.arrayBuffer()) 63 | } 64 | return block 65 | } 66 | 67 | const ret: Buffer[] = [] 68 | let i = 0 69 | while (Buffer.concat(ret).length < params.keysize + params.ivsize) { 70 | ret[i] = iter(i === 0 ? Buffer.alloc(0) : ret[i - 1]) 71 | i++ 72 | } 73 | const tmp = Buffer.concat(ret) 74 | 75 | return { 76 | key: tmp.subarray(0, params.keysize), 77 | iv: tmp.subarray(params.keysize, params.keysize + params.ivsize), 78 | } 79 | } 80 | 81 | // http://stackoverflow.com/questions/25288311/cryptojs-aes-pattern-always-ends-with 82 | function decodeCryptojsSalt(input: string): { ciphertext: Buffer; salt?: Buffer } { 83 | const ciphertext = Buffer.from(input, 'base64') 84 | if (ciphertext.subarray(0, 8).toString() === 'Salted__') { 85 | return { 86 | salt: ciphertext.subarray(8, 16), 87 | ciphertext: ciphertext.subarray(16), 88 | } 89 | } 90 | return { ciphertext } 91 | } 92 | 93 | // { 94 | // "address": "0x169aab499b549eac087035e640d3f7d882ef5e2d", 95 | // "encrypted": true, 96 | // "locked": true, 97 | // "hash": "342f636d174cc1caa49ce16e5b257877191b663e0af0271d2ea03ac7e139317d", 98 | // "private": "U2FsdGVkX19ZrornRBIfl1IDdcj6S9YywY8EgOeOtLj2DHybM/CHL4Jl0jcwjT+36kDnjj+qEfUBu6J1mGQF/fNcD/TsAUgGUTEUEOsP1CKDvNHfLmWLIfxqnYHhHsG5", 99 | // "public": "U2FsdGVkX19EaDNK52q7LEz3hL/VR3dYW5VcoP04tcVKNS0Q3JINpM4XzttRJCBtq4g22hNDrOR8RWyHuh3nPo0pRSe9r5AUfEiCLaMBAhI16kf2KqCA8ah4brkya9ZLECdIl0HDTMYfDASBnyNXd87qodt46U0vdRT3PppK+9hsyqP8yqm9kFcWqMHktqubBE937LIU0W22Rfw6cJRwIw==" 100 | // } 101 | 102 | export interface EtherWalletOptions { 103 | address: string 104 | encrypted: boolean 105 | locked: boolean 106 | hash: string 107 | private: string 108 | public: string 109 | } 110 | 111 | /* 112 | * Third Party API: Import a wallet generated by EtherWallet 113 | * This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts 114 | * and used on https://www.myetherwallet.com/ 115 | */ 116 | export async function fromEtherWallet( 117 | input: string | EtherWalletOptions, 118 | password: string 119 | ): Promise { 120 | const json: EtherWalletOptions = typeof input === 'object' ? input : JSON.parse(input) 121 | 122 | let privateKey: Buffer 123 | if (!json.locked) { 124 | if (json.private.length !== 64) { 125 | throw new Error('Invalid private key length') 126 | } 127 | privateKey = Buffer.from(json.private, 'hex') 128 | } else { 129 | if (typeof password !== 'string') { 130 | throw new Error('Password required') 131 | } 132 | 133 | if (password.length < 7) { 134 | throw new Error('Password must be at least 7 characters') 135 | } 136 | 137 | // the "encrypted" version has the low 4 bytes 138 | // of the hash of the address appended 139 | const hash = json.encrypted ? json.private.slice(0, 128) : json.private 140 | 141 | // decode openssl ciphertext + salt encoding 142 | const cipher = decodeCryptojsSalt(hash) 143 | if (!cipher.salt) { 144 | throw new Error('Unsupported EtherWallet key format') 145 | } 146 | 147 | // derive key/iv using OpenSSL EVP as implemented in CryptoJS 148 | const evp = evp_kdf(Buffer.from(password), cipher.salt, { keysize: 32, ivsize: 16 }) 149 | 150 | const pr = await decrypt(cipher.ciphertext, evp.key, evp.iv, 'aes-256-cbc') 151 | 152 | // NOTE: yes, they've run it through UTF8 153 | privateKey = Buffer.from(bytesToUtf8(pr), 'hex') 154 | } 155 | const wallet = new Wallet(privateKey) 156 | if (wallet.getAddressString() !== json.address) { 157 | throw new Error('Invalid private key or address') 158 | } 159 | return wallet 160 | } 161 | 162 | /** 163 | * Third Party API: Import a brain wallet used by Ether.Camp 164 | */ 165 | export function fromEtherCamp(passphrase: string): Wallet { 166 | return new Wallet(Buffer.from(keccak256(Buffer.from(passphrase)))) 167 | } 168 | 169 | /** 170 | * Third Party API: Import a brain wallet used by Quorum Wallet 171 | */ 172 | export function fromQuorumWallet(passphrase: string, userid: string): Wallet { 173 | if (passphrase.length < 10) { 174 | throw new Error('Passphrase must be at least 10 characters') 175 | } 176 | if (userid.length < 10) { 177 | throw new Error('User id must be at least 10 characters') 178 | } 179 | 180 | const merged = utf8ToBytes(passphrase + userid) 181 | const seed = pbkdf2Sync(merged, merged, 2000, 32, 'sha256') 182 | return new Wallet(Buffer.from(seed)) 183 | } 184 | 185 | const Thirdparty = { 186 | fromEtherWallet, 187 | fromEtherCamp, 188 | fromQuorumWallet, 189 | } 190 | 191 | export default Thirdparty 192 | -------------------------------------------------------------------------------- /test/hdkey.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import EthereumHDKey from '../src/hdkey' 3 | 4 | // from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown 5 | const fixtureseed = Buffer.from( 6 | '747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03', 7 | 'hex' 8 | ) 9 | const fixturehd = EthereumHDKey.fromMasterSeed(fixtureseed) 10 | 11 | describe('.fromMasterSeed()', function () { 12 | it('should work', function () { 13 | assert.doesNotThrow(function () { 14 | EthereumHDKey.fromMasterSeed(fixtureseed) 15 | }) 16 | }) 17 | }) 18 | 19 | describe('.privateExtendedKey()', function () { 20 | it('should work', function () { 21 | assert.strictEqual( 22 | fixturehd.privateExtendedKey(), 23 | 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY' 24 | ) 25 | }) 26 | }) 27 | 28 | describe('.publicExtendedKey()', function () { 29 | it('should work', function () { 30 | assert.strictEqual( 31 | fixturehd.publicExtendedKey(), 32 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 33 | ) 34 | }) 35 | }) 36 | 37 | describe('.fromExtendedKey()', function () { 38 | it('should work with public', function () { 39 | const hdnode = EthereumHDKey.fromExtendedKey( 40 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 41 | ) 42 | assert.strictEqual( 43 | hdnode.publicExtendedKey(), 44 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 45 | ) 46 | assert.throws(function () { 47 | hdnode.privateExtendedKey() 48 | }, /^Error: No private key$/) 49 | }) 50 | it('should work with private', function () { 51 | const hdnode = EthereumHDKey.fromExtendedKey( 52 | 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY' 53 | ) 54 | assert.strictEqual( 55 | hdnode.publicExtendedKey(), 56 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 57 | ) 58 | assert.strictEqual( 59 | hdnode.privateExtendedKey(), 60 | 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY' 61 | ) 62 | }) 63 | }) 64 | 65 | describe('.deriveChild()', function () { 66 | it('should work', function () { 67 | const hdnode = fixturehd.deriveChild(1) 68 | assert.strictEqual( 69 | hdnode.privateExtendedKey(), 70 | 'xprv9vYSvrg3eR5FaKbQE4Ao2vHdyvfFL27aWMyH6X818mKWMsqqQZAN6HmRqYDGDPLArzaqbLExRsxFwtx2B2X2QKkC9uoKsiBNi22tLPKZHNS' 71 | ) 72 | }) 73 | }) 74 | 75 | describe('.derivePath()', function () { 76 | it('should work with m', function () { 77 | const hdnode = fixturehd.derivePath('m') 78 | assert.strictEqual( 79 | hdnode.privateExtendedKey(), 80 | 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY' 81 | ) 82 | }) 83 | it("should work with m/44'/0'/0/1", function () { 84 | const hdnode = fixturehd.derivePath("m/44'/0'/0/1") 85 | assert.strictEqual( 86 | hdnode.privateExtendedKey(), 87 | 'xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG' 88 | ) 89 | }) 90 | }) 91 | 92 | describe('.getWallet()', function () { 93 | it('should work', function () { 94 | assert.strictEqual( 95 | fixturehd.getWallet().getPrivateKeyString(), 96 | '0x26cc9417b89cd77c4acdbe2e3cd286070a015d8e380f9cd1244ae103b7d89d81' 97 | ) 98 | assert.strictEqual( 99 | fixturehd.getWallet().getPublicKeyString(), 100 | '0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1' 101 | ) 102 | }) 103 | it('should work with public nodes', function () { 104 | const hdnode = EthereumHDKey.fromExtendedKey( 105 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 106 | ) 107 | assert.throws(function () { 108 | hdnode.getWallet().getPrivateKeyString() 109 | }, /^Error: This is a public key only wallet$/) 110 | assert.strictEqual( 111 | hdnode.getWallet().getPublicKeyString(), 112 | '0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1' 113 | ) 114 | }) 115 | }) 116 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-invalid-this */ 2 | import * as assert from 'assert' 3 | import { Wallet as ethersWallet } from 'ethers' 4 | 5 | const zip = require('lodash.zip') 6 | 7 | import Wallet from '../src' 8 | import Thirdparty from '../src/thirdparty' 9 | 10 | const n = 262144 11 | const r = 8 12 | const p = 1 13 | 14 | const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378' 15 | const fixturePrivateKeyStr = '0x' + fixturePrivateKey 16 | const fixturePrivateKeyBuffer = Buffer.from(fixturePrivateKey, 'hex') 17 | 18 | const fixturePublicKey = 19 | '5d4392f450262b276652c1fc037606abac500f3160830ce9df53aa70d95ce7cfb8b06010b2f3691c78c65c21eb4cf3dfdbfc0745d89b664ee10435bb3a0f906c' 20 | const fixturePublicKeyStr = '0x' + fixturePublicKey 21 | const fixturePublicKeyBuffer = Buffer.from(fixturePublicKey, 'hex') 22 | 23 | const fixtureWallet = Wallet.fromPrivateKey(fixturePrivateKeyBuffer) 24 | const fixtureEthersWallet = new ethersWallet(fixtureWallet.getPrivateKeyString()) 25 | 26 | const isRunningInKarma = () => { 27 | return typeof (global as any).window !== 'undefined' && (global as any).window.__karma__ 28 | } 29 | 30 | describe('.getPrivateKey()', function () { 31 | it('should work', function () { 32 | assert.strictEqual(fixtureWallet.getPrivateKey().toString('hex'), fixturePrivateKey) 33 | }) 34 | it('should fail', function () { 35 | assert.throws(function () { 36 | Wallet.fromPrivateKey(Buffer.from('001122', 'hex')) 37 | }, /^Error: Private key does not satisfy the curve requirements \(ie\. it is invalid\)$/) 38 | }) 39 | }) 40 | 41 | describe('.getPrivateKeyString()', function () { 42 | it('should work', function () { 43 | assert.strictEqual(fixtureWallet.getPrivateKeyString(), fixturePrivateKeyStr) 44 | }) 45 | }) 46 | 47 | describe('.getPublicKey()', function () { 48 | it('should work', function () { 49 | assert.strictEqual(fixtureWallet.getPublicKey().toString('hex'), fixturePublicKey) 50 | }) 51 | }) 52 | 53 | describe('.getPublicKeyString()', function () { 54 | it('should work', function () { 55 | assert.strictEqual(fixtureWallet.getPublicKeyString(), fixturePublicKeyStr) 56 | }) 57 | }) 58 | 59 | describe('.getAddress()', function () { 60 | it('should work', function () { 61 | assert.strictEqual( 62 | fixtureWallet.getAddress().toString('hex'), 63 | 'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c' 64 | ) 65 | }) 66 | }) 67 | 68 | describe('.getAddressString()', function () { 69 | it('should work', function () { 70 | assert.strictEqual( 71 | fixtureWallet.getAddressString(), 72 | '0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c' 73 | ) 74 | }) 75 | }) 76 | 77 | describe('.getChecksumAddressString()', function () { 78 | it('should work', function () { 79 | assert.strictEqual( 80 | fixtureWallet.getChecksumAddressString(), 81 | '0xB14Ab53E38DA1C172f877DBC6d65e4a1B0474C3c' 82 | ) 83 | }) 84 | }) 85 | 86 | describe('.verifyPublicKey()', function () { 87 | it('should return true if publicKey, privateKey pair is valid', function () { 88 | assert.strictEqual(fixtureWallet.verifyPublicKey(fixturePublicKeyBuffer), true) 89 | }) 90 | it('should return false if publicKey, privateKey pair is invalid', function () { 91 | assert.strictEqual(fixtureWallet.verifyPublicKey(Buffer.alloc(64, 0)), false) 92 | }) 93 | }) 94 | 95 | describe('public key only wallet', function () { 96 | const pubKey = Buffer.from(fixturePublicKey, 'hex') 97 | it('.fromPublicKey() should work', function () { 98 | assert.strictEqual( 99 | Wallet.fromPublicKey(pubKey).getPublicKey().toString('hex'), 100 | fixturePublicKey 101 | ) 102 | }) 103 | it('.fromPublicKey() should not accept compressed keys in strict mode', function () { 104 | assert.throws(function () { 105 | Wallet.fromPublicKey( 106 | Buffer.from('030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d', 'hex') 107 | ) 108 | }, /^Error: Invalid public key$/) 109 | }) 110 | it('.fromPublicKey() should accept compressed keys in non-strict mode', function () { 111 | const tmp = Buffer.from( 112 | '030639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973d', 113 | 'hex' 114 | ) 115 | assert.strictEqual( 116 | Wallet.fromPublicKey(tmp, true).getPublicKey().toString('hex'), 117 | '0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1' 118 | ) 119 | }) 120 | it('.getAddress() should work', function () { 121 | assert.strictEqual( 122 | Wallet.fromPublicKey(pubKey).getAddress().toString('hex'), 123 | 'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c' 124 | ) 125 | }) 126 | it('.getPrivateKey() should fail', function () { 127 | assert.throws(function () { 128 | Wallet.fromPublicKey(pubKey).getPrivateKey() 129 | }, /^Error: This is a public key only wallet$/) 130 | }) 131 | // it('.toV3() should fail', function () { 132 | // assert.throws(function () { 133 | // Wallet.fromPublicKey(pubKey).toV3() 134 | // }, /^Error: This is a public key only wallet$/) 135 | // }) 136 | }) 137 | 138 | describe('.fromExtendedPrivateKey()', function () { 139 | it('should work', function () { 140 | const xprv = 141 | 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY' 142 | assert.strictEqual( 143 | Wallet.fromExtendedPrivateKey(xprv).getAddressString(), 144 | '0xb800bf5435f67c7ee7d83c3a863269969a57c57c' 145 | ) 146 | }) 147 | }) 148 | 149 | describe('.fromExtendedPublicKey()', function () { 150 | it('should work', function () { 151 | const xpub = 152 | 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ' 153 | assert.strictEqual( 154 | Wallet.fromExtendedPublicKey(xpub).getAddressString(), 155 | '0xb800bf5435f67c7ee7d83c3a863269969a57c57c' 156 | ) 157 | }) 158 | }) 159 | 160 | describe('.generate()', function () { 161 | it('should generate an account', function () { 162 | assert.strictEqual(Wallet.generate().getPrivateKey().length, 32) 163 | }) 164 | it('should generate an account compatible with ICAP Direct', function () { 165 | const max = BigInt('0x088f924eeceeda7fe92e1f5b0fffffffffffffff') 166 | const wallet = Wallet.generate(true) 167 | assert.strictEqual(wallet.getPrivateKey().length, 32) 168 | const addr = wallet.getAddress().toString('hex') 169 | assert.strictEqual(BigInt('0x' + addr) <= max, true) 170 | }) 171 | }) 172 | 173 | describe('.generateVanityAddress()', function () { 174 | it('should generate an account with 000 prefix (object)', function () { 175 | this.timeout(0) // never 176 | const wallet = Wallet.generateVanityAddress(/^000/) 177 | assert.strictEqual(wallet.getPrivateKey().length, 32) 178 | assert.strictEqual(wallet.getAddress()[0], 0) 179 | assert.strictEqual(wallet.getAddress()[1] >>> 4, 0) 180 | }) 181 | it('should generate an account with 000 prefix (string)', function () { 182 | this.timeout(0) // never 183 | const wallet = Wallet.generateVanityAddress('^000') 184 | assert.strictEqual(wallet.getPrivateKey().length, 32) 185 | assert.strictEqual(wallet.getAddress()[0], 0) 186 | assert.strictEqual(wallet.getAddress()[1] >>> 4, 0) 187 | }) 188 | }) 189 | 190 | describe('.getV3Filename()', function () { 191 | it('should work', function () { 192 | assert.strictEqual( 193 | fixtureWallet.getV3Filename(1457917509265), 194 | 'UTC--2016-03-14T01-05-09.265Z--b14ab53e38da1c172f877dbc6d65e4a1b0474c3c' 195 | ) 196 | }) 197 | }) 198 | 199 | describe('.toV3()', function () { 200 | const pw = 'testtest' 201 | const salt = 'dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6' 202 | const iv = 'cecacd85e9cb89788b5aab2f93361233' 203 | const uuid = '7e59dc028d42d09db29aa8a0f862cc81' 204 | 205 | const strKdfOptions = { iv, salt, uuid } 206 | const buffKdfOptions = { 207 | salt: Buffer.from(salt, 'hex'), 208 | iv: Buffer.from(iv, 'hex'), 209 | uuid: Buffer.from(uuid, 'hex'), 210 | } 211 | 212 | // generate all possible combinations of salt, iv, uuid properties, e.g. 213 | // {salt: [string], iv: [buffer], uuid: [string]} 214 | // the number of objects is naturally a radix for selecting one of the 215 | // input values for a given property; example, three objects and two keys: 216 | // [{a: 0, b: 0}, 217 | // {a: 1, b: 1}, 218 | // {a: 2, b: 2}] 219 | type Perm = Array<{ 220 | salt: string | Buffer 221 | iv: string | Buffer 222 | uuid: string | Buffer 223 | }> 224 | const makePermutations = (...objs: Array): Perm => { 225 | const permus = [] 226 | const keys = Array.from( 227 | objs.reduce((acc: any, curr: object) => { 228 | Object.keys(curr).forEach((key) => { 229 | acc.add(key) 230 | }) 231 | return acc 232 | }, new Set()) 233 | ) 234 | const radix = objs.length 235 | const numPermus = radix ** keys.length 236 | for (let permuIdx = 0; permuIdx < numPermus; permuIdx++) { 237 | const selectors = permuIdx 238 | .toString(radix) 239 | .padStart(keys.length, '0') 240 | .split('') 241 | .map((v) => parseInt(v, 10)) 242 | const obj: any = {} 243 | zip(selectors, keys).forEach(([sel, k]: [number, string]) => { 244 | if ((objs as any)[sel].hasOwnProperty(k)) { 245 | obj[k] = (objs as any)[sel][k] 246 | } 247 | }) 248 | permus.push(obj) 249 | } 250 | return permus 251 | } 252 | 253 | const makeEthersOptions = (opts: object) => { 254 | const obj: any = {} 255 | Object.entries(opts).forEach(([key, val]: [string, string | Buffer]) => { 256 | obj[key] = typeof val === 'string' ? '0x' + val : val 257 | }) 258 | return obj 259 | } 260 | 261 | let permutations = makePermutations(strKdfOptions, buffKdfOptions) 262 | 263 | if (isRunningInKarma()) { 264 | // These tests take a long time in the browser due to 265 | // the amount of permutations so we will shorten them. 266 | permutations = permutations.slice(1) 267 | } 268 | 269 | it('should work with PBKDF2', async function () { 270 | this.timeout(0) // never 271 | const w = 272 | '{"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"}}' 273 | 274 | await Promise.all( 275 | permutations.map(async function ({ salt, iv, uuid }) { 276 | const encFixtureWallet = await fixtureWallet.toV3String(pw, { 277 | kdf: 'pbkdf2', 278 | c: n, 279 | uuid: uuid, 280 | salt: salt, 281 | iv: iv, 282 | }) 283 | 284 | assert.deepStrictEqual(JSON.parse(w), JSON.parse(encFixtureWallet)) 285 | // ethers doesn't support encrypting with PBKDF2 286 | }) 287 | ) 288 | }) 289 | describe('should work with Scrypt', () => { 290 | const wStaticJSON = 291 | '{"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"}}' 292 | const wStatic = JSON.parse(wStaticJSON) 293 | const wRandom = Wallet.generate() 294 | const wEthers = new ethersWallet(wRandom.getPrivateKeyString()) 295 | for (const perm of permutations) { 296 | it(`vector ${JSON.stringify(perm)}`, async function () { 297 | this.timeout(0) 298 | const { salt, iv, uuid } = perm 299 | const ethersOpts = makeEthersOptions({ salt, iv, uuid }) 300 | 301 | const encFixtureWallet = await fixtureWallet.toV3String(pw, { 302 | kdf: 'scrypt', 303 | uuid: uuid, 304 | salt: salt, 305 | iv: iv, 306 | n: n, 307 | r: r, 308 | p: p, 309 | }) 310 | 311 | const encFixtureEthersWallet = ( 312 | await fixtureEthersWallet.encrypt(pw, { 313 | scrypt: { N: n, r: r, p: p }, 314 | salt: ethersOpts.salt, 315 | iv: ethersOpts.iv, 316 | uuid: ethersOpts.uuid, 317 | }) 318 | ).toLowerCase() 319 | 320 | const encRandomWallet = await wRandom.toV3String(pw, { 321 | kdf: 'scrypt', 322 | uuid: uuid, 323 | salt: salt, 324 | iv: iv, 325 | n: n, 326 | r: r, 327 | p: p, 328 | }) 329 | 330 | const encEthersWallet = ( 331 | await wEthers.encrypt(pw, { 332 | scrypt: { N: n, r: r, p: p }, 333 | salt: ethersOpts.salt, 334 | iv: ethersOpts.iv, 335 | uuid: ethersOpts.uuid, 336 | }) 337 | ).toLowerCase() 338 | 339 | assert.deepStrictEqual(wStatic, JSON.parse(encFixtureWallet)) 340 | assert.deepStrictEqual(wStatic, JSON.parse(encFixtureEthersWallet)) 341 | assert.deepStrictEqual(JSON.parse(encRandomWallet), JSON.parse(encEthersWallet)) 342 | }) 343 | } 344 | }) 345 | it('should work without providing options', async function () { 346 | this.timeout(0) // never 347 | const wallet = await fixtureWallet.toV3('testtest') 348 | assert.strictEqual(wallet['version'], 3) 349 | }) 350 | it('should fail for unsupported kdf', function () { 351 | this.timeout(0) // never 352 | assert.rejects(async function () { 353 | await fixtureWallet.toV3('testtest', { kdf: 'superkey' }) 354 | }, /^Error: Unsupported kdf$/) 355 | }) 356 | it('should fail for bad salt', function () { 357 | const pw = 'test' 358 | const errStr = 359 | /^Error: Invalid salt, string must be empty or a non-zero even number of hex characters$/ 360 | 361 | assert.rejects(async function () { 362 | await fixtureWallet.toV3(pw, { salt: 'f' }) 363 | }, errStr) 364 | assert.rejects(async function () { 365 | await fixtureWallet.toV3(pw, { salt: 'fff' }) 366 | }, errStr) 367 | assert.rejects(async function () { 368 | await fixtureWallet.toV3(pw, { salt: 'xfff' }) 369 | }, errStr) 370 | assert.rejects(async function () { 371 | await fixtureWallet.toV3(pw, { salt: 'fffx' }) 372 | }, errStr) 373 | assert.rejects(async function () { 374 | await fixtureWallet.toV3(pw, { salt: 'fffxff' }) 375 | }, errStr) 376 | assert.rejects(async function () { 377 | // @ts-ignore 378 | await fixtureWallet.toV3(pw, { salt: {} }) 379 | }, /^Error: Invalid salt, must be a string \(empty or a non-zero even number of hex characters\) or buffer$/) 380 | }) 381 | it('should work with empty salt', async function () { 382 | this.timeout(0) // never 383 | const pw = 'test' 384 | let salt: any = '' 385 | let w = await fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' }) 386 | 387 | assert.strictEqual(salt, w.crypto.kdfparams.salt) 388 | assert.strictEqual( 389 | fixtureWallet.getPrivateKeyString(), 390 | (await Wallet.fromV3(w, pw)).getPrivateKeyString() 391 | ) 392 | 393 | salt = '0x' 394 | w = await fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' }) 395 | 396 | assert.strictEqual('', w.crypto.kdfparams.salt) 397 | assert.strictEqual( 398 | fixtureWallet.getPrivateKeyString(), 399 | (await Wallet.fromV3(w, pw)).getPrivateKeyString() 400 | ) 401 | 402 | salt = Buffer.from('', 'hex') 403 | w = await fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' }) 404 | 405 | assert.strictEqual('', w.crypto.kdfparams.salt) 406 | assert.strictEqual( 407 | fixtureWallet.getPrivateKeyString(), 408 | (await Wallet.fromV3(w, pw)).getPrivateKeyString() 409 | ) 410 | 411 | salt = '' 412 | let iv = 'ffffffffffffffffffffffffffffffff' 413 | let uuid = 'ffffffffffffffffffffffffffffffff' 414 | let wStr = await fixtureWallet.toV3String(pw, { 415 | salt: salt, 416 | iv: iv, 417 | uuid: uuid, 418 | kdf: 'scrypt', 419 | n: n, 420 | r: r, 421 | p: p, 422 | }) 423 | let wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, { 424 | scrypt: { N: n, r: r, p: p }, 425 | salt: '0x' + (salt as string), 426 | iv: '0x' + iv, 427 | uuid: '0x' + uuid, 428 | }) 429 | 430 | assert.strictEqual(salt, JSON.parse(wStr).crypto.kdfparams.salt) 431 | assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase())) 432 | assert.strictEqual( 433 | fixtureWallet.getPrivateKeyString(), 434 | (await Wallet.fromV3(JSON.parse(wStr), pw)).getPrivateKeyString() 435 | ) 436 | assert.strictEqual( 437 | fixtureWallet.getPrivateKeyString(), 438 | (await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey 439 | ) 440 | 441 | salt = '0x' 442 | iv = '0x' + iv 443 | uuid = '0x' + uuid 444 | wStr = await fixtureWallet.toV3String(pw, { 445 | salt: salt, 446 | iv: iv, 447 | uuid: uuid, 448 | kdf: 'scrypt', 449 | n: n, 450 | r: r, 451 | p: p, 452 | }) 453 | wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, { 454 | scrypt: { N: n, r: r, p: p }, 455 | salt: salt, 456 | iv: iv, 457 | uuid: uuid, 458 | }) 459 | 460 | assert.strictEqual('', JSON.parse(wStr).crypto.kdfparams.salt) 461 | assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase())) 462 | assert.strictEqual( 463 | fixtureWallet.getPrivateKeyString(), 464 | (await Wallet.fromV3(JSON.parse(wStr), pw)).getPrivateKeyString() 465 | ) 466 | assert.strictEqual( 467 | fixtureWallet.getPrivateKeyString(), 468 | (await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey 469 | ) 470 | 471 | salt = Buffer.from('', 'hex') 472 | wStr = await fixtureWallet.toV3String(pw, { 473 | salt: salt, 474 | iv: iv, 475 | uuid: uuid, 476 | kdf: 'scrypt', 477 | n: n, 478 | r: r, 479 | p: p, 480 | }) 481 | wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, { 482 | scrypt: { N: n, r: r, p: p }, 483 | salt: salt, 484 | iv: iv, 485 | uuid: uuid, 486 | }) 487 | 488 | assert.strictEqual('', JSON.parse(wStr).crypto.kdfparams.salt) 489 | assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase())) 490 | assert.strictEqual( 491 | fixtureWallet.getPrivateKeyString(), 492 | (await Wallet.fromV3(JSON.parse(wStr), pw)).getPrivateKeyString() 493 | ) 494 | assert.strictEqual( 495 | fixtureWallet.getPrivateKeyString(), 496 | (await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey 497 | ) 498 | }) 499 | it('should fail for bad iv', function () { 500 | const pw = 'test' 501 | const errStrLength = /^Error: Invalid iv, string must be 32 hex characters$/ 502 | const errBuffLength = /^Error: Invalid iv, buffer must be 16 bytes$/ 503 | 504 | assert.rejects(async function () { 505 | await fixtureWallet.toV3(pw, { iv: '' }) 506 | }, errStrLength) 507 | assert.rejects(async function () { 508 | await fixtureWallet.toV3(pw, { iv: 'ff' }) 509 | }, errStrLength) 510 | assert.rejects(async function () { 511 | await fixtureWallet.toV3(pw, { iv: 'ffffffffffffffffffffffffffffffffff' }) 512 | }, errStrLength) 513 | assert.rejects(async function () { 514 | await fixtureWallet.toV3(pw, { iv: 'xfffffffffffffffffffffffffffffff' }) 515 | }, errStrLength) 516 | assert.rejects(async function () { 517 | await fixtureWallet.toV3(pw, { iv: 'fffffffffffffffffffffffffffffffx' }) 518 | }, errStrLength) 519 | assert.rejects(async function () { 520 | await fixtureWallet.toV3(pw, { iv: 'fffffffffffffffxffffffffffffffff' }) 521 | }, errStrLength) 522 | assert.rejects(async function () { 523 | await fixtureWallet.toV3(pw, { iv: Buffer.from('', 'hex') }) 524 | }, errBuffLength) 525 | assert.rejects(async function () { 526 | await fixtureWallet.toV3(pw, { iv: Buffer.from('ff', 'hex') }) 527 | }, errBuffLength) 528 | assert.rejects(async function () { 529 | await fixtureWallet.toV3(pw, { iv: Buffer.from('ffffffffffffffffffffffffffffffffff', 'hex') }) 530 | }, errBuffLength) 531 | assert.rejects(async function () { 532 | // @ts-ignore 533 | await fixtureWallet.toV3(pw, { iv: {} }) 534 | }, /^Error: Invalid iv, must be a string \(32 hex characters\) or buffer \(16 bytes\)$/) 535 | }) 536 | it('should fail for bad uuid', function () { 537 | const pw = 'test' 538 | const errStrLength = /^Error: Invalid uuid, string must be 32 hex characters$/ 539 | const errBuffLength = /^Error: Invalid uuid, buffer must be 16 bytes$/ 540 | 541 | assert.rejects(async function () { 542 | await fixtureWallet.toV3(pw, { uuid: '' }) 543 | }, errStrLength) 544 | assert.rejects(async function () { 545 | await fixtureWallet.toV3(pw, { uuid: 'ff' }) 546 | }, errStrLength) 547 | assert.rejects(async function () { 548 | await fixtureWallet.toV3(pw, { uuid: 'ffffffffffffffffffffffffffffffffff' }) 549 | }, errStrLength) 550 | assert.rejects(async function () { 551 | await fixtureWallet.toV3(pw, { uuid: 'xfffffffffffffffffffffffffffffff' }) 552 | }, errStrLength) 553 | assert.rejects(async function () { 554 | await fixtureWallet.toV3(pw, { uuid: 'fffffffffffffffffffffffffffffffx' }) 555 | }, errStrLength) 556 | assert.rejects(async function () { 557 | await fixtureWallet.toV3(pw, { uuid: 'fffffffffffffffxffffffffffffffff' }) 558 | }, errStrLength) 559 | assert.rejects(async function () { 560 | await fixtureWallet.toV3(pw, { uuid: Buffer.from('', 'hex') }) 561 | }, errBuffLength) 562 | assert.rejects(async function () { 563 | await fixtureWallet.toV3(pw, { uuid: Buffer.from('ff', 'hex') }) 564 | }, errBuffLength) 565 | assert.rejects(async function () { 566 | await fixtureWallet.toV3(pw, { 567 | uuid: Buffer.from('ffffffffffffffffffffffffffffffffff', 'hex'), 568 | }) 569 | }, errBuffLength) 570 | assert.rejects(async function () { 571 | // @ts-ignore 572 | await fixtureWallet.toV3(pw, { uuid: {} }) 573 | }, /^Error: Invalid uuid, must be a string \(32 hex characters\) or buffer \(16 bytes\)$/) 574 | }) 575 | it('should strip leading "0x" from salt, iv, uuid', async function () { 576 | this.timeout(0) // never 577 | const pw = 'test' 578 | const salt = 579 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 580 | const iv = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 581 | const uuid = 'cccccccccccccccccccccccccccccccc' 582 | let w = await fixtureWallet.toV3(pw, { 583 | salt: '0x' + salt, 584 | iv: '0X' + iv, 585 | uuid: '0x' + uuid, 586 | kdf: 'pbkdf2', 587 | }) 588 | let w2 = await fixtureWallet.toV3(pw, { 589 | salt: '0x' + salt, 590 | iv: '0X' + iv, 591 | uuid: uuid, 592 | kdf: 'pbkdf2', 593 | }) 594 | 595 | assert.strictEqual(salt, w.crypto.kdfparams.salt) 596 | assert.strictEqual(iv, w.crypto.cipherparams.iv) 597 | assert.strictEqual(w.id, w2.id) 598 | assert.strictEqual( 599 | fixtureWallet.getPrivateKeyString(), 600 | (await Wallet.fromV3(w, pw)).getPrivateKeyString() 601 | ) 602 | assert.strictEqual( 603 | fixtureWallet.getPrivateKeyString(), 604 | (await Wallet.fromV3(w2, pw)).getPrivateKeyString() 605 | ) 606 | 607 | w = await fixtureWallet.toV3(pw, { 608 | salt: '0x' + salt, 609 | iv: '0X' + iv, 610 | uuid: '0x' + uuid, 611 | kdf: 'scrypt', 612 | }) 613 | w2 = await fixtureWallet.toV3(pw, { 614 | salt: '0x' + salt, 615 | iv: '0X' + iv, 616 | uuid: uuid, 617 | kdf: 'scrypt', 618 | }) 619 | 620 | assert.strictEqual(salt, w.crypto.kdfparams.salt) 621 | assert.strictEqual(iv, w.crypto.cipherparams.iv) 622 | assert.strictEqual(w.id, w2.id) 623 | assert.strictEqual( 624 | fixtureWallet.getPrivateKeyString(), 625 | (await Wallet.fromV3(w, pw)).getPrivateKeyString() 626 | ) 627 | assert.strictEqual( 628 | fixtureWallet.getPrivateKeyString(), 629 | (await Wallet.fromV3(w2, pw)).getPrivateKeyString() 630 | ) 631 | }) 632 | }) 633 | 634 | /* 635 | describe('.fromV1()', function () { 636 | it('should work', function () { 637 | 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"}' 638 | const wallet = Wallet.fromV1(sample, 'foo') 639 | assert.strictEqual(wallet.getAddressString(), '0xd4584b5f6229b7be90727b0fc8c6b91bb427821f') 640 | }) 641 | }) 642 | */ 643 | 644 | describe('.fromV3()', function () { 645 | it('should work with PBKDF2', async function () { 646 | this.timeout(0) // never 647 | const w = 648 | '{"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}' 649 | let wEthersCompat = JSON.parse(w) 650 | // see: https://github.com/ethers-io/ethers.js/issues/582 651 | wEthersCompat.address = '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b' 652 | wEthersCompat = JSON.stringify(wEthersCompat) 653 | const pw = 'testpassword' 654 | const wallet = await Wallet.fromV3(w, pw) 655 | const wRandom = await Wallet.generate().toV3String(pw, { kdf: 'pbkdf2' }) 656 | const walletRandom = await Wallet.fromV3(wRandom, pw) 657 | 658 | assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b') 659 | assert.strictEqual( 660 | wallet.getAddressString(), 661 | (await ethersWallet.fromEncryptedJson(wEthersCompat, pw)).address.toLowerCase() 662 | ) 663 | assert.strictEqual( 664 | walletRandom.getAddressString(), 665 | (await ethersWallet.fromEncryptedJson(wRandom, pw)).address.toLowerCase() 666 | ) 667 | }) 668 | it.skip('should work with Scrypt', async function () { 669 | this.timeout(0) // never 670 | const sample = 671 | '{"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}' 672 | const pw = 'testtest' 673 | const wallet = await Wallet.fromV3(sample, pw) 674 | const sampleRandom = await Wallet.generate().toV3String(pw) 675 | const walletRandom = await Wallet.fromV3(sampleRandom, pw) 676 | 677 | assert.strictEqual(wallet.getAddressString(), '0x2f91eb73a6cd5620d7abb50889f24eea7a6a4feb') 678 | assert.strictEqual( 679 | wallet.getAddressString(), 680 | (await ethersWallet.fromEncryptedJson(sample, pw)).address.toLowerCase() 681 | ) 682 | assert.strictEqual( 683 | walletRandom.getAddressString(), 684 | (await ethersWallet.fromEncryptedJson(sampleRandom, pw)).address.toLowerCase() 685 | ) 686 | }) 687 | it.skip("should work with 'unencrypted' wallets", async function () { 688 | this.timeout(0) // never 689 | const w = 690 | '{"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}' 691 | const wallet = await Wallet.fromV3(w, '') 692 | assert.strictEqual(wallet.getAddressString(), '0xa9886ac7489ecbcbd79268a79ef00d940e5fe1f2') 693 | }) 694 | it('should fail with invalid password', async function () { 695 | this.timeout(0) // never 696 | const w = 697 | '{"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}' 698 | assert.rejects(async function () { 699 | await Wallet.fromV3(w, 'wrongtestpassword') 700 | }, /^Error: Key derivation failed - possibly wrong passphrase$/) 701 | }) 702 | it('should work with (broken) mixed-case input files', async function () { 703 | const w = 704 | '{"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}' 705 | this.timeout(0) // never 706 | const wallet = await Wallet.fromV3(w, 'testpassword', true) 707 | assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b') 708 | }) 709 | it("shouldn't work with (broken) mixed-case input files in strict mode", function () { 710 | const w = 711 | '{"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}' 712 | this.timeout(0) // never 713 | assert.rejects(async function () { 714 | await Wallet.fromV3(w, 'testpassword') 715 | }) // FIXME: check for assert message(s) 716 | }) 717 | it('should fail for wrong version', function () { 718 | const w = '{"version":2}' 719 | assert.rejects(async function () { 720 | await Wallet.fromV3(w, 'testpassword') 721 | }, /^Error: Not a V3 wallet$/) 722 | }) 723 | it('should fail for wrong kdf', function () { 724 | const w = '{"crypto":{"kdf":"superkey"},"version":3}' 725 | assert.rejects(async function () { 726 | await Wallet.fromV3(w, 'testpassword') 727 | }, /^Error: Unsupported key derivation scheme$/) 728 | }) 729 | it('should fail for wrong prf in pbkdf2', function () { 730 | const w = '{"crypto":{"kdf":"pbkdf2","kdfparams":{"prf":"invalid"}},"version":3}' 731 | assert.rejects(async function () { 732 | await Wallet.fromV3(w, 'testpassword') 733 | }, /^Error: Unsupported parameters to PBKDF2$/) 734 | }) 735 | }) 736 | 737 | describe('.fromEthSale()', function () { 738 | // Generated using https://github.com/ethereum/pyethsaletool/ [4afd19ad60cee8d09b645555180bc3a7c8a25b67] 739 | it('should work with short password (8 characters)', async function () { 740 | const json = 741 | '{"encseed": "81ffdfaf2736310ce87df268b53169783e8420b98f3405fb9364b96ac0feebfb62f4cf31e0d25f1ded61f083514dd98c3ce1a14a24d7618fd513b6d97044725c7d2e08a7d9c2061f2c8a05af01f06755c252f04cab20fee2a4778130440a9344", "ethaddr": "22f8c5dd4a0a9d59d580667868df2da9592ab292", "email": "hello@ethereum.org", "btcaddr": "1DHW32MFwHxU2nk2SLAQq55eqFotT9jWcq"}' 742 | const wallet = await Wallet.fromEthSale(json, 'testtest') 743 | assert.strictEqual(wallet.getAddressString(), '0x22f8c5dd4a0a9d59d580667868df2da9592ab292') 744 | }) 745 | it('should work with long password (19 characters)', async function () { 746 | const json = 747 | '{"encseed": "0c7e462bd67c6840ed2fa291090b2f46511b798d34492e146d6de148abbccba45d8fcfc06bea2e5b9d6c5d17b51a9a046c1054a032f24d96a56614a14dcd02e3539685d7f09b93180067160f3a9db648ccca610fc2f983fc65bf973304cbf5b6", "ethaddr": "c90b232231c83b462723f473b35cb8b1db868108", "email": "thisisalongpassword@test.com", "btcaddr": "1Cy2fN2ov5BrMkzgrzE34YadCH2yLMNkTE"}' 748 | const wallet = await Wallet.fromEthSale(json, 'thisisalongpassword') 749 | assert.strictEqual(wallet.getAddressString(), '0xc90b232231c83b462723f473b35cb8b1db868108') 750 | }) 751 | // From https://github.com/ryepdx/pyethrecover/blob/master/test_wallets/ico.json 752 | it("should work with pyethrecover's wallet", async function () { 753 | const json = 754 | '{"encseed": "8b4001bf61a10760d8e0876fb791e4ebeb85962f565c71697c789c23d1ade4d1285d80b2383ae5fc419ecf5319317cd94200b65df0cc50d659cbbc4365fc08e8", "ethaddr": "83b6371ba6bd9a47f82a7c4920835ef4be08f47b", "bkp": "9f566775e56486f69413c59f7ef923bc", "btcaddr": "1Nzg5v6uRCAa6Fk3CUU5qahWxEDZdZ1pBm"}' 755 | const wallet = await Wallet.fromEthSale(json, 'password123') 756 | assert.strictEqual(wallet.getAddressString(), '0x83b6371ba6bd9a47f82a7c4920835ef4be08f47b') 757 | }) 758 | }) 759 | 760 | describe('.fromEtherWallet()', function () { 761 | // it('should work with unencrypted input', function () { 762 | // const etherWalletUnencrypted = '{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":false,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"a2c6222146ca2269086351fda9f8d2dfc8a50331e8a05f0f400c13653a521862","public":"2ed129b50b1a4dbbc53346bf711df6893265ad0c700fd11431b0bc3a66bd383a87b10ad835804a6cbe092e0375a0cc3524acf06b1ec7bb978bf25d2d6c35d120"}' 763 | // const wallet = Thirdparty.fromEtherWallet(etherWalletUnencrypted) 764 | // assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c') 765 | // }) 766 | it('should work with encrypted input', async function () { 767 | const etherWalletEncrypted = 768 | '{"address":"0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c","encrypted":true,"locked":true,"hash":"b7a6621e8b125a17234d3e5c35522696a84134d98d07eab2479d020a8613c4bd","private":"U2FsdGVkX1/hGPYlTZYGhzdwvtkoZfkeII4Ga4pSd/Ak373ORnwZE4nf/FFZZFcDTSH1X1+AmewadrW7dqvwr76QMYQVlihpPaFV307hWgKckkG0Mf/X4gJIQQbDPiKdcff9","public":"U2FsdGVkX1/awUDAekZQbEiXx2ct4ugXwgBllY0Hz+IwYkHiEhhxH+obu7AF7PCU2Vq5c0lpCzBUSvk2EvFyt46bw1OYIijw0iOr7fWMJEkz3bfN5mt9pYJIiPzN0gxM8u4mrmqLPUG2SkoZhWz4NOlqRUHZq7Ep6aWKz7KlEpzP9IrvDYwGubci4h+9wsspqtY1BdUJUN59EaWZSuOw1g=="}' 769 | const wallet = await Thirdparty.fromEtherWallet(etherWalletEncrypted, 'testtest') 770 | assert.strictEqual(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c') 771 | }) 772 | }) 773 | 774 | describe('.fromEtherCamp()', function () { 775 | it('should work with seed text', function () { 776 | const wallet = Thirdparty.fromEtherCamp('ethercamp123') 777 | assert.strictEqual(wallet.getAddressString(), '0x182b6ca390224c455f11b6337d74119305014ed4') 778 | }) 779 | }) 780 | 781 | describe('.fromQuorumWallet()', function () { 782 | it('should work', function () { 783 | const wallet = Thirdparty.fromQuorumWallet('testtesttest', 'ethereumjs-wallet') 784 | assert.strictEqual(wallet.getAddressString(), '0x1b86ccc22e8f137f204a41a23033541242a48815') 785 | }) 786 | }) 787 | 788 | describe('raw new Wallet() init', function () { 789 | it('should fail when both priv and pub key provided', function () { 790 | assert.throws(function () { 791 | new Wallet(fixturePrivateKeyBuffer, fixturePublicKeyBuffer) 792 | }, /^Error: Cannot supply both a private and a public key to the constructor$/) 793 | }) 794 | }) 795 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "esModuleInterop": false, 11 | "allowSyntheticDefaultImports": true, 12 | "resolveJsonModule": true, 13 | "downlevelIteration": true, 14 | "strict": true, 15 | "target": "es2020", 16 | "lib": ["ES2020", "DOM"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["src/**/*.ts"] 7 | } 8 | --------------------------------------------------------------------------------