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

Deepanshu
  • 13
  • 6
  • 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 '23 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 '23 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 '23 at 11:32
  • Hello @karask this is the Jupyter Nootebook attached on Github

    https://github.com/Deepanshu2017/bitcoingist/blob/main/P2SH-P2WSH.ipynb

    I have modified the question to attach the notebook URL

    – Deepanshu Aug 31 '23 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 01 '23 at 13:23
  • Hello @karask Thank you for the response. I generated two private keys using some libraries and then using the keys to p2wsh witness script followed by p2sh address. This is the address that got generated "2N4vHiXTCCVMt4TYimTGXRCySzNLXkdiPyH". (Cell number 3 in Jupyter notebook) After this, I created a transaction using bitcoin cli. bitcoin-cli -regtest sendtoaddress "2N4vHiXTCCVMt4TYimTGXRCySzNLXkdiPyH" 0.1 Post this starting from cell number 5 in the notebook, I am trying to spend this. The choice of env (regtest/test) doesn't matter in my case. All I am trying to do is P2WSH in P2SH – Deepanshu Sep 01 '23 at 14:48
  • Essentially P2WSH is supposed to be backward compatible with P2SH and that's what I am trying to demonstrate with Python Code. We have the deadline to submit the paper tomorrow and if we could somehow make it work, it would be useful addition in our paper. – Deepanshu Sep 01 '23 at 14:49
  • I don't have time to test it right now. Going though the code I noticed that you sign with sign_input but you should use sign_segwit_input. I hope this helps. – karask Sep 02 '23 at 16:31
  • Hello @karask thank you so much for the comment, however changing the sign to sign_segwit_input did not help. Thank you for the help. Whenever you have time, you can fix the issue, this may help the community to have some sort of reference and would help me as well to eventually figure out the issue. – Deepanshu Sep 03 '23 at 07:28

1 Answers1

0

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

karask
  • 2,540
  • 1
  • 10
  • 20
  • 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

    https://github.com/Deepanshu2017/bitcoingist/blob/main/P2SH-P2WSH%20Ka.ipynb

    – Deepanshu Sep 11 '23 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 '23 at 22:19
  • I have tried these transactions as well https://live.blockcypher.com/btc-testnet/tx/3fc77c60aab834c83f88e041c399ccefbb1938ec25eaf9914b6fcf775996bf1d/

    However I can replicate the error while pushing to blockstream

    https://blockstream.info/testnet/tx/push

    – Deepanshu Sep 12 '23 at 05: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 '23 at 06: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 '23 at 06:59
  • By directly running them I mean your either use your own keys instead of the ones I used in the example or you need to import the keys in the example to your bitcoin node. Not sure which one you tried. I am also not sure if you got the above error running the two examples I provided or by trying to fix your code. – karask Sep 12 '23 at 07:13
  • Hi @karask,

    Thank you so much for the response. I tried running the example from GitHub, cloned the repo, ran both the examples and did mempool accept test by providing both the hex. It worked. I tried individual hex as well and it worked. Thank you so much.

    One last question, When I tried running it from Notebook it failed. Let me try again.

    https://github.com/Deepanshu2017/bitcoingist/blob/main/P2SH-P2WSH%20Ka.ipynb

    Again thank you so much for the help!

    – Deepanshu Sep 12 '23 at 07:18
  • You are welcome. I would suggest compare the raw hex of the txs to see how they differ to pinpoint what changes. You will need to get acquainted with the structure of the bitcoin transaction to do that properly. e.g. https://bitcoin.stackexchange.com/questions/113697/what-are-the-parts-of-a-bitcoin-transaction-in-segwit-format – karask Sep 12 '23 at 07:28
  • Okay so the error must be either the way I am creating transaction or Private Key. To generate Private Key I have used PrivateKey class from bitcoinutils.keys

    To generate the transaction, I copied the from address (which was in this case 2MuxtgnkyrUSudv99YbeApZQtfQmtuw7e8p ), went to https://coinfaucet.eu/en/btc-testnet/ and sent some funds. That gave me the transaction ID which I am using in the code.

    – Deepanshu Sep 12 '23 at 07:28
  • This is the notebook, that is the same code as yours however uses the different Private Key. https://github.com/Deepanshu2017/bitcoingist/blob/main/P2WSH%20Ka.ipynb

    Basic trimmed down version.

    – Deepanshu Sep 12 '23 at 07:32