ethereumjs/ethereumjs-wallet
Posted 慢行厚积
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\', 16) while (true) { var privKey = randomBytes(32) if (new ethUtil.BN(ethUtil.privateToAddress(privKey)).lte(max)) { return new Wallet(privKey) //可以看见,其实生成一个wallet实例就是将相应的公钥或私钥记录到this._pubKey或this._privKey上 } } } else { return new Wallet(randomBytes(32)) } } Wallet.generateVanityAddress = function (pattern) {//创建一个实例,其中私钥随机生成,转成的address必须要满足提供的正则表达式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 = Buffer.concat([ cipher.update(this.privKey), cipher.final() ]) var mac = ethUtil.sha3(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) {//如何根据时间戳来得到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(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)) } 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.sha3(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 = 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, 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 = decipherBuffer(decipher, encseed.slice(16)) 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(\'001122\', \'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 () {以上是关于ethereumjs/ethereumjs-wallet的主要内容,如果未能解决你的问题,请参考以下文章