I’ve been working in a bitcoin library for instructional functions (being taught in programs/meetups/and many others.). I had fairly a while to work on it and determined it was about time to assist taproot.
I’ve efficiently spend taproot UTXOs created by bitcoin core in addition to created p2tr UTXOs that I may then spend. Key path spending appears to be working as anticipated.
I’m having points (creating/) spending from script path.
I’m not certain whether it is an implementation subject or simply not following the specs correctly however I hoped that recent (and extra skilled) eyes will establish the problem.
I exploit a single faucet leaf script for now.
I’ll submit the essential code snippets under however the entire department might be discovered right here.
I get:
“reject-reason”: “non-mandatory-script-verify-flag (Witness program hash mismatch)”
The uncooked transaction is the next:
model: 02000000
segwith marker/flag 0001
inputs 01
txid b193e4af59323b28ed8a37432bf65e0418cbcdc310d8eb733b9b50e27a578f34
vout 00000000
scriptSig 00
nSequence ffffffff
outputs 01
quantity 3421000000000000
scriptPubKey 225120d4213cd57207f22a9e905302007b99b84491534729bd5f4065bdcb42ed10fcd5
witness stack gadgets 03
signature 400efe4d084bf03c3aa72d856a6aa17078eb002d6330a53230b4ac832cac8fd5b6fd49d95854afffeaeec2f2163c32ba8129383ae367f54a2b32dfbae042569ccd
script 22205d238354a7e74c9e373317053226537dec221c5c775bcca01e806ec358c5c08dac
management block measurement 41
model c0
inside pubkey 1036a7ed8d24eac9057e114f22342ebf20c16d37f0d25cfd2c900bf401ec09c9
tapleaf_hash 7f57a76fa4006c7f31e695ad977dfe9f8b736e2f82249a7c9c1fc7072544ca29
locktime 00000000
Once I had points with correctly tweaking the keys I used to be getting ‘Invalid schnorr signature’ so I assume that the above subject has to do with the management block I exploit..?
The code of the spending script that produces the error is as follows:
from binascii import hexlify
from bitcoinutils.setup import setup
from bitcoinutils.utils import to_satoshis, ControlBlock
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
from bitcoinutils.keys import P2pkhAddress, PrivateKey
from bitcoinutils.hdwallet import HDWallet
def fundamental():
# all the time bear in mind to setup the community
setup('testnet')
# Keys are hard-coded within the instance for simplicity however it is rather unhealthy
# apply. Usually you'll purchase them from env variables, db, and many others
#######################
# Assemble the enter #
#######################
# INTERNAL PRIVKEY of UTXO
# get an HDWallet wrapper object by prolonged personal key and path
xprivkey = "tprv8ZgxMBicQKsPdQR9RuHpGGxSnNq8Jr3X4WnT6Nf2eq7FajuXyBep5KWYpYEixxx5XdTm1Ntpe84f3cVcF7mZZ7mPkntaFXLGJD2tS7YJkWU"
path = "m/86'/1'/0'/0/7"
hdw = HDWallet(xprivkey, path)
priv1 = hdw.get_private_key()
pub1 = priv1.get_public_key()
# taproot script is a straightforward P2PK with the next keys
# TAPLEAF script (P2PK)
privkey_tr_script = PrivateKey('cQwzrJyTNWbEwhPEmQ3Qoo4jSfHdHEtdbL4kNBgHUKhirgzcQw7G')
pubkey_tr_script = privkey_tr_script.get_public_key()
tr_script_p2pk = Script([pubkey_tr_script.to_x_only_hex(), 'OP_CHECKSIG'])
# taproot script path handle
# notice that .get_taproot_address(script) negates if vital, then tweaks
# after which negates once more if vital (I've tried w/o 2nd negation as effectively)
fromAddress = pub1.get_taproot_address(tr_script_p2pk)
print('From Taproot script handle', fromAddress.to_string())
# UTXO of fromAddress
txid1 = '348f577ae2509b3b73ebd810c3cdcb18045ef62b43378aed283b3259afe493b1'
vout1 = 0
# create transaction enter from tx id of UTXO
txin1 = TxInput(txid1, vout1)
# all quantities are wanted to signal a taproot enter
# (relying on sighash)
amount1 = to_satoshis(0.00009)
quantities = [ amount1 ]
# all scriptPubKeys (in hex) are wanted to signal a taproot enter
# (relying on sighash however all the time of the spend enter)
scriptPubkey1 = fromAddress.to_script_pub_key()
utxos_scriptPubkeys = [ scriptPubkey1 ]
########################
# Assemble the output #
########################
hdw.from_path("m/86'/1'/0'/0/5")
priv2 = hdw.get_private_key()
print('To Personal key:', priv2.to_wif())
pub2 = priv2.get_public_key()
print('To Public key:', pub2.to_hex())
# taproot key path handle
toAddress = pub2.get_taproot_address()
print('To Taproot handle:', toAddress.to_string())
# create transaction output
txOut = TxOutput(to_satoshis(0.000085), toAddress.to_script_pub_key())
# create transaction with out change output - if at the least a single enter is
# segwit we have to set has_segwit=True
tx = Transaction([txin1], [txOut], has_segwit=True)
print("nRaw transaction:n" + tx.serialize())
print('ntxid: ' + tx.get_txid())
print('ntxwid: ' + tx.get_wtxid())
# signal taproot enter
# to create the digest message to check in taproot we have to
# move all of the utxos' scriptPubKeys, their quantities and taproot script
# tweak=False signifies that the important thing shouldn't be tweaked, however it's nonetheless negated
sig1 = privkey_tr_script.sign_taproot_input(tx, 0, utxos_scriptPubkeys, quantities, script_path=True, script=tr_script_p2pk, tweak=False)
control_block = ControlBlock(pub1, [ tr_script_p2pk ])
tx.witnesses.append( TxWitnessInput([ sig1, tr_script_p2pk.to_hex(), control_block.to_hex() ]) )
# print uncooked signed transaction able to be broadcasted
print("nRaw signed transaction:n" + tx.serialize())
# sendrawtransaction is used to ship it to a node
if __name__ == "__main__":
fundamental()
The important thing path might be spend usually with one other script.
The code for the ControlBlock is the next:
class ControlBlock:
'''Represents a management block for spending a taproot script path'''
def __init__(self, pubkey, scripts):
self.pubkey = pubkey
self.scripts = scripts
def to_bytes(self):
# leaf model is mounted however we verify if the general public key required negation
# if negated (y is odd) add one to the leaf_version
#if int(self.pubkey.to_hex()[-2:], 16) % 2 == 0:
# leaf_version = bytes([LEAF_VERSION_TAPSCRIPT])
#else:
# leaf_version = bytes([LEAF_VERSION_TAPSCRIPT + 1])
leaf_version = bytes([LEAF_VERSION_TAPSCRIPT])
# x-only public secret's required
pub_key = bytes.fromhex( self.pubkey.to_x_only_hex() )
# TODO solely single different script path for now
script_bytes = self.scripts[0].to_bytes()
# tag hash the script
th = tagged_hash(bytes([LEAF_VERSION_TAPSCRIPT]) + prepend_varint(script_bytes),
"TapLeaf").digest()
return leaf_version + pub_key + th
def to_hex(self):
"""Converts object to hexadecimal string"""
return hexlify(self.to_bytes()).decode('utf-8')
The message digest created contains the script that we’re spending with ext_flag=1:
...
# Information about this enter
spend_type = ext_flag * 2 + 0 # 0 for hard-coded - no annex_present
...
if ext_flag == 1: # script spending path (Signature Message Extension BIP-342)
# committing the tapleaf hash - makes it protected to reuse keys for separate
# scripts in the identical output
leaf_ver = LEAF_VERSION_TAPSCRIPT # move as a parameter if a brand new model comes
tx_for_signing += tagged_hash(bytes([leaf_ver]) + prepend_varint(script.to_bytes()),
"TapLeaf").digest()
# key model - sort of public key used for this signature, at the moment solely 0
tx_for_signing += bytes([0])
# code separator place - information place of when the final OP_CODESEPARATOR
# was executed; not supported for now, we all the time use 0xffffffff
tx_for_signing += b'xffxffxffxff'
Tweaking appears to be working positive since I’ve no points with the important thing spending path, which is why I do not embody the code for tweaking the keys however the whole lot is right here for the courageous hearted.
Any assist shall be tremendously appreciated!