(原创)比特币的签名机制以及BIP143的golang实现

1.背景
工作中使用了BIP143的算法。在隔离见证中VERSION 0采用了BIP143的签名验证机制来提高效率,但BIP143S算法并没有用在普通交易中。我们对普通交易的签名和验证进行了优化,使用BIP143的签名和验证机制,提高签名和验证效率。
1.1比特币的签名算法
比特币采用了ECDSA(椭圆曲线数字签名算法)的数字签名算法,数字签名算法在比特币中有三个用途:第一,签名证明其为私钥的拥有者,即该笔交易中支出资金的所有者。第二,授权证明具有不可否认性,即交易的不可否认。第三,签名不可伪造,证明交易(或交易的具体部分)在签名后不能被任何人修改。
数字签名由两部分组成:第一部分是使用私钥(签名密钥)对消息(交易)的hash进行签名,第二部分是允许任何人通过给定的公钥和消息来验证签名,

签名算法
比特币签名算法如下:

Sig = Fsig( Fhash(m), dA )
其中:
dA 是签名私钥
m 是交易(或其部分)
Fhash是散列函数
Fsig是签名算法
Sig是结果签名
在整个签名过程中,有两个函数:Fhash和Fsig。
Fhash函数
Fhash函数用来生成交易的Hash,需要先将交易序列化,根据序列化后的二进制数据,使用SHA256函数来计算交易Hash。普通交易(单个输入和单个输出)过程如下:
交易的序列化:
1.nVersion交易版本
2.InputCount 输入数量
3.Prevouts 对输入UTXO进行序列化
4.OutputCount 输出数量
5.outpoint 输出的UTXO 进行序列化
6.nLocktime交易锁定时间
7.Hash 将上述步骤产生的数据,进行两次SHA256计算
Fsig函数
Fsig函数的签名机制是基于椭圆曲线算法。在椭圆曲线中每次加密都会产生一个K值,根据K值,算法会生成一个临时公私密钥对(K,Q),由临时公钥Q的X坐标得到一个值R, 公式如下:

S=K-1 *(Hash(m) + dA *R) mod p
其中:
k是临时私钥
R是临时公钥的x坐标
dA是签名私钥
m是交易数据
p是椭圆曲线的主要顺序
该函数会生成一个值S。
在椭圆曲线中每次加密都会产生一个K值,重用相同的K值会导致私钥暴露,K值是需要严格保密的,比特币采用FRC6979规范来保证确定性,通过SHA256保证K值的安全性。其简单公式如下
K  =SHA256(dA+HASH(m))
其中,
dA是私钥,
m是消息。

最终签名会产生 由(R和S)两个值组成的签名。
验证签名

验证过程是签名生成函数的倒数,其公式如下:

P=S-1 *Hash(m)*G +S-1*R*Qa
其中:
R和S是签名值
Qa是用户(签名者)的公钥
m是签署的交易数据
G是椭圆曲线发生器点
从公式可以看出,根据消息(交易或其部分的Hash值)、签名者的公钥和签名(R和S值),计算一个值P,该值是椭圆曲线上的一个点,如果该点的X坐标等于R,那么签名有效。
1.2 Bip143简述
比特币有4个ECDSA(椭圆曲线数字签名算法)的签名验证操作码(sigops):CHECKSIG,CHECKSIGVERIFY,CHECKMULTISIG,CHECKMULTISIGVERIFY。一笔交易 的摘要信息被两次SHA256 。
比特币的原始数字签名摘要算法存在至少两个缺点:
    验证签名数据的hash与交易的字节大小成比例,验证签名的计算量是按照O(N2)的时间复杂度增长,验证时间过长,BIP143通过引入一些可重用的“中间状态”来优化摘要算法,使验证签名的时间复杂度变为O(n)。
    原始签名的第二个缺点:签名中未包括交易输入的比特币数量,对于网络节点来说,这不是弱点,但对于离线交易签名设备(冷钱包),由于的输入金额未知,造成无法计算所花费的确切金额和交易费用。BIP143在签名中明确包含了每一笔交易输入的金额。
BIP143定义了一个新的事务摘要算法,其规范如下
  交易的序列化
1. nVersion交易版本(4字节小端)
2. hashPrevouts 对所有输入UTXO进行两次SHA256计算的结果 (32字节HASH)
3. hashSequence 对所有输入nSequence进行两次SHA256计算的结果(32字节HASH)
4. outpoint 输入的UTXO(32字节HASH+ 4字节小端) 
5.输入的scriptCode(序列化为CTxOuts中的脚本)
6.输入所花费的数量(8字节小端)
7.输入的nSequence(4字节 小端)
8. hashOutputs 对所有输出进行两次SHA256计算的结果(32字节 HASH)
9. nLocktime交易锁定时间(4字节小端)
10. sighash签名类型(4字节小端)

以上条目中的 1,4,7,9,10与原始SIGHASH算法含有相同,原始的SIGHASH类型的语义保持不变。变动的有以下内容:
    序列化的方式
    所有SIGHASH都承诺签名输入所花费的金额
    FindAndDelete签名不适合scripteCode;
    OP_CODESEPARATOR(S)后执行的最后OP_CODESEPARATOR不会从删除scriptCode(最后执行的OP_CODESEPARATOR任何脚本之前它总是删除);
    SINGLE不提交输入的索引。当ANYONECANPAY没有设置,语义是不变的,hashPrevouts和outpoint一起隐式提交到输入索引。当SINGLE使用ANYONECANPAY时,对签名过的输入和输出成对出现,但对索引无约束。

2.BIP143签名
在go语言中,我们使用了btcsuite库来完成签名,btcsuite库是个完整的比特币代码库,可以编译生成比特币全节点的程序,但这里我们只用btcsuite库的公私钥接口包、SHA接口包和signRFC6979签名接口包。为省略篇幅,下面代码未对错误进行处理。
2.1 生成交易HASH
生成交易信息的hash值,交易中每个输入,会生成一个对应hash值,如果交易中有多输入,那么会生成一个hash数组,数组中的每个hash对应交易中的一个输入。
 
例如上图的交易有两笔交易输入,每一笔都会生成一个hash,上图中的交易会生成两个hash。Fhash函数 
CalcSignatureHash(script []byte, hashType SigHashType, tx *EMsgTx, idx int)
其中:
Script,pubscript 即输入utxo的解锁脚本
HashType,签名方式或签名类型
Tx,交易的具体数据
Idx,交易输入的序号,即当前给交易的第几笔输入计算hash
下面为Fhash代码。
1.Encode Version 
binarySerializer.PutUint32(w, littleEndian, uint32(msg.Version))
2.Encode hashPrevouts
var hashPrevoutBuff bytes.Buffer
for _, ti := range msg.TxIn {
    hashPrevoutBuff.Write(ti.PreviousOutPoint.Hash[:])       
    binarySerializer.PutUint32(&hashPrevoutBuff, littleEndian, ti.PreviousOutPoint.Index)     
}
w.Write(chainhash.DoubleHashB(hashPrevoutBuff.Bytes()))    
3.Encode HashSequence
var hashSequenceBuff bytes.Buffer
for _, ti := range msg.TxIn {
   binarySerializer.PutUint32(&hashSequenceBuff, littleEndian, ti.Sequence)
}
w.Write(chainhash.DoubleHashB(hashSequenceBuff.Bytes()))
4.Encode Outpoint
w.Write(msg.TxIn[idx].PreviousOutPoint.Hash[:])
binarySerializer.PutUint32(w, littleEndian, op.Index)
5.Encode ScriptCode
WriteVarBytes(w, pver, msg.TxIn[idx].SignatureScript)
6. Encode  Amount , 
binarySerializer.PutUint64(w, littleEndian, uint64(msg.Amount[idx]))
7. Encode  nSequence
binarySerializer.PutUint32(w, littleEndian, msg.TxIn[idx].Sequence)
8.Encode hashOutputs
var hashOutputBuff bytes.Buffer
for _, to := range msg.TxOut {
  binarySerializer.PutUint64(&hashOutputBuff, littleEndian, uint64(to.Value))
  WriteVarBytes(&hashOutputBuff, pver, to.PkScript)
}
w.Write(chainhash.DoubleHashB(hashOutputBuff.Bytes()))
9.Encode Locktime
binarySerializer.PutUint32(w, littleEndian, msg.LockTime)
10.Encode Sighash Type
binarySerializer.PutUint32(w, littleEndian, uint32(SigHashType))

对于一个交易内有多笔UTXO输入的情况,对每一笔输入,依此调用上面步骤,生成一个hash数组。生成hash前,需要将其它输入中包含的 “SigantureScript”字段内容清空,只留当前输入的“SigantureScript”字段内容,即下图的“ScriptSig”字段。
 
每笔输入UTXO对应花费的金额是不同的,在第六步需要注意 需要填入的是每笔的交易输入的花费金额。
多笔输入生成函数
func txHash(tx msgtx) ( *[][]byte)
代码细节
    for idx := range tx.TxIn {
        hash, err := CalcSignatureHash(pkScript, SigHashAll|SigHashForkId, tx, idx)       
       sigHash = append(sigHash, hash)
    }
循环调用Fhash函数(CalcSignatureHash)即可生成一个hash数组。
2.2对HASH签名
在上面的步骤中生成了一个hash数组,对数据中每个hash对应于交易的每一笔输入,采用signRFC6979签名函数对hash进行签名,这里直接调用 btcsuite库中的函数。
signRFC6979(PrivateKey, hash)
通过该函数,会生成SigantureScript,将该值付给交易中每笔输入的SigantureScript字段。

2.3.多重签名(Multisig)
多重签名技术,简单来说,就是花费一笔UTXO需要多个私钥签名才有效。脚本设置了一个条件,其中N个公钥被记录在脚本中,并且至少有M个必须提供签名来解锁资金。这也称为M-N方案,其中N是密钥的总数,M是验证所需的签名的数量。
以下go语言实现 一个基于P2SH(Pay-to-Script-Hash)脚本的2-2多重签名。
2-2赎回脚本的生成函数代码:

builder := txscript.NewScriptBuilder().AddInt64(int64(2))
builder.AddData(pk1)
builder.AddData(pk2)
builder.AddInt64(2)
builder.AddOp(txscript.OP_CHECKMULTISIG)

上面的函数生成了如下赎回脚本
2    2 OP_C HECKMULTISIG

签名函数

1.    根据交易TX,其包括输入数组[]TxIn,生成交易HASH数组,此步骤与上面普通交易的步骤相同,直接调用上面普通交易的摘要生成函数。
func txHash(tx msgtx) ( *[][]byte)
该函数生成一个hash数组,即每个交易的输入对应一个hash值。
2.    使用在赎回脚本中的第一个公钥KEY,对应的私钥进行签名。签名过程如普通交易。
signRFC6979(PrivateKey, hash)
签名后生成了每笔输入的签名数组SignatureScriptArr1。根据这个数组中的签名值,更新交易TX中每个输入TxIn的"SigantureScript"字段。
3.    根据更新后的TX,再次调用 txHash函数,生成新的hash数组。
func txHash(tx msgtx) ( *[][]byte)
4.    使用在赎回脚本中的第二个公钥KEY,对应的私钥进行签名。使用上一步骤中更新过的TX,生成每个输入的hash并签名
signRFC6979(PrivateKey, hash) 
//合并第一个key生成的签名、第二个key生成的签名和赎回脚本
etxscript.EncodeSigScript(&(TX.TxIn[i].SignatureScript), &SigHash2, pkScript)

交易中有N笔交易,那么上面步骤执行N次。
最后生成的数据内容如下图所示
 


参考文献
https://en.wikipedia.org/wiki/Digital_signature*
https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
《OReilly.Mastering.Bitcoin.2nd.Edition》
http://www.8btc.com/rfc6979
 

你可能感兴趣的:(btc)