離線發送簽名交易
何爲離線?
指你的機器上並沒有運行着以太坊客戶端,也就沒有節點可言了
何爲簽名交易?
即利用keystore文件和解鎖賬戶所需的密碼重新計算出一個對應着賬戶地址的私鑰,利用這個私鑰對想要發起的交易進行簽名,簽名後會得到一串16進制形式的字符串,即可通過廣播將該交易發佈到區塊鏈中,以待被礦工打包確認。
應用場景?
一些以太坊Dapp、以及應用程序並不希望其用戶也在終端上運行以太坊節點,(用戶肯定也不想這麼麻煩),那就在用戶終端上計算出用戶的私鑰(爲了安全,這個私鑰並不傳到服務端,而是直接在用戶終端本地對交易進行簽名,最後將已經簽名過的交易發送到服務端,由服務端進行廣播,從而達到離線發送交易。
實現框架
網頁端:
- html
- js/ethereumjs-all-2018-1-17.min.js
- js/keythereum.min.js
服務端:
- web3.js 1.0
下面的步驟爲了簡化,我將服務端部分也整合到網頁端,以作展示。
實現過程
一、在html中引入js文件
js文件下載路徑:
- https://github.com/ethereumjs/browser-builds/tree/master/dist/ethereumjs-all
- https://github.com/ethereumjs/keythereum
<script src="js/web3.min.js" type="text/javascript"></script>
<script src="js/ethereumjs-all-2018-1-17.min.js" type="text/javascript"></script>
<script src="js/keythereum.min.js" type="text/javascript"></script>
<script src="eth4Remote.js" type="text/javascript"></script>
二、新建一個js文件,名爲eth4Remote.js
(1)先給出利用keystore文件和解鎖密碼計算出私鑰Privatekey的部分
//新建tx對象,簽名交易對象
var tx = new ethereumjs.Tx();
//新建私鑰
var privatekey;
let web3;
//定義一個計算私鑰的函數
function getPrivateKey(){
//jsonStr爲節點目錄下keystore文件的內容
var jsonStr='{"address":"ade50c7dd4a010063059eeaadf73aa016d9892f9","crypto":{"cipher":"aes-128-ctr","ciphertext":"c17459c90e981f455509dd0257b8642a428c6b4029a0b606c2f4442b68dda602","cipherparams":{"iv":"3bb79f892f8971dde7a03bbf0fefd047"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"d06768ba21ea2474779fdde7a868378751619cbc2ae75324423477383d3d9dd1"},"mac":"14bb7150ad442949e7676813b074ef859f4693fda59a96268b1f6c414e3a0d5a"},"id":"bf3b1b34-8dcf-4828-91ce-dfaecafb2368","version":3}';
//轉換爲json對象
var keyObject = JSON.parse(jsonStr);
//利用keythereum計算privatekey(私鑰)
//傳入參數爲("解鎖賬戶時所需的密碼",keyObject)
var privatekey = keythereum.recover("123",keyObject);
console.log("the private key is:",privatekey.toString('hex'));
return privatekey;
}
$(document).ready(function() {
privatekey=getPrivateKey();
}
上面這一段,在html打開時變開始計算私鑰了,計算過程需要一段時間,最好做點提示標誌。
輸出的privatekey如下:
(2)有了privatekey,接下來實現交易的簽名
從官方說明:https://github.com/ethereumjs/ethereumjs-tx
可以看到,要實現交易的簽名,需要先構造一個tx
tx的一些參數說明如下:
- nonce:賬戶發起的交易數,這個值不能過大,也不能過小,需要利用web3.js的getTransactionCount() 獲取,如果設置過大,就會發現交易雖然發起,但是卻不會被礦工打包確認,因爲前面的nonce的交易還沒發起呢!!這點是要注意的
- gasPrice:設置爲'0x09184e72a000' 即可
- gasLimit:設置爲跟Remix裏面大小一樣即可,"0x2dc6c0"
- to:這個是關鍵參數,應設置爲要轉賬的以太坊地址或者是要調用函數的合約地址
- value:要發送的以太幣或者是調用合約方法時要傳入的以太幣
- data:如果是轉賬以太幣,則可爲空;如果是調用合約裏面的方法,則需要傳入abi編碼格式的參數;1)官方關於abi的說明:https://solidity.readthedocs.io/en/develop/abi-spec.html#types 2)也可以利用官方集成的abi庫進行簡化調用:https://github.com/ethereumjs/ethereumjs-abi
//新建tx對象,簽名交易對象
var tx = new ethereumjs.Tx();
//這裏要調用合約中的方法,所以
//這裏的地址是合約地址
tx.to="0x73e52e2bdeda481ac5ba92e24ec59521d2d75a01";
//data可以直接從remix中調用方法時的回執中複製
tx.data="0x950887d7000000000000000000000000000000000000000000000000000000000000000a";
//gas設置
tx.gasLimit="0x2dc6c0";
tx.gasPrice='0x09184e72a000';
由於要設置tx的nonce屬性,這就要先獲取該賬戶的交易數,這裏不展開,具體思路爲:(1)先從服務端得到賬戶的交易數,從而設置tx對象的nonce屬性(2)得到nonce屬性和在第一步中獲取到的privatekey之後,就可以簽名交易了。
爲了簡化流程,假設我已經獲取到nonce爲828,轉化爲16進制則爲:0x33c
進一步的,設置tx屬性
//設置nonce屬性
tx.nonce="0x33c";
//開始簽名交易
tx.sign(privatekey);
var serializedTx = tx.serialize();
//輸出簽名後的字符串
console.log('0x' + serializedTx.toString('hex'));
(3)全部代碼爲:
//新建tx對象,簽名交易對象
var tx = new ethereumjs.Tx();
//新建私鑰
var privatekey;
let web3;
//定義一個計算私鑰的函數
function getPrivateKey(){
//jsonStr爲節點目錄下keystore文件的內容
var jsonStr='{"address":"ade50c7dd4a010063059eeaadf73aa016d9892f9","crypto":{"cipher":"aes-128-ctr","ciphertext":"c17459c90e981f455509dd0257b8642a428c6b4029a0b606c2f4442b68dda602","cipherparams":{"iv":"3bb79f892f8971dde7a03bbf0fefd047"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"d06768ba21ea2474779fdde7a868378751619cbc2ae75324423477383d3d9dd1"},"mac":"14bb7150ad442949e7676813b074ef859f4693fda59a96268b1f6c414e3a0d5a"},"id":"bf3b1b34-8dcf-4828-91ce-dfaecafb2368","version":3}';
//轉換爲json對象
var keyObject = JSON.parse(jsonStr);
//利用keythereum計算privatekey(私鑰)
//傳入參數爲("解鎖賬戶時所需的密碼",keyObject)
var privatekey = keythereum.recover("123",keyObject);
console.log("the private key is:",privatekey.toString('hex'));
return privatekey;
}
$(document).ready(function() {
//獲取私鑰Privatekey
privatekey=getPrivateKey();
//新建tx對象,簽名交易對象
var tx = new ethereumjs.Tx();
//這裏要調用合約中的方法,所以
//這裏的地址是合約地址
tx.to="0x73e52e2bdeda481ac5ba92e24ec59521d2d75a01";
//data可以直接從remix中調用方法時的回執中複製
tx.data="0x950887d7000000000000000000000000000000000000000000000000000000000000000a";
//gas設置
tx.gasLimit="0x2dc6c0";
tx.gasPrice='0x09184e72a000';
//設置nonce屬性
tx.nonce="0x33c";
//開始簽名交易
tx.sign(privatekey);
var serializedTx = tx.serialize();
//輸出簽名後的字符串
console.log('0x' + serializedTx.toString('hex'));
}
最終,可以看到輸出簽名後的字符串:
(4)得到這個簽名後的交易,那麼就可以直接使用web3.js的eth.senSignedTransaction()方法實現廣播,具體可參見api:http://cw.hubwiz.com/card/c/web3.js-1.0/1/2/22/ ,或者也可以直接在geth客戶端裏面使用eth.sendRawTransaction("簽名後的交易字符串")進行廣播,這樣就不需要解鎖賬戶,也可以實現發起交易了。
發起交易後,在geth客戶端可以看到交易已提交:
這裏給出的是直接在網頁端實現的純前端的發送離線簽名交易javascript文件內容,記得先在html文件中引入所需的js文件:
<script src="js/web3.min.js" type="text/javascript"></script>
<script src="js/ethereumjs-all-2018-1-17.min.js" type="text/javascript"></script>
<script src="js/keythereum.min.js" type="text/javascript"></script>
<script src="eth4Remote.js" type="text/javascript"></script>
eth4Remote.js如下:
var tx = new ethereumjs.Tx();
tx.to="0x73e52e2bdeda481ac5ba92e24ec59521d2d75a01";
tx.data="0x950887d7000000000000000000000000000000000000000000000000000000000000000a";
tx.gasLimit="0x2dc6c0";
tx.gasPrice='0x09184e72a000';
//var abi=new ethereumjs.ABI();
let web3;
var privatekey;
$(document).ready(function() {
if (typeof web3 !== 'undefined') { //檢查是否已有web3實例
web3 = new Web3(web3.currentProvider);
} else {
//否則就連接到給出節點
web3 = new Web3();
web3.setProvider(new Web3.providers.WebsocketProvider("ws://localhost:8546")); //注意這裏注意端口不用一致,直接默認8546即可(若剛剛啓動節點的rpc端口是8545的情況下)
}
//判斷Web與節點的連接狀態
web3.eth.getBlock(0, function(error, result) {
if (!error){
console.log("connect should be success");
}
else{
console.log("something wrong,the connection might be failed");
alert("連接節點失敗");
}
})
console.log(tx);
privatekey=getPrivateKey();
});
$("#Btn").click(function() {
//先獲取賬戶nonce
web3.eth.getTransactionCount("0xade50c7dd4a010063059eeaadf73aa016d9892f9",function(error,result)
{
if(!error){
//設置tx的nonce屬性
console.log("TransactionCount is:",result);
tx.nonce="0x"+(Number(result)).toString(16);
//簽名交易
tx.sign(privatekey);
console.log("tx from :",tx.from.toString('hex'));
//序列化
var serializedTx = tx.serialize();
console.log('0x' + serializedTx.toString('hex'));
//發起簽名交易
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
.on('receipt', console.log)
}else{
console.log(error);
}
});
});
//定義一個計算私鑰的函數
function getPrivateKey(){
//jsonStr爲節點目錄下keystore文件的內容
var jsonStr='{"address":"ade50c7dd4a010063059eeaadf73aa016d9892f9","crypto":{"cipher":"aes-128-ctr","ciphertext":"c17459c90e981f455509dd0257b8642a428c6b4029a0b606c2f4442b68dda602","cipherparams":{"iv":"3bb79f892f8971dde7a03bbf0fefd047"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"d06768ba21ea2474779fdde7a868378751619cbc2ae75324423477383d3d9dd1"},"mac":"14bb7150ad442949e7676813b074ef859f4693fda59a96268b1f6c414e3a0d5a"},"id":"bf3b1b34-8dcf-4828-91ce-dfaecafb2368","version":3}';
//轉換爲json對象
var keyObject = JSON.parse(jsonStr);
//利用keythereum計算privatekey(私鑰)
//傳入參數爲("解鎖賬戶時所需的密碼",keyObject)
var privatekey = keythereum.recover("123",keyObject);
console.log("the private key is:",privatekey.toString('hex'));
return privatekey;
}