Python

如何在 Python 中籤署交易?

  • October 18, 2019

如何P2SH在 Python 中籤署交易?我在目錄中使用比特幣原始碼附帶的 Python API bitcoin/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 應該可以工作。

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