如何兌換基本 Tx?
給定一個原始的標準 Tx ( wiki ):
01000000 01 26c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb 00000000 8b 48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078 ffffffff 02 b06c191e01000000 19 76a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac 00e1f50500000000 19 76a914010966776006953d5567439e5e39f86a0d273bee88ac 00000000
和一個私鑰:
18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725
如何建構一個新的交易來從第二個輸出中贖回硬幣?我一直在嘗試使用etotheipi 的圖表來解決這個問題,但似乎無法掌握如何創建正確的事務。歡迎提供分步指南。
在這個答案中,我將完成贖回上面列出的交易的第二個輸出的必要步驟。答案將僅限於贖回此交易中存在的特定類型的輸出(需要提供使用私鑰簽名的新交易的輸出,其對應的公鑰散列到相關輸出腳本中的散列),如這個答案已經相當長了,即使沒有考慮其他輸出類型。
**簡短摘要:**我們首先建構一個新交易,其中包含我們要贖回的輸出的 scriptPubKey 的 scriptSig。此交易的 scriptPubKey 將包含一個腳本,該腳本支付公鑰(比特幣地址)的雜湊值。我們對該交易執行雙 SHA256 雜湊,並在末尾附加四字節雜湊碼類型 SIGHASH_ALL。我們使用上面提供的私鑰對該雜湊進行簽名。然後這個新交易的 scriptSig 被替換為一個腳本,該腳本首先將 DER 編碼的簽名以及單字節雜湊碼類型 SIGHASH_ALL 推送到堆棧,然後是 DER 編碼的私鑰的相應公鑰。
分步說明:
我們開始創建一個新的原始交易,我們對其進行雜湊和簽名。
- 添加四字節版本欄位:
01000000
- 指定輸入數量的一字節 varint:
01
- 我們要從中贖回輸出的交易的 32 字節雜湊:
eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
- 四字節欄位,表示我們要從具有上述雜湊的交易中贖回的輸出索引(輸出編號 2 = 輸出索引 1):
01000000
- 現在是 scriptSig。出於簽署交易的目的,這裡臨時填充了我們要兌換的輸出的 scriptPubKey。首先,我們編寫一個單字節的 varint,它表示 scriptSig 的長度(0x19 = 25 字節):
19
- 然後我們編寫臨時 scriptSig,它再次是我們要兌換的輸出的 scriptPubKey:
76a914010966776006953d5567439e5e39f86a0d273bee88ac
- 然後我們寫一個四字節的欄位來表示這個序列。目前始終設置為 0xffffffff:
ffffffff
- 接下來是一個單字節的 varint,其中包含我們新交易中的輸出數量。在本例中,我們將其設置為 1:
01
- 然後,我們編寫一個 8 字節欄位(64 位整數),其中包含我們想要從指定輸出中贖回的金額。我將其設置為輸出中可用的總金額減去 0.001 BTC(0.999 BTC,或 99900000 Satoshis)的費用:
605af40500000000
- 然後我們開始編寫交易的輸出。我們從一個單字節的 varint 開始,表示輸出腳本的長度(0x19 或 25 字節):
19
- 然後是實際的輸出腳本:
76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
- 然後我們寫入四字節的“鎖定時間”欄位:
00000000
- 最後,我們編寫了一個四字節的“雜湊碼類型”(在我們的例子中為 1):
01000000
我們現在有以下原始交易數據:
01000000 01 eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2 01000000 19 76a914010966776006953d5567439e5e39f86a0d273bee88ac ffffffff 01 605af40500000000 19 76a914097072524438d003d23a2f23edb65aae1bb3e46988ac 00000000 01000000
- (簽名階段)現在我們對整個結構進行雙重 SHA256 雜湊處理,得到雜湊
9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e
- 然後,我們從提供的私鑰中創建一個公鑰/私鑰對。我們使用私鑰對第 14 步的雜湊進行簽名,這會產生以下 DER 編碼的簽名(在您的情況下,此簽名會有所不同):
30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06
我們將單字節雜湊碼類型附加到此簽名中:01
. 公鑰是:0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
- 我們通過連接建構最終的 scriptSig:
- 一字節腳本 OPCODE 包含 DER 編碼簽名的長度加 1(一字節雜湊碼類型的長度)
- 實際的 DER 編碼簽名加上一字節雜湊碼類型
- 包含公鑰長度的一字節腳本 OPCODE
- 實際公鑰
- 然後,我們將步驟 5 中的單字節 varint 長度欄位替換為步驟 16 中的數據長度。長度為 140 字節,或 0x8C 字節:
8c
- 我們將步驟 6 中的臨時 scriptSig 替換為步驟 16 中建構的資料結構。這變為:
4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
- 我們通過刪除我們在第 13 步中添加的四字節雜湊碼類型來結束,我們最終得到以下字節流,這是最終交易:
01000000 01 eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2 01000000 8c 4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6 ffffffff 01 605af40500000000 19 76a914097072524438d003d23a2f23edb65aae1bb3e46988ac 00000000
Python 範常式式碼:
我創建了一個範例 Python 腳本,它完成了上述所有操作。為了類似於上面的分步指南,它故意盡可能冗長,並大量評論,盡可能少的功能。程式碼行數可以很容易地減少到一半,但我選擇以這種冗長的格式發布它,因為我認為它是最容易遵循的(即沒有在函式中前後“跳躍”)。該腳本包含 76 個非空、非註釋行。該腳本依賴於bitcointools(用於序列化和反序列化交易,以及 base58 編碼/解碼)和ecdsa_ssl.py來自我的 joric 的 brutus 儲存庫的分支(用於建構公共/私有 EC 密鑰對和 ECDSA 簽名)。讓腳本執行的最簡單方法是將 bitcointools 複製到一個文件夾中,然後將來自上述 URL 的 ecdsa_ssl.py 與該腳本放在同一文件夾中,然後從那裡執行該腳本。您將希望
SEND_TO_ADDRESS
將此腳本中變數中的地址替換為您希望將硬幣發送到的地址,除非您感覺很慷慨:)。#比特幣工具 從反序列化導入 parse_Transaction,操作碼 從 BCDataStream 導入 BCDataStream 從 base58 導入 bc_address_to_hash_160、b58decode、public_key_to_bc_address、hash_160_to_bc_address 導入 ecdsa_ssl 將 Crypto.Hash.SHA256 導入為 sha256 導入 Crypto.Random #transaction,我們要從中兌換輸出 HEX_TRANSACTION =“010000000126c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb000000008b48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078ffffffff02b06c191e010000001976a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac00e1f505000000001976a914010966776006953d5567439e5e39f86a0d273bee88ac00000000” #輸出兌換。必須存在於 HEX_TRANSACTION 中 OUTPUT_INDEX=1 #address 我們要將兌換的硬幣發送到。 #用你自己的地址替換,除非你覺得很慷慨 SEND_TO_ADDRESS="1L4xtXCdJNiYnyqE6UsB8KSJvqEuXjz6aK" #我們要支付的費用(BTC) TX_FEE = 0.001 #constant 定義每個 BTC 的 Satoshis 數量 硬幣=100000000 #constant 用於確定交易的哪一部分被散列。 SIGHASH_ALL=1 #private 密鑰,其公鑰散列到在 HEX_TRANSACTION 中描述的事務中輸出編號 *OUTPUT_INDEX* 的 scriptPubKey 中包含的散列 PRIVATE_KEY=0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 def dsha256(數據): 返回 sha256.new(sha256.new(data).digest()).digest() tx_data=HEX_TRANSACTION.decode('hex_codec') tx_hash=dsha256(tx_data) #這裡我們使用比特幣工具來解析交易。這可以輕鬆訪問我們想要從中贖回輸出的交易的各個欄位 流 = BCDataStream() stream.write(tx_data) tx_info = parse_Transaction(流) 如果 len(tx_info['txOut']) < (OUTPUT_INDEX+1): 引發 RuntimeError, "只有 %d 個輸出在您嘗試從中贖回。您想贖回輸出索引 %d" % (len(tx_info['txOut']), OUTPUT_INDEX) #此字典用於儲存各種交易欄位的值 # 這很有用,因為我們需要構造一個交易來散列和簽名 # 另一個將是最終交易 tx_fields = {} ##這裡我們開始創建我們散列和簽名的交易 sign_tx = BCDataStream() ##首先我們寫版本號,也就是1 tx_fields['版本'] = 1 sign_tx.write_int32(tx_fields['version']) ##然後我們寫交易輸入的數量,也就是1 tx_fields ['num_txin'] = 1 sign_tx.write_compact_size(tx_fields['num_txin']) ##然後我們寫入實際的交易數據 #'prevout_hash' tx_fields['prevout_hash'] = tx_hash sign_tx.write(tx_fields['prevout_hash']) #我們要從中贖回輸出的交易的雜湊值 #'prevout_n' tx_fields['output_index'] = OUTPUT_INDEX sign_tx.write_uint32(tx_fields['output_index']) #我們要贖回 tx id 'prevout_hash' 的交易的哪個輸出? ##next 是交易輸入的一部分。在這裡我們放置我們想要兌換的 *output* 的腳本 tx_fields['scriptSigHash'] = tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'] #先寫大小 sign_tx.write_compact_size(len(tx_fields['scriptSigHash'])) #然後是數據 sign_tx.write(tx_fields['scriptSigHash']) #'順序' tx_fields['sequence'] = 0xffffffff sign_tx.write_uint32(tx_fields['sequence']) ##然後我們寫交易輸出的數量。在此範例中,我們將僅使用單個輸出 tx_fields ['num_txout'] = 1 sign_tx.write_compact_size(tx_fields['num_txout']) ##然後我們寫入實際的交易輸出數據 #我們將從原始輸出減去 TX_FEE 兌換一切 tx_fields['value'] = tx_info['txOut'][OUTPUT_INDEX]['value']-(TX_FEE*COIN) sign_tx.write_int64(tx_fields['value']) ##這是我們的 scriptPubKey 所在的地方(支付給地址的腳本) #我們想要以下腳本: #"OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG" address_hash = bc_address_to_hash_160(SEND_TO_ADDRESS) #chr(20) 是 address_hash 的長度(20 字節或 160 位) scriptPubKey = chr(opcodes.OP_DUP) + chr(opcodes.OP_HASH160) + \ chr(20) + address_hash + chr(opcodes.OP_EQUALVERIFY) + chr(opcodes.OP_CHECKSIG) #先寫入這塊數據的長度 tx_fields['scriptPubKey'] = scriptPubKey sign_tx.write_compact_size(len(tx_fields['scriptPubKey'])) #然後是數據 sign_tx.write(tx_fields['scriptPubKey']) #寫入鎖定時間(0) tx_fields ['鎖定時間'] = 0 sign_tx.write_uint32(tx_fields['locktime']) #和雜湊碼類型(1) tx_fields['hash_type'] = SIGHASH_ALL sign_tx.write_int32(tx_fields['hash_type']) #然後我們獲得無簽名交易的雜湊(我們使用我們的私鑰簽名的雜湊) hash_scriptless = dsha256(sign_tx.input) ##現在我們從 ECDSA 開始。 ## 我們從提供的私鑰數據創建一個私鑰,並用它簽署 hash_scriptless ##我們也檢查一下私鑰對應的公鑰是否真的可以兌換指定的輸出 k = ecdsa_ssl.KEY() k.generate(('%064x' % PRIVATE_KEY).decode('hex')) #這裡我們檢索從提供的私鑰生成的公鑰數據 pubkey_data = k.get_pubkey() #然後我們在無簽名交易的雜湊上創建一個簽名 sig_data=k.sign(hash_scriptless) #a 一個字節的“雜湊類型”附加到簽名的末尾(https://en.bitcoin.it/wiki/OP_CHECKSIG) sig_data = sig_data + chr (SIGHASH_ALL) #讓我們檢查提供的私鑰是否真的可以兌換有問題的輸出 if (bc_address_to_hash_160(public_key_to_bc_address(pubkey_data)) != tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2]): 字節 = b58decode(SEND_TO_ADDRESS, 25) raise RuntimeError, "提供的私鑰不能用於兌換輸出索引 %d\n您需要提供地址 %s 的私鑰" % \ (OUTPUT_INDEX, hash_160_to_bc_address(tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2], bytes[0])) ##現在我們開始創建最終交易。這是無簽名交易的副本, ## 用 scriptSig 填充一個腳本,該腳本將簽名加上一字節雜湊碼類型和公鑰從上面推送到堆棧 final_tx = BCDataStream() final_tx.write_int32(tx_fields['version']) final_tx.write_compact_size(tx_fields['num_txin']) final_tx.write(tx_fields['prevout_hash']) final_tx.write_uint32(tx_fields['output_index']) ##現在我們需要編寫實際的scriptSig。 ## 這由簽名中的 DER 編碼值 r 和 s、單字節雜湊碼類型和未壓縮格式的公鑰組成 ## 我們還需要在前面加上這兩個數據片段的長度(編碼為單個字節 ## 包含長度),在每個數據塊之前。這個長度是一個腳本操作碼,告訴 ## 比特幣腳本解釋器將以下 x 字節推入堆棧 scriptSig = chr (len (sig_data)) + sig_data + chr (len (pubkey_data)) + pubkey_data #先寫入這個數據的長度 final_tx.write_compact_size(len(scriptSig)) #然後是數據 final_tx.write(scriptSig) ##然後我們只需在無簽名交易中的 scriptSig 之後寫入相同的數據, # 省略四字節雜湊碼類型(因為這是在簽名數據後面的單個字節中編碼的) final_tx.write_uint32(tx_fields['sequence']) final_tx.write_compact_size(tx_fields['num_txout']) final_tx.write_int64(tx_fields['value']) final_tx.write_compact_size(len(tx_fields['scriptPubKey'])) final_tx.write(tx_fields['scriptPubKey']) final_tx.write_uint32(tx_fields['locktime']) #以十六進制格式列印出最終交易(可以用作 bitcoind 的 sendrawtransaction 的參數) 列印 final_tx.input.encode('hex')