如何在 Python 中籤署交易?
如何
P2SH
在 Python 中籤署交易?我在目錄中使用比特幣原始碼附帶的 Python APIbitcoin/test/functional
。我正在使用regtest
.這是我的單元測試的一個片段,它按我的預期工作。它創建一個事務,對其進行簽名並廣播它:
tx2 = CTransaction() # txid0 is the ID of a previous TX, v is the index of the relevant output: tx2.vin.append(CTxIn(COutPoint(int(txid0, 16), v), b"")) # For now, send to a dummy address: tx2.vout.append(CTxOut(int(9 * COIN), CScript([OP_TRUE]))) tx2 = w0_rpc.signrawtransactionwithwallet(ToHex(tx2))["hex"] #tx2.vin[0].scriptSig = CScript(signature, public_key) txid = n1.sendrawtransaction(tx2, True)
而不是呼叫
signrawtransactionwithwallet()
,我想明確地為 分配一個值tx2.vin[0].scriptSig
。我收集到這個值應該看起來像(signature, public_key)
,但我不知道該怎麼做。我該怎麼做?這個 Python 庫是否有任何文件?到目前為止,我正在查看現有的 Python 測試案例,並遵循比特幣 RPC 介面的文件。
編輯. 我設法通過對現有單元測試進行逆向工程來實現這一點。我能夠使用
CScript
對象並且不需要像接受的答案中的方法那樣低級別,儘管如果我需要它,很高興知道該選項存在。這是一個單元測試的程式碼,它 1) 支付給被兩個簽名鎖定的 P2SH 和 2) 從該地址花費而不呼叫signrawtransactionwithwallet()
,而是手動分配簽名給tx.vin[0].scriptSig
。#!/usr/bin/env python3 import pprint from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN, sha256 from test_framework.script import CScript, CScriptOp, OP_1, OP_DROP, OP_2, OP_3, OP_HASH160, OP_EQUAL, hash160, OP_TRUE, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKMULTISIG, OP_CHECKSIGVERIFY, SignatureHash, SIGHASH_ALL from test_framework.util import hex_str_to_bytes, bytes_to_hex_str from test_framework.address import byte_to_base58 from test_framework.key import ECKey class RawTxTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [[],["-txindex"]] def run_test(self): """Implement a P2SH transaction with OP_CHECKSIG without using signrawtransactionwithkey()""" self.log.info(f"\n DEBUG START") # initialize node variables n0 = self.nodes[0] # This node used for initial balance n1 = self.nodes[1] # All test wallets created on this node # TRANSACTION #0 - pay from node 0 to addr0 w0 = n1.createwallet(wallet_name="wallet0") w0_rpc = n1.get_wallet_rpc('wallet0') addr0 = w0_rpc.getnewaddress() self.log.info(f"\n DEBUG addr0={addr0}") txid0 = n0.sendtoaddress(addr0, 10.0) n0.generate(6) self.sync_all() # dump the tx to the log raw_tx0 = w0_rpc.getrawtransaction(txid0, True) s = pprint.pformat(raw_tx0) self.log.info(f"\n DEBUG tx0={s}") # write the balance to the log bal0 = w0_rpc.getbalance() self.log.info(f"\n DEBUG bal0={bal0:f}") # TRANSACTION #1 - Pay from addr0 to P2SH coinbase_key0 = ECKey() coinbase_key0.generate() coinbase_pubkey0 = coinbase_key0.get_pubkey().get_bytes() coinbase_key1 = ECKey() coinbase_key1.generate() coinbase_pubkey1 = coinbase_key1.get_pubkey().get_bytes() #redeemScript = CScript([coinbase_pubkey0, OP_CHECKSIG]) redeemScript = CScript([coinbase_pubkey0, OP_CHECKSIGVERIFY, coinbase_pubkey1, OP_CHECKSIG]) redeemScriptHex = redeemScript.hex() self.log.info(f"\n DEBUG redeemScriptHex={redeemScriptHex}") redeemScript160 = hash160(redeemScript) madd1 = byte_to_base58(redeemScript160, 196) self.log.info(f"\n DEBUG madd1={madd1}") p2sh_script = CScript([OP_HASH160, redeemScript160, OP_EQUAL]) tx1 = CTransaction() vout = [v["n"] for v in raw_tx0["vout"] if addr0 in v["scriptPubKey"].get("addresses", [])] assert len(vout) == 1 v = vout[0] tx1.vin.append(CTxIn(COutPoint(int(txid0, 16), v))) a1 = w0_rpc.getaddressinfo(madd1) self.log.info(f"\n DEBUG a1={a1}") scriptPubKey = a1["scriptPubKey"] pubkey1 = hex_str_to_bytes(scriptPubKey) tx1.vout.append(CTxOut(int(8 * COIN), p2sh_script)) priv0 = w0_rpc.dumpprivkey(addr0) tx1 = w0_rpc.signrawtransactionwithkey(ToHex(tx1), [priv0])["hex"] txid1 = n0.sendrawtransaction(tx1, True) n0.generate(6) self.sync_all() # dump the tx to the log raw_tx1 = w0_rpc.getrawtransaction(txid1, True) s = pprint.pformat(raw_tx1) self.log.info(f"\n DEBUG tx1={s}") # write the unspent tx to the log w0_rpc.importaddress(madd1) us1 = w0_rpc.listunspent() self.log.info(f"\n DEBUG us1={us1}") # TRANSACTION #2 - Pay from P2SH to addr2 addr2 = w0_rpc.getnewaddress() self.log.info(f"\n DEBUG addr2={addr2}") a2 = w0_rpc.getaddressinfo(addr2) pubkey2 = hex_str_to_bytes(a2['pubkey']) p2pkh2 = CScript([OP_DUP, OP_HASH160, hash160(pubkey2), OP_EQUALVERIFY, OP_CHECKSIG]) tx2 = CTransaction() tx2.vin.append(CTxIn(COutPoint(int(txid1, 16), 0))) tx2.vout.append(CTxOut(int(7 * COIN), p2pkh2)) (sighash, err) = SignatureHash(redeemScript, tx2, 0, SIGHASH_ALL) sig0 = coinbase_key0.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) sig1 = coinbase_key1.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) #tx2.vin[0].scriptSig = CScript([sig0, redeemScript]) tx2.vin[0].scriptSig = CScript([sig1, sig0, redeemScript]) #tx2.rehash() txid2 = n0.sendrawtransaction(ToHex(tx2), True) n0.generate(6) self.sync_all() # dump the tx to the log raw_tx2 = w0_rpc.getrawtransaction(txid2, True) s = pprint.pformat(raw_tx2) self.log.info(f"\n DEBUG tx2={s}") # write the balance to the log bal2 = w0_rpc.getbalance() self.log.info(f"\n DEBUG bal2={bal2:f}") self.log.info(f"\n DEBUG END") if __name__ == '__main__': RawTxTest().main()
要手動建構 scriptSig,您需要連接簽名的長度、簽名、pubkey 的長度,然後是二進制形式的 pubkey。
例如,讓我們看一下我碰巧在我的控制台上打開的隨機 TX:
./bitcoin-cli getrawtransaction 055f9c6dc094cf21fa224e1eb4a54ee3cc44ae9daa8aa47f98df5c73c48997f9 1
我們想要 vin:
{ "txid": "b187426f2fdd5a7ac2f49d822f68e07f48486ee53a8a45de2494f12acb37a0d8", "vout": 3, "scriptSig": { "asm": "3046022100d78c31a20fa11533475be893b229eb4d252e600dcc2a0735d360c541b6aec813022100e3eaa72c915ef47d94ccbd18c2ba6d9ae5b98be6e9fbf968d4bbbb003e06d687[ALL] 030e001332b43924be343986cca3df669f57b0dedd120990e727787f8dea50fdbc", "hex": "493046022100d78c31a20fa11533475be893b229eb4d252e600dcc2a0735d360c541b6aec813022100e3eaa72c915ef47d94ccbd18c2ba6d9ae5b98be6e9fbf968d4bbbb003e06d6870121030e001332b43924be343986cca3df669f57b0dedd120990e727787f8dea50fdbc" }, "sequence": 4294967295 }
如您所見,scriptSig 有兩種格式:十六進制和彙編語言。彙編語言只是帶有解釋操作碼的十六進制。注意十六進制在彙編格式之前有一個前導“49”嗎?0x49 是十進制的 73,它告訴它將 73 個字節壓入堆棧。在 49 和 73 字節(146 個字元)之後,我們看到 0x21,即十進制的 33。所以我們將 33 個字節壓入堆棧(剩餘的 66 個字元)。這樣就完成了解鎖腳本的 scriptSig 部分:將 sig 和 pubkey 推入堆棧。
(請注意,在十六進制編碼中,每個字元編碼 4 位,因此一個字節需要 2 個字元。這就是為什麼在推送 33 個字節時,您會在十六進制編碼中看到 66 個字元。)
要從上述數據建構您自己的 scriptSig,您需要將十六進制解碼為二進製字節數組:
myscript = bytearray.fromhex(script_hex) tx2.vin[0].scriptSig = myscript
在您的情況下,您還沒有十六進制,因為您正在自己建構 TX,所以您需要做的是自己建構一個字節數組並將其歸因於
tx2.vin[0].scriptSig
在 Python 虛擬碼中:
# assuming signature,pubkey are byte arrays # otherwise you need to encode them first mybytes = [len(signature)] # initial byte = signature length mybytes.append(signature) mybytes.append(len(pubkey)) mybytes.append(pubkey) tx2.vin[0].scriptSig = mybytes
在 Python API 原始碼中,您可以看到 scriptSig 的定義是二進制的,因此如果您正確執行上述步驟,您的 TX 應該可以工作。