区块链学习(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;
}

 

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