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