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:

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)使用一个单独的、维护的加密库版本
  • 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 (setting icap to true will generate an address suitable for the ICAP 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). Set nonStricttrue to accept files with mixed-caps.  设置nonStricttrue为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 key
  • getPublicKey() - return the public key
  • getAddress() - return the address
  • getChecksumAddressString() - return the address with checksum
  • getV3Filename([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 via crypto.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 via crypto.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 or aes-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 is hmac-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的主要内容,如果未能解决你的问题,请参考以下文章