比特币中对交易进行签名的详细过程
最近在和同事交流我们中对UTXO和签名的处理,有些心得,写下此博文。对比特币有点基本概念的都知道,比特币是通过ECDSA数字签名来解锁UTXO中的未花费余额。 关于UTXO我不需要做太多介绍,毕竟介绍这个概念的文章已经很多了。我主要是谈谈已经有UTXO了,该怎么花掉。 交易的结构我们先来看看在比特币中,一个交易的结构是什么样的? type MsgTx
Version int32
TxIn []</span>*<span style="color: #000000;">TxIn
TxOut []</span>*<span style="color: #000000;">TxOut
LockTime uint32
} type TxOut <span style="color: #0000ff;">struct<span style="color: #000000;"> {
} type TxIn <span style="color: #0000ff;">struct<span style="color: #000000;"> {
} type OutPoint <span style="color: #0000ff;">struct<span style="color: #000000;"> {
} 我们可以看到,一个交易(MsgTx)是由多个Input和多个Output组成的,而在Input中是由指向UTXO的OutPoint,解锁脚本SignatureScript和序列Sequence组成。 UTXO我们可以认为是一个KeyValue的大表,在该表中,交易的Hash和该交易中Output所在的位置索引Index就构成了UTXO的Key,而Value就是比特币Amount、锁定脚本等信息,所以在UTXO数据库中,我们通过OutPoint能够很快的找到对应的Amount和锁定脚本。 在比特币中,要做一笔交易分为三个步骤:
构建原始交易RawTransaction现在假设我有一个地址mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV(这是一个测试网地址),该地址收到了两笔转账,一笔0.4BTC(),另一笔1.1BTC(),这两笔收入都是在其交易Output的第二条,也就是Index=1(Index从0开始算)。现在我们想要做一笔1.2BTC的转账,然后给一定的手续费后,找零到原地址,所以我们会构建一笔交易,该交易有2Input和2Output。 以下是我用Go基于btcd写的示例代码,这里我们就构建好了一个RawTransaction。 func buildRawTx() *<span style="color: #008000;">//<span style="color: #008000; text-decoration: underline;">https://testnet.blockchain.info/tx/f0d9d482eb122535e32a3ae92809dd87839e63410d5fd52816fc9fc6215018cc?show_adv=true
<span style="color: #000000;"> tx :=<span style="color: #000000;"> wire.NewMsgTx(wire.TxVersion) <span style="color: #008000;">//<span style="color: #008000; text-decoration: underline;">https://testnet.blockchain.info/tx-index/239152566/1<span style="color: #008000;"> 0.4BTC point := wire.OutPoint{Hash: *utxoHash,Index: <span style="color: #800080;">1<span style="color: #000000;">} <span style="color: #008000;">//<span style="color: #008000;">构建第一个Input,指向一个0.4BTC的UTXO,第二个参数是解锁脚本,现在是nil <span style="color: #008000;">//<span style="color: #008000; text-decoration: underline;">https://testnet.blockchain.info/tx-index/239157459/1<span style="color: #008000;"> 1.1BTC point2 := wire.OutPoint{Hash: *utxoHash2,Index: <span style="color: #800080;">1<span style="color: #000000;">} <span style="color: #008000;">//<span style="color: #008000;">构建第二个Input,指向一个1.1BTC的UTXO,第二个参数是解锁脚本,现在是nil <span style="color: #008000;">//<span style="color: #008000;">找零的地址(这里是16进制形式,变成Base58格式就是mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV) <span style="color: #0000ff;">lock,_ :=<span style="color: #000000;"> txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160). AddData(pubKeyHash).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG). Script() <span style="color: #008000;">//<span style="color: #008000;">构建第一个Output,是找零0.2991024 BTC <span style="color: #008000;">//<span style="color: #008000;">支付给了某个地址,仍然是16进制形式,Base58形式是:mxqnGTekzKqnMqNFHKYi8FhV99WcvQGhfH。 lock2,_ :=<span style="color: #000000;"> txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160). AddData(pubKeyHash2).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG). Script() <span style="color: #008000;">//<span style="color: #008000;">构建第二个Output,支付1.2 BTC出去 <span style="color: #0000ff;">return<span style="color: #000000;"> tx } 交易的签名过程现在我们知道私钥,需要对该交易进行签名,因为有2个Input,所以我们要签名2次,每个签名的原理是一样的,我就以第一个Input为例来说明吧。 在比特币中,对一笔交易的签名流程是这样的: 1.查找该笔交易对应的UTXO 2.获得该UTXO对应的锁定脚本 3.复制该交易对象,并在复制副本中将该Input的解锁脚本字段的值设置为对应的锁定脚本 4.清除其他Input的解锁脚本字段 5.对这个改造后的交易对象计算Hash 6.使用私钥对Hash进行签名。 用表格的形式可以更容易表达: 这是原始未签名的交易RawTransaction,主要是第二列和第三列: 所以签完名后,我们的交易变成: 我们把这个签名和公钥再放回原始交易中,就变成我们需要的完整签名的交易: 总结实际上在比特币的源码中比我上面说的还要复杂一些,还涉及到这个hash是对整个交易进行SigHashAll还是SigHashSingle或者SigHashNone,这些都是很特殊的情况,一般的比特币钱包也不支持,具体可以参加精通比特币书中的介绍:6.5.3签名哈希类型( SIGHASH) 普通来说,我们要对一笔交易进行签名或者验签,就是把当前Input中的解锁脚本替换成锁定脚本,而其他Input的解锁脚本情况,然后计算Hash和签名! 其实我还是有点不明白,为什么比特币中不直接对没有任何解锁脚本的RawTransaction进行签名呢?而是非要加上锁定脚本来签名?不知道这里面有什么更深的考虑。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |