ConsenSys/eth-lightwallet(browserless)

https://github.com/ConsenSys/eth-lightwallet

LightWallet

A minimal ethereum javascript wallet.一个小型的钱包

About

LightWallet is a HD wallet that can store your private keys encrypted in the browser to allow you to run Ethereum dapps even if you're not running a local Ethereum node. It uses BIP32 and BIP39 to generate an HD tree of addresses from a randomly generated 12-word seed.

LightWallet是一款HD钱包,它可以在浏览器中加密你的私钥,使得即使你没有运行Ethereum本地节点也能让你运行Ethereum dapp,。它使用BIP32和BIP39从随机生成的12个单词的seed中生成地址的HD树。

LightWallet is primarily intended to be a signing provider for the Hooked Web3 provider through the keystore module. This allows you to have full control over your private keys while still connecting to a remote node to relay signed transactions. Moreover, the txutils functions can be used to construct transactions when offline, for use in e.g. air-gapped coldwallet implementations.

LightWallet主要用于通过keystore模块为 Hooked Web3 provider提供签名。这允许您完全控制私钥,同时仍然连接到远程节点以中继已签名的事务。此外,txutils函数可以用于在脱机时构造交易。

The default BIP32 HD derivation path has been m/0'/0'/0'/i, but any HD path can be chosen.

默认的派生path为m/0'/0'/0'/i,但是还可以使用别的,如m/44'/60'/0'/0/i

Security

Please note that LightWallet has not been through a comprehensive security review at this point. It is still experimental software, intended for small amounts of Ether to be used for interacting with smart contracts on the Ethereum blockchain. Do not rely on it to store larger amounts of Ether yet.

⚠️请注意,目前LightWallet还没有通过全面的安全审查。它仍然是试验性的软件,用于在Ethereum区块链上使用少量eth与智能合约进行交互。不要依赖它来储存大量的eth。

Get Started

npm install eth-lightwallet

The eth-lightwallet package contains dist/lightwallet.min.js that can be included in an HTML page:页面端的使用

<html>
  <body>
    <script src="lightwallet.min.js"></script>
  </body>
</html>

The file lightwallet.min.js exposes the global object lightwallet to the browser which has the two main modules lightwallet.keystore and lightwallet.txutils.

 lightwallet.min.js文件暴露了lightwallet全局对象给浏览器使用,它有着lightwallet.keystorelightwallet.txutils两个主模块

Sample recommended usage with hooked web3 provider:

// the seed is stored encrypted by a user-defined password
var password = prompt('Enter password for encryption', 'password');

keyStore.createVault({//根据password生成UTC文件
  password: password,
  // seedPhrase: seedPhrase, // Optionally provide a 12-word seed phrase
  // salt: fixture.salt,     // Optionally provide a salt.
                             // A unique salt will be generated otherwise.
  // hdPathString: hdPath    // Optional custom HD Path String
}, function (err, ks) {

  // Some methods will require providing the `pwDerivedKey`,
  // Allowing you to only decrypt private keys on an as-needed basis.
  // You can generate that value with this convenient method:
  ks.keyFromPassword(password, function (err, pwDerivedKey) {//通过密码得到了派生key
    if (err) throw err;

    // generate five new address/private key pairs
    // the corresponding private keys are also encrypted
    ks.generateNewAddress(pwDerivedKey, 5);//通过派生key生成5个公私钥对
    var addr = ks.getAddresses();

    ks.passwordProvider = function (callback) {
      var pw = prompt("Please enter password", "Password");
      callback(null, pw);
    };

    // Now set ks as transaction_signer in the hooked web3 provider
    // and you can start using web3 using the keys/addresses in ks!
  });
});

Sample old-style usage with hooked web3 provider (still works, but less secure because uses fixed salts).

老方法,还是能用,只是不太安全

// generate a new BIP32 12-word seed
var secretSeed = lightwallet.keystore.generateRandomSeed();//生成seed

// the seed is stored encrypted by a user-defined password
var password = prompt('Enter password for encryption', 'password');
lightwallet.keystore.deriveKeyFromPassword(password, function (err, pwDerivedKey) {//通过密码生成派生key

var ks = new lightwallet.keystore(secretSeed, pwDerivedKey);//通过seed和派生key生成UTC文件

// generate five new address/private key pairs
// the corresponding private keys are also encrypted
ks.generateNewAddress(pwDerivedKey, 5);//生成5个公私钥对
var addr = ks.getAddresses();

// Create a custom passwordProvider to prompt the user to enter their
// password whenever the hooked web3 provider issues a sendTransaction
// call.
ks.passwordProvider = function (callback) {
  var pw = prompt("Please enter password", "Password");
  callback(null, pw);
};

// Now set ks as transaction_signer in the hooked web3 provider
// and you can start using web3 using the keys/addresses in ks!
});

 

keystore Function definitions

These are the interface functions for the keystore object. The keystore object holds a 12-word seed according to BIP39spec. From this seed you can generate addresses and private keys, and use the private keys to sign transactions.

这些是keystore对象的接口函数。keystore对象根据bip39规范持有12个单词的种子。从这个种子中,您可以生成地址和私钥,并使用私钥签署交易。

Note: Addresses and RLP encoded data are in the form of hex-strings. Hex-strings start with 0x.地址和RLP编码的数据以十六进制字符串的形式出现。十六进制字符串从0x开始。

keystore.createVault(options, callback)

This is the interface to create a new lightwallet keystore.这是创建一个新的lightwallet keystore(密钥库)的接口。

Options

  • password: (mandatory) A string used to encrypt the vault when serialized.序列化时用于加密vault的字符串
  • seedPhrase: (mandatory) A twelve-word mnemonic used to generate all accounts.一个十二字的助记符,用来产生所有的账户
  • salt: (optional) The user may supply the salt used to encrypt & decrypt the vault, otherwise a random salt will be generated.用户可以提供用于加密和解密vault的salt,否则将生成随机salt。
  • hdPathString (mandatory): The user must provide a BIP39 compliant HD Path String. Previously the default has been m/0'/0'/0', another popular one is the BIP44 path string m/44'/60'/0'/0.用户必须提供符合BIP39的HD路径字符串。之前默认的是m/0'/0'/0',另一个流行的是BIP44路径字符串m/44'/60'/0'/0。

keystore.keyFromPassword(password, callback)

This instance method uses any internally-configured salt to return the appropriate pwDerivedKey.

Takes the user's password as input and generates a symmetric key of type Uint8Array that is used to encrypt/decrypt the keystore.

这个实例方法使用任何内部配置的salt来返回适当的pwDerivedKey
以用户的密码作为输入,并生成一个Uint8Array类型的对称密钥,用于加密/解密密钥存储库

keystore.isDerivedKeyCorrect(pwDerivedKey)

Returns true if the derived key can decrypt the seed, and returns false otherwise.如果派生密钥可以解密seed,则返回true,否则返回false。

keystore.generateRandomSeed([extraEntropy])

Generates a string consisting of a random 12-word seed and returns it. If the optional argument string extraEntropy is present the random data from the Javascript RNG will be concatenated with extraEntropy and then hashed to produce the final seed. The string extraEntropy can be something like entropy from mouse movements or keyboard presses, or a string representing dice throws.

生成一个由随机的12字种子组成的字符串并返回它。如果出现可选参数字符串extraEntropy,Javascript RNG中的随机数据将与extraEntropy连接起来,然后进行散列以生成最终seed。extraEntropy可以是类似于来自鼠标移动或键盘按压的熵,或者是代表掷骰子的字符串。

keystore.isSeedValid(seed)

Checks if seed is a valid 12-word seed according to the BIP39 specification.根据BIP39规范检查seed是否为有效的12字种子。

keystore.generateNewAddress(pwDerivedKey, [num])

Allows the vault to generate additional internal address/private key pairs.

The simplest usage is ks.generateNewAddress(pwDerivedKey).

Generates num new address/private key pairs (defaults to 1) in the keystore from the seed phrase, which will be returned with calls to ks.getAddresses().

允许vault生成额外的内部地址/私钥对。
最简单的用法是ks.generateNewAddress(pwDerivedKey)。
在密钥存储库中从seed短语生成num个新的 address/private key对(默认为1),它将与对ks. getaddress()的调用一起返回。

keystore.deserialize(serialized_keystore)

Takes a serialized keystore string serialized_keystore and returns a new keystore object.获取一个序列化的keystore字符串serialized_keystore并返回一个新的keystore对象。

keystore.serialize()

Serializes the current keystore object into a JSON-encoded string and returns that string.将当前keystore对象序列化为json编码的字符串并返回该字符串。

keystore.getAddresses()

Returns a list of hex-string addresses currently stored in the keystore.返回当前存储在密钥存储库中的十六进制字符串地址列表。

keystore.getSeed(pwDerivedKey)

Given the pwDerivedKey, decrypts and returns the users 12-word seed.给定pwDerivedKey,解密并返回用户12字seed。

keystore.exportPrivateKey(address, pwDerivedKey)

Given the derived key, decrypts and returns the private key corresponding to address. This should be done sparingly as the recommended practice is for the keystore to sign transactions using signing.signTx, so there is normally no need to export private keys.

给定派生密钥,解密并返回与地址对应的私钥。这应该尽量少做,因为推荐的做法是keystore使用签名来签署交易。因此通常不需要导出私钥。

实现代码:

var CryptoJS = require('crypto-js');
var Transaction = require('ethereumjs-tx');
var EC = require('elliptic').ec;
var ec = new EC('secp256k1');
var bitcore = require('bitcore-lib');
var Random = bitcore.crypto.Random;
var Hash = bitcore.crypto.Hash;
var Mnemonic = require('bitcore-mnemonic');
var nacl = require('tweetnacl');
var scrypt = require('scrypt-async');

var encryption = require('./encryption');
var signing = require('./signing');

function strip0x (input) {//删除0x前缀
  if (typeof(input) !== 'string') {
    return input;
  }
  else if (input.length >= 2 && input.slice(0,2) === '0x') {
    return input.slice(2);
  }
  else {
    return input;
  }
}

function add0x (input) {//添加0x前缀
  if (typeof(input) !== 'string') {
    return input;
  }
  else if (input.length < 2 || input.slice(0,2) !== '0x') {
    return '0x' + input;
  }
  else {
    return input;
  }
}

function leftPadString (stringToPad, padChar, length) {//对stringToPad左填充padChar填充字符使其长度为length

  var repreatedPadChar = '';
  for (var i=0; i<length; i++) {//生成长度为length的repreatedPadChar,以防stringToPad为空字符串
    repreatedPadChar += padChar;
  }

  return ( (repreatedPadChar + stringToPad).slice(-length) );//repreatedPadChar与stringToPad相加,然后从后取下length长度的string为返回值
}

function nacl_encodeHex(msgUInt8Arr) {
  var msgBase64 = nacl.util.encodeBase64(msgUInt8Arr);
  return (new Buffer(msgBase64, 'base64')).toString('hex');
}

function nacl_decodeHex(msgHex) {
  var msgBase64 = (new Buffer(msgHex, 'hex')).toString('base64');
  return nacl.util.decodeBase64(msgBase64);
}

var KeyStore = function() {}//构造函数,什么都不操作

KeyStore.prototype.init = function(mnemonic, pwDerivedKey, hdPathString, salt) {

  this.salt = salt
  this.hdPathString = hdPathString
  this.encSeed = undefined;
  this.encHdRootPriv = undefined;
  this.version = 3;
  this.hdIndex = 0;
  this.encPrivKeys = {};//存储key为address,value为加密后的privateKey的数组
  this.addresses = [];

  if ( (typeof pwDerivedKey !== 'undefined') && (typeof mnemonic !== 'undefined') ){
    var words = mnemonic.split(' ');
    if (!Mnemonic.isValid(mnemonic, Mnemonic.Words.ENGLISH) || words.length !== 12){
      throw new Error('KeyStore: Invalid mnemonic');
    }

    // Pad the seed to length 120 before encrypting
    var paddedSeed = leftPadString(mnemonic, ' ', 120);//填充seed
    this.encSeed = encryptString(paddedSeed, pwDerivedKey);//然后使用pwDerivedKey对填充seed进行加密

    // hdRoot is the relative root from which we derive the keys using
    // generateNewAddress(). The derived keys are then
    // `hdRoot/hdIndex`.

    var hdRoot = new Mnemonic(mnemonic).toHDPrivateKey().xprivkey;//得到扩展私钥
    var hdRootKey = new bitcore.HDPrivateKey(hdRoot);//得到HDPrivateKey
    var hdPathKey = hdRootKey.derive(hdPathString).xprivkey;//使用HDPrivateKey去派生路径为hdPathString的child并获得其扩展私钥
    this.encHdRootPriv = encryptString(hdPathKey, pwDerivedKey);//然后使用pwDerivedKey对child的扩展私钥进行加密
  }
}

KeyStore.createVault = function(opts, cb) {

  // Default hdPathString ,!!!!!这里和上面说的不太一样,可能是后面开发时有所更改
  if (!('hdPathString' in opts)) {//一定要有hdPathString
    var err = new Error("Keystore: Must include hdPathString in createVault inputs. Suggested alternatives are m/0'/0'/0' for previous lightwallet default, or m/44'/60'/0'/0 for BIP44 (used by Jaxx & MetaMask)")
    return cb(err)
  }

  if (!('seedPhrase' in opts)) {//一定要有seedPhrase
    var err = new Error('Keystore: Must include seedPhrase in createVault inputs.')
    return cb(err)
  }

  if (!('salt' in opts)) {//没有则默认
    opts.salt = generateSalt(32);
  }

  KeyStore.deriveKeyFromPasswordAndSalt(opts.password, opts.salt, function(err, pwDerivedKey) {//这个方法就是使用了使用kdf-scrypt生成了pwDerivedKey派生密钥
    if (err) return cb(err);

    var ks = new KeyStore();//创建一个实例
    ks.init(opts.seedPhrase, pwDerivedKey, opts.hdPathString, opts.salt);//调用init,将相应的内部变量值设定好

    cb(null, ks);//这样一个vault就设置好了
  });
};

KeyStore.generateSalt = generateSalt;//随机生成salt

function generateSalt (byteCount) {
  return bitcore.crypto.Random.getRandomBuffer(byteCount || 32).toString('base64');
}

KeyStore.prototype.isDerivedKeyCorrect = function(pwDerivedKey) {

  var paddedSeed = KeyStore._decryptString(this.encSeed, pwDerivedKey);
  if (paddedSeed.length > 0) {
    return true;
  }

  return false;

};

function encryptString (string, pwDerivedKey) {//用pwDerivedKey加密string
  var nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
  var encObj = nacl.secretbox(nacl.util.decodeUTF8(string), nonce, pwDerivedKey);
  var encString = { 'encStr': nacl.util.encodeBase64(encObj),
                    'nonce': nacl.util.encodeBase64(nonce)};
  return encString;
};
KeyStore._encryptString = encryptString

KeyStore._decryptString = function (encryptedStr, pwDerivedKey) {//解密

  var secretbox = nacl.util.decodeBase64(encryptedStr.encStr);
  var nonce = nacl.util.decodeBase64(encryptedStr.nonce);

  var decryptedStr = nacl.secretbox.open(secretbox, nonce, pwDerivedKey);

  if (decryptedStr === undefined) {
    throw new Error("Decryption failed!");
  }

  return nacl.util.encodeUTF8(decryptedStr);
};

KeyStore._encryptKey = function (privKey, pwDerivedKey) {//用pwDerivedKey加密privKey

  var privKeyArray = nacl_decodeHex(privKey);
  var nonce = nacl.randomBytes(nacl.secretbox.nonceLength);

  var encKey = nacl.secretbox(privKeyArray, nonce, pwDerivedKey);
  encKey = { 'key': nacl.util.encodeBase64(encKey), 'nonce': nacl.util.encodeBase64(nonce)};

  return encKey;
};

KeyStore._decryptKey = function (encryptedKey, pwDerivedKey) {//解密

  var secretbox = nacl.util.decodeBase64(encryptedKey.key);
  var nonce = nacl.util.decodeBase64(encryptedKey.nonce);
  var decryptedKey = nacl.secretbox.open(secretbox, nonce, pwDerivedKey);

  if (decryptedKey === undefined) {
    throw new Error("Decryption failed!");
  }

  return nacl_encodeHex(decryptedKey);
};

KeyStore._computeAddressFromPrivKey = function (privKey) {//从私钥算出address
  var keyPair = ec.genKeyPair();
  keyPair._importPrivate(privKey, 'hex');
  var compact = false;
  var pubKey = keyPair.getPublic(compact, 'hex').slice(2);
  var pubKeyWordArray = CryptoJS.enc.Hex.parse(pubKey);
  var hash = CryptoJS.SHA3(pubKeyWordArray, { outputLength: 256 });
  var address = hash.toString(CryptoJS.enc.Hex).slice(24);

  return address;
};

KeyStore._computePubkeyFromPrivKey = function (privKey, curve) {//从私钥算出公钥

  if (curve !== 'curve25519') {
    throw new Error('KeyStore._computePubkeyFromPrivKey: Only "curve25519" supported.')
  }

  var privKeyBase64 = (new Buffer(privKey, 'hex')).toString('base64')
  var privKeyUInt8Array = nacl.util.decodeBase64(privKeyBase64);
  var pubKey = nacl.box.keyPair.fromSecretKey(privKeyUInt8Array).publicKey;
  var pubKeyBase64 = nacl.util.encodeBase64(pubKey);
  var pubKeyHex = (new Buffer(pubKeyBase64, 'base64')).toString('hex');

  return pubKeyHex;
}


KeyStore.prototype._generatePrivKeys = function(pwDerivedKey, n) {//生成私钥

  if(!this.isDerivedKeyCorrect(pwDerivedKey)) {//先判断pwDerivedKey正确吗
    throw new Error("Incorrect derived key!");
  }

  var hdRoot = KeyStore._decryptString(this.encHdRootPriv, pwDerivedKey);//然后解密得到root的HdRootPriv私钥

  if (hdRoot.length === 0) {//再判断root的HdRootPriv私钥正确吗
    throw new Error('Provided password derived key is wrong');
  }

  var keys = [];
  for (var i = 0; i < n; i++){
    var hdprivkey = new bitcore.HDPrivateKey(hdRoot).derive(this.hdIndex++);//然后从root开始创建其n个child的私钥,this.hdIndex初始值为0
    var privkeyBuf = hdprivkey.privateKey.toBuffer();//将私钥转成buffer格式

    var privkeyHex = privkeyBuf.toString('hex');//再转为16进制,长度应大于16,小于32
    if (privkeyBuf.length < 16) {//私钥无效
      // Way too small key, something must have gone wrong
      // Halt and catch fire
      throw new Error('Private key suspiciously small: < 16 bytes. Aborting!');
    }
    else if (privkeyBuf.length < 32) {//填充0
      // Pad private key if too short
      // bitcore has a bug where it sometimes returns
      // truncated keys
      privkeyHex = leftPadString(privkeyBuf.toString('hex'), '0', 64);
    }
    else if (privkeyBuf.length > 32) {//无效
      throw new Error('Private key larger than 32 bytes. Aborting!');
    }

    var encPrivKey = KeyStore._encryptKey(privkeyHex, pwDerivedKey);
    keys[i] = {
      privKey: privkeyHex,
      encPrivKey: encPrivKey
    }
  }

  return keys;
};


// This function is tested using the test vectors here:
// http://www.di-mgt.com.au/sha_testvectors.html
KeyStore._concatAndSha256 = function(entropyBuf0, entropyBuf1) {//连接随机buffer和extraEntropy熵buffer并求其hash

  var totalEnt = Buffer.concat([entropyBuf0, entropyBuf1]);
  if (totalEnt.length !== entropyBuf0.length + entropyBuf1.length) {
    throw new Error('generateRandomSeed: Logic error! Concatenation of entropy sources failed.')
  }

  var hashedEnt = Hash.sha256(totalEnt);

  return hashedEnt;
}

// External static functions


// Generates a random seed. If the optional string
// extraEntropy is set, a random set of entropy
// is created, then concatenated with extraEntropy
// and hashed to produce the entropy that gives the seed.
// Thus if extraEntropy comes from a high-entropy source
// (like dice) it can give some protection from a bad RNG.
// If extraEntropy is not set, the random number generator
// is used directly.

KeyStore.generateRandomSeed = function(extraEntropy) {

  var seed = '';
  if (extraEntropy === undefined) {//如果熵没设置,则直接生成助记词
    seed = new Mnemonic(Mnemonic.Words.ENGLISH);
  }
  else if (typeof extraEntropy === 'string') {
    var entBuf = new Buffer(extraEntropy);
    var randBuf = Random.getRandomBuffer(256 / 8);
    var hashedEnt = this._concatAndSha256(randBuf, entBuf).slice(0, 128 / 8);
    seed = new Mnemonic(hashedEnt, Mnemonic.Words.ENGLISH);
  }
  else {
    throw new Error('generateRandomSeed: extraEntropy is set but not a string.')
  }

  return seed.toString();
};

KeyStore.isSeedValid = function(seed) {
  return Mnemonic.isValid(seed, Mnemonic.Words.ENGLISH)
};

// Takes keystore serialized as string and returns an instance of KeyStore
KeyStore.deserialize = function (keystore) {
  var jsonKS = JSON.parse(keystore);//将序列化keystore转成json格式

  if (jsonKS.version === undefined || jsonKS.version !== 3) {//版本老了,应使用upgradeOldSerialized()更新其版本
    throw new Error('Old version of serialized keystore. Please use KeyStore.upgradeOldSerialized() to convert it to the latest version.')
  }

  // Create keystore
  var keystoreX = new KeyStore();
  //然后赋值,返回对象实例
  keystoreX.salt = jsonKS.salt
  keystoreX.hdPathString = jsonKS.hdPathString
  keystoreX.encSeed = jsonKS.encSeed;
  keystoreX.encHdRootPriv = jsonKS.encHdRootPriv;
  keystoreX.version = jsonKS.version;
  keystoreX.hdIndex = jsonKS.hdIndex;
  keystoreX.encPrivKeys = jsonKS.encPrivKeys;
  keystoreX.addresses = jsonKS.addresses;

  return keystoreX;
};

KeyStore.deriveKeyFromPasswordAndSalt = function(password, salt, callback) {

  // Do not require salt, and default it to 'lightwalletSalt',如果没有传入salt,则默认为'lightwalletSalt'
  // (for backwards compatibility)
  if (!callback && typeof salt === 'function') {
    callback = salt
    salt = 'lightwalletSalt'
  } else if (!salt && typeof callback === 'function') {
    salt = 'lightwalletSalt'
  }

  var logN = 14;
  var r = 8;
  var dkLen = 32;
  var interruptStep = 200;

  var cb = function(derKey) {
    var err = null
    var ui8arr = null
    try{
      ui8arr = (new Uint8Array(derKey));
    } catch (e) {
      err = e
    }
    callback(err, ui8arr);//得到生成的deriveKey
  }

  scrypt(password, salt, logN, r, dkLen, interruptStep, cb, null);//使用kdf-scrypt,回调cb
}

// External API functions

KeyStore.prototype.serialize = function () {//将json格式转成序列化,并以字符串格式输出
  var jsonKS = {'encSeed': this.encSeed,
                'encHdRootPriv' : this.encHdRootPriv,
                'addresses' : this.addresses,
                'encPrivKeys' : this.encPrivKeys,
                'hdPathString' : this.hdPathString,
                'salt': this.salt,
                'hdIndex' : this.hdIndex,
                'version' : this.version};

  return JSON.stringify(jsonKS);
};

KeyStore.prototype.getAddresses = function () {

  var prefixedAddresses = this.addresses.map(function (addr) {
    return add0x(addr)
  })

  return prefixedAddresses;

};

KeyStore.prototype.getSeed = function (pwDerivedKey) {//使用pwDerivedKey把加密的seed解密即可

  if(!this.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var paddedSeed = KeyStore._decryptString(this.encSeed, pwDerivedKey);
  return paddedSeed.trim();
};

KeyStore.prototype.exportPrivateKey = function (address, pwDerivedKey) {

  if(!this.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var address = strip0x(address).toLowerCase();
  if (this.encPrivKeys[address] === undefined) {
    throw new Error('KeyStore.exportPrivateKey: Address not found in KeyStore');
  }

  var encPrivKey = this.encPrivKeys[address];
  var privKey = KeyStore._decryptKey(encPrivKey, pwDerivedKey);

  return privKey;
};

KeyStore.prototype.generateNewAddress = function(pwDerivedKey, n) {

  if(!this.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  if (!this.encSeed) {
    throw new Error('KeyStore.generateNewAddress: No seed set');
  }
  n = n || 1;
  var keys = this._generatePrivKeys(pwDerivedKey, n);

  for (var i = 0; i < n; i++) {
    var keyObj = keys[i];
    var address = KeyStore._computeAddressFromPrivKey(keyObj.privKey);
    this.encPrivKeys[address] = keyObj.encPrivKey;
    this.addresses.push(address);
  }

};

KeyStore.prototype.keyFromPassword = function(password, callback) {
  KeyStore.deriveKeyFromPasswordAndSalt(password, this.salt, callback);
}


// Async functions exposed for Hooked Web3-provider
// hasAddress(address, callback)
// signTransaction(txParams, callback)
//
// The function signTransaction() needs the
// function KeyStore.prototype.passwordProvider(callback)
// to be set in order to run properly.
// The function passwordProvider is an async function
// that calls the callback(err, password) with a password
// supplied by the user or by other means.
// The user of the hooked web3-provider is encouraged
// to write their own passwordProvider.
//
// Uses defaultHdPathString for the addresses.

KeyStore.prototype.passwordProvider = function (callback) {//需要用户输入一个密码

  var password = prompt("Enter password to continue","Enter password");
  callback(null, password);

}


KeyStore.prototype.hasAddress = function (address, callback) {

  var addrToCheck = strip0x(address);

  if (this.encPrivKeys[addrToCheck] === undefined) {
    callback('Address not found!', false);
  }
  else {
    callback(null, true);
  }

};

KeyStore.prototype.signTransaction = function (txParams, callback) {
  var _this = this

  var ethjsTxParams = {};

  ethjsTxParams.from = add0x(txParams.from);
  ethjsTxParams.to = add0x(txParams.to);
  ethjsTxParams.gasLimit = add0x(txParams.gas);
  ethjsTxParams.gasPrice = add0x(txParams.gasPrice);
  ethjsTxParams.nonce = add0x(txParams.nonce);
  ethjsTxParams.value = add0x(txParams.value);
  ethjsTxParams.data = add0x(txParams.data);

  var txObj = new Transaction(ethjsTxParams);
  var rawTx = txObj.serialize().toString('hex');
  var signingAddress = strip0x(txParams.from);
  var salt = this.salt;
  var self = this;
  this.passwordProvider( function (err, password, salt) {//输入密码
    if (err) return callback(err);

    if (!salt) {
      salt = _this.salt
    }

    _this.keyFromPassword(password, function (err, pwDerivedKey) {//根据密码得到pwDerivedKey
      if (err) return callback(err);
      var signedTx = signing.signTx(self, pwDerivedKey, rawTx, signingAddress, self.defaultHdPathString);//然后使用pwDerivedKey签署交易
      callback(null, '0x' + signedTx);
    })
  })

};


module.exports = KeyStore;

 

 

upgrade Function definitions

keystore.upgradeOldSerialized(oldSerialized, password, callback)

Takes a serialized keystore in an old format and a password. The callback takes the upgraded serialized keystore as its second argument.

keystore的版本过小(现在是小于3时),就要更新keystore的版本,需要输入密码。回调为callback(error,newSerialized

实现代码:

var CryptoJS = require('crypto-js');
var keystore = require('./keystore');

var Transaction = require('ethereumjs-tx');
var EC = require('elliptic').ec;
var ec = new EC('secp256k1');
var bitcore = require('bitcore-lib');
var Random = bitcore.crypto.Random;
var Hash = bitcore.crypto.Hash;
var Mnemonic = require('bitcore-mnemonic');
var nacl = require('tweetnacl');
var scrypt = require('scrypt-async');

var legacyDecryptString = function (encryptedStr, password) {
  var decryptedStr = CryptoJS.AES.decrypt(encryptedStr.encStr, password, {'iv': encryptedStr.iv, 'salt': encryptedStr.salt });
  return decryptedStr.toString(CryptoJS.enc.Latin1);
};

var legacyGenerateEncKey = function(password, salt, keyHash) {
  var encKey = CryptoJS.PBKDF2(password, salt, { keySize: 512 / 32, iterations: 150 }).toString();
  var hash = CryptoJS.SHA3(encKey).toString();
  if (keyHash !== hash){
      throw new Error('Invalid Password');
  }
  return encKey;
};

var upgradeOldSerialized = function (oldSerialized, password, callback) {

  // Upgrades old serialized version of the keystore
  // to the latest version
  var oldKS = JSON.parse(oldSerialized);//将序列化的oldSerialized转成json格式

  if (oldKS.version === undefined || oldKS.version === 1) {//版本没定义或为1时

    var derivedKey = legacyGenerateEncKey(password, oldKS.salt, oldKS.keyHash);//得到派生密钥
    var seed = legacyDecryptString(oldKS.encSeed, derivedKey);//使用派生密钥解密seed

    keystore.createVault({//重新根据相关数据生成vault
      password: password,
      seedPhrase: seed,
      salt: 'lightwalletSalt',
      hdPathString: "m/0'/0'/0'"
    }, function (err, newKeyStore) {

      newKeyStore.keyFromPassword(password, function(err, pwDerivedKey){
        var hdIndex = oldKS.hdIndex;//之前有生成几个child
        newKeyStore.generateNewAddress(pwDerivedKey, hdIndex);

        callback(null, newKeyStore.serialize());
      })
    })

  }
  else if (oldKS.version === 2) {//如果版本为2
    var salt = 'lightWalletSalt'
    if (oldKS.salt !== undefined) {
      salt = oldKS.salt
    }
    
    keystore.deriveKeyFromPasswordAndSalt(password, salt, function(err, pwKey) {
      var seed = keystore._decryptString(oldKS.encSeed, pwKey).trim()
      var hdPaths = Object.keys(oldKS.ksData)

      var hdPathString = ''
      if (hdPaths.length === 1) {
        hdPathString = hdPaths[0]
      }
      
      keystore.createVault({
        password: password,
        seedPhrase: seed,
        salt: salt,
        hdPathString: hdPathString
      }, function (err, newKeyStore) {

        newKeyStore.keyFromPassword(password, function(err, pwDerivedKey){
          var hdIndex = oldKS.ksData[hdPathString].hdIndex;
          newKeyStore.generateNewAddress(pwDerivedKey, hdIndex);
          callback(null, newKeyStore.serialize());
        })
      })

    })
  }
  else {
    throw new Error('Keystore is not of correct version.')
  }

}


module.exports.upgradeOldSerialized = upgradeOldSerialized;

 

signing Function definitions

signing.signTx(keystore, pwDerivedKey, rawTx, signingAddress, hdPathString)

Signs a transaction with the private key corresponding to signingAddress.

使用与签名地址(即发送者)的私钥来对交易进行签名

Inputs

  • keystore: An instance of the keystore with which to sign the TX with.
  • pwDerivedKey: the users password derived key (Uint8Array)
  • rawTx: Hex-string defining an RLP-encoded raw transaction.
  • signingAddress: hex-string defining the address to send the transaction from.
  • hdPathString: (Optional) A path at which to create the encryption keys.

Return value

Hex-string corresponding to the RLP-encoded raw transaction.

signing.signMsg(keystore, pwDerivedKey, rawMsg, signingAddress, hdPathString)

Creates and signs a sha3 hash of a message with the private key corresponding to signingAddress.先对消息进行hash,然后使用与签名地址(即发送者)的私钥来对消息的hash值进行签名,其实还是调用了signMsgHash

Inputs

  • keystore: An instance of the keystore with which to sign the TX with.
  • pwDerivedKey: the users password derived key (Uint8Array)
  • rawMsg: Message to be signed
  • signingAddress: hex-string defining the address corresponding to the signing private key.
  • hdPathString: (Optional) A path at which to create the encryption keys.

Return value

Signed hash as signature object with v, r and s values.

得到签名v, r and s

signing.signMsgHash(keystore, pwDerivedKey, msgHash, signingAddress, hdPathString)

Signs a sha3 message hash with the private key corresponding to signingAddress.

使用与签名地址(即发送者)的私钥来对消息的hash值进行签名

Inputs

  • keystore: An instance of the keystore with which to sign the TX with.
  • pwDerivedKey: the users password derived key (Uint8Array)
  • msgHash: SHA3 hash to be signed
  • signingAddress: hex-string defining the address corresponding to the signing private key.
  • hdPathString: (Optional) A path at which to create the encryption keys.

Return value

Signed hash as signature object with v, r and s values.

signing.concatSig(signature)

将签名h v, r and s按顺序r,s,v连成字符串

Concatenates signature object to return signature as hex-string in the same format as eth_sign does.

Inputs

  • signature: Signature object as returned from signMsg or ``signMsgHash`.

Return value

Concatenated signature object as hex-string.

signing.recoverAddress(rawMsg, v, r, s)

Recovers the signing address from the message rawMsg and the signature v, r, s.

从消息和签名 v, r, s得到进行签名的address

实现代码:

var Transaction = require("ethereumjs-tx")
var util = require("ethereumjs-util")

var signTx = function (keystore, pwDerivedKey, rawTx, signingAddress) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {//pwDerivedKey无效
    throw new Error("Incorrect derived key!");
  }
  
  rawTx = util.stripHexPrefix(rawTx);//去掉前缀0x
  signingAddress = util.stripHexPrefix(signingAddress);//去掉前缀0x

  var txCopy = new Transaction(new Buffer(rawTx, 'hex'));

  var privKey = keystore.exportPrivateKey(signingAddress, pwDerivedKey);//获得私钥

  txCopy.sign(new Buffer(privKey, 'hex'));//签名
  privKey = '';

  return txCopy.serialize().toString('hex');//返回序列化的签名
};

module.exports.signTx = signTx;

var signMsg = function (keystore, pwDerivedKey, rawMsg, signingAddress) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var msgHash = util.addHexPrefix(util.sha3(rawMsg).toString('hex'));//将消息hash转为16进制并添加0x前缀
  return this.signMsgHash(keystore, pwDerivedKey, msgHash, signingAddress);//其实还是调用了signMsgHash
};

module.exports.signMsg = signMsg;

var signMsgHash = function (keystore, pwDerivedKey, msgHash, signingAddress) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  signingAddress = util.stripHexPrefix(signingAddress);//去掉前缀0x

  var privKey = keystore.exportPrivateKey(signingAddress, pwDerivedKey);

  return util.ecsign(new Buffer(util.stripHexPrefix(msgHash), 'hex'), new Buffer(privKey, 'hex'));
};

module.exports.signMsgHash = signMsgHash;

var recoverAddress = function (rawMsg, v, r, s) {

  var msgHash = util.sha3(rawMsg);

  return util.pubToAddress(util.ecrecover(msgHash, v, r, s));
};

module.exports.recoverAddress = recoverAddress;

var concatSig = function (signature) {//将签名h v, r and s按顺序r,s,v连成字符串
  var v = signature.v;
  var r = signature.r;
  var s = signature.s;
  r = util.fromSigned(r);
  s = util.fromSigned(s);
  v = util.bufferToInt(v);
  r = util.setLengthLeft(util.toUnsigned(r), 32).toString('hex');
  s = util.setLengthLeft(util.toUnsigned(s), 32).toString('hex');
  v = util.stripHexPrefix(util.intToHex(v));
  return util.addHexPrefix(r.concat(s, v).toString("hex"));
};

module.exports.concatSig = concatSig;

 

 

encryption Function definitions

encryption.multiEncryptString(keystore, pwDerivedKey, msg, myAddress, theirPubKeyArray)多重签名

NOTE: The format of encrypted messages has not been finalized and may change at any time, so only use this for ephemeral messages that do not need to be stored encrypted for a long time.

注意:加密消息的格式还没有最终确定,并且可能会在任何时候发生更改,所以只在短时间内不需要加密存储的消息中使用此格式。

Encrypts the string msg with a randomly generated symmetric key, then encrypts that symmetric key assymetrically to each of the pubkeys in theirPubKeyArray. The encrypted message can then be read only by sender and the holders of the private keys corresponding to the public keys in theirPubKeyArray. The returned object has the following form, where nonces and ciphertexts are encoded in base64:

用随机生成的对称密钥加密字符串msg,然后将对称密钥加密到 theirPubKeyArray中的每个pubkey。然后,加密的消息只能由发送方和theirPubKeyArray中的公钥对应的私钥的持有者读取。

返回的对象具有以下形式,其中nonces和密文被编码为base64:

{ version: 1,
  asymAlg: 'curve25519-xsalsa20-poly1305',
  symAlg: 'xsalsa20-poly1305',
  symNonce: 'SLmxcH3/CPMCCJ7orkI7iSjetRlMmzQH',
  symEncMessage: 'iN4+/b5InlsVo5Bc7GTmaBh8SgWV8OBMHKHMVf7aq5O9eqwnIzVXeX4yzUWbw2w=',
  encryptedSymKey:
   [ { nonce: 'qcNCtKqiooYLlRuIrNlNVtF8zftoT5Cb',
       ciphertext: 'L8c12EJsFYM1K7udgHDRrdHhQ7ng+VMkzOdVFTjWu0jmUzpehFeqyoEyg8cROBmm' },
     { nonce: 'puD2x3wmQKu3OIyxgJq2kG2Hz01+dxXs',
       ciphertext: 'gLYtYpJbeFKXL/WAK0hyyGEelaL5Ddq9BU3249+hdZZ7xgTAZVL8tw+fIVcvpgaZ' },
     { nonce: '1g8VbftPnjc+1NG3zCGwZS8KO73yjucu',
       ciphertext: 'pftERJOPDV2dfP+C2vOwPWT43Q89V74Nfu1arNQeTMphSHqVuUXItbyCMizISTxG' },
     { nonce: 'KAH+cCxbFGSDjHDOBzDhMboQdFWepvBw',
       ciphertext: 'XWmmBmxLEyLTUmUBiWy2wDqedubsa0KTcufhKM7YfJn/eHWhDDptMxYDvaKisFmn' } ] }

Note that no padding is applied to msg, so it's possible to deduce the length of the string msg from the ciphertext. If you don't want this information to be known, please apply padding to msg before calling this function.

注意,msg不使用填充,因此可以从密文推断字符串msg的长度。如果您不想知道这些信息,请在调用此函数之前对msg应用填充。

encryption.multiDecryptString(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress)多重解密

Decrypt a message encMsg created with the function multiEncryptString(). If successful, returns the original message string. If not successful, returns false.

解密使用multiEncryptString()函数创建的消息encMsg。如果成功,返回原始消息字符串。如果没有成功,返回false。

encryption.addressToPublicEncKey(keystore, pwDerivedKey, address)

Gets the public encryption key corresponding to the private key of address in the keystore.

获取与keystore中的地址的私钥相对应的公开加密密钥。

实现代码:

var util = require("ethereumjs-util");
var nacl = require('tweetnacl');

function nacl_encodeHex(msgUInt8Arr) {
  var msgBase64 = nacl.util.encodeBase64(msgUInt8Arr);
  return (new Buffer(msgBase64, 'base64')).toString('hex');
}

function nacl_decodeHex(msgHex) {
  var msgBase64 = (new Buffer(msgHex, 'hex')).toString('base64');
  return nacl.util.decodeBase64(msgBase64);
}

function addressToPublicEncKey (keystore, pwDerivedKey, address) {
  var privKey = keystore.exportPrivateKey(address, pwDerivedKey)//得到私钥
  var privKeyUInt8Array = nacl_decodeHex(privKey)
  var pubKeyUInt8Array = nacl.box.keyPair.fromSecretKey(privKeyUInt8Array).publicKey//由私钥得到公钥
  return nacl_encodeHex(pubKeyUInt8Array)
}


function _asymEncryptRaw (keystore, pwDerivedKey, msgUint8Array, myAddress, theirPubKey) {//非对称加密

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var privKey = keystore.exportPrivateKey(myAddress, pwDerivedKey);
  var privKeyUInt8Array = nacl_decodeHex(privKey);
  var pubKeyUInt8Array = nacl_decodeHex(theirPubKey);
  var nonce = nacl.randomBytes(nacl.box.nonceLength);
  var encryptedMessage = nacl.box(msgUint8Array, nonce, pubKeyUInt8Array, privKeyUInt8Array);

  var output = {
    alg: 'curve25519-xsalsa20-poly1305',
    nonce: nacl.util.encodeBase64(nonce),
    ciphertext: nacl.util.encodeBase64(encryptedMessage)
  };

  return output;
}

function _asymDecryptRaw (keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {//非对称解密

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var privKey = keystore.exportPrivateKey(myAddress, pwDerivedKey);
  var privKeyUInt8Array = nacl_decodeHex(privKey);
  var pubKeyUInt8Array = nacl_decodeHex(theirPubKey);

  var nonce = nacl.util.decodeBase64(encMsg.nonce);
  var ciphertext = nacl.util.decodeBase64(encMsg.ciphertext);
  var cleartext = nacl.box.open(ciphertext, nonce, pubKeyUInt8Array, privKeyUInt8Array);

  return cleartext;

}

var asymEncryptString = function (keystore, pwDerivedKey, msg, myAddress, theirPubKey) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var messageUInt8Array = nacl.util.decodeUTF8(msg);

  return _asymEncryptRaw(keystore, pwDerivedKey, messageUInt8Array, myAddress, theirPubKey);

}

var asymDecryptString = function (keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var cleartext = _asymDecryptRaw(keystore, pwDerivedKey, encMsg, theirPubKey, myAddress);

  if (cleartext === false) {
    return false;
  }
  else {
    return nacl.util.encodeUTF8(cleartext);
  }

}

var multiEncryptString = function (keystore, pwDerivedKey, msg, myAddress, theirPubKeyArray) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var messageUInt8Array = nacl.util.decodeUTF8(msg);
  var symEncryptionKey = nacl.randomBytes(nacl.secretbox.keyLength);
  var symNonce = nacl.randomBytes(nacl.secretbox.nonceLength);

  var symEncMessage = nacl.secretbox(messageUInt8Array, symNonce, symEncryptionKey);

  if (theirPubKeyArray.length < 1) {
    throw new Error('Found no pubkeys to encrypt to.');
  }

  var encryptedSymKey = {};
  encryptedSymKey = []
  for (var i=0; i<theirPubKeyArray.length; i++) {

    var encSymKey = _asymEncryptRaw(keystore, pwDerivedKey, symEncryptionKey, myAddress, theirPubKeyArray[i]);//使用theirPubKeyArray中的一个个加密

    delete encSymKey['alg'];
    encryptedSymKey.push(encSymKey);//然后将结果push进数组中,得到所有的加密数据
  }

  var output = {};
  output.version = 1;
  output.asymAlg = 'curve25519-xsalsa20-poly1305';
  output.symAlg = 'xsalsa20-poly1305';
  output.symNonce = nacl.util.encodeBase64(symNonce);
  output.symEncMessage = nacl.util.encodeBase64(symEncMessage);
  output.encryptedSymKey = encryptedSymKey;

  return output;
}

var multiDecryptString = function (keystore, pwDerivedKey, encMsg, theirPubKey, myAddress) {

  if(!keystore.isDerivedKeyCorrect(pwDerivedKey)) {
    throw new Error("Incorrect derived key!");
  }

  var symKey = false;
  for (var i=0; i < encMsg.encryptedSymKey.length; i++) {
    var result = _asymDecryptRaw(keystore, pwDerivedKey, encMsg.encryptedSymKey[i], theirPubKey, myAddress)
    if (result !== false) {
      symKey = result;
      break;
    }
  }

  if (symKey === false) {
    return false;
  }
  else {
    var symNonce = nacl.util.decodeBase64(encMsg.symNonce);
    var symEncMessage = nacl.util.decodeBase64(encMsg.symEncMessage);
    var msg = nacl.secretbox.open(symEncMessage, symNonce, symKey);

    if (msg === false) {
      return false;
    }
    else {
      return nacl.util.encodeUTF8(msg);
    }
  }

}

module.exports = {
  asymEncryptString: asymEncryptString,
  asymDecryptString: asymDecryptString,
  multiEncryptString: multiEncryptString,
  multiDecryptString: multiDecryptString,
  addressToPublicEncKey: addressToPublicEncKey
};

 

 

txutils Function definitions都是得到交易的rlp编码十六进制字符串,仅仅只是交易的性质的不同

These are the interface functions for the txutils module. These functions will create RLP encoded raw unsigned transactions which can be signed using the keystore.signTx() command.

这些是txutils模块的接口函数。这些函数将创建RLP编码的原始未签名交易(rawTX),这些交易可以使用keystore.signTx()命令进行签名。

txutils.createContractTx(fromAddress, txObject)创建合约的交易

Using the data in txObject, creates an RLP-encoded transaction that will create the contract with compiled bytecode defined by txObject.data. Also computes the address of the created contract.

使用txObject中的数据,创建一个rlp编码的交易,该交易将使用txObject.data定义的编译字节码(即encode)创建合约。还计算创建的合约的地址。

Inputs

  • fromAddress: Address to send the transaction from
  • txObject.gasLimit: Gas limit
  • txObject.gasPrice: Gas price
  • txObject.value: Endowment (optional)
  • txObject.nonce: Nonce of fromAddress
  • txObject.data: Compiled code of the contract合约的encode

Output

Object obj with fields

  • obj.tx: RLP encoded transaction (hex string)
  • obj.addr: Address of the created contract

txutils.functionTx(abi, functionName, args, txObject)调用合约函数的交易

Creates a transaction calling a function with name functionName, with arguments args conforming to abi. The function is defined in a contract with address txObject.to.

创建一个交易,使用name functionName调用函数,使用符合abi的参数args。该函数是在地址txObject.to的合约中定义的。其实就是使用合约中的函数

Inputs

  • abi: Json-formatted ABI as returned from the solc compiler,合约API
  • functionName: string with the function name,合约函数名
  • args: Array with the arguments to the function,传入函数参数
  • txObject.to: Address of the contract,合约地址
  • txObject.gasLimit: Gas limit
  • txObject.gasPrice: Gas price
  • txObject.value: Value to send
  • txObject.nonce: Nonce of sending address

Output

RLP-encoded hex string defining the transaction.

得到该交易定义的RLP编码的十六进制字符串

 

txutils.valueTx(txObject)单纯的交易

Creates a transaction sending value to txObject.to.

创建一个转钱给txObject.to的交易

Inputs

  • txObject.to: Address to send to
  • txObject.gasLimit: Gas limit
  • txObject.gasPrice: Gas price
  • txObject.value: Value to send
  • txObject.nonce: Nonce of sending address

Output

RLP-encoded hex string defining the transaction.

就是得到这个交易的参数的一个RLP编码的十六进制字符串

 

实现代码:

var Transaction = require('ethereumjs-tx');
var coder = require('web3/lib/solidity/coder');
// When updating to web3 1.0.0, replace by
// var coder = require('web3-eth-abi');
var rlp = require('rlp');
var CryptoJS = require('crypto-js');

function add0x (input) {
  if (typeof(input) !== 'string') {
    return input;
  }
  if (input.length < 2 || input.slice(0,2) !== '0x') {
    return '0x' + input;
  }

  return input;
}

function strip0x (input) {
  if (typeof(input) !== 'string') {
    return input;
  }
  else if (input.length >= 2 && input.slice(0,2) === '0x') {
    return input.slice(2);
  }
  else {
    return input;
  }
}

function _encodeFunctionTxData (functionName, types, args) {

  var fullName = functionName + '(' + types.join() + ')';//5 如下面的例子得到的就是sendToken(uint256,address)
  var signature = CryptoJS.SHA3(fullName, { outputLength: 256 }).toString(CryptoJS.enc.Hex).slice(0, 8);//6得到的就是"72378554": "sendToken(uint256,address)",functionHash
  var dataHex = '0x' + signature + coder.encodeParams(types, args);//7然后和参数连起来得到txData
// When updating to web3 1.0.0, replace by
// var dataHex = coder.encodeFunctionSignature(fullName) + coder.encodeParameters(types, args).replace('0x','')

  return dataHex;
}

function _getTypesFromAbi (abi, functionName) {//1获得函数的类型

  function matchesFunctionName(json) {
    return (json.name === functionName && json.type === 'function');
  }

  function getTypes(json) {
    return json.type;
  }

  var funcJson = abi.filter(matchesFunctionName)[0];//2就是将abi作为matchesFunctionName(json)的json参数,然后过滤掉那些类型不为function的functionName函数,得到索引为0的那个

  return (funcJson.inputs).map(getTypes);//3然后将符合条件的functionName函数的那部分json API中的参数inputs一个个输入getTypes(json)中,得到他的参数的类型
}

function functionTx (abi, functionName, args, txObject) {//调用合约函数的交易的rlp编码十六进制字符串
  // txObject contains gasPrice, gasLimit, nonce, to, value

  var types = _getTypesFromAbi(abi, functionName);//1 4得到函数的参数的类型,比如例子"sendToken(uint256,address)"的参数类型为[uint256,address]
  var txData = _encodeFunctionTxData(functionName, types, args);//5

  var txObjectCopy = {};
  txObjectCopy.to = add0x(txObject.to);
  txObjectCopy.gasPrice = add0x(txObject.gasPrice);
  txObjectCopy.gasLimit = add0x(txObject.gasLimit);
  txObjectCopy.nonce = add0x(txObject.nonce);
  txObjectCopy.data = add0x(txData);
  txObjectCopy.value = add0x(txObject.value);

  return '0x' + (new Transaction(txObjectCopy)).serialize().toString('hex');
}

function createdContractAddress (fromAddress, nonce) {
  var rlpEncodedHex = rlp.encode([new Buffer(strip0x(fromAddress), 'hex'), nonce]).toString('hex');
  var rlpEncodedWordArray = CryptoJS.enc.Hex.parse(rlpEncodedHex);
  var hash = CryptoJS.SHA3(rlpEncodedWordArray, {outputLength: 256}).toString(CryptoJS.enc.Hex);

  return '0x' + hash.slice(24);
}

function createContractTx (fromAddress, txObject) {//创建合约的交易的rlp编码十六进制字符串
  // txObject contains gasPrice, gasLimit, value, data, nonce

  var txObjectCopy = {};
  txObjectCopy.to = add0x(txObject.to);
  txObjectCopy.gasPrice = add0x(txObject.gasPrice);
  txObjectCopy.gasLimit = add0x(txObject.gasLimit);
  txObjectCopy.nonce = add0x(txObject.nonce);
  txObjectCopy.data = add0x(txObject.data);
  txObjectCopy.value = add0x(txObject.value);

  var contractAddress = createdContractAddress(fromAddress, txObject.nonce);//根据部署合约的发送者的地址以及它的nonce值就能够生成下一次交易的address,然后就能够将其作为合约的address
  var tx = new Transaction(txObjectCopy);

  return {tx: '0x' + tx.serialize().toString('hex'), addr: contractAddress};
}

function valueTx (txObject) {//仅仅就是交易的rlp编码十六进制字符串
  // txObject contains gasPrice, gasLimit, value, nonce

  var txObjectCopy = {};
  txObjectCopy.to = add0x(txObject.to);
  txObjectCopy.gasPrice = add0x(txObject.gasPrice);
  txObjectCopy.gasLimit = add0x(txObject.gasLimit);
  txObjectCopy.nonce = add0x(txObject.nonce);
  txObjectCopy.value = add0x(txObject.value);

  var tx = new Transaction(txObjectCopy);

  return '0x' + tx.serialize().toString('hex');//都是将交易的参数进行rlp编码并得到其十六进制的字符串
}

module.exports = {
  _encodeFunctionTxData: _encodeFunctionTxData,
  _getTypesFromAbi: _getTypesFromAbi,
  functionTx: functionTx,
  createdContractAddress: createdContractAddress,
  createContractTx: createContractTx,
  valueTx: valueTx
};

 

 

Examples

See the file example_usage.js for usage of keystore and txutils in node.

eth-lightwallet/example/example_usage.js

// Example usage: Name Registry
// Create the contract, register the key 123, set the value 456
// 创建一个合约,这个合约有register函数,使用该函数register(123)将key=123登录到函数调用者,并将该key的value设为456

var lightwallet = require('../index.js')
var txutils = lightwallet.txutils
var signing = lightwallet.signing
var encryption = lightwallet.encryption

var source = '\ncontract NameCoin {\n\n    struct Item {\n\taddress owner;\n\tuint value;\n    }\n\n    mapping (uint => Item) registry;\n\n    function register(uint key) {\n\tif (registry[key].owner == 0) {\n\t    registry[key].owner = msg.sender;\n\t}\n    }\n\n    function transferOwnership(uint key, address newOwner) {\n\tif (registry[key].owner == msg.sender) {\n\t    registry[key].owner = newOwner;\n\t}\n    }\n\n    function setValue(uint key, uint newValue) {\n\tif (registry[key].owner == msg.sender) {\n\t    registry[key].value = newValue;\n\t}\n    }\n\n    function getValue(uint key) constant returns (uint value) {\n\treturn registry[key].value;\n    }\n\n    function getOwner(uint key) constant returns (address owner) {\n\treturn registry[key].owner;\n    }\n}\n'

// contract json abi, this is autogenerated using solc CLI
var abi = [{"constant":true,"inputs":[{"name":"key","type":"uint256"}],"name":"getValue","outputs":[{"name":"value","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"key","type":"uint256"},{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"key","type":"uint256"},{"name":"newValue","type":"uint256"}],"name":"setValue","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"key","type":"uint256"}],"name":"getOwner","outputs":[{"name":"owner","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"key","type":"uint256"}],"name":"register","outputs":[],"type":"function"}]

var code = '6060604052610381806100136000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480630ff4c9161461006557806329507f731461008c5780637b8d56e3146100a5578063c41a360a146100be578063f207564e146100fb57610063565b005b610076600480359060200150610308565b6040518082815260200191505060405180910390f35b6100a36004803590602001803590602001506101b3565b005b6100bc60048035906020018035906020015061026e565b005b6100cf600480359060200150610336565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61010c60048035906020015061010e565b005b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156101af57336000600050600083815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b50565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561026957806000600050600084815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b5050565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610303578060006000506000848152602001908152602001600020600050600101600050819055505b5b5050565b600060006000506000838152602001908152602001600020600050600101600050549050610331565b919050565b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061037c565b91905056'

// You can change this to your seed
// and the nonce of the first address
var seed = 'unhappy nerve cancel reject october fix vital pulse cash behind curious bicycle'
var nonce = 2
var hdPath = 'm/44\'/60\'/0\'/0'
var password = 'mypassword'
//后面发现这是老办法了,所以进行了更改
// lightwallet.keystore.deriveKeyFromPassword('mypassword', function(err, pwDerivedKey) {//使用密码生成一个派生密钥,????现在keystore中没有这个函数了,只有deriveKeyFromPasswordAndSalt,改成下面
lightwallet.keystore.createVault({password:password,seedPhrase:seed,hdPathString: hdPath}, function(err, keystore) {//看keystore.js可以看出是一定要有hdPathString和seedPhrase的
    if (err) throw err;

    // var keystore = new lightwallet.keystore(seed, pwDerivedKey)//然后新创建一个keystore实例,????为什么会传参数,不应该调用init的时候传吗,感觉这里写错了我
    keystore.keyFromPassword(password, function (err, pwDerivedKey){
        if (err) throw err;
        keystore.generateNewAddress(pwDerivedKey)//生成一个address

        var sendingAddr = keystore.getAddresses()[0]


        // The transaction data follows the format of ethereumjs-tx
        txOptions = {
            gasPrice: 10000000000000,
            gasLimit: 3000000,
            value: 10000000,
            nonce: nonce,
            data: code
        }

        // sendingAddr is needed to compute the contract address
        var contractData = txutils.createContractTx(sendingAddr, txOptions)//得到部署合约的交易的rlp编码十六进制字符串
        var signedTx = signing.signTx(keystore, pwDerivedKey, contractData.tx, sendingAddr)//对交易进行签名

        console.log('Signed Contract creation TX: ' + signedTx)
        console.log('')
        console.log('Contract Address: ' + contractData.addr)
        console.log('')

        // TX to register the key 123
        txOptions.to = contractData.addr
        txOptions.nonce += 1
        var registerTx = txutils.functionTx(abi, 'register', [123], txOptions)//调用里面的register函数,生成该交易的rlp编码十六进制字符串
        var signedRegisterTx = signing.signTx(keystore, pwDerivedKey, registerTx, sendingAddr)//对交易进行签名

        // inject signedRegisterTx into the network...
        console.log('Signed register key TX: ' + signedRegisterTx)
        console.log('')

        // TX to set the value corresponding to key 123 to 456
        txOptions.nonce += 1
        var setValueTx = txutils.functionTx(abi, 'setValue', [123, 456], txOptions)//调用里面的setValue函数,生成该交易的rlp编码十六进制字符串
        var signedSetValueTx = signing.signTx(keystore, pwDerivedKey, setValueTx, sendingAddr)//对交易进行签名


        // inject signedSetValueTx into the network...
        console.log('Signed setValueTx: ' + signedSetValueTx)
        console.log('')

        // Send a value transaction
        txOptions.nonce += 1
        txOptions.value = 1500000000000000000
        txOptions.data = undefined
        txOptions.to = 'eba8cdda5058cd20acbe5d1af35a71cfc442450e'
        var valueTx = txutils.valueTx(txOptions)//进行简单的转钱交易,生成该交易的rlp编码十六进制字符串

        var signedValueTx = signing.signTx(keystore, pwDerivedKey, valueTx, sendingAddr)//对交易进行签名
        console.log('Signed value TX: ' + signedValueTx)
        console.log('')        
    })



})

返回:

userdeMBP:example user$ node example_usage.js 
Signed Contract creation TX: f903eb028609184e72a000832dc6c08083989680b903946060604052610381806100136000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480630ff4c9161461006557806329507f731461008c5780637b8d56e3146100a5578063c41a360a146100be578063f207564e146100fb57610063565b005b610076600480359060200150610308565b6040518082815260200191505060405180910390f35b6100a36004803590602001803590602001506101b3565b005b6100bc60048035906020018035906020015061026e565b005b6100cf600480359060200150610336565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61010c60048035906020015061010e565b005b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156101af57336000600050600083815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b50565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561026957806000600050600084815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5b5050565b3373ffffffffffffffffffffffffffffffffffffffff166000600050600084815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610303578060006000506000848152602001908152602001600020600050600101600050819055505b5b5050565b600060006000506000838152602001908152602001600020600050600101600050549050610331565b919050565b60006000600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061037c565b919050561ca0a4672ccc673a016d2a71d0811995295af791a5ce0daba2f78e74a28a3f543a21a00c313a0c5141daf60615485032cac101e2baa6d8c1004a2b82a1abd1768d761f

Contract Address: 0xeac17cc90d40a343fee0952edfd00499d3e5d935

Signed register key TX: f88d038609184e72a000832dc6c094eac17cc90d40a343fee0952edfd00499d3e5d93583989680a4f207564e000000000000000000000000000000000000000000000000000000000000007b1ca05341356c7590e1a524ecb6774860b2a63158fac478acf8b3b1ed2cab8dadb8a0a04a79ac38c630a2df1441779601993260edb3a0e8cd342327758d7b672ea68568

Signed setValueTx: f8ae048609184e72a000832dc6c094eac17cc90d40a343fee0952edfd00499d3e5d93583989680b8447b8d56e3000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c81ba04a0663ce4347dabdada79ecd374623c36596ab4b0b73425c5dba14d9ce41198ca0065ad166a897c4380a4068c161e789fd26fb1e2dbaf315e2daca1d3ae7763378

Signed value TX: f86e058609184e72a000832dc6c094eba8cdda5058cd20acbe5d1af35a71cfc442450e8814d1120d7b160000801ba07e9b585f4406dd51366c35ee2656302ea2dc93a9cfcf8559924e0fdc6b620654a079888c48f8b2eaa7076c2946e397f9c85dc08127c1701b5be292c8a394c25b6c

然后就可以使用ethereum-tx来sendRawTransaction来实现这些交易

 

See the file ewebwallet.html for an example of how to use the LightWallet keystore together with the Hooked Web3 Provider in the browser.

页面webwallet.html:

<html>
  <body>
    <script src="../dist/lightwallet.min.js"></script>
    <script type="text/javascript" src="../node_modules/web3/dist/web3.js"></script>
    <script type="text/javascript" src="../node_modules/hooked-web3-provider/build/hooked-web3-provider.js"></script>
    <script type="text/javascript" src="../node_modules/async/lib/async.js"></script>

    <script>

      var web3 = new Web3();
      var global_keystore;

      function setWeb3Provider(keystore) {
        var web3Provider = new HookedWeb3Provider({
          host: "http://127.0.0.1:7545",
          transaction_signer: keystore
        });

        web3.setProvider(web3Provider);
      }

      function newAddresses(password) {
        
        if (password == '') {
          password = prompt('Enter password to retrieve addresses', 'Password');
        }

        var numAddr = parseInt(document.getElementById('numAddr').value)

        global_keystore.keyFromPassword(password, function(err, pwDerivedKey) {

        global_keystore.generateNewAddress(pwDerivedKey, numAddr);

        var addresses = global_keystore.getAddresses();

        document.getElementById('sendFrom').innerHTML = ''
        document.getElementById('functionCaller').innerHTML = ''
        for (var i=0; i<addresses.length; ++i) {
          document.getElementById('sendFrom').innerHTML += '<option value="' + addresses[i] + '">' + addresses[i] + '</option>'
          document.getElementById('functionCaller').innerHTML += '<option value="' + addresses[i] + '">' + addresses[i] + '</option>'
        }

        getBalances();
      })
      }

      function getBalances() {
        
        var addresses = global_keystore.getAddresses();
        document.getElementById('addr').innerHTML = 'Retrieving addresses...'

        async.map(addresses, web3.eth.getBalance, function(err, balances) {
          async.map(addresses, web3.eth.getTransactionCount, function(err, nonces) {
            document.getElementById('addr').innerHTML = ''
            for (var i=0; i<addresses.length; ++i) {
              document.getElementById('addr').innerHTML += '<div>' + addresses[i] + ' (Bal: ' + (balances[i] / 1.0e18) + ' ETH, Nonce: ' + nonces[i] + ')' + '</div>'
            }
          })
        })

      }

      function setSeed() {
        var password = prompt('Enter Password to encrypt your seed', 'Password');

      lightwallet.keystore.createVault({
        password: password,
        seedPhrase: document.getElementById('seed').value,
        //random salt 
        hdPathString: "m/44'/60'/0'/0"
      }, function (err, ks) {

        global_keystore = ks

        document.getElementById('seed').value = ''
        
        newAddresses(password);
        setWeb3Provider(global_keystore);
        
        getBalances();
        })
      }

      function newWallet() {
        var extraEntropy = document.getElementById('userEntropy').value;//100002
        document.getElementById('userEntropy').value = '';
        var randomSeed = lightwallet.keystore.generateRandomSeed(extraEntropy);//mistake rifle risk grape wrestle describe empower pulse draft border awkward aerobic

        var infoString = 'Your new wallet seed is: "' + randomSeed + 
          '". Please write it down on paper or in a password manager, you will need it to access your wallet. Do not let anyone see this seed or they can take your Ether. ' +
          'Please enter a password to encrypt your seed while in the browser.'
        var password = prompt(infoString, 'Password');//mypassword


      lightwallet.keystore.createVault({
        password: password,
        seedPhrase: randomSeed,
        //random salt 
        hdPathString: "m/44'/60'/0'/0"
      }, function (err, ks) {

        global_keystore = ks
                
        newAddresses(password);
        setWeb3Provider(global_keystore);
        getBalances();
        })
      }

      function showSeed() {
        var password = prompt('Enter password to show your seed. Do not let anyone else see your seed.', 'Password');

        global_keystore.keyFromPassword(password, function(err, pwDerivedKey) {
        var seed = global_keystore.getSeed(pwDerivedKey);
        alert('Your seed is: "' + seed + '". Please write it down.');
        });
      }

      function sendEth() {
        var fromAddr = document.getElementById('sendFrom').value
        var toAddr = document.getElementById('sendTo').value
        var valueEth = document.getElementById('sendValueAmount').value
        var value = parseFloat(valueEth)*1.0e18
        var gasPrice = 18000000000
        var gas = 50000
        web3.eth.sendTransaction({from: fromAddr, to: toAddr, value: value, gasPrice: gasPrice, gas: gas}, function (err, txhash) {
          console.log('error: ' + err)
          console.log('txhash: ' + txhash)
        })
      }

      function functionCall() {
        var fromAddr = document.getElementById('functionCaller').value
        var contractAddr = document.getElementById('contractAddr').value
        var abi = JSON.parse(document.getElementById('contractAbi').value)
        var contract = web3.eth.contract(abi).at(contractAddr)
        var functionName = document.getElementById('functionName').value
        var args = JSON.parse('[' + document.getElementById('functionArgs').value + ']')
        var valueEth = document.getElementById('sendValueAmount').value
        var value = parseFloat(valueEth)*1.0e18
        var gasPrice = 50000000000
        var gas = 4541592
        if(value === "0"){
          args.push({from: fromAddr, gasPrice: gasPrice, gas: gas})
        }else{
          args.push({from: fromAddr, value: value, gasPrice: gasPrice, gas: gas})
        }
        
        var callback = function(err, txhash) {
          console.log('error: ' + err)
          console.log('txhash: ' + txhash)
        }
        args.push(callback)
        contract[functionName].apply(this, args)
      }

    </script>
    <h1>LightWallet</h1>
    <h2>New Wallet</h2>
    <div><input type="text" id="userEntropy" placeholder="Type random text to generate entropy" size="80"></input><button onclick="newWallet()">Create New Wallet</button></div>
    <h2>Restore Wallet</h2>
    <div><input type="text" id="seed" value="" size="80"></input><button onclick="setSeed()">Restore wallet from Seed</button></div>
    <h2>Show Addresses</h2>
    <div>Show <input type="text" id="numAddr" size="5" value="3"></input> more address(es) <button onclick="newAddresses('')">Show</button></div>
    <div id="addr"></div>
    <div><button onClick='getBalances()'>Refresh</button></div>
    <h2>Send Ether</h2>
    <div>From: <select id="sendFrom"></select></div>
    <div>To: <input type="text" size="40" id="sendTo"></input></div>
    <div>Ether: <input type="text" id="sendValueAmount"></div>
    <div><button onclick="sendEth()">Send Ether</button></div>
    <h2>Show Seed</h2>
    <button onclick="showSeed()">Show Seed</button>
    <h2>Function Call</h2>
    <div>Caller: <select id="functionCaller"></select></div>
    <div>Contract Address: <input type="text" size="40" id="contractAddr"></input></div>
    <div>Contract ABI: <input type="text" size="40" id="contractAbi"></input></div>
    <div>Function Name: <input type="text" size="20" id="functionName"></input></div>
    <div>Function Arguments: <input type="text" size="40" id="functionArgs"></input></div>
    <div>Value (Ether): <input type="text" id="sendValueAmount"></div>
    <div><button onclick="functionCall()">Call Function</button></div>

  </body>
</html>

打开是:

 

开始调用:

1.new wallet:

点击确定后可见:

在wallet中创建了三个address,因为这个账号中并没有资金,所以我打算使用restore wallet将ganache上的钱包import进去

 

2.restore wallet

然后可见:

 

3.show seed

确定后:

 

4.send ether

确定后,可见ganache:

页面refresh:

 

5.function call

首先使用remix-ide进行合约的部署:

ganache:

然后调用function call:

返回:

 

posted @ 2018-11-22 16:57  慢行厚积  阅读(2226)  评论(0编辑  收藏  举报