Created
November 22, 2021 19:53
-
-
Save AdamISZ/6233d9d9b8d25483dc5d39cc6b9892a7 to your computer and use it in GitHub Desktop.
Testing script path spending in taproot with python-bitcointx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bitcointx as btc | |
btc.allow_secp256k1_experimental_modules() | |
btc.select_chain_params("bitcoin/testnet") | |
from bitcointx.wallet import CCoinKey | |
from bitcointx.core import COutPoint, CTxIn, CTxOut, CMutableTransaction, CTxInWitness | |
from bitcointx.core.script import (CScript, OP_CHECKSIGADD, OP_CHECKSIG, OP_NUMEQUAL, | |
TaprootScriptTree, CScriptWitness) | |
from bitcointx.wallet import P2TRCoinAddress | |
from binascii import hexlify, unhexlify | |
# phase 1: create an address for a script of OP_CHECKSIGADD pub1, pub2/OP_CHECKSIGADD pub3, pub4/pub5 | |
# generate 5 different privkeys: | |
keys = [CCoinKey.from_secret_bytes(bytes([i]*32)) for i in range(1, 6)] | |
scr1 = CScript([keys[0].xonly_pub, OP_CHECKSIG, keys[1].xonly_pub, OP_CHECKSIGADD, 2, OP_NUMEQUAL], name="multisig1") | |
scr2 = CScript([keys[2].xonly_pub, OP_CHECKSIG, keys[3].xonly_pub, OP_CHECKSIGADD, 2, OP_NUMEQUAL], name="multisig2") | |
scr3 = CScript([keys[4].xonly_pub, OP_CHECKSIG], name="single key") | |
# TaprootScriptTree automatically creates the tree for us, given a list of CScript objects: | |
tree = TaprootScriptTree([scr1, scr2, scr3]) | |
# set a dummy internal pubkey; note in future we might want to use the provably unspendable form as in BIP341 recommendation: | |
tree.set_internal_pubkey(CCoinKey.from_secret_bytes(bytes([6]*32)).xonly_pub) | |
addr = P2TRCoinAddress.from_script_tree(tree) | |
# (this was run before completing the rest of the script to fund:) | |
print("The address to fund is: {}".format(addr)) | |
# this transaction funds the above with 0.01 coins: | |
hextx1 = "02000000000102198b3500bfb5264bf4e782b857d0f0f897e3ca26a35c441b51059fe6b81350f10100000000feffffff905be7600b8c4fd1cac0937f17f3e2f8dfdba2062e413eb4be2a5fbfb8aaa7f20200000000feffffff0240420f000000000022512033efb169849874c88f60edb29cbe6e612c2f84e0fd6e1c5b0737cf69973e01958d82030000000000160014b847d36a181d996499836b5c2d05fad5d6b0111002473044022019f08a15f217eac47fe18862a4996e9e6818e94c0be98f54402f67e9d95ea54202203e8a3864238cad87efe8e547617ad6ee843ebf0d38f9ef1b5bdcfd517f473e180121025da8ce82bc23bba542155aaa2774e65f86a19b01502eb8fc05c72ad3d9e06e690247304402201398d6f9a41ff5c2959ebf6133f4d93aabebbd17af95febdc377b87ca763a196022048624afd49d0e27d435d57a6a73f9c5681d65e19b84f508e6df023d67ebf032c0121023fc390fb03735dfe14d48edec7bf5e6c06868a46941cdb5f88a1b7da9ad0fcdf4eff0000" | |
tx1id = unhexlify("18e1602c29fc063a24973179d244b1e09443fb396553e99038617084898c0c5c") | |
outpoint = COutPoint(tx1id[::-1], 0) | |
vin = [CTxIn(prevout=outpoint, nSequence=0xffffffff)] | |
sPK = addr.to_scriptPubKey() | |
vout = [CTxOut(998000, sPK)] | |
tx2 = CMutableTransaction(vin, vout, nVersion=2) | |
print(tx2) | |
# phase 2: given a transaction (tx1) in hex which funds an output for addr addr1, we construct a transaction spending that (tx2). | |
# we use the second of the three scripts: | |
s, cb = tree.get_script_with_control_block('multisig2') | |
sh = s.sighash_schnorr(tx2, 0, (CTxOut(1000000, sPK),)) | |
sig_for_key_2 = keys[2].sign_schnorr_no_tweak(sh) | |
print(hexlify(sig_for_key_2)) | |
print() | |
print("Verificationresult: ", keys[2].xonly_pub.verify_schnorr(sh, sig_for_key_2)) | |
print(len(sig_for_key_2)) | |
print() | |
sig_for_key_3 = keys[3].sign_schnorr_no_tweak(sh) | |
tx2.wit.vtxinwit[0] = CTxInWitness(CScriptWitness([sig_for_key_3, sig_for_key_2, s, cb])) | |
print(tx2) | |
print(hexlify(tx2.serialize())) |
You're correct, the problem was that the hash that was calculated by that line sh = s.sighash_schnorr(...)
(1) was different from the hash that was calculated by the node based (among other things) on the amount on the actual prevout (2), and thus the signature that was created for (1) was invalid for the (2)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Found it! :)
The amount on the line
sh = s.sighash_schnorr(tx2, 0, (CTxOut(amount, sPK),))
was the source of the problem, it must be the exact amount of the input being unlocked, regardless of how much is going to be spent and regardless of the fee. In other words, it must be the exact amount of the selected output in the previous transaction. Please correct me if I'm wrong.