Support EtherWallet, EtherCamp and KryptoKit wallets
This commit is contained in:
parent
e95b314950
commit
eacd3c0692
|
@ -26,6 +26,9 @@ Constructors:
|
|||
* `fromV1(input, password)` - import a wallet (Version 1 of the Ethereum wallet format)
|
||||
* `fromV3(input, password)` - import a wallet (Version 3 of the Ethereum wallet format)
|
||||
* `fromEthSale(input, password)` - import an Ethereum Pre Sale 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
|
||||
|
||||
For the V1, V3 and EthSale formats the input is a JSON serialized string. All these formats require a password.
|
||||
|
||||
|
|
134
index.js
134
index.js
|
@ -2,6 +2,7 @@ var ethUtil = require('ethereumjs-util')
|
|||
var crypto = require('crypto')
|
||||
var scryptsy = require('scrypt.js')
|
||||
var uuid = require('uuid')
|
||||
var utf8 = require('utf8')
|
||||
|
||||
function assert (val, msg) {
|
||||
if (!val) {
|
||||
|
@ -218,4 +219,137 @@ Wallet.fromEthSale = function (input, password) {
|
|||
return wallet
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
// 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) ? new Buffer(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 = new Buffer(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/
|
||||
*/
|
||||
Wallet.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 = new Buffer(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)
|
||||
|
||||
// derive key/iv using OpenSSL EVP as implemented in CryptoJS
|
||||
var evp = evp_kdf(new Buffer(password), cipher.salt, { keysize: 32, ivsize: 16 })
|
||||
|
||||
var decipher = crypto.createDecipheriv('aes-256-cbc', evp.key, evp.iv)
|
||||
privKey = decipherBuffer(decipher, new Buffer(cipher.ciphertext))
|
||||
|
||||
// NOTE: yes, they've run it through UTF8
|
||||
privKey = new Buffer(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
|
||||
}
|
||||
|
||||
Wallet.fromEtherCamp = function (passphrase) {
|
||||
return new Wallet(ethUtil.sha3(new Buffer(passphrase)))
|
||||
}
|
||||
|
||||
Wallet.fromKryptoKit = function (entropy) {
|
||||
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 {
|
||||
throw new Error('Unsupported or invalid entropy type')
|
||||
}
|
||||
|
||||
return new Wallet(privKey)
|
||||
}
|
||||
|
||||
module.exports = Wallet
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
"dependencies": {
|
||||
"ethereumjs-util": "^4.1.0",
|
||||
"scrypt.js": "^0.1.0",
|
||||
"uuid": "^2.0.1"
|
||||
"uuid": "^2.0.1",
|
||||
"utf8": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.3.4",
|
||||
|
|
|
@ -117,3 +117,30 @@ describe('.fromEthSale()', function () {
|
|||
assert.equal(wallet.getAddressString(), '0x22f8c5dd4a0a9d59d580667868df2da9592ab292')
|
||||
})
|
||||
})
|
||||
|
||||
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 = Wallet.fromEtherWallet(etherWalletUnencrypted)
|
||||
assert.equal(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 = Wallet.fromEtherWallet(etherWalletEncrypted, 'testtest')
|
||||
assert.equal(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.fromEtherCamp()', function () {
|
||||
it('should work with seed text', function () {
|
||||
var wallet = Wallet.fromEtherCamp('ethercamp123')
|
||||
assert.equal(wallet.getAddressString(), '0x182b6ca390224c455f11b6337d74119305014ed4')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.fromKryptoKit()', function () {
|
||||
it('should work with basic input (d-type)', function () {
|
||||
var wallet = Wallet.fromKryptoKit('dBWfH8QZSGbg1sAYHLBhqE5R8VGAoM7')
|
||||
assert.equal(wallet.getAddressString(), '0x3611981ad2d6fc1d7579d6ce4c6bc37e272c369c')
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue