1

I'm in the process of attempting to generate a transaction using the Python Bitcoin Utils library. that involves P2WSH-over-P2SH (P2SH-P2WSH). My activity is on the regtest network. Below is the code snippet and address that I've prepared.

txin_redeemScript = Script([
    'OP_2',
    private_key1.get_public_key().to_hex(),
    private_key2.get_public_key().to_hex(),
    'OP_2',
    'OP_CHECKMULTISIG'
])
script_bytes = txin_redeemScript.to_bytes()
hashsha256 = hashlib.sha256(script_bytes).digest()
hashsha256 = hexlify(hashsha256).decode('utf-8')

txin_scriptPubKey = Script(['OP_0', hashsha256])
txin_p2wsh_address = P2shAddress.from_script(txin_scriptPubKey)

This address is where I intend to send the coins, and this is the transaction I'm forming:

p2pkh_addr = P2pkhAddress('n4bkvTyU1dVdzsrhWBqBw8fEMbHjJvtmJR')
txout = TxOutput(to_satoshis(amount_to_send), p2pkh_addr.to_script_pub_key())
tx = Transaction(inputs=p2sh_utxo, outputs=[txout], has_segwit=False)

This is the transaction and raw transaction ID

[{'address': '2N4vHiXTCCVMt4TYimTGXRCySzNLXkdiPyH', 'category': 'send', 'amount': Decimal('-0.10000000'), 'vout': 1, 'fee': Decimal('-0.00001430'), 'confirmations': 0, 'trusted': True, 'txid': 'f5f9c98b0c7b0731cba11f5a1b0494a62d2a2de5c09aaf3ac37fa56d48c271bd', 'wtxid': 'bed63897ca9c23ee891a9752b143ee063bf9aa97c813c5a455210a5f8437be6a', 'walletconflicts': [], 'time': 1693271904, 'timereceived': 1693271904, 'bip125-replaceable': 'yes', 'abandoned': False}]

0200000001bd71c2486da57fc33aaf9ac0e52d2a2da694041b5a1fa1cb31077b0c8bc9f9f50100000000ffffffff015f5d9800000000001976a914fd337ad3bf81e086d96a68e1f8d6a0a510f8c24a88ac00000000

The following code is utilized to sign the transaction:

for tx_idx, txin in enumerate(p2sh_utxo):
    sig1 = sk1.sign_input(tx, tx_idx, txin_redeemScript)
    sig2 = sk2.sign_input(tx, tx_idx, txin_redeemScript)
    unlocking_script = Script(['OP_0', txin_redeemScript.to_hex()])
    txin.script_sig = unlocking_script

    witness_data = [b'','OP_0', sig1, sig2]
    tx.witnesses.append(TxWitnessInput(witness_data))

The mempool acceptance test is failing with the error 'reject-reason': 'mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)'}

My familiarity with bitcoin programming is limited, and I've been unable to locate any sample code or references concerning P2WSH over P2SH. Despite searching various online resources, I have yet to find any explanations on how the spending process operates. Any help would be appreciated.

You can find the complete code here

9
  • It is quite difficult to understand what is going on from the above. Are you certain that the funds are locked in the exact same output type that you are trying to spend? (p2sh(p2wsh(multi-sig) ) ) ? If not, it will never work.
    – karask
    Aug 31, 2023 at 10:53
  • I noticed that in witness_data you add two values (instead of one for the multisig bug) before the signatures. Why?
    – karask
    Aug 31, 2023 at 10:55
  • Hello @karask thank you for the comment. I am trying to spend P2SH (P2WSH (Multi-sig) ). I tried so many things in the last couple of days that I have lost track. I tried only one value in the witness data before adding the signatures and hash however that was not working. I have tried to reach out to you over email as well (with the Jupyter notebook) however if you like (and if helps the community) I can attach Jupyter notebook here.
    – Deepanshu
    Aug 31, 2023 at 11:32
  • Hello @karask this is the Jupyter Nootebook attached on Github github.com/Deepanshu2017/bitcoingist/blob/main/P2SH-P2WSH.ipynb I have modified the question to attach the notebook URL
    – Deepanshu
    Aug 31, 2023 at 11:35
  • Hi @Deepanshu I see in ipynb that you work in a regtest environment. So I assume you created this p2sh-p2wsh(multisig) yourself and are trying to spend it. In order for me to take a thorough look I would need the code for the creation of that UTXO first... and then the code for spending it (like the one your provided) but in testnet/signet so that I can test directly. Too many times, esp. in complex scenarios like this, the problem is that the UTXO is not what you thought it was...
    – karask
    Sep 1, 2023 at 13:23

1 Answer 1

0
+100

Construction of transaction to send to a P2SH(P2WSH) UTXO follows:

from bitcoinutils.keys import P2shAddress, PrivateKey
from bitcoinutils.script import Script
from bitcoinutils.setup import setup
from bitcoinutils.hdwallet import HDWallet
from bitcoinutils.transactions import Transaction, TxInput, TxOutput

setup("testnet")

#
# Send from a P2PKH UTXO and send to P2SH(P2WSH(P2PK))
# Change back to the same address (not recommended for privacy reasons)
#

xprivkey = (
    "tprv8ZgxMBicQKsPdQR9RuHpGGxSnNq8Jr3X4WnT6Nf2eq7FajuXyBep5KWYpYEixxx5XdTm1N"
    "tpe84f3cVcF7mZZ7mPkntaFXLGJD2tS7YJkWU"
)
path = "m/86'/1'/0'/0/1"
hdw = HDWallet(xprivkey, path)
from_priv = hdw.get_private_key()
from_pub = from_priv.get_public_key()
from_addr = from_pub.get_address()
print("From address:", from_addr.to_string())

hdw.from_path("m/86'/1'/0'/0/20")
to_priv = hdw.get_private_key()
to_pub = to_priv.get_public_key()

witness_script = Script([to_pub.to_hex(), "OP_CHECKSIG"])
p2sh_redeem_script = witness_script.to_p2wsh_script_pub_key()  # maybe to_p2sh_...

p2sh_address = P2shAddress.from_script(p2sh_redeem_script)
print("To address:", p2sh_address.to_string())

# UTXO's info
txid = "d4616b3050d2a0fac4783cd9a8c727aafa7b1374098d049e91ecc66d655e79e7"
vout = 0

txin = TxInput(txid, vout)
txout = TxOutput(5000, p2sh_redeem_script.to_p2sh_script_pub_key())
txout_change = TxOutput(1530000, from_addr.to_script_pub_key())
tx = Transaction([txin], [txout, txout_change])

sig = from_priv.sign_input(tx, 0, from_addr.to_script_pub_key())

txin.script_sig = Script([sig, from_pub.to_hex()])

signed_tx = tx.serialize()

print(signed_tx)

Construction of transaction to spend from a P2SH(P2WSH) UTXO follows:

from bitcoinutils.keys import P2shAddress, PrivateKey
from bitcoinutils.script import Script
from bitcoinutils.setup import setup
from bitcoinutils.hdwallet import HDWallet
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput

setup("testnet")

#
# Send from a P2SH(P2WSH(P2PK)) UTXO to a P2PKH UTXO
#

xprivkey = (
    "tprv8ZgxMBicQKsPdQR9RuHpGGxSnNq8Jr3X4WnT6Nf2eq7FajuXyBep5KWYpYEixxx5XdTm1N"
    "tpe84f3cVcF7mZZ7mPkntaFXLGJD2tS7YJkWU"
)
path = "m/86'/1'/0'/0/20"
hdw = HDWallet(xprivkey, path)
from_priv = hdw.get_private_key()
print(from_priv.to_wif())
from_pub = from_priv.get_public_key()
from_addr = from_pub.get_address()

witness_script = Script([from_pub.to_hex(), "OP_CHECKSIG"])
p2sh_redeem_script = witness_script.to_p2wsh_script_pub_key()
print("From address:", P2shAddress.from_script(p2sh_redeem_script).to_string())

hdw.from_path("m/86'/1'/0'/0/25")
to_priv = hdw.get_private_key()
to_address = to_priv.get_public_key().get_address()
print("To address:", to_address.to_string())

# UTXO's info
txid = "217f123726bd8ace101afd705ae31384fd818fce17c8e00ce6fc0d24c0364355"
vout = 0
amount = 5000

txin = TxInput(txid, vout)
txout = TxOutput(3000, to_address.to_script_pub_key())
tx = Transaction([txin], [txout], has_segwit=True)

sig = from_priv.sign_segwit_input(tx, 0, witness_script, amount)

txin.script_sig = Script([p2sh_redeem_script.to_hex()])
tx.witnesses.append(TxWitnessInput([sig, witness_script.to_hex()]))

signed_tx = tx.serialize()

print(signed_tx)

See also: https://github.com/karask/python-bitcoin-utils/blob/master/examples/send_to_p2sh_p2wsh_p2pk_address.py

and

https://github.com/karask/python-bitcoin-utils/blob/master/examples/spend_from_p2sh_p2wsh_p2pk_address.py

11
  • I want to express my gratitude for providing the examples, but it seems they aren't functioning correctly. I attempted to perform a transaction from a P2SH(P2WSH(P2PK)) to P2PKH, and it resulted in an error bad-witness-nonstandard. To investigate further, I decided to verify the UTXOs mentioned in the code on Bitcoin Explorer. Unfortunately, I couldn't locate these UTXOs on Bitcoin Explorer. The specific UTXO in question is 217f123726bd8ace101afd705ae31384fd818fce17c8e00ce6fc0d24c0364355 github.com/Deepanshu2017/bitcoingist/blob/main/…
    – Deepanshu
    Sep 11, 2023 at 22:15
  • I would like to point out 1 thing though, the way I am creating transaction (to get the transaction id for code in regtest) is by passing the to address in sendtoaddress command ./bitcoin-cli -regtest sendtoaddress "mhZ7PvZZp5dafdGdds37m5AbZzArtqFsgT" 0.1 After running the above command, I am getting the transaction id which I have used in code. This is the only command that I am running on bitcoin cli. My regtest Private Keys are correct (hence the address generated must be correct) however there could be an issue with the way I am generating the transaction.
    – Deepanshu
    Sep 11, 2023 at 22:19
  • I have tried these transactions as well live.blockcypher.com/btc-testnet/tx/… However I can replicate the error while pushing to blockstream blockstream.info/testnet/tx/push
    – Deepanshu
    Sep 12, 2023 at 5:27
  • You cannot see the transactions with a testnet explorer because I have not broadcasted them. I am testing them with my local node with testmempoolaccept. This way I don't need to be sending funds back and forth all the time for the tests.
    – karask
    Sep 12, 2023 at 6:55
  • Just to be certain try the examples directly from the github repo (don't copy/paste from here). I just tried the examples and they worked. You can try them yourself, even with regtest by directly running them and then do a: bitcoin-cli testmempoolaccept ["first tx raw hex", "second tx raw hex"] (this way you also test the 2nd tx that depends on the first without broadcasting them)
    – karask
    Sep 12, 2023 at 6:59

Your Answer

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

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