Support EtherWallet, EtherCamp and KryptoKit wallets

This commit is contained in:
Alex Beregszaszi 2016-02-23 20:12:18 +00:00
parent e95b314950
commit eacd3c0692
4 changed files with 166 additions and 1 deletions

View File

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

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

View File

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

View File

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