converted files to ts, fixed tests, implemented ethereumjs standards for coverage, ts compiler, and tslint

This commit is contained in:
Jack Cook 2019-06-27 19:10:15 -07:00
parent c04b80e2b1
commit 695da18e44
23 changed files with 1347 additions and 1067 deletions

View File

@ -1,7 +0,0 @@
{
"presets": [
[
"env"
]
]
}

9
.gitignore vendored
View File

@ -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

3
.nycrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "@ethereumjs/config-nyc"
}

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
.vscode
package.json
dist
.nyc_output

View File

@ -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

View File

@ -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`

View File

@ -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"
}
}

View File

@ -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

41
src/hdkey.ts Normal file
View File

@ -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)
}
}

View File

@ -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

444
src/index.ts Normal file
View File

@ -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
}

View File

@ -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)
}

22
src/provider-engine.ts Normal file
View File

@ -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)
}
}

View File

@ -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')
})
})

View File

@ -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$/)
})
})

View File

@ -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

247
src/thirdparty.ts Normal file
View File

@ -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,
}

115
test/hdkey.ts Normal file
View File

@ -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',
)
})
})

365
test/index.ts Normal file
View File

@ -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$/)
})
})

4
test/mocha.opts Normal file
View File

@ -0,0 +1,4 @@
--require ./node_modules/ts-node/register
--require ./node_modules/source-map-support/register
--recursive
--exit

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "@ethereumjs/config-tsc",
"include": ["src/**/*.ts", "test/**/*.ts"]
}

7
tsconfig.prod.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": "@ethereumjs/config-tsc",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*.ts"]
}

3
tslint.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "@ethereumjs/config-tslint"
}