天天看点

(原创)比特币的签名机制以及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  <Partner1 Public Key> <Partner2 Public Key>  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