码迷,mamicode.com
首页 > Web开发 > 详细

ethereumjs/ethereumjs-wallet

时间:2018-11-21 15:59:50      阅读:471      评论:0      收藏:0      [点我收藏+]

标签:SHA256   level   amp   sam   https   bdd   p12   dbf   defaults   

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 () {
    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, 32)
  })
  it(should generate an account compatible with ICAP Direct, function () {
    var max = new ethUtil.BN(088f924eeceeda7fe92e1f5b0fffffffffffffff, 16)
    var wallet = Wallet.generate(true)
    assert.equal(wallet.getPrivateKey().length, 32)
    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(180000) // 3minutes
    var wallet = Wallet.generateVanityAddress(/^000/)//以000作为前缀
    assert.equal(wallet.getPrivateKey().length, 32)
    assert.equal(wallet.getAddress()[0], 0)
    assert.equal(wallet.getAddress()[1] >>> 4, 0)
  })
  it(should generate an account with 000 prefix (string), function () {
    this.timeout(180000) // 3minutes
    var wallet = Wallet.generateVanityAddress(^000)
    assert.equal(wallet.getPrivateKey().length, 32)
    assert.equal(wallet.getAddress()[0], 0)
    assert.equal(wallet.getAddress()[1] >>> 4, 0)
  })
})


//V3
describe(.getV3Filename(), function () {
  it(should work, function () {
    assert.equal(fixtureWallet.getV3Filename(1457917509265), 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(180000) // 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(180000) // 3minutes
    assert.equal(fixtureWallet.toV3(testtest)[version], 3)
  })
  it(should fail for unsupported kdf, function () {
    this.timeout(180000) // 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(180000) // 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(180000) // 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.Camp
  • fromEtherWallet(input, password) - import a wallet generated by EtherWallet
  • fromKryptoKit(seed) - import a wallet from a KryptoKit seed
  • fromQuorumWallet(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 !== 64) {
      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 < 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)//对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: 32, ivsize: 16 })

    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 = 0; i < buf.length; i++) {
      if (buf[i] <= 0x7F) {
        res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
        tmp = ‘‘
      } else {
        tmp += % + buf[i].toString(16)
      }
    }

    return Buffer.from(res + decodeUtf8Char(tmp))
  }

  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 if (type === q) {
    if (typeof password !== string) {
      throw new Error(Password required)
    }

    var encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
    var checksum = entropy.slice(30, 46)

    var salt = kryptoKitBrokenScryptSeed(encryptedSeed)
    var aesKey = scryptsy(Buffer.from(password, utf8), salt, 16384, 8, 1, 32)

    /* 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(0, 16))),
      Buffer.from(decipher.decrypt(encryptedSeed.slice(16, 32)))
    ])

    if (checksum.length > 0) {
      if (checksum !== ethUtil.sha256(ethUtil.sha256(privKey)).slice(0, 8).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 >= 10)
  assert(userid.length >= 10)

  var seed = passphrase + userid
  seed = crypto.pbkdf2Sync(seed, seed, 2000, 32, 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 a Wallet 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(1)
    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 strictconst 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

标签:SHA256   level   amp   sam   https   bdd   p12   dbf   defaults   

原文地址:https://www.cnblogs.com/wanghui-garcia/p/9990683.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!