天天看點

(原創)比特币的簽名機制以及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