區塊鏈學習(11)發送離線簽名交易

離線發送簽名交易


何爲離線?

指你的機器上並沒有運行着以太坊客戶端,也就沒有節點可言了

何爲簽名交易?

即利用keystore文件和解鎖賬戶所需的密碼重新計算出一個對應着賬戶地址的私鑰,利用這個私鑰對想要發起的交易進行簽名,簽名後會得到一串16進制形式的字符串,即可通過廣播將該交易發佈到區塊鏈中,以待被礦工打包確認。

應用場景?

一些以太坊Dapp、以及應用程序並不希望其用戶也在終端上運行以太坊節點,(用戶肯定也不想這麼麻煩),那就在用戶終端上計算出用戶的私鑰(爲了安全,這個私鑰並不傳到服務端,而是直接在用戶終端本地對交易進行簽名,最後將已經簽名過的交易發送到服務端,由服務端進行廣播,從而達到離線發送交易。


實現框架

 網頁端:

  • html
  • js/ethereumjs-all-2018-1-17.min.js
  • js/keythereum.min.js

服務端:

  • web3.js 1.0

 

下面的步驟爲了簡化,我將服務端部分也整合到網頁端,以作展示。


實現過程

一、在html中引入js文件

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>

二、新建一個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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章