比特幣中對交易進行簽名的詳細過程

來源:互聯網
上載者:User

最近在和同事交流我們PalletOne中對UTXO和簽名的處理,有些心得,寫下此博文。對比特幣有點基本概念的都知道,比特幣是通過ECDSA數位簽章來解鎖UTXO中的未花費餘額。

關於UTXO我不需要做太多介紹,畢竟介紹這個概念的文章已經很多了。我主要是談談已經有UTXO了,該怎麼花掉。

交易的結構

我們先來看看在比特幣中,一個交易的結構是什麼樣的?

type MsgTx struct {    Version int32    TxIn []*TxIn    TxOut []*TxOut    LockTime uint32}type TxOut struct {    Value int64    PkScript []byte}type TxIn struct {    PreviousOutPoint OutPoint    SignatureScript []byte    Sequence uint32}type OutPoint struct {    Hash chainhash.Hash    Index uint32}

我們可以看到,一個交易(MsgTx)是由多個Input和多個Output組成的,而在Input中是由指向UTXO的OutPoint,解鎖指令碼SignatureScript和序列Sequence組成。

UTXO我們可以認為是一個KeyValue的大表,在該表中,交易的Hash和該交易中Output所在的位置索引Index就構成了UTXO的Key,而Value就是比特幣Amount、鎖定指令碼等資訊,所以在UTXO資料庫中,我們通過OutPoint能夠很快的找到對應的Amount和鎖定指令碼。

在比特幣中,要做一筆交易分為三個步驟:

  1. 構建原始交易RawTransaction,該交易包含了輸入指向的OutPoint,也包含了完整的Output,但是沒有簽名,也就是沒有設定SignatureScript的內容。
  2. 用私密金鑰對簽名構建的RawTransaction進行簽名,並將簽名構建成完整的解鎖指令碼,填入對應的Input的SignatureScript欄位中。
  3. 將簽名後的Transaction發送到P2P網路中。
構建原始交易RawTransaction

現在假設我有一個地址mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV(這是一個測試網地址),該地址收到了兩筆轉賬,一筆0.4BTC(testnet.blockchain.info/tx-index/239152566/1),另一筆1.1BTC(testnet.blockchain.info/tx-index/239157459/1),這兩筆收入都是在其交易Output的第二條,也就是Index=1(Index從0開始算)。現在我們想要做一筆1.2BTC的轉賬,然後給一定的手續約後,找零到原地址,所以我們會構建一筆交易,該交易有2Input和2Output。

以下是我用Go基於btcd寫的範例程式碼,這裡我們就構建好了一個RawTransaction。

func buildRawTx() *wire.MsgTx {//testnet.blockchain.info/tx/f0d9d482eb122535e32a3ae92809dd87839e63410d5fd52816fc9fc6215018cc?show_adv=truetx := wire.NewMsgTx(wire.TxVersion)//testnet.blockchain.info/tx-index/239152566/1 0.4BTCutxoHash, _ := chainhash.NewHashFromStr("1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52")point := wire.OutPoint{Hash: *utxoHash, Index: 1}//構建第一個Input,指向一個0.4BTC的UTXO,第二個參數是解鎖指令碼,現在是nil    tx.AddTxIn(wire.NewTxIn(&point, nil, nil))//testnet.blockchain.info/tx-index/239157459/1 1.1BTCutxoHash2, _ := chainhash.NewHashFromStr("24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66")point2 := wire.OutPoint{Hash: *utxoHash2, Index: 1}//構建第二個Input,指向一個1.1BTC的UTXO,第二個參數是解鎖指令碼,現在是nil    tx.AddTxIn(wire.NewTxIn(&point2, nil, nil))//找零的地址(這裡是16進位形式,變成Base58格式就是mx3KrUjRzzqYTcsyyvWBiHBncLrrTPXnkV)pubKeyHash, _ := hex.DecodeString("b5407cec767317d41442aab35bad2712626e17ca")lock, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).AddData(pubKeyHash).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).Script()//構建第一個Output,是找零0.2991024 BTC    tx.AddTxOut(wire.NewTxOut(29910240, lock))//支付給了某個地址,仍然是16進位形式,Base58形式是:mxqnGTekzKqnMqNFHKYi8FhV99WcvQGhfH。pubKeyHash2, _ := hex.DecodeString("be09abcbfda1f2c26899f062979ab0708731235a")lock2, _ := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).AddData(pubKeyHash2).AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).Script()//構建第二個Output,支付1.2 BTC出去    tx.AddTxOut(wire.NewTxOut(120000000, lock2))return tx}
交易的簽名過程

現在我們知道私密金鑰,需要對該交易進行簽名,因為有2個Input,所以我們要簽名2次,每個簽名的原理是一樣的,我就以第一個Input為例來說明吧。

在比特幣中,對一筆交易的簽名流程是這樣的:

1.尋找該筆交易對應的UTXO

2.獲得該UTXO對應的鎖定指令碼

3.複製該交易對象,並在複製副本中將該Input的解鎖指令碼欄位的值設定為對應的鎖定指令碼

4.清除其他Input的解鎖指令碼欄位

5.對這個改造後的交易對象計算Hash

6.使用私密金鑰對Hash進行簽名。

用表格的形式可以更容易表達:

這是原始未簽名的交易RawTransaction,主要是第二列和第三列:

UTXO Input Output

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1,

Amount:0.4BTC,PkScript:

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1,

Amount:1.1BTC,PkScript:

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

接下來我們要對第一個Input簽名,於是我們需要將交易複製一個副本,並改為:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =

OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

接下來對這個交易計算Hash,然後進行簽名。得到簽名結果:3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501 而我們知道這個地址的公開金鑰是:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb

所以簽完名後,我們的交易變成:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =

PUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd2

2ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

這才只是完成了第一個Input的簽名,接下來我們再對第二個Input進行簽名,同樣的道理,我們需要製造一個交易的副本,然後把第一個Input的SignatureScript清空,然後給第二個Input的SignatureScript賦值:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =NULL,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =OP_DUP OP_HASH160 PUSHDATA(20) b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

顯然這個副本與第一個簽名時的資料是不一樣的,所以簽名結果也不一樣,最終簽名結果為:30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501,當然我們因為是同一個地址,所以公開金鑰是一樣的:038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb

我們把這個簽名和公開金鑰再放回原始交易中,就變成我們需要的完整簽名的交易:

Input Output
PreviousOutPoint={

TxHash:1dda832890f85288fec616ef1f4113c0c86b7bf36b560ea244fd8a6ed12ada52,

OutIndex:1}

SignatureScript =PUSHDATA(72)[3045022100c435eb458b295381d6e1f489b8683d1b10ecad0a7691949a4ae7ffee74bd22ae022031e47b9ebed5b90f6d51cd05e6f53bdc59f5d6d754aff14a88a6e8659b5fdad501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=29910240

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)b5407cec767317d41442aab35bad2712626e17ca OP_EQUALVERIFY OP_CHECKSIG

PreviousOutPoint={

TxHash:24f284aed2b9dbc19f0d435b1fe1ee3b3ddc763f28ca28bad798d22b6bea0c66,

OutIndex:1}

SignatureScript =PUSHDATA(71)[30440220196bce75f0a25ac8afa7218aefc86cba3924845450f3d311c89e9c2a3438a99c0220230bed598a610be971ca49690f4b42ac2acfa80c09d4cbabd278b03c824af14501] PUSHDATA(33)[038cc8c907b29a58b00f8c2590303bfc93c69d773b9da204337678865ee0cafadb]

,Sequence =0xFFFFFFFF

Value=120000000

PkScript=

OP_DUP OP_HASH160 PUSHDATA(20)be09abcbfda1f2c26899f062979ab0708731235a OP_EQUALVERIFY OP_CHECKSIG

這就是一個真實完整的交易了,接下來就可以通過P2P網路發送該交易,並最終被礦工打包確認。

總結

實際上在比特幣的源碼中比我上面說的還要複雜一些,還涉及到這個hash是對整個交易進行SigHashAll還是SigHashSingle或者SigHashNone,這些都是很特殊的情況,一般的比特幣錢包也不支援,具體可以參加精通比特幣書中的介紹:6.5.3簽名雜湊類型( SIGHASH)

普通來說,我們要對一筆交易進行簽名或者驗簽,就是把當前Input中的解鎖指令碼替換成鎖定指令碼,而其他Input的解鎖指令碼情況,然後計算Hash和簽名!

其實我還是有點不明白,為什麼比特幣中不直接對沒有任何解鎖指令碼的RawTransaction進行簽名呢?而是非要加上鎖定指令碼來簽名?不知道這裡面有什麼更深的考慮。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.