How to Pay to Multisig Wallet Using python-bitcoinrpc?

1

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()

eric

Posted 2019-10-14T14:27:51.407

Reputation: 55

Answers

1

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()

eric

Posted 2019-10-14T14:27:51.407

Reputation: 55