2

How do I pay to a multisig wallet? I am using the Python API (based on python-bitcoinrpc) that ships with the bitcoin source code, in directory bitcoin/test/functional. I'm using regtest.

I call createmultisig() in order to generate a P2SH address. I create a CTransaction object. I attempt to append to the vout list a CTxOut object that pays to the P2SH address, but this operation corrupts the object. I think I need to do something to the P2SH address before passing it as an input to the CTxOut object, but I can't figure out what.

Below is the script for my unit test. The line where it goes wrong is:

tx1.vout.append(CTxOut(int(8 * COIN), madd))

After that the tx1 object is corrupted and when I subsequently try to print it to the log, the operation fails with the error TypeError: can't concat str to bytes.

Any idea what I'm doing wrong?

#!/usr/bin/env python3

import pprint

from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN
from test_framework.script import CScript, CScriptOp, OP_1, OP_DROP, OP_2, OP_HASH160, OP_EQUAL, hash160, OP_TRUE, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
from test_framework.util import hex_str_to_bytes, bytes_to_hex_str
from test_framework.address import byte_to_base58

class RawTxTest(BitcoinTestFramework):

    def set_test_params(self):
        self.num_nodes = 2
        self.extra_args = [[],["-txindex"]]

    def run_test(self):
        """Main test logic"""

        self.log.info(f"\n    DEBUG START")

        # initialize node variables
        n0 = self.nodes[0]
        n1 = self.nodes[1]

        # initialize wallet and address
        w0 = n1.createwallet(wallet_name="test_wallet")
        w0_rpc = n1.get_wallet_rpc('test_wallet')
        addr0 = w0_rpc.getnewaddress()
        self.log.info(f"\n    DEBUG addr0={addr0}")

        # TRANSACTION #0 - pay from node 0 to addr0
        txid0 = n0.sendtoaddress(addr0, 10.0)
        n0.generate(6)
        self.sync_all()

        # dump the transaction
        raw_tx0 = w0_rpc.getrawtransaction(txid0, True)
        s = pprint.pformat(raw_tx0)
        self.log.info(f"\n    DEBUG tx={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 multisig
        NUM_SIGS=2  # M
        NUM_KEYS=3  # N
        add = [w0_rpc.getnewaddress() for _ in range(NUM_KEYS)]
        pub = [w0_rpc.getaddressinfo(a)["pubkey"] for a in add]
        msig = w0_rpc.createmultisig(NUM_KEYS, pub, "bech32")
        madd = msig["address"]
        self.log.info(f"\n    DEBUG madd={madd}")

        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]

        # append the input
        tx1.vin.append(CTxIn(COutPoint(int(txid0, 16), v)))

        # dump the tx to the log
        s = pprint.pformat(w0_rpc.decoderawtransaction(ToHex(tx1)))
        self.log.info(f"\n    DEBUG tx={s}")

        # append the output
        tx1.vout.append(CTxOut(int(8 * COIN), madd))

        # dump the tx to the log
        s = pprint.pformat(w0_rpc.decoderawtransaction(ToHex(tx1)))
        self.log.info(f"\n    DEBUG tx={s}")

        priv1 = w0_rpc.dumpprivkey(addr0)
        tx1 = w0_rpc.signrawtransactionwithkey(ToHex(tx1), [priv1])["hex"]
        txid1 = n1.sendrawtransaction(tx1, True)
        n0.generate(6)
        self.sync_all()

        # dump the transaction
        raw_tx1 = w0_rpc.getrawtransaction(txid1, True)
        s = pprint.pformat(raw_tx1)
        self.log.info(f"\n    DEBUG tx={s}")

        # write the balance to the log
        bal1 = w0_rpc.getbalance()
        self.log.info(f"\n    DEBUG bal1={bal1:f}")

        self.log.info(f"\n    DEBUG END")

if __name__ == '__main__':
    RawTxTest().main()

1 Answer 1

2

I came up with a script to pay to, and spend from, a multisig wallet.

Here are the steps I was missing before:

madd1 = msig1["address"]
a1 = w0_rpc.getaddressinfo(madd1)
scriptPubKey1 = a1["scriptPubKey"]
pubkey1 = hex_str_to_bytes(scriptPubKey1)
tx1.vout.append(CTxOut(int(8 * COIN), pubkey1))

Below is the full script.

#!/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
from test_framework.util import hex_str_to_bytes, bytes_to_hex_str
from test_framework.address import byte_to_base58

class RawTxTest(BitcoinTestFramework):

    def set_test_params(self):
        self.num_nodes = 2
        self.extra_args = [[],["-txindex"]]

    def run_test(self):
        """Main test logic"""

        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 multisig
        NUM_SIGS = 2 # M
        NUM_KEYS = 3 # N
        add1 = [w0_rpc.getnewaddress() for _ in range(NUM_KEYS)]
        pub1 = [w0_rpc.getaddressinfo(a)["pubkey"] for a in add1]
        priv1 = [w0_rpc.dumpprivkey(a) for a in add1]
        msig1 = w0_rpc.createmultisig(NUM_KEYS, pub1, "bech32")
        self.log.info(f"\n    DEBUG msig1={msig1}")
        madd1 = msig1["address"]
        redeemScript = msig1["redeemScript"]
        self.log.info(f"\n    DEBUG madd1={madd1}")

        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}")
        scriptPubKey1 = a1["scriptPubKey"]
        pubkey1 = hex_str_to_bytes(scriptPubKey1)
        tx1.vout.append(CTxOut(int(8 * COIN), pubkey1))

        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 balance to the log
        bal1 = w0_rpc.getbalance()
        self.log.info(f"\n    DEBUG bal1={bal1:f}")

        # TRANSACTION #2 - Pay from multisig 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'])
        p2pk2 = CScript([pubkey2, OP_CHECKSIG])
        tx2 = CTransaction()
        tx2.vin.append(CTxIn(COutPoint(int(txid1, 16), 0)))
        tx2.vout.append(CTxOut(int(7 * COIN), p2pk2))
        scriptPubKey = raw_tx1["vout"][0]["scriptPubKey"]["hex"]
        amount = raw_tx1["vout"][0]["value"]
        prevtxs = [{"txid": txid1, "vout": 0, "scriptPubKey": scriptPubKey, "redeemScript": redeemScript, "amount": amount}]

        tx2 = w0_rpc.signrawtransactionwithkey(ToHex(tx2), priv1, prevtxs)["hex"]
        txid2 = n0.sendrawtransaction(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()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.