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.keystore
和lightwallet.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, );//通过派生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个公私钥对
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 beenm/0'/0'/0'
, another popular one is the BIP44 path stringm/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 >= && input.slice(,) === '0x') {
return input.slice();
}
else {
return input;
}
} function add0x (input) {//添加0x前缀
if (typeof(input) !== 'string') {
return input;
}
else if (input.length < || input.slice(,) !== '0x') {
return '0x' + input;
}
else {
return input;
}
} function leftPadString (stringToPad, padChar, length) {//对stringToPad左填充padChar填充字符使其长度为length var repreatedPadChar = '';
for (var i=; 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 = ;
this.hdIndex = ;
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 !== ){
throw new Error('KeyStore: Invalid mnemonic');
} // Pad the seed to length 120 before encrypting
var paddedSeed = leftPadString(mnemonic, ' ', );//填充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();
} 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 || ).toString('base64');
} KeyStore.prototype.isDerivedKeyCorrect = function(pwDerivedKey) { var paddedSeed = KeyStore._decryptString(this.encSeed, pwDerivedKey);
if (paddedSeed.length > ) {
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();
var pubKeyWordArray = CryptoJS.enc.Hex.parse(pubKey);
var hash = CryptoJS.SHA3(pubKeyWordArray, { outputLength: });
var address = hash.toString(CryptoJS.enc.Hex).slice(); 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 === ) {//再判断root的HdRootPriv私钥正确吗
throw new Error('Provided password derived key is wrong');
} var keys = [];
for (var i = ; 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 < ) {//私钥无效
// 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 < ) {//填充0
// Pad private key if too short
// bitcore has a bug where it sometimes returns
// truncated keys
privkeyHex = leftPadString(privkeyBuf.toString('hex'), '', );
}
else if (privkeyBuf.length > ) {//无效
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( / );
var hashedEnt = this._concatAndSha256(randBuf, entBuf).slice(, / );
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 !== ) {//版本老了,应使用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 = ;
var r = ;
var dkLen = ;
var interruptStep = ; 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 || ;
var keys = this._generatePrivKeys(pwDerivedKey, n); for (var i = ; 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: / , iterations: }).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时 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
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 === ) {
hdPathString = hdPaths[]
} 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 signedsigningAddress
: 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 signedsigningAddress
: 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 fromsignMsg
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), ).toString('hex');
s = util.setLengthLeft(util.toUnsigned(s), ).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: ,
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 < ) {
throw new Error('Found no pubkeys to encrypt to.');
} var encryptedSymKey = {};
encryptedSymKey = []
for (var i=; 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 = ;
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=; 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 fromtxObject.gasLimit
: Gas limittxObject.gasPrice
: Gas pricetxObject.value
: Endowment (optional)txObject.nonce
: Nonce offromAddress
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 thesolc
compiler,合约APIfunctionName
: string with the function name,合约函数名args
: Array with the arguments to the function,传入函数参数txObject.to
: Address of the contract,合约地址txObject.gasLimit
: Gas limittxObject.gasPrice
: Gas pricetxObject.value
: Value to sendtxObject.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 totxObject.gasLimit
: Gas limittxObject.gasPrice
: Gas pricetxObject.value
: Value to sendtxObject.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 < || input.slice(,) !== '0x') {
return '0x' + input;
} return input;
} function strip0x (input) {
if (typeof(input) !== 'string') {
return input;
}
else if (input.length >= && input.slice(,) === '0x') {
return input.slice();
}
else {
return input;
}
} function _encodeFunctionTxData (functionName, types, args) { var fullName = functionName + '(' + types.join() + ')';//5 如下面的例子得到的就是sendToken(uint256,address)
var signature = CryptoJS.SHA3(fullName, { outputLength: }).toString(CryptoJS.enc.Hex).slice(, );//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)[];//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);// 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: }).toString(CryptoJS.enc.Hex); return '0x' + hash.slice();
} 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 =
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()[] // The transaction data follows the format of ethereumjs-tx
txOptions = {
gasPrice: ,
gasLimit: ,
value: ,
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 +=
var registerTx = txutils.functionTx(abi, 'register', [], 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 +=
var setValueTx = txutils.functionTx(abi, 'setValue', [, ], 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 +=
txOptions.value =
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=; 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=; 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;//
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 =
var gas =
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 =
var gas =
if(value === ""){
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=""></input><button onclick="newWallet()">Create New Wallet</button></div>
<h2>Restore Wallet</h2>
<div><input type="text" id="seed" value="" size=""></input><button onclick="setSeed()">Restore wallet from Seed</button></div>
<h2>Show Addresses</h2>
<div>Show <input type="text" id="numAddr" size="" value=""></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="" 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="" id="contractAddr"></input></div>
<div>Contract ABI: <input type="text" size="" id="contractAbi"></input></div>
<div>Function Name: <input type="text" size="" id="functionName"></input></div>
<div>Function Arguments: <input type="text" size="" 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:
返回:
ConsenSys/eth-lightwallet(browserless)的更多相关文章
- ETH&EOS开发资源及工具集合(完整汇总版)
ETH&EOS开发资源及工具集合(完整汇总版) 3113 ETH开发资源篇 一.开发语言 · Solidity - 官方推荐以太坊智能合约开发语言,也是目前最为主流的智能合约语 ...
- TCP/IP协议学习(三) STM32中ETH驱动配置注意事项
1.MII/RMII/SMI接口连接和配置 SMI又称站点管理接口,用于cpu与外置PHY芯片通讯,配置相关参数,包含MDC和MDIO两个管脚(CPU上有对应引脚,当然用普通GPIO口模拟SMI管理也 ...
- 是否可能两个ETH私钥对应同一个地址
原提问在这里. 笔者在使用到neon-js中的私钥生成方法时发现其使用了getRandomValues方法来生成64字符长度的私钥,进而考虑到其随机性,若是调用足够多次,依然有可能生成两个完全一样的私 ...
- js 校验 btc eth 地址
NPM 安装 npm install wallet-address-validator Browser <script src="wallet-address-validator.mi ...
- centos7将网卡名字改成eth样式
ll /etc/sysconfig/grub lrwxrwxrwx 1 root root 17 Jun 12 2016 /etc/sysconfig/grub -> /etc/default/ ...
- 以太坊: ETH 发送交易 sendRawTransaction 方法数据的签名 和 验证过程
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- ETH跌破400美元大关 一场收割全球的计划完成闭环
ETH跌破400美元大关 一场收割全球的计划完成闭环 三大交易所,ETH的价格均跌下400美元,看空者占比近80%. 在普通人眼里,这可能只是熊市的一个自然表现. 但事实完全不是这样.这本质上一场历时 ...
- 以太坊ETH中智能合约消耗的Gas、Gas Limit是什么?
以太坊ETH中智能合约消耗的Gas.Gas Limit是什么? 数字货币交易一般是有交易费的,比特币(BTC)的交易费很容易理解,就是直接支付一定额度的BTC作为手续费.而以太坊(ETH)的交易费表面 ...
- 在ETH交易区块链里查看北大的那封信
本文仅限于科普编码知识使用,随便举的例子不代表本人立场. 欢迎在其他网站传播,但转载不得标注来源及作者. 1.随便打开一个ETH区块链浏览网站,比如:https://www.etherchain.or ...
随机推荐
- MYSQL查询优化:Limit
Limit语法: SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT子句可以被用于强制 SELECT 语句返回指定的 ...
- IntelliJ IDEA 启动tomcat服务器报Error running 'Unnamed': Address localhost:1099 is already in use错误的问题
在使用Intellij IDEA运行web项目时,出现 :Error running Tomcat8: Address localhost:1099 is already in use,使其web项目 ...
- Java基础——collection接口
一.Collection接口的定义 public interfaceCollection<E>extends iterable<E> 从接口的定义中可以发现,此接口使用了泛型 ...
- 了解java虚拟机—CMS回收器(8)
CMS(Concurrent Mark Sweep)回收器 它使用的是标记清除算法,同时又是一个使用多线程并行回收的垃圾回收器. CMS主要工作步骤 CMS工作时主要步骤有初始标记.并发标记.预清理. ...
- 一个人的旅行(hdu2066)Dijkstra算法模版
一个人的旅行 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub ...
- SpringBoot+Mybatis+PageHelper实现分页
SpringBoot+Mybatis+PageHelper实现分页 mybatis自己没有分页功能,我们可以通过PageHelper工具来实现分页,非常简单方便 第一步:添加依赖 <depend ...
- MYSQL一次千万级连表查询优化
概述:交代一下背景,这算是一次项目经验吧,属于公司一个已上线平台的功能,这算是离职人员挖下的坑,随着数据越来越多,原本的SQL查询变得越来越慢,用户体验特别差,因此SQL优化任务交到了我手上. 这个S ...
- php扩展php-redis安装与使用
一.redis的安装 1,安装redis版本 下载页面:https://redis.io/download 安装一个老版本3.2.11:http://download.redis.io/release ...
- BZOJ5323:[JXOI2018]游戏
传送门 不难发现,所有不能被其他数筛掉的数是一定要选的,只有选了这些数字才能结束 假设有 \(m\) 个,枚举结束时间 \(x\),答案就是 \(\sum \binom{x-1}{m-1}m!(n-m ...
- 从零开始学习html(九)CSS的继承、层叠和特殊性
一.继承 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" co ...