schnorr signatures – taproot script spending subject

0
66


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!

LEAVE A REPLY

Please enter your comment!
Please enter your name here