1. 程式人生 > >比特幣的機制2:P2SH交易,Base58,TimeLock

比特幣的機制2:P2SH交易,Base58,TimeLock

在上一篇比特幣的機制1:記賬的方式,交易的語法,比特幣指令碼,P2PKH交易中介紹了比特幣交易的一些基本概念,簡單介紹了比特幣指令碼,並且詳細分析了最常見的P2PKH交易型別。這一節中將繼續對比特幣指令碼和其他交易型別進行介紹。

OP_CHECKMULSIG

和OP_CHECKSIG相比,OP_CHECKMULSIG多了個MUL,也即它需要檢查多個簽名。這個指令並不是比特幣設計之初就有的,而是在2011年的BIP(bitcoin improvement protocol)中提出的,詳見參考1

提出該指令的目的是使能安全錢包、託管交易、以及其他需要多餘一個簽名的使用情況。舉例來說,可以使用的例子包括:

  • 使用WPS(wallet protection service)保護的錢包 譬如需要2-of-2的簽名的交易,其中一個簽名來自於安裝了錢包的(可能不安全的)電腦,另一個簽名來自WPS。當傳送受保護的比特幣時,使用者的比特幣客戶端會將交易發給WPS,WPS會向用戶請求確認,譬如使用者確實發起了這個交易,以及交易的細節是正確的。
  • 第三方託管服務(買方、賣方、以及信任第三方 Escrow transaction) 適用於需要2-of-3簽名的交易。買方、賣方和第三方沒人都提供公鑰,買方將建立交易,並使用2-of-3 CHECKMULTISIG,然後將交易ID傳送給賣方和第三方。賣方履行自己的責任,然後要求買方共同簽名,才能獲得支付的幣。如果買賣雙方起了爭執,則第三方會參與進來,譬如如果買方收到貨卻不肯簽名,那麼第三方將進行簽名,這樣滿足條件,從而賣方可以獲得應得的報酬。

關於第三方託管的例子,詳細來說,譬如Alice想要使用比特幣向Bob購買一些物品。問題是,Alice希望在收到物品之後才付錢,而Bob希望收到錢之後才發貨。這時候就需要第三方託管(類似於支付寶)。而第三方託管可以通過MultiSig方便地實現。

Alice和Bob同意找到信任的第三方Carol。然後Alice建立了交易。但是這個交易不是直接發給Bob的,而是一個MULTISIG的交易,需要2-of-3的簽名。也即,Alice將自己的幣發給了一個地址,要求Alice、Bob以及Carol三人中任意兩人的簽名才能使用這個輸出。

這個交易首先是正常的交易,因為Alice確實擁有這個幣。然後如果Alice和Bob都是誠實的,也即Bob看到Alice建立的交易之後便發貨,而Alice收到貨之後也承認收到貨,那麼為了使用MULTISIG交易中的幣,只要Alice和Bob兩人簽名就可以建立一個新的交易,這個交易的輸入是MULTISIG交易中的輸出,輸出則是Bob的地址,那麼Bob就能獲得幣。此時,並不需要第三方Carol的參與。

如果Alice不誠實,也即收到貨了又不承認,此時Bob可以向Coral申請仲裁,如果Coral相信Bob確實發了貨,那麼Coral可以和Bob兩人建立新的交易,將幣從MULTISIG交易中贖出,並且輸出是Bob的地址。這樣Bob也能夠獲得幣。即使Alice想抵賴也不能成功。

如果Bob不誠實,沒有發貨或者發的貨物不對,那麼Alice可以向Carol申請仲裁。如果Carol相信了Alice,那麼就可以和Alice一起簽名,將幣從MULTISIG交易中贖出,並且輸出是Alice的地址。這樣Alice就能夠獲得退款。即使Bob想抵賴也不能成功。

除此之外,multisig還可以有其他的一些例子:如公司裡有多個合夥人,必須要2個或以上的合夥人同時簽名才能動用公司的資金…

MULTISIG的規範如下——

scriptPubKey 中包括的內容:

 m {pubkey}...{pubkey} n OP_CHECKMULTISIG

n的值小於或等於3 OP_CHECKMULTISIG交易贖出幣時需要如下的scriptSig:

OP_0 ...signatures...

這裡需要OP_0是因為OP_CHECKMULTISIG中存在Bug,它在實現的時候多出棧了一個元素,所以只好使用OP_0進行填充。

再具體看一下MULTISIG的執行情況。 比如說贖出幣的交易中(新交易Tx2)的scriptSig是這樣的: (其實就對應著兩個人對交易進行簽名的情況)

(sig2)
(sig1)
    0

MultiSig交易(Tx1)中的輸出部分的scriptPubKey是這樣的: (其實就對應著三個人蔘與合約的情況)

3
(pubKey3)
(pubKey2)
(pubKey1)
2

當執行時,在 OP_CHECKMULTISIG執行之前,棧中的資料是這樣的:

3
(pubKey3)
(pubKey2)
(pubKey1)
2
(sig2)
(sig1)
0

初始化之後,各個變數獲得了值:

n->    3
ikey-> (pubKey3)
       (pubKey2)
       (pubKey1)
m->    2
isig-> (sig2)
       (sig1)
       0

然後首先嚐試使用pubKey3來驗證sig2, 很明顯會失敗,那麼將ikey朝前走一步指向pubKey2,isig保持不動:

n->    3
       (pubKey3)
ikey-> (pubKey2)
       (pubKey1)
m->    2
isig-> (sig2)
       (sig1)
       0

使用pubKey2驗證sig2成功了,然後ikey和isig都向前:

n->    3
       (pubKey3)
       (pubKey2)
ikey-> (pubKey1)
m->    2
       (sig2)
isig-> (sig1)
       0

基本演算法如下:

  1. 首先彈出 n , n 是公鑰的個數
  2. 出棧 n 個公鑰
  3. 出棧 m , m 是所需的簽名的個數
  4. 出棧 m 個簽名
  5. 將OP_0出棧(歷史遺留問題)
  6. 對公鑰可以迴圈,從最上面的開始,對每一個公鑰,檢查一個簽名。都從最上的開始,如果失敗,則使用下一個公鑰來檢查同一個簽名;如果成功,則使用下一個公鑰來檢查下一個簽名(簽名必須和公鑰的順序相同)。
  7. 如果簽名成功,則CHECKMULTISIG返回1,否則返回0。

以上部分是對MULTISIG的介紹,下面將介紹P2SH,順便用一下MULTISIG。

2. P2SH(pay-to-script-hash)

之前我們介紹了兩種交易型別P2PKH和P2PK,現在來討論一種新的交易型別,pay-to-script-hash。顧名思義,pay-to-script-hash就是把幣發到一個指令碼的雜湊,而不是公鑰或者公鑰雜湊。

典型的比特幣地址長得像15Cytz9sHqeqtKCw2vnpEyNQ8teKtrTPjp,也是Pay-to-PubKeyHash (P2PKH) 的輸出指令碼中所用的地址。Pay-to-ScriptHash (P2SH) 長的和用的都不一樣。典型的P2SH地址像347N1Thc213QqfYCz3PZkjoJpNv5b14kBd,另外,P2SH 總是以3開頭的,而P2PKH地址總是以1開頭。這是因為P2SH地址的版本字首是0x05, 而P2PKH的地址字首是0x00, 在base58check編碼中分別生成3和1。

和MULTICHECKSIG一樣,P2SH也不是比特幣誕生之初就有的,它是2012年的BIP 16中提出的。提出P2SH的目的主要是因為在之前的交易中,都是由傳送者負責指定贖出幣的條件。這樣的話,如果贖出幣的過程比較複雜,譬如要使用MULTISIG,那麼對付錢的使用者,也就是買家,就不夠友好。使用P2SH的方式,可以由幣的接收方設計好執行的指令碼,然後不論指令碼多麼複雜,傳送方只需要將幣傳送到一個20位元的雜湊地址就行。

譬如Alice向Bob購買一件物品,但是Bob的物品不是自己獨有的,他需要和其他人一起分享Alice付的幣,就例如上面介紹的MULTICHECK,需要3個人中的兩人同意才能使用幣。把這個任務交給Alice是不合理的,因為Alice只關心自己付了錢能夠拿到物品,並不關心Bob拿到錢之後怎麼分。這個時候Bob可以建立一個Script,然後Alice可以將幣傳送到這個Script地址。

我們來看一下P2SH的規範—— 在支付使用者的交易Tx1中的輸出指令碼是:

OP_HASH160 [20-byte-hash-value] OP_EQUAL

其中,[20-byte-hash-value]是push-20-bytes-onto-the-stack opcode (0x14) 之後跟著20個位元組。考慮到驗證時是將Bob的簽名指令碼+Alice的輸出指令碼,所以基本上可以猜出,在OP_HASH160之前,棧裡應該是Bob提供給Alice的指令碼的原文,然後Alice的輸出指令碼中的SH也入棧,最後是執行OP_EQUAL,判斷是否相等。

在這裡插入圖片描述

接收方Bob要提取幣的時候,scriptSig的形式應該是:

  ...signatures... {serialized script}

在這裡插入圖片描述

用一個具體的例子來說明。在這個例子中,P2SH的指令碼是一個2-of-3的多簽名。 首先Bob需要建立2-of-3 multisig P2SH地址。 為了建立這個地址,首先Bob需要生成3個十六進位制的公鑰地址。這裡使用go-bitcoin-multisig生成3對公私鑰對:

go-bitcoin-multisig keys --count 3 --concise

結果如下:

--------------
KEY #1  
Private key:  
5JruagvxNLXTnkksyLMfgFgf3CagJ3Ekxu5oGxpTm5mPfTAPez3  
Public key hex:  
04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd  
Public Bitcoin address:  
1JzVFZSN1kxGLTHG41EVvY5gHxLAX7q1Rh  
--------------
--------------
KEY #2  
Private key:  
5JX3qAwDEEaapvLXRfbXRMSiyRgRSW9WjgxeyJQWwBugbudCwsk  
Public key hex:  
046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187  
Public Bitcoin address:  
14JfSvgEq8A8S7qcvxeaSCxhn1u1L71vo4  
--------------
--------------
KEY #3  
Private key:  
5JjHVMwJdjPEPQhq34WMUhzLcEd4SD7HgZktEh8WHstWcCLRceV  
Public key hex:  
0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83  
Public Bitcoin address:  
1Kyy7pxzSKG75L9HhahRZgYoer9FePZL4R  
--------------

這樣我們就有了3個十六進位制的公鑰:

Key A: 04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd

Key B: 046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187

Key C: 0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83

然後,我們指明我們需要一個2-of-3的地址,並且將我們的3個公鑰作為輸入,以生成該P2SH地址:

go-bitcoin-multisig address --m 2 --n 3 --public-keys 04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd,046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187,0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83

以上命令的輸出是:

---------------------
Your *P2SH ADDRESS* is:  
347N1Thc213QqfYCz3PZkjoJpNv5b14kBd  
Give this to sender funding multisig address with Bitcoin.  
---------------------
---------------------
Your *REDEEM SCRIPT* is:  
524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae  
Keep private and provide this to redeem multisig balance later.  

生成的P2SH地址提供給Alice(支付方)。 同時生成了Redeem Script,也即Bob在將Alice支付的錢贖出的時候提供的簽名指令碼。我們來細緻地看一下這個redeem script的組成—— 根據Bitocoin協議的multisignature redeem script, 也結合上面對multisig的解釋,一個正確的Multisig的贖出指令碼應該是這樣的:

<OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> <OP_CHECKMULTISIG>

(OP_2~OP_16,指令程式碼0x52-0x60就是將2-16入棧) 將以上redeem的輸出指令碼的內容分解一下如下: 在這裡插入圖片描述 使用這個redeemScript,又經過兩個步驟生成了P2SH地址:

  1. 對redeemScript進行兩次雜湊:
redeemScriptHash = RIPEMD160(SHA256(redeemScript))
  1. Base58check使用字首0x05對redeemscriptHash進行編碼:
P2SHAddress := base58check.Encode("05", redeemScriptHash)

這樣就得到了go-bitcoin-multisig給出的P2SH 地址347N1Thc213QqfYCz3PZkjoJpNv5b14kBd。 這時候可以將這個地址傳送給Alice,Alice可以用這個地址生成支付交易。

使用P2SH地址生成交易

為了形成交易,Alice需要以下的資訊:來自標準 P2PKH的輸出,該 P2PKH的交易id(txid),相對應的私鑰,需要傳送的幣的個數,以及目標P2SH地址(也就是上面剛生成的地址)。 使用如下命令:

go-bitcoin-multisig fund --input-tx 3ad337270ac0ba14fbce812291b7d95338c878709ea8123a4d88c3c29efbc6ac --private-key 5JJyqG4bb15zqi7fTA4b227aUxQhBo1Ux6qX69ngeXYLr7fk2hs --destination 347N1Thc213QqfYCz3PZkjoJpNv5b14kBd --amount 65600

輸出是:

-----------------------------------------------------------------------------------------------------------------------------------
Your raw funding transaction is:  
0100000001acc6fb9ec2c3884d3a12a89e7078c83853d9b7912281cefb14bac00a2737d33a000000008a47304402204e63d034c6074f17e9c5f8766bc7b5468a0dce5b69578bd08554e8f21434c58e0220763c6966f47c39068c8dcd3f3dbd8e2a4ea13ac9e9c899ca1fbc00e2558cbb8b01410431393af9984375830971ab5d3094c6a7d02db3568b2b06212a7090094549701bbb9e84d9477451acc42638963635899ce91bacb451a1bb6da73ddfbcf596bddfffffffff01400001000000000017a9141a8b0026343166625c7475f01e48b5ede8c0252e8700000000  
Broadcast this transaction to fund your P2SH address.  
-----------------------------------------------------------------------------------------------------------------------------------

注意的是,如果多次執行這個命令,每次結果會有些不同,因為在生成數字簽名的時候nonce值每次會不同,其他值應該都是一樣的。 同樣地,來分解一下這個結果: 在這裡插入圖片描述 和典型P2PKH 交易的主要不同之處在於scriptPubKey,這裡scriptPubKey的形式是:

<OP_HASH160> <redeemScriptHash> <OP_EQUAL>

這裡的OP_HASH160 就是RIPEMD160(SHA256()) 函式。 這時便可以將以上交易向網路廣播,以獲得認證。以上交易的 txid 為02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d。

關於Base58

為什麼會使用Base58呢?

主要是為了更簡潔方便地表示長串的數字。譬如,十進位制計數系統使用0-9十個數字,而十六進位制系統使用了額外的 A-F 六個字母。同樣的數字,它的十六進位制表 示就會比十進位制表示更短。更進一步,Base64使用了26個小寫字母、26個大寫字母、10個數字以及兩個符號(例 如“+”和“/”)。Base58是Base64編碼格式的子集,同樣使用大小寫字母和10個數字,但捨棄了一些容易錯 讀和在特定字型中容易混淆的字元。具體地,Base58不含Base64中的0(數字0)、O(大寫字母o)、l(小寫字母 L)、I(大寫字母i),以及“+”和“/”兩個字元。簡而言之,Base58就是由不包括(0,O,l,I)的大小寫字母和數字組成。之所以做出這樣的選擇,就是對人友好,讓人在看到Base58編碼的資料之後不會疑惑,從而防止出錯。這是因為,如果在比特幣交易中如果因為看不清楚地址而輸錯了目標地址,那麼付出去的錢是拿不回來的,所以一定要防止這種錯誤。

Base58是怎麼工作的呢?

Base58 用在比特幣和其它的加密貨幣中,不僅實現了資料壓縮,保持了易讀性,還具有錯誤診斷功能。具體工作方式如下圖。 在這裡插入圖片描述

Base58編碼實際上就相當於是10進位制轉換為16進位制,只不過範圍更大而不僅僅是0~F;Base58的字符集範圍是123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz。但是和十六進位制轉換思路是一樣的。也和進位制轉換可以相互轉一樣,經過Base58編碼的資料也很容易到推出原始資料。

贖回multisig P2SH的幣

在Alice的支付交易獲得確認之後,Bob就可以贖回其中的幣了。現在生成另一對公私鑰作為Bob贖出幣後的目的地址。

go-bitcoin-multisig keys --concise

輸出如下:

--------------
KEY #1  
Private key:  
5Jmnhuc5gPWtTNczYVfL9yTbM6RArzXe3QYdnE9nbV4SBfppLcx  
Public key hex:  
04459b7e1711f31e64507061bccb89fb618e86b254140dc98a42093e449fef067f2ece0a9b11a63697a11c5176528c436570499a13aa22824be53ea2718173b45a  
Public Bitcoin address:  
18tiB1yNTzJMCg6bQS1Eh29dvJngq8QTfx  
--------------

現在,需要上面生成P2SH地址的3對金鑰中的兩個私鑰來生成Tx2中的簽名指令碼。在這裡使用第一個和第三個私鑰做例子。

為了生成tx2交易,Bob需要以下資訊:首先是輸入txid,也即Alice生成的交易Tx1的txid,使用的數量,以及支付的目標。同時在簽名指令碼中還必須有redeem script原文。因為之前僅提供了redeem script的雜湊,所以只有Alice交易的接收方才知道它的原文。

go-bitcoin-multisig spend --input-tx 02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d --amount 55600 --destination 18tiB1yNTzJMCg6bQS1Eh29dvJngq8QTfx --private-keys 5JruagvxNLXTnkksyLMfgFgf3CagJ3Ekxu5oGxpTm5mPfTAPez3,5JjHVMwJdjPEPQhq34WMUhzLcEd4SD7HgZktEh8WHstWcCLRceV --redeemScript 524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae

使用以上資訊可以生成輸出:

Your raw spending transaction is:  
01000000013dcd7d87904c9cb7f4b79f36b5a03f96e2e729284c09856238d5353e1182b00200000000fd5d01004730440220762ce7bca626942975bfd5b130ed3470b9f538eb2ac120c2043b445709369628022051d73c80328b543f744aa64b7e9ebefa7ade3e5c716eab4a09b408d2c307ccd701483045022100abf740b58d79cab000f8b0d328c2fff7eb88933971d1b63f8b99e89ca3f2dae602203354770db3cc2623349c87dea7a50cee1f78753141a5052b2d58aeb592bcf50f014cc9524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353aeffffffff0130d90000000000001976a914569076ba39fc4ff6a2291d9ea9196d8c08f9c7ab88ac00000000  
Broadcast this transaction to spend your multisig P2SH funds.

同樣地,來分析一下這個交易: 在這裡插入圖片描述 在這裡插入圖片描述 OP_PUSHDATA1命令的功能是指出下一個位元組是將要入棧的位元組數。 來具體地看一下Bitcoin協議怎麼樣執行這個指令碼,首先是將scriptPubKey指令碼和scriptSig指令碼合併,然後得到:

<OP_0> <sig A> <sig C> <redeemScript> <OP_HASH160> <redeemScriptHash> <OP_EQUAL>

詳細的步驟:

  1. OP_0和 sig A 以及 sigC 入棧。
  2. redeemScript 入棧。
  3. OP_HASH160 對redeemScript執行雜湊,棧頂是redeemScript的雜湊值。
  4. redeemScriptHash入棧。
  5. OP_EQUAL將會比較 OP_HASH160(redeemScript)的結果和後入棧的 redeemScriptHash ,這一步證明了是否提供了正確的redeemscript,也即是否是幣的合法的所有者。
  6. 然後開始執行redeemScript: <OP_2> <A pubkey> .<B pubkey> <C pubkey> <OP_3> <OP_CHECKMULTISIG>
  7. OP_CHECKMULTISIG 將對 3個公鑰和棧中的2 個簽名進行驗證。

注意事項:

  • 上一節中我們講過生成scriptSig時,為了對交易進行簽名,(因為簽名是交易的一部分,在在生成簽名之前,需要有替代的填充項),使用了scriptPubkey作為填充,在P2SH中,進行填充的是redeemScript。
  • 當將資料入棧的時候,一般的格式是 ,但是,如果多於75位元組,則需要使用特殊的指令 OP_PUSHDATA1, OP_PUSHDATA2 and OP_PUSHDATA4,分別指示其後1、2、4個位元組是需要入棧的資料的長度 。
  • scriptSig的長度需要包括在交易中,資料型別是var_int,如果scriptSig長度很長,長於253 ,則需要使用額外的位元組表示。此時,使用 0xfd (253) 後面跟上2個位元組指示scriptSig 的長度。但是,一定要是必要的時候,也即scriptSig確實很長的時候才這樣做,否則會出錯。

此時就可以將本交易廣播,可以看到這個交易已經被確認了, txid 是eeab3ef6cbea5f812b1bb8b8270a163b781eb7cde10ae5a7d8a3f452a57dca93。

TimeLock

在剛開始看交易的細節時,我們就遇到過Lock Time這個域。Lock Time顧名思義,就是鎖定一些幣,在達到某個時間或者某個區塊之前不能使用這些幣。在之前的交易中這個值都是0,也即不用鎖定。那麼在什麼情況下需要使用lock time呢?

雖說比特幣交易比傳統的交易費用低——譬如信用卡,當使用信用卡時,如果花費的金額較低,商家可能會拒絕接受信用卡,因為每一筆信用卡使用都需要付手續費,但是為了鼓勵礦工儘快將自己的交易打包,一般都會在交易中預留交易費用。但是,有些情況下,可能需要快速地變更支付的費用,因此,就有必要防止快速而經常地進行交易而導致的交易費用。

例如,使用者需要在一段時間內連續地使用咖啡店的wifi,咖啡店希望每天支付一次流量費用。但是如果每天產生一筆交易,交易費用會很高。可以提出一種zero-trust的方案,意味著,交易是完全自動的, 只需要在最初預留一部分錢,然後系統會自動地按需進行支付,而咖啡店也能夠放心地讓使用者使用而不至於擔心使用者會賴賬。而真正進行廣播,也即需要支付交易費用的交易的數量也能受到控制。

思路是這樣的:

假設Alice是使用者,Bob代表咖啡店。首先Alice生成一個交易Tx1,譬如支付100個幣到一個2-of-2的multisig地址,也即這筆錢需要Alice和Bob共同簽名才能使用。Alice首先對這個交易進行簽名,然後廣播這個交易。

Bob看到這個交易之後可以讓Alice使用wifi。接下來每天Alice生成新的一個交易發給Bob,使用Tx1中的錢支付給Bob,譬如第一天支付1個幣給Bob,99個幣給Alice;第二天支付2個幣給Bob,98個幣給Alice;等等。每天Bob看到這個交易,就會同意Alice繼續使用網路。因為Tx1是2-of-2的交易型別,所以Bob看到Alice的簽名,如果他想要獲得支付,只要完成自己的簽名部分就行了,所以Bob可以放心Alice不會賴賬。

當第28天Alice的工作完成不再需要咖啡店的網路了,就會通知Bob,對第28天的交易進行簽名,也即總共支付28個幣給Bob,剩餘的72個幣會返還給Alice。

我們來想一下,這個過程中,Bob可以放心,對Alice會不會有損失?

如果Bob是誠實的,這個過程會很順利;但是如果Bob比較坑,在Alice使用完網路之後他一直不簽名,那麼Alice預付的100個幣就一直鎖死在網路中了。雖然Bob沒有獲得自己應得的那部分錢,但是Alice的損失更大。

為了防止出現這種情況,可以使用lock_time。

  1. 首先Alice建立public key (K1),然後請求Bob的公鑰(K2)。
  2. 建立一個OP_CHECKMULTISIG交易Tx1,支付100個幣到Multisig地址,也即需要Alice和Bob兩人簽名才能使用。Alice對這個交易簽名,但是暫時並不廣播。
  3. Alice建立退款交易Tx2,Tx2使用Tx1的輸出作為輸入,並且將所有的錢都返回給Alice。這個交易設定了lock_time,譬如30天之後。Alice將這個交易提供給Bob。
  4. Tx2主要是為了防止Bob坑,所以Bob為了證明自己不坑,會給Tx2簽名,然後將簽名返回給Alice。
  5. Alice驗證Bob的簽名,如果正確,說明她的退款有保障,因此也就可以放心。
  6. Alice此時對Tx1進行簽名(這是對Tx1的input的支付簽名),並且將簽名傳送給Bob。此時Alice或者Bob可以釋出Tx1。此時Alice的100個幣相當於被鎖定了。
  7. 然後Alice建立新的交易Tx3,使用Tx1的輸出作為輸入。Tx3類似於Tx2,但是有兩個輸出,譬如1個幣給Bob,99個幣給Alice。Alice對這個交易簽名,發給Bob。
  8. Bob收到Tx3和Alice的之後,驗證簽名的正確性。此時Bob如果加上自己的簽名,就可以釋出和廣播這個交易,並獲得1個幣。但是因為Alice還在持續地使用Bob提供的服務,馬上對這個交易進行簽名,很明顯是不明智的。
  9. 之後每天Alice會繼續建立類似的交易Tx3,都是用Tx1的輸出作為輸入。但是每次支付給Bob的幣都在增多,留給自己的在減少。Bob收到之後進行驗證。
  10. 當Alice決定停止使用服務的時候,通知Bob,Bob對收到的最後一個Tx3進行簽名並且廣播。

如果Alice想利用Tx2進行雙重支付,會不會成功呢?這時就是locktime起作用的時候了。因為這個Tx2不會立刻生效,所以Bob簽字的Tx3會被首先確認,之後Tx2因為和Tx3使用的同一個輸入,所以Tx2就是一個無效交易,因此雙重支付不會成功。

智慧合約

以上的例子實際上就是智慧合約(在區塊鏈上執行的程式)。通過使用指令碼、礦工和交易驗證能夠實現傳統上需要第三方中心機構才能完成的一些功能,這是一件相當了不起的事情。對智慧合約的研究遠遠超出了上面列出來的例子,雖然比特幣對智慧合約的支援並不完善,但是如上所示,已經可以完成不少有意義的工作。