2
How do I sign a P2SH transaction in Python? I am using the Python API that ships with the bitcoin source code, in directory bitcoin/test/functional. I'm using regtest.
Here is a fragment of my unit test, it works as I expect. It creates a transaction, signs it, and broadcasts it:
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)
Instead of making the call to signrawtransactionwithwallet(), I would like to explicity assign a value to tx2.vin[0].scriptSig. I gather that this value should look something like (signature, public_key), but I can't figure out how to do it.
How do I go about this? Is there any documentation of this Python library? So far I am looking at the existing Python test cases, and following the docs for the Bitcoin RPC interface.
Edit. I managed to accomplish this by reverse engineering the existing unit tests. I was able to use CScript objects and did not need to go as low level as the approach in the accepted answer, although it's good to know that that option exists if I need it. Here is the code for a unit test which 1) pays to a P2SH that is locked with two signatures and 2) spends from that address without calling signrawtransactionwithwallet(), instead manually assigning a signature to 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()
1I can't thank you enough for that thorough response. I managed to accomplish what I needed with a slightly higher level approach. I just updated my OP to include the code. I'm glad to know about the lower level approach in case I ever need it. – eric – 2019-10-18T16:03:50.083