ethereumjs/ethereumjs-wallet
Utilities for handling Ethereum keys
ethereumjs-wallet
A lightweight wallet implementation. At the moment it supports key creation and conversion between various formats.
轻量级钱包实现。目前,它支持各种格式之间的key的创建和转换
It is complemented by the following packages:
- ethereumjs-tx to sign transactions(看ethereumjs/ethereumjs-tx)
- ethereumjs-icap to manipulate ICAP addresses(看Inter exchange Client Address Protocol (ICAP)- 互换客户端地址协议)
- 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
andethereumjs-tx
)使用一个单独的、维护的加密库版本 - support import/export between various wallet formats支持各种钱包格式之间的导入/导出
- support BIP32 HD keys支持BIP32
Features not supported:不支持
- signing transactions交易签名
- managing storage (neither in node.js or the browser)管理存储
Wallet API
Constructors:
generate([icap])
- create an instance based on a new random key (settingicap
to true will generate an address suitable for theICAP Direct mode
)基于新的随机key创建实例(将icap设置为true将生成一个适合于icap直接模式的地址,即过程中将私钥转成地址判断是否符合icap直接模式)使用的是私钥generateVanityAddress(pattern)
- create an instance where the address is valid against the supplied pattern (this will be very slow)创建一个实例,其中私钥随机生成,转成的address必须要满足提供的正则表达式pattern,所以这个过程可能会比较缓慢
fromPrivateKey(input)
- create an instance based on a raw private key基于raw私钥创建实例fromExtendedPrivateKey(input)
- create an instance based on a BIP32 extended private key (xprv)基于BIP32扩展私钥创建实例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)基于BIP32扩展公钥创建实例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). SetnonStrict
true to accept files with mixed-caps. 设置nonStrict
true为true则input内容不区分大小写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.
对于V1、V3和EthSale格式,输入是一个JSON序列化的字符串。所有这些格式都需要密码。其实就是想在的UTC文件,需要密码才能得到密钥,现在的版本version是3,即V3
Note: fromPublicKey()
only accepts uncompressed Ethereum-style public keys, unless the nonStrict
flag is set to true.注意:除非nonStrict
标志被设置为true,否则fromPublicKey()只接受未压缩的ethereum样式的公钥。
⚠️扩展公私钥可以使用bs58、bs58check库与公私钥转换
Instance methods:
getPrivateKey()
- return the private keygetPublicKey()
- return the public keygetAddress()
- return the addressgetChecksumAddressString()
- return the address with checksumgetV3Filename([timestamp])
- return the suggested filename for V3 keystores 根据时间戳来得到UTC文件的文件名toV3(password, [options])
- return the wallet as a JSON string (Version 3 of the Ethereum wallet format)返回一个UTC文件里的内容
All of the above instance methods return a Buffer or JSON. Use the String
suffixed versions for a string output, such as getPrivateKeyString()
.上面的所有实例方法都返回一个Buffer或JSON。对字符串输出使用字符串后缀版本,例如getPrivateKeyString()、toV3String()等添加String后缀后,输出的即为字符串。
Note: getPublicKey()
only returns uncompressed Ethereum-style public keys.
注意:getPublicKey()只返回未压缩的Ethereum类型公钥。
Remarks about toV3(相关内容可见geth中UTC文件与私钥的关系)
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 viacrypto.getRandomBytes
if nothing is supplied.kdf的随机salt。大小必须符合KDF(key派生函数)的要求。通过密码生成的随机数。如果没有提供任何内容,则为getRandomBytes。 - iv - Initialization vector for the
cipher
. Size must match the requirements of the cipher. Random number generated viacrypto.getRandomBytes
if nothing is supplied.密码的初始化向量。大小必须符合密码的要求。通过密码生成的随机数。如果没有提供任何内容,则为getRandomBytes。 - kdf - The key derivation function, see below.key的推导函数
- dklen - Derived key length. For certain
cipher
settings, this must match the block sizes of those.派生key的长度,对于某些密码设置,这必须与这些设置的块大小匹配 - cipher - The cipher to use. Names must match those of supported by
OpenSSL
, e.g.aes-128-ctr
oraes-128-cbc
.使用密码。名称必须与OpenSSL支持的名称匹配,例如aes-128-ctr或aes-128-cbc。
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 ishmac-sha256
. So no point changing it.唯一支持的(也是默认的)值是hmac_sha256。所以没有必要改变它。
For scrypt
:
n
- Iteration count. Defaults to 262144.迭代次数r
- Block size for the underlying hash. Defaults to 8.底层哈希的块大小。默认为8。p
- Parallelization factor. Defaults to 1.并行化的因素。默认为1。
The following settings are favoured by the Go Ethereum implementation and we default to the same:
下面的设置是Go Ethereum实现支持的,我们默认的设置也是如此
kdf
:scrypt
dklen
:32
n
:262144
r
:8
p
:1
cipher
:aes-128-ctr
代码实现:
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 decipherBuffer (decipher, data) {
return Buffer.concat([ decipher.update(data), decipher.final() ])
} var Wallet = function (priv, pub) {//根据输入的公钥、私钥来内容判断,只有满足条件的才能记录到wallet中
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', {//所以当下面的Wallet.prototype.getPrivateKey中的this.privKey被调用时就会调用这里的get函数
get: function () {
assert(this._privKey, 'This is a public key only wallet')//即如果this._privKey没有值,则说明使用的是公钥创建的实例,则不能活得私钥的值
return this._privKey
}
}) Object.defineProperty(Wallet.prototype, 'pubKey', {
get: function () {
if (!this._pubKey) {//如果没有公钥,即实例是使用私钥创建的,那么就用私钥来生成公钥,并记录到this._pubKey中
this._pubKey = ethUtil.privateToPublic(this.privKey)
}
return this._pubKey
}
}) Wallet.generate = function (icapDirect) {//使用的是私钥
if (icapDirect) {
var max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', )
while (true) {
var privKey = randomBytes()
if (new ethUtil.BN(ethUtil.privateToAddress(privKey)).lte(max)) {
return new Wallet(privKey) //可以看见,其实生成一个wallet实例就是将相应的公钥或私钥记录到this._pubKey或this._privKey上
}
}
} else {
return new Wallet(randomBytes())
}
} Wallet.generateVanityAddress = function (pattern) {//创建一个实例,其中私钥随机生成,转成的address必须要满足提供的正则表达式pattern
if (typeof pattern !== 'object') {
pattern = new RegExp(pattern)
} while (true) {
var privKey = randomBytes()
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()
var iv = opts.iv || randomBytes() var derivedKey
var kdf = opts.kdf || 'scrypt'
var kdfparams = {
dklen: opts.dklen || ,
salt: salt.toString('hex')
} if (kdf === 'pbkdf2') {
kdfparams.c = opts.c ||
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 ||
kdfparams.r = opts.r ||
kdfparams.p = opts.p ||
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(, ), iv)
if (!cipher) {
throw new Error('Unsupported cipher')
} var ciphertext = Buffer.concat([ cipher.update(this.privKey), cipher.final() ]) var mac = ethUtil.sha3(Buffer.concat([ derivedKey.slice(, ), Buffer.from(ciphertext, 'hex') ])) return {
version: ,
id: uuidv4({ random: opts.uuid || randomBytes() }),
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) {//如何根据时间戳来得到UTC文件的文件名
/*
* 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) {//本质还是使用fromPublicKey(),只是将ExtendedPublicKey转成了PublicKey
assert(pub.slice(, ) === 'xpub', 'Not an extended public key')
pub = bs58check.decode(pub).slice()
// 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(, ) === 'xprv', 'Not an extended private key')
var tmp = bs58check.decode(priv)
assert(tmp[] === , 'Invalid extended private key')
return Wallet.fromPrivateKey(tmp.slice())
} 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 !== ) {
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.sha3(Buffer.concat([ derivedKey.slice(, ), 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(, ), Buffer.from(json.crypto.cipherparams.iv, 'hex'))
var seed = decipherBuffer(decipher, ciphertext, 'hex') 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, , , 'sha256').slice(, ) // 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(, ))
var seed = decipherBuffer(decipher, encseed.slice()) var wallet = new Wallet(ethUtil.sha3(seed))
if (wallet.getAddress().toString('hex') !== json.ethaddr) {
throw new Error('Decoded key mismatch - possibly wrong passphrase')
}
return wallet
} module.exports = Wallet
扩展:Object.defineProperty(obj,prop,descriptor)
参数 obj 需要定义属性的对象,比如上面例子为Wallet.prototype prop 需定义或修改的属性的名字,'privKey' descriptor 将被定义或修改的属性的描述符,{get:function(){}}
使用:
ethereumjs-wallet/src/test/index.js
var assert = require('assert')
var Buffer = require('safe-buffer').Buffer
var Wallet = require('../')
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') //fromPrivateKey()
var fixtureWallet = Wallet.fromPrivateKey(fixturePrivateKeyBuffer) describe('.getPrivateKey()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getPrivateKey().toString('hex'), fixturePrivateKey)
})
it('should fail', function () {//Private key无效
assert.throws(function () {
Wallet.fromPrivateKey(Buffer.from('', 'hex'))
}, /^Error: Private key does not satisfy the curve requirements \(ie. it is invalid\)$/)
})
}) describe('.getPrivateKeyString()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getPrivateKeyString(), fixturePrivateKeyStr)
})
}) describe('.getPublicKey()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getPublicKey().toString('hex'), fixturePublicKey)
})
}) describe('.getPublicKeyString()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getPublicKeyString(), fixturePublicKeyStr)
})
}) describe('.getAddress()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getAddress().toString('hex'), 'b14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
})
}) describe('.getAddressString()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getAddressString(), '0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c')
})
}) describe('.getChecksumAddressString()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getChecksumAddressString(), '0xB14Ab53E38DA1C172f877DBC6d65e4a1B0474C3c')
})
}) //fromPublicKey()
describe('public key only wallet', function () {//生成了仅有公钥的钱包
var pubKey = Buffer.from(fixturePublicKey, 'hex')
it('.fromPublicKey() should work', function () {
assert.equal(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.equal(Wallet.fromPublicKey(tmp, true).getPublicKey().toString('hex'),
'0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
})
it('.getAddress() should work', function () {
assert.equal(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$/)
})
}) //fromExtended
describe('.fromExtendedPrivateKey()', function () {
it('should work', function () {
var xprv = 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY'
assert.equal(Wallet.fromExtendedPrivateKey(xprv).getAddressString(), '0xb800bf5435f67c7ee7d83c3a863269969a57c57c')
})
}) describe('.fromExtendedPublicKey()', function () {
it('should work', function () {
var xpub = 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ'
assert.equal(Wallet.fromExtendedPublicKey(xpub).getAddressString(), '0xb800bf5435f67c7ee7d83c3a863269969a57c57c')
})
}) //generate
describe('.generate()', function () {
it('should generate an account', function () {
assert.equal(Wallet.generate().getPrivateKey().length, )
})
it('should generate an account compatible with ICAP Direct', function () {
var max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', )
var wallet = Wallet.generate(true)
assert.equal(wallet.getPrivateKey().length, )
assert.equal(new ethUtil.BN(wallet.getAddress()).lte(max), true)//生成的address是符合ICAP的,所以小于最大值'088f924eeceeda7fe92e1f5b0fffffffffffffff'
})
}) describe('.generateVanityAddress()', function () {
it('should generate an account with 000 prefix (object)', function () {
this.timeout() // 3minutes
var wallet = Wallet.generateVanityAddress(/^/)//以000作为前缀
assert.equal(wallet.getPrivateKey().length, )
assert.equal(wallet.getAddress()[], )
assert.equal(wallet.getAddress()[] >>> , )
})
it('should generate an account with 000 prefix (string)', function () {
this.timeout() // 3minutes
var wallet = Wallet.generateVanityAddress('^000')
assert.equal(wallet.getPrivateKey().length, )
assert.equal(wallet.getAddress()[], )
assert.equal(wallet.getAddress()[] >>> , )
})
}) //V3
describe('.getV3Filename()', function () {
it('should work', function () {
assert.equal(fixtureWallet.getV3Filename(), '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 () {//"kdf"为"pbkdf2"
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.equal(fixtureWallet.toV3String('testtest', { kdf: 'pbkdf2', uuid: uuid, salt: salt, iv: iv }), w)//"cipher"默认为"aes-128-ctr","dklen"默认为32
})
it('should work with Scrypt', function () {//"kdf"为"scrypt"
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() // 3minutes
// FIXME: just test for ciphertext and mac?
assert.equal(fixtureWallet.toV3String('testtest', { kdf: 'scrypt', uuid: uuid, salt: salt, iv: iv }), w)//"cipher"默认为"aes-128-ctr","dklen"默认为32
})
it('should work without providing options', function () {
this.timeout() // 3minutes
assert.equal(fixtureWallet.toV3('testtest')['version'], )
})
it('should fail for unsupported kdf', function () {
this.timeout() // 3minutes
assert.throws(function () {
fixtureWallet.toV3('testtest', { kdf: 'superkey' })//只支持两种kdf
}, /^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.equal(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.equal(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() // 3minutes
assert.equal(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() // 3minutes
assert.equal(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.equal(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')//没有设置nonStricttrue=true
}) // 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$/)
})
}) //fromEthSale()
describe('.fromEthSale()', function () {
// Generated using https://github.com/ethereum/pyethsaletool/ [4afd19ad60cee8d09b645555180bc3a7c8a25b67]
var json = '{"encseed": "81ffdfaf2736310ce87df268b53169783e8420b98f3405fb9364b96ac0feebfb62f4cf31e0d25f1ded61f083514dd98c3ce1a14a24d7618fd513b6d97044725c7d2e08a7d9c2061f2c8a05af01f06755c252f04cab20fee2a4778130440a9344", "ethaddr": "22f8c5dd4a0a9d59d580667868df2da9592ab292", "email": "hello@ethereum.org", "btcaddr": "1DHW32MFwHxU2nk2SLAQq55eqFotT9jWcq"}'
it('should work', function () {
var wallet = Wallet.fromEthSale(json, 'testtest')
assert.equal(wallet.getAddressString(), '0x22f8c5dd4a0a9d59d580667868df2da9592ab292')
})
})
Thirdparty API
Importing various third party wallets is possible through the thirdparty
submodule:
通过第三方子模块可以导入各种第三方钱包
var thirdparty = require('ethereumjs-wallet/thirdparty')
Constructors:
fromEtherCamp(passphrase)
- import a brain wallet used by Ether.CampfromEtherWallet(input, password)
- import a wallet generated by EtherWalletfromKryptoKit(seed)
- import a wallet from a KryptoKit seedfromQuorumWallet(passphrase, userid)
- import a brain wallet used by Quorum Wallet
实现代码:
/*
* 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 !== ) {
throw new Error('Invalid private key length')//私钥长度不对
} privKey = Buffer.from(json.private, 'hex')//得到私钥
} else {//上锁了,则先用password解锁
if (typeof password !== 'string') {
throw new Error('Password required')//密码类型不对
}
if (password.length < ) {//密码不能太过简单
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(, ) : json.private // decode openssl ciphertext + salt encoding
cipher = decodeCryptojsSalt(cipher)//对cipher进行解密,得到ciphertext + salt if (!cipher.salt) {//cipher.salt为false,则说明不支持EtherWallet的key格式
throw new Error('Unsupported EtherWallet key format')
} //下面就是生成密钥的过程
// derive key/iv using OpenSSL EVP as implemented in CryptoJS,派生key/iv初始化向量
var evp = evp_kdf(Buffer.from(password), cipher.salt, { keysize: , ivsize: }) var decipher = crypto.createDecipheriv('aes-256-cbc', evp.key, evp.iv)
privKey = decipherBuffer(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)//然后使用上面得到的私钥生成wallet if (wallet.getAddressString() !== json.address) {//查看该wallet得到的address与传入的json中的address是否相同来判定是否有效
throw new Error('Invalid private key or address')
} return wallet
} Thirdparty.fromEtherCamp = function (passphrase) {
return new Wallet(ethUtil.sha3(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 = ; i < buf.length; i++) {
if (buf[i] <= 0x7F) {
res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
tmp = ''
} else {
tmp += '%' + buf[i].toString()
}
} return Buffer.from(res + decodeUtf8Char(tmp))
} if (entropy[] === '#') {
entropy = entropy.slice()
} var type = entropy[]
entropy = entropy.slice() 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(, )))
var checksum = entropy.slice(, ) var salt = kryptoKitBrokenScryptSeed(encryptedSeed)
var aesKey = scryptsy(Buffer.from(password, 'utf8'), salt, , , , ) /* 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(, ))),
Buffer.from(decipher.decrypt(encryptedSeed.slice(, )))
]) if (checksum.length > ) {
if (checksum !== ethUtil.sha256(ethUtil.sha256(privKey)).slice(, ).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 >= )
assert(userid.length >= ) var seed = passphrase + userid
seed = crypto.pbkdf2Sync(seed, seed, , , 'sha256') return new Wallet(seed)
}
使用:
ethereumjs-wallet/src/test/index.js
ar assert = require('assert')
var Thirdparty = require('../thirdparty.js')
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)//"locked":false,所以不需要输入密码
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 = Thirdparty.fromEtherWallet(etherWalletEncrypted, 'testtest')//"locked":true,所以需要输入密码testtest来解密
assert.equal(wallet.getAddressString(), '0x9d6abd11d36cc20d4836c25967f1d9efe6b1a27c')
})
}) describe('.fromEtherCamp()', function () {
it('should work with seed text', function () {
var wallet = Thirdparty.fromEtherCamp('ethercamp123')
assert.equal(wallet.getAddressString(), '0x182b6ca390224c455f11b6337d74119305014ed4')
})
}) describe('.fromKryptoKit()', function () {
it('should work with basic input (d-type)', function () {
var wallet = Thirdparty.fromKryptoKit('dBWfH8QZSGbg1sAYHLBhqE5R8VGAoM7')
assert.equal(wallet.getAddressString(), '0x3611981ad2d6fc1d7579d6ce4c6bc37e272c369c')
})
it('should work with encrypted input (q-type)', function () {
var wallet = Thirdparty.fromKryptoKit('qhah1VeT0RgTvff1UKrUrxtFViiQuki16dd353d59888c25', 'testtest')
assert.equal(wallet.getAddressString(), '0x3c753e27834db67329d1ec1fab67970ec1e27112')
})
}) describe('.fromQuorumWallet()', function () {
it('should work', function () {
var wallet = Thirdparty.fromQuorumWallet('testtesttest', 'ethereumjs-wallet')
assert.equal(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$/)
})
})
HD Wallet API
To use BIP32 HD wallets, first include the hdkey
submodule:
var hdkey = require('ethereumjs-wallet/hdkey')
Constructors:
fromMasterSeed(seed)
- create an instance based on a seed 使用seed创建实例fromExtendedKey(key)
- create an instance based on a BIP32 extended private or public key使用基于 BIP32的扩展私钥或公钥来创建实例
For the seed we suggest to use 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) 基于path派生节点deriveChild(index)
- derive a node based on a child index 基于child index派生结点,即将该层节点作为父节点getWallet()
- return aWallet
instance as seen above 返回上面生成的wallet实例
代码实现:
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
使用:
ethereumjs-wallet/src/test/hdkey.js
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 () {//使用seed创建实例
it('should work', function () {
assert.doesNotThrow(function () {
HDKey.fromMasterSeed(fixtureseed)
})
})
}) describe('.privateExtendedKey()', function () {//返回扩展私钥
it('should work', function () {
assert.equal(fixturehd.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
}) describe('.publicExtendedKey()', function () {//返回扩展公钥
it('should work', function () {
assert.equal(fixturehd.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
})
}) describe('.fromExtendedKey()', function () {//使用基于 BIP32的扩展私钥或公钥来创建实例
it('should work with public', function () {//使用扩展公钥,即wallet只有公钥,不能得到私钥值
var hdnode = HDKey.fromExtendedKey('xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.equal(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.equal(hdnode.publicExtendedKey(), 'xpub661MyMwAqRbcGout4B6s29b6gGQsowyoiF6UgXBEr7eFCWYfXuZDvRxP9zEh1Kwq3TLqDQMbkbaRpSnoC28oWvjLeshoQz1StZ9YHM1EpcJ')
assert.equal(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
}) describe('.deriveChild()', function () {//在当前层次下生成index=1的child节点
it('should work', function () {
var hdnode = fixturehd.deriveChild()
assert.equal(hdnode.privateExtendedKey(), 'xprv9vYSvrg3eR5FaKbQE4Ao2vHdyvfFL27aWMyH6X818mKWMsqqQZAN6HmRqYDGDPLArzaqbLExRsxFwtx2B2X2QKkC9uoKsiBNi22tLPKZHNS')
})
}) describe('.derivePath()', function () {//该方法可以越级到子子子层去生成child节点
it('should work with m', function () {
var hdnode = fixturehd.derivePath('m')
assert.equal(hdnode.privateExtendedKey(), 'xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY')
})
it('should work with m/44\'/0\'/0/1', function () {
var hdnode = fixturehd.derivePath('m/44\'/0\'/0/1')
assert.equal(hdnode.privateExtendedKey(), 'xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG')
})
}) describe('.getWallet()', function () {//能够让该fixturehd wallet实例去使用wallet API
it('should work', function () {
assert.equal(fixturehd.getWallet().getPrivateKeyString(), '0x26cc9417b89cd77c4acdbe2e3cd286070a015d8e380f9cd1244ae103b7d89d81')
assert.equal(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.equal(hdnode.getWallet().getPublicKeyString(), '0x0639797f6cc72aea0f3d309730844a9e67d9f1866e55845c5f7e0ab48402973defa5cb69df462bcc6d73c31e1c663c225650e80ef14a507b203f2a12aea55bc1')
})
})
Provider Engine
The Wallet can be easily plugged into provider-engine to provide signing:
作为subProvider被添加进Provider Engine中
const WalletSubprovider = require('ethereumjs-wallet/provider-engine') <engine>.addProvider(new WalletSubprovider(<wallet instance>))
Note it only supports the basic wallet. With a HD Wallet, call getWallet()
first.
代码实现:
ethereumjs-wallet/src/provider-engine.js
'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)
}
provider-engine/subproviders/hooked-wallet-ethtx.js
⚠️用户必须要自己实现getAccounts()和getPrivateKey(address)这两个函数,因为下面的self.signTransaction()等函数中有用到,所以上面的provider-engine.js实现了
approveTransaction()和approveMessage()这两个函数则可以有选择性地实现
/*
* Uses ethereumjs-tx to sign a transaction.
*
* The two callbacks a user needs to implement are:
* - getAccounts() -- array of addresses supported
* - getPrivateKey(address) -- return private key for a given address
*
* Optionally approveTransaction(), approveMessage() can be supplied too.
*/ const inherits = require('util').inherits
const HookedWalletProvider = require('./hooked-wallet.js')
const EthTx = require('ethereumjs-tx')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util') module.exports = HookedWalletEthTxSubprovider inherits(HookedWalletEthTxSubprovider, HookedWalletProvider) function HookedWalletEthTxSubprovider(opts) {
const self = this HookedWalletEthTxSubprovider.super_.call(self, opts) self.signTransaction = function(txData, cb) {
// defaults
if (txData.gas !== undefined) txData.gasLimit = txData.gas
txData.value = txData.value || '0x00'
txData.data = ethUtil.addHexPrefix(txData.data) opts.getPrivateKey(txData.from, function(err, privateKey) {
if (err) return cb(err) var tx = new EthTx(txData)
tx.sign(privateKey)
cb(null, '0x' + tx.serialize().toString('hex'))
})
} self.signMessage = function(msgParams, cb) {
opts.getPrivateKey(msgParams.from, function(err, privateKey) {
if (err) return cb(err)
var dataBuff = ethUtil.toBuffer(msgParams.data)
var msgHash = ethUtil.hashPersonalMessage(dataBuff)
var sig = ethUtil.ecsign(msgHash, privateKey)
var serialized = ethUtil.bufferToHex(concatSig(sig.v, sig.r, sig.s))
cb(null, serialized)
})
} self.signPersonalMessage = function(msgParams, cb) {
opts.getPrivateKey(msgParams.from, function(err, privateKey) {
if (err) return cb(err)
const serialized = sigUtil.personalSign(privateKey, msgParams)
cb(null, serialized)
})
} self.signTypedMessage = function (msgParams, cb) {
opts.getPrivateKey(msgParams.from, function(err, privateKey) {
if (err) return cb(err)
const serialized = sigUtil.signTypedData(privateKey, msgParams)
cb(null, serialized)
})
} } function concatSig(v, r, s) {
r = ethUtil.fromSigned(r)
s = ethUtil.fromSigned(s)
v = ethUtil.bufferToInt(v)
r = ethUtil.toUnsigned(r).toString('hex').padStart(64, 0)
s = ethUtil.toUnsigned(s).toString('hex').padStart(64, 0)
v = ethUtil.stripHexPrefix(ethUtil.intToHex(v))
return ethUtil.addHexPrefix(r.concat(s, v).toString("hex"))
}
provider-engine/subproviders/hooked-wallet.js
用来说明hooked-wallet能够处理的RPC methods以及是怎么处理的,而且强调在使用该subProvider时,一定要自己声明getAccounts()和signTransaction(tx)这两个函数如何调用
⚠️mustProvideInConstructor()的四个函数是要求一定要在构造时声明的,所以上面的hooked-wallet-ethtx.js就在其构造函数中声明了该四个函数
/*
* Emulate 'eth_accounts' / 'eth_sendTransaction' using 'eth_sendRawTransaction'
*
* The two callbacks a user needs to implement are:
* - getAccounts() -- array of addresses supported
* - signTransaction(tx) -- sign a raw transaction object
*/ const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const extend = require('xtend')
const Semaphore = require('semaphore')
const Subprovider = require('./subprovider.js')
const estimateGas = require('../util/estimate-gas.js')
const hexRegex = /^[0-9A-Fa-f]+$/g module.exports = HookedWalletSubprovider // handles the following RPC methods:
// eth_coinbase
// eth_accounts
// eth_sendTransaction
// eth_sign
// eth_signTypedData
// personal_sign
// personal_ecRecover
// parity_postTransaction
// parity_checkRequest
// parity_defaultAccount //
// Tx Signature Flow
//
// handleRequest: eth_sendTransaction
// validateTransaction (basic validity check)
// validateSender (checks that sender is in accounts)
// processTransaction (sign tx and submit to network)
// approveTransaction (UI approval hook)
// checkApproval
// finalizeAndSubmitTx (tx signing)
// nonceLock.take (bottle neck to ensure atomic nonce)
// fillInTxExtras (set fallback gasPrice, nonce, etc)
// signTransaction (perform the signature)
// publishTransaction (publish signed tx to network)
// inherits(HookedWalletSubprovider, Subprovider) function HookedWalletSubprovider(opts){
const self = this
// control flow
self.nonceLock = Semaphore(1) // data lookup
if (opts.getAccounts) self.getAccounts = opts.getAccounts
// high level override
if (opts.processTransaction) self.processTransaction = opts.processTransaction
if (opts.processMessage) self.processMessage = opts.processMessage
if (opts.processPersonalMessage) self.processPersonalMessage = opts.processPersonalMessage
if (opts.processTypedMessage) self.processTypedMessage = opts.processTypedMessage
// approval hooks
self.approveTransaction = opts.approveTransaction || self.autoApprove
self.approveMessage = opts.approveMessage || self.autoApprove
self.approvePersonalMessage = opts.approvePersonalMessage || self.autoApprove
self.approveTypedMessage = opts.approveTypedMessage || self.autoApprove
// actually perform the signature
if (opts.signTransaction) self.signTransaction = opts.signTransaction || mustProvideInConstructor('signTransaction')
if (opts.signMessage) self.signMessage = opts.signMessage || mustProvideInConstructor('signMessage')
if (opts.signPersonalMessage) self.signPersonalMessage = opts.signPersonalMessage || mustProvideInConstructor('signPersonalMessage')
if (opts.signTypedMessage) self.signTypedMessage = opts.signTypedMessage || mustProvideInConstructor('signTypedMessage')
if (opts.recoverPersonalSignature) self.recoverPersonalSignature = opts.recoverPersonalSignature
// publish to network
if (opts.publishTransaction) self.publishTransaction = opts.publishTransaction
} //...太长,大家自己看 function mustProvideInConstructor(methodName) {
return function(params, cb) {
cb(new Error('ProviderEngine - HookedWalletSubprovider - Must provide "' + methodName + '" fn in constructor options'))
}
}
ethereumjs/ethereumjs-wallet的更多相关文章
- ethereumjs/ethereumjs-account-1-简介和API
https://github.com/ethereumjs/ethereumjs-account Encoding, decoding and validation of Ethereum's Acc ...
- ethereumjs/ethereumjs-util
ethereumjs/ethereumjs-util Most of the string manipulation methods are provided by ethjs-util 更多的字符串 ...
- ethereumjs/ethereumjs-tx
https://github.com/ethereumjs/ethereumjs-tx A simple module for creating, manipulating and signing e ...
- ethereumjs/ethereumjs-icap
https://github.com/ethereumjs/ethereumjs-icap ethereumjs-icap 安装: npm install ethereumjs-icap --save ...
- ethereumjs/ethereumjs-common-1-简介
为了了解ethereumjs/ethereumjs-block-3-代码的使用需要了解的一个模块 https://github.com/ethereumjs/ethereumjs-common Com ...
- ethereumjs/ethereumjs-common-2-API文档
https://github.com/ethereumjs/ethereumjs-common/blob/master/docs/index.md 该API的调用的详细例子可见ethereumjs/e ...
- ethereumjs/browser-builds
https://github.com/ethereumjs/browser-builds ethereumjs - Browser Builds This repository contains br ...
- ethereumjs/ethereumjs-vm-3-StateManager
https://github.com/ethereumjs/ethereumjs-vm/blob/master/docs/stateManager.md StateManager 要与本博客的ethe ...
- ethereumjs/ethereumjs-vm-2-API文档
https://github.com/ethereumjs/ethereumjs-vm/blob/master/docs/index.md vm.runBlockchain Processes blo ...
随机推荐
- 简明log4j配置教程
先准备好log4j需要对应的开发包: apache-log4j-extras-1.2.17.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.1.jar 然后就是在项 ...
- SpringMVC拦截器和@ResponseBody注解冲突
在使用@ResponseBody注解后controller方法只会返回ModelandView对象的数据模型,不会返回视图,这样有很多好处,但是如果在拦截器中进行了页面转发,在满足页面转发条件时,不会 ...
- JS 词法作用域 p2
关于js 还是写的简短些,利于个人理解: 先看一个例子: var a = 2; function fn(){ var a = 3; console.log(a); } fn(a); 说明:作用域查找会 ...
- python MRO及c3算法
1. 了解python2和python3类的区别 python2在2.3之前使用的是经典类, 2.3之后, 使用的是新式类 2. 经典类的MRO 树形结构的深度优先遍历 -> 树形结构遍历 cl ...
- android开发之braodCast
广播问题: 遇到了broadcast中sendBroadcast之后,注册registerReceiver的receiver接受不到广播,原因是permisson权限问题. 自定义的permissio ...
- QQ运动步数&自定义ProgressBar
效果如下 gif图展示效果不好,实际体验无卡顿 1.自定义属性 早Values目录下New-values resource file,命名为attrs.xml(命名随意,但规范命名为attrs.xml ...
- Python+Selenium笔记(五):生成测试报告
#HTMLTestRunner代码修改参考 微微微笑 的说明,下面是链接,这个已经说的很详细了 https://www.cnblogs.com/miniren/p/5301081.html (一) 前 ...
- word2vec前世今生
word2vec前世今生 2013年,Google开源了一款用于词向量计算的工具--word2vec,引起了工业界和学术界的关注.首先,word2vec可以在百万数量级的词典和上亿的数据集上进行高效地 ...
- oracle数据库occi接口写入中文乱码解决方法
将初始化代码中 Environment::createEnvironment(Environment::DEFAULT); 改为 Environment::createEnvironment(“UTF ...
- 重学C语言---05运算符、表达式和语句
一.循环简介 实例程序 /*shoes1.c--把一双鞋的尺码变为英寸*/#include <stdio.h>#define ADJUST 7.64#define SCALE 0.325 ...