Transactions

如何兌換基本 Tx?

  • September 7, 2019

給定一個原始的標準 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 編碼的私鑰的相應公鑰。

分步說明:

我們開始創建一個新的原始交易,我們對其進行雜湊和簽名。

  1. 添加四字節版本欄位:01000000
  2. 指定輸入數量的一字節 varint:01
  3. 我們要從中贖回輸出的交易的 32 字節雜湊:eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
  4. 四字節欄位,表示我們要從具有上述雜湊的交易中贖回的輸出索引(輸出編號 2 = 輸出索引 1):01000000
  5. 現在是 scriptSig。出於簽署交易的目的,這裡臨時填充了我們要兌換的輸出的 scriptPubKey。首先,我們編寫一個單字節的 varint,它表示 scriptSig 的長度(0x19 = 25 字節):19
  6. 然後我們編寫臨時 scriptSig,它再次是我們要兌換的輸出的 scriptPubKey:76a914010966776006953d5567439e5e39f86a0d273bee88ac
  7. 然後我們寫一個四字節的欄位來表示這個序列。目前始終設置為 0xffffffff:ffffffff
  8. 接下來是一個單字節的 varint,其中包含我們新交易中的輸出數量。在本例中,我們將其設置為 1:01
  9. 然後,我們編寫一個 8 字節欄位(64 位整數),其中包含我們想要從指定輸出中贖回的金額。我將其設置為輸出中可用的總金額減去 0.001 BTC(0.999 BTC,或 99900000 Satoshis)的費用:605af40500000000
  10. 然後我們開始編寫交易的輸出。我們從一個單字節的 varint 開始,表示輸出腳本的長度(0x19 或 25 字節):19
  11. 然後是實際的輸出腳本:76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
  12. 然後我們寫入四字節的“鎖定時間”欄位:00000000
  13. 最後,我們編寫了一個四字節的“雜湊碼類型”(在我們的例子中為 1):01000000

我們現在有以下原始交易數據:

01000000
01
eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
01000000
19
76a914010966776006953d5567439e5e39f86a0d273bee88ac
ffffffff
01
605af40500000000
19
76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
00000000
01000000
  1. (簽名階段)現在我們對整個結構進行雙重 SHA256 雜湊處理,得到雜湊9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e
  2. 然後,我們從提供的私鑰中創建一個公鑰/私鑰對。我們使用私鑰對第 14 步的雜湊進行簽名,這會產生以下 DER 編碼的簽名(在您的情況下,此簽名會有所不同):30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06我們將單字節雜湊碼類型附加到此簽名中:01. 公鑰是:0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  3. 我們通過連接建構最終的 scriptSig:
  • 一字節腳本 OPCODE 包含 DER 編碼簽名的長度加 1(一字節雜湊碼類型的長度)
  • 實際的 DER 編碼簽名加上一字節雜湊碼類型
  • 包含公鑰長度的一字節腳本 OPCODE
  • 實際公鑰
  1. 然後,我們將步驟 5 中的單字節 varint 長度欄位替換為步驟 16 中的數據長度。長度為 140 字節,或 0x8C 字節:8c
  2. 我們將步驟 6 中的臨時 scriptSig 替換為步驟 16 中建構的資料結構。這變為:4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  3. 我們通過刪除我們在第 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')

引用自:https://bitcoin.stackexchange.com/questions/3374