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:

返回:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM