ethereumjs/ethereumjs-wallet geth中UTC檔案與私鑰的關係
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
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 (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 () { 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": "[email protected]", "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 !== 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":"b7a6621e8b125a