1. 程式人生 > >比特幣開發指南——交易

比特幣開發指南——交易

交易使得使用者花費聰。每筆交易都由幾個部分構成,它們既能實現簡易直接支付也能實現複雜的交易。本章將描述每一部分並演示如何一起使用構建完整交易。

為了使得事情簡單,本章假設coinbase交易不存在。coinbase交易只能由比特幣礦工建立,而且對於下面列舉的許多規則來說它們被視之為異常。代之為每種規則指出coinbase交易異常,我們希望您能閱讀coinbase交易章節。


image.png

上圖展示了比特幣交易的主要部分。每筆交易至少有一筆輸入和輸出。每筆輸入花費之前交易中支付給輸出的聰(就是說本筆交易的輸入就是上一筆交易的輸出)。每筆輸出將作為未花費輸出(UTXO)存在,直到後續交易的輸入花費它。當比特幣錢包告訴你你有10000聰零錢時,事實上你真的有10000聰在一筆或多筆UTXOs中。

每筆交易都添加了4位元組的交易版本號字首,交易版本號告訴節點和礦工使用哪套規則驗證本次交易。這使得開發者能為將來的交易建立新的規則而不用擔心以前的交易無效(新規則可能不相容舊規則)。


image.png

輸出都有一個基於在交易中的位置的索引號——第一個輸出的索引是0。輸出也包括支付給一個條件scriptPubKey的一定數量的聰。任何滿足scriptPubKey條件的人都可以花費支付給他的一定數量的聰。

一筆輸入使用交易識別符號(txid)和輸出索引號(也叫vout:output vector)標識一筆要被花費的特定的輸出。也有一個提供資料引數滿足scriptPubKey條件的簽名指令碼。(序列號和鎖定時間後續子章節會談到)

下圖通過展示Alice支付給Bob一筆交易而Bob之後將會花費這筆交易的工作流,幫助理解這些特性是如何被使用的。Bob和Alice都使用最常見的標準的Pay-To-Public-Key-Hash(P2PKH)交易型別。P2PKH讓Alice傳送聰到一個比特幣地址上,然後Bob使用一個簡單的加密金鑰對花費這些聰。


image.png

在Alice建立交易之前,Bob必須首先生成一個公私鑰對。比特幣使用了利用secp256k1曲線的橢圓曲線數字簽名演算法(ECDSA)。secp256k1私鑰是256 bytes的隨機資料。因為轉換可以可靠地重複,所以公鑰不需要儲存。

公鑰然後被加密hash。公鑰hash可以穩定重複,所以也不需要儲存。hash縮短和混淆公鑰,使得手工抄寫更加容易,而且提供安全免於遭受可能在之後的某個時間允許從公鑰中重建私鑰的問題。

Bob提供公鑰hash給Alice。公鑰hash幾乎總是被編碼作為比特幣地址——base58編碼的字串,包含一個地址版本號、hash和一個捕獲異常的錯誤檢測校驗和。地址可通過任意媒體傳輸,包括避免花費者和接受者通訊的單向媒體,更進一步也可被編碼成其他形式,比如包含bitcoin:URL的二維碼。

一旦Alice擁有地址並解碼成標準hash,就可以建立第一筆交易。她建立一個標準的P2PKH交易輸出,包含了允許任何人花費該輸出的指令,只要他們能提供與Bob的hash公鑰相對應的私鑰。這些指令叫做公鑰指令碼(pubkey script或scriptPubKey)。

Alice廣播該交易,然後該交易被加入到區塊鏈中。比特幣網路把它劃分為未花費輸出(UTXO),而且Bob的錢包軟體將之作為可花費餘額展示出來。

一段時間以後,Bob決定花費這筆UTXO,他必須建立一個輸入——Alice通過交易識別符號(txid)建立的交易,和使用輸出索引號標識的特定的輸出。然後必須建立一個簽名指令碼——滿足Alice在之前的輸出scriptPubKey中設定的條件的資料引數集合。簽名指令碼也叫做scriptSigs。

scriptPubKey和scriptSigs聯合secp256k1公鑰和條件邏輯簽名,建立一個可程式設計認證機制。


image.png

對於P2PKH型別的輸出,Bob的scriptSigs包含兩塊資料:

  1. 他的完整公鑰(未hash),以便scriptPubKey可以校驗該公鑰的hash值和Alice提供的公鑰hash是否相同。
  2. 一個利用ECDSA加密公式結合具有Bob私鑰的交易資料生成的secp256k1簽名。使得scriptPubKey驗證Bob擁有建立該公鑰的私鑰。

Bob的secp256k1簽名不僅證明Bob控制著他的私鑰;也使得他的交易的未scriptSigs部分免遭篡改以便Bob可以將交易安全傳播給P2P網路。


image.png

如上圖所示,Bob簽名的資料包括txid、之前交易的輸出索引、之前的scriptPubKey、Bob建立使得下一個接收者花費這筆交易輸出的輸出scriptPubKey、支付給下一個接受者的一定數量的聰。本質上,除了持有完整公鑰和secp256k1簽名的scriptSigs外,整個交易都被簽名。

在把簽名和公鑰放到scriptSigs中後,Bob通過P2P網路把交易廣播給礦工。在交易被納入新的區塊之前,每個節點和礦工獨立驗證交易。

P2PKH 指令碼驗證

驗證過程需要scriptSigs和scriptPubKey的評定。在一個P2PKH輸出中,scriptPubKey如下:

OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG

花費者的scriptSigs被評估並放到指令碼開始的地方。在P2PKH交易中,scriptSigs包含一個secp256k1簽名(sig)和完整公鑰(pubkey),建立如下:

<Sig> <PubKey> OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG

這種指令碼語言是一種類似於Forth的基於堆疊的語言,它是故意被設計成無狀態和非圖靈完備的。無狀態確保一旦交易被新增到區塊鏈中,就沒有條件使得它永久不可花費。圖靈不完備(缺少迴圈或goto)使得指令碼語言更少靈活性和更多可預測性,極大地簡化了安全模組。

為了測試交易是否合法,scriptSigs和scriptPubKey操作是依次進行。從Bob的scriptSigs開始直到Alice的scriptPubKey末端。下面的圖表展示了標準P2PKH scriptPubKey的判斷;下圖是過程描述:


image.png
  • 簽名(源於Bob的scriptSigs)被加入到一個空的棧,公鑰(源於Bob的scriptSigs)被放到簽名的頂部
  • 從Alice的scriptPubKey中,OP_DUP操作被執行。OP_DUP把當前位於棧頂的資料備份,然後將該備份放到棧頂——本例中建立了一個Bob提供的公鑰的備份
  • 下一步執行的操作OP_HASH160,先對棧頂的資料進行hash,再將hash值放到棧頂——本例中建立了Bob的公鑰hash
  • Alice將第一次交易中Bob給她的公鑰hash(比特幣地址)推到棧中。此時,在棧頂有兩份Bob的公鑰備份
  • 現在開始變得有趣:Alice的scriptPubKey執行OP_EQUALVERIFYOP_EQUALVERIFY等同於先執行OP_EQUAL然後執行OP_VERIFY(沒有顯示出來)。
    OP_EQUAL(沒顯示出來)檢查棧頂的兩個值;本例中,檢查由Bob提供的完整公鑰生成的公鑰hash和由Alice建立交易#1時提供的公鑰hash是否相同。
    OP_EQUAL移除要比較的兩個值,並替換成比較結果:0(false)或1(true)。
    OP_VERIFY檢查棧頂的值。如果值是false立刻終止判斷,交易驗證失敗;否則的話從棧頂把true移除。
  • 最後,Alice的scriptPubKey執行OP_CHECKSIG,該函式檢查Bob提供的簽名和他提供的及時認證的公鑰。如果簽名匹配公鑰而且是使用所有要簽名的資料生成的,OP_CHECKSIG把true推到棧頂。

在scriptPubKey被判斷完畢之後,false不位於棧頂,交易就是有效的(證明它沒有其他問題)。

P2SH 指令碼

scriptPubKey 由對指令碼行為感興趣的花費者建立。接收者只關心指令碼條件,而且如果他們願意的話,可以讓花費者使用一個特定的scriptPubKey 。不幸的是,自定義scriptPubKey 較之短小的比特幣地址來說更加不方便,而且在之後討論的BIP70支付協議被廣泛實現以前程式之間沒有標準的通訊方式。

為了解決這些問題,在2012年建立了pay-to-script-hash(P2SH)交易,讓花費者建立一個包含另一個指令碼hash的scriptPubKey。這另一個指令碼即贖回指令碼。

基本的P2SH工作流如下,看起來和P2PKH工作流幾乎相同。Bob隨心所以建立一個贖回指令碼,然後對之hash,並將hash值提供給Alice。Alice建立一個P2PSH型別的輸出包含了Bob的贖回指令碼hash值。


image.png

當Bob想要花費輸出時,需要在scriptSigs中提供他的簽名和完整的(序列化的)贖回指令碼。P2P網路確保把完整的贖回指令碼進行hash,得到的hash值和Alice放到她的輸出中的script hash相同。然後像處理Alice的scriptPubKey一樣處理贖回指令碼。


image.png

贖回指令碼的hash值和pubkey hash值具有相同的屬性——所以可被轉換成標準的比特幣地址格式,而且二者只有一點小的差異。這使得收集P2SH和P2PKH型別的地址一樣簡單。hash也混淆了贖回指令碼中的公鑰,所以P2SH 指令碼和P2PKH pubkey 指令碼同等安全。

標準交易

在早期的比特幣版本中發現了幾個危險的bug後,補充了一些測試——只接受網路中scriptPubKey和scriptSigs符合安全模板的交易,且交易的其餘部分沒有違反另一套實施良好網路行為的規則。這就是IsStandard()測試,通過該測試的交易稱之為標準交易。
非標準交易——驗證失敗——可能會被沒有使用比特幣核心預設設定的節點接受。如果這些交易被加入到區塊中,將會避免IsStandard測試並被處理。
除了使得通過廣播有害的交易免費的攻擊比特幣更加困難之外,也幫助使用者避免建立使得在將來新增新的交易屬性更加困難的交易。例如,如上所述,每筆交易包含版本號——如果使用者任意修改版本號,交易作為引入向後不相容特性的工具將變得無用。
從比特幣核心0.9開始,標準的scriptPubKey型別如下:

支付給公鑰hash(P2PKH)

P2PKH是最常用的scriptPubKey形式,用於把交易傳送給一個或多個比特幣地址。

Pubkey script: OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Signature script: <sig> <pubkey>

支付給指令碼hash(P2SH)

P2SH用於傳送交易給指令碼hash。每個標準的scriptPubKey都可用作P2SH 贖回指令碼,但是實際上只有多重簽名scriptPubKey有意義,除非更多的交易型別成為標準的交易型別。

Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
Signature script: <sig> [sig] [sig...] <redeemScript>

多重簽名

雖然P2SH多重簽名現在一般用於多重簽名交易,這種基本的指令碼也可用於要求UTXO被花費之前需要多重簽名。

在多重簽名scriptPubKey中,叫做m-of-n,m是必須匹配公鑰的最大數量的簽名;n是被提供的公鑰數量。m和n都應該通過 OP_16操作OP_1,對應於目標數字。
因為比特幣早期實現中的差一錯誤(考慮到相容問題,需要被保留),OP_CHECKMULTISIG從棧中消耗一個比m更大的值,所以scriptSigs中的secp256k1簽名列表必須新增一個額外的字首值(OP_0),該值會被消耗並不使用。
scriptSigs必須提供和scriptPubKey或贖回指令碼中出現的pubkey相同順序的簽名。詳細請看OP_CHECKMULTISIG描述。

Pubkey script: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
Signature script: OP_0 <A sig> [B sig] [C sig...]

雖然不是一種獨立的交易型別,這是一個2-of-3 P2SH多重簽名:

Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
Redeem script: <OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> OP_CHECKMULTISIG
Signature script: OP_0 <A sig> <C sig> <redeemScript>

公鑰

公鑰輸出是P2PKH scriptPubKey簡化的形式,但是並不像P2PKH一樣安全,所以在新的交易中它們一般不再被使用。

Pubkey script: <pubkey> OP_CHECKSIG
Signature script: <sig>

空資料

空資料交易型別在比特幣核心0.9及以上版本預設被轉發和開採,0.9版本以後添加了任意資料到可證明不可花費的scriptPubKey中,這些scriptPubKey並沒有儲存到完整節點本地的UTXO資料庫中。最好是在交易中使用空資料交易,從而擴充UTXO資料庫,因為它們不能自動精簡。然而,如果可能的話,一般最好儲存外部交易。

非標準交易

如果在輸出中除了標準scriptPubKey之外還有其他的東西,使用比特幣核心預設設定的節點和礦工將既不接受、廣播,也不處理你的交易。當你嘗試廣播交易給執行預設設定的節點,將收到錯誤提示。

如果建立一個贖回指令碼,對之hash,並在P2SH輸出中使用該hash,網路只關注這個hash,所以將會接受輸出並視之為有效,而不管該贖回指令碼說了些什麼。這允許支付給非標準指令碼,而且從比特幣核心0.11開始,幾乎所有有效的贖回指令碼都可被花費。異常時使用了未賦值的NOP操作碼的指令碼。

注:標準交易被設計用於保護和幫助網路,而非阻止你免犯錯誤。

從比特幣核心0.9.3開始,標準交易必須滿足以下條件:

  • 交易必須被完成:要麼交易的鎖定時間必須是過去(少於或等於當前的區塊高度),要麼交易的所有的序列號都是0xffffffff
  • 交易必須小於100000位元組,大概是一個典型的單輸入,單輸出的P2PKH交易的200倍
  • 每個交易的scriptSigs必須小於1650 bytes。使用壓縮公鑰的話,足以允許P2SH型別的15-of-15多重簽名交易。
  • 赤裸(非P2SH)多重簽名交易要求超過三個公鑰是非標準的
  • 交易的scriptSigs必須僅把資料推送到指令碼的判斷棧中。不能推送新的操作碼
  • 交易的輸出不能少於輸入的1/3。當前預設的轉發費用是546聰。標準空資料輸出必須是0.

簽名hash型別

OP_CHECKSIG從它判斷的每個簽名中提取非堆疊引數,允許簽名者自行決定簽署交易的哪一部分。因為簽名保護交易免遭修改,這讓簽名者有選擇地讓其他人修改他們的交易。
要簽署的選項叫做簽名hash型別,有三種基礎的型別:

  • SIGHASH_ALL:預設型別,簽署所有的輸入和輸出,保護所有的事情免遭修改除了scriptSigs。
  • SIGHASH_NONE:簽署所有的輸入,允許任何人改變聰的去向,除非其他的簽名使用了其他簽名hash型別包含輸出
  • SIGHASH_SINGLE:唯一被簽署的輸出是與輸入相對應的(輸出和輸入具有相同的輸出索引號),確保沒人可以修改交易中屬於你的部分,但是允許其他簽署者改變他們的交易部分。相應的輸出必須存在否則value 1將被簽署,打破安全機制。輸入和輸出一樣,都被加入到簽名中。其他輸入的序列號沒有被新增到簽名中,而且會被更新。

基本型別可以使用SIGHASH_ANYONECANPAY標籤被修改,建立三種新的組合型別:

  • SIGHASH_ALL|SIGHASH_ANYONECANPAY:簽署所有的輸出但只有一個輸入,而且允許任何人新增或移除其他的輸入,所以任何人都可以貢獻額外的聰,但是他們不能改變要傳送多少聰和傳送到哪
  • SIGHASH_NONE|SIGHASH_ANYONECANPAY:只簽署這一個輸入,並允許任何人新增或移除其他的輸入或輸出,所以任何獲得輸入備份的人都可以花費它
    SIGHASH_SINGLE|SIGHASH_ANYONECANPAY:只簽署這一個輸入和與之對應的輸出,允許任何人新增或移除他們的輸入。

因為每個輸入都被簽署,有多個輸入的交易可以有多個簽名hash型別簽署交易的不同部分。例如,使用NONE簽署的單輸入交易可以使得它的輸出被新增它到區塊鏈上的礦工改變。另一方面,如果雙輸入交易有一個輸入使用NONE簽署,另一個使用ALL簽署,ALL簽署者可以選擇把聰花費到哪而不用自行NONE簽署者——但是沒有其他人可以修改交易。

鎖定時間和序列號

所有的簽名hash型別都可以簽署的一個東西就是鎖定時間(在比特幣核心原始碼中叫做nLockTme)。鎖定時間意味著可被新增到區塊鏈中的交易的最早時間。

鎖定時間允許簽署者建立僅在將來才能有效的時間鎖定交易,給簽署者改變想法的機會

如果任意簽署者改變想法,他們可以建立一個新的非鎖定時間交易。這筆新的交易將使用在鎖定交易中用作輸入的一個輸出作為它的輸入。這使得鎖定時間交易無效,如果新的交易在預期的鎖定時間之前被新增到區塊鏈中。

必須注意時間鎖的有效期限。P2P網路執行區塊時間比真實時間早2個小時,所以一筆鎖定時間交易可被加入到區塊鏈中直到它的時間鎖觸發的前兩個小時。而且,區塊不一定會在保證的週期裡建立,所以任何曲線價值交易的請求都應在鎖定時間觸發之前兩小時發出。

比特幣核心以前的版本提供了一個特性,避免交易簽署者使用上述的方法取消時間鎖定的交易,但是該特性的必要的部分無法阻止服務拒絕攻擊。但一個系統遺產就是每個輸出中的4位元組的序列號。序列號意味著允許多個簽署者一致同意更新交易;當完成交易更新後,他們同意把每個輸入的序列號設定成4位元組的無符號的最大值(0xffffffff),允許在時間鎖尚未觸發時把交易被新增到區塊中。

甚至今天,把所有的序列號設定成0xffffffff(比特幣核心預設值)仍然可以禁用時間鎖。至少一筆輸入必須有一個低於最大值的序列號。因此序列號不被網路用作它途,把任意序列號設定成0表示啟用時間鎖。

時間鎖自身是一個4位元組的無符號數字,可按照兩種方式解析

  • 如果小於5億,時間鎖被解析成區塊高度。交易可被新增到有相同或更高高度的區塊中。
  • 如果>=5億,使用Unix紀元時間格式(從1970-01-01T00:00 UTC到當下的秒數)解析時間鎖。交易可被新增到區塊時間比鎖定時間大的任意區塊中。

交易費用和找零

交易根據被簽署交易總位元組大小支付費用。每個位元組的費用是根據當前開採區塊的空間需求計算的,隨著需求的增加,收費也隨之增加。正如在區塊鏈章節解釋的,交易費用被給予礦工,而且每個礦工自由選擇他們願意接受的最低交易費。

有一個所謂的“高優先順序交易”,指花費很久沒有被移動的聰的交易。

過去,這些“優先”交易經常不用滿足正常費用要求。在比特幣核心0.12之前,每個區塊為高優先順序交易保留50kb空間,然而現在預設設定成0。在該優先區域之後,所有的交易按照每位元組費用排序,更高支付的交易按序被加入到區塊中,直至所有可用空間被填滿。

從比特幣核心0.9開始,最低費用要求在網路中廣播一個交易(目前是1000聰)。這意味著在區塊有足夠的多餘空間加入它之前,僅支付最低費用的交易應該等待很長時間。請看驗證支付章節瞭解為什麼它是非常重要的。
因為每筆交易都花費UTXO,而且一筆UTXO只能花費一次,所以被新增的UTXO的必須要給礦工一些作為交易費。很少人會僅使得UTXO和他們要花費的一樣(沒有交易費),所以大部分的交易包括一個找零輸出。

找零輸出是合理的輸出,把多餘的聰再返回給花費者。他們可以重用在UTXO中使用的相同的P2PKH公鑰hash或P2SH指令碼hash,但是出於下一子章節描述的原因,強烈建議使用新的P2PKH或P2SH地址花費找零輸出。

避免祕鑰重用

在交易中,花費者和接收者互相透露在交易中使用的所有的公鑰或地址。這允許人使用區塊鏈追蹤包含他人相同的公鑰或地址的過去和將來的交易。

如果相同的祕鑰經常重用,當人們使用比特幣地址(公鑰hash)作為靜態的支付地址時,其他人可以輕易地追蹤該使用者的花費和接收習慣,包括他們在已知的地址上有多少聰。

並非全部如此,如果每個公鑰只用兩次——一次接收,一次花費——使用者可以獲得相當程度的經濟隱私。

更好的方法是,接收支付或建立找零輸出時,使用新的公鑰或唯一地址可以和後續討論的技術結合起來。

避免祕鑰重用也可以提供安全,免遭可能允許從公鑰或簽名比較中構建私鑰的攻擊(在下面描述的情況下,使用更多通用的攻擊假設)

  1. 唯一的(非重用)P2PKH和P2SH地址保護免遭第一種型別的攻擊,通過保持ECDSA公鑰隱藏(被hash)直到第一次傳送給這些地址的聰被花費,所以攻擊很大程度上是無用的,除非他們能夠在少於被區塊鏈很好保護的交易所花費的時間裡重建私鑰。

  2. 唯一的(非重用)私鑰保護免遭第二種型別的攻擊,通過為每個私鑰生成一個簽名,所以攻擊者從不可能獲得隨後的簽名在基於比較的攻擊中使用。現有的基於比較的攻擊只有在簽名時使用的熵不足或熵在某種程度上暴露了時才具有實用性。

所以,為了隱私和安全,我們鼓勵你構建自己的應用避免公鑰重用,而且如果可能的話,我們不鼓勵你重用地址。如果你的應用需要提供一個固定的URI,請檢視bitcoin:URI章節

交易延展性

比特幣簽名hash型別中沒有保護簽名指令碼的,為服務拒絕攻擊敞開大門叫做交易延展性。簽名指令碼包含了不能簽署自身的secp256k1簽名,允許攻擊者非功能性的修改交易而不會致使其無效。例如,攻擊者可以在之前的公鑰指令碼被處理之前停用的簽名指令碼中新增一些資料。

雖然修改是非功能性的——所以沒有修改交易的輸入和輸出——修改了交易的hash。每筆交易使用hash作為交易識別符號(txid)連結到之前的交易,被修改的交易沒有建立者預期的txid。

對於大多數被設計成立刻被新增到區塊鏈中的比特幣交易來說,這不算是個問題。但是當源於一筆已被加入到區塊鏈中的交易的輸出被花費時,就是個問題了。

比特幣開發者已經致力於在標準交易型別中減少交易延展性,努力的成果之一就是BIP 141:Segregated Witness,它已經被比特幣核心支援但尚未啟用。當下,新的交易不應依賴於尚未被加入到區塊鏈中的之前的交易,尤其大數量的聰交易的時候。

交易延展性也影響了支付追蹤。比特幣核心RPC介面使得你可以通過txid追蹤交易——但是如果因為交易被修改,txid被改變,可能會出現交易從網路中消失的情況。
針對交易追蹤,當前最好的方式就是通過交易花費的作為輸入的UTXO跟蹤交易,因為不驗證交易,UTXO無法修改。

更進一步的方式就是如果交易從網路中消失而且需要補發,補發在某種程度上驗證了丟失的交易。行之有效的方法就是確保被補發的支付花費丟失的交易用作輸入的相同的輸出。