I get an Invalid Schnorr signature when trying to broadcast a taproot transaction

I have 0.00036134 testnet bitcoin at the segwit v1 address: tb1p3au39skpdd8suuhunr4ymp6ca6akv9keq20v5twuagpmyqntqaasl22f4w
I wrote this script to achieve 2 things: archive a hash in a transaction and send the money in tb1p3au39skpdd8suuhunr4ymp6ca6akv9keq20v5twuagpmyqntqaasl22f4w to a new address.
import random
from io import BytesIO
from util import CTxInWitness, CTxOut, create_spending_transaction_nonode
from helpers.address import program_to_witness_testnet
from helpers.key import ECKey, generate_key_pair, generate_bip340_key_pair, generate_schnorr_nonce, int_or_bytes
from helpers.segwit_addr2 import Encoding, decode, encode, segwit_scriptpubkey, bech32_decode
from helpers.script import CScript, CTransaction, OP_RETURN, SIGHASH_ALL, SIGHASH_ALL_TAPROOT, TaprootSignatureHash, tagged_hash
# Sending wallet creation (tb1p3au39skpdd8suuhunr4ymp6ca6akv9keq20v5twuagpmyqntqaasl22f4w)
sendPrivKeyParsed1 = ECKey().set(int('SENDING PRIVATE KEY IN INTEGER'))
sendPubKeyObj = sendPrivKeyParsed1.get_pubkey()
recPubKeyBytes = sendPubKeyObj.get_bytes()
if sendPubKeyObj.get_y()%2 != 0:
sendPrivKeyParsed1.negate()
sendPubKeyObj.negate()
# Receiving wallet creation
recPrivKeyParsed1 = ECKey().set(int('RECEIVING ADDRESS PRIVATE KEY IN INTEGER'))
recPubKeyObj = recPrivKeyParsed1.get_pubkey()
recPubKeyBytes = recPubKeyObj.get_bytes()
if recPubKeyObj.get_y()%2 != 0:
recPrivKeyParsed1.negate()
recPubKeyObj.negate()
# Example tweak
taptweak = bytes.fromhex('3e75c57bb5493f932d418f8ff604f96a5d0565a056c0f0f6a9c1c07f25e2a403')
# Tweak the private key
# Method: ECKey.add()
tweaked_privkey = recPrivKeyParsed1.add(taptweak)
# Tweak the public key
# Method: use tweak_add()
taproot_pubkey = recPubKeyObj.tweak_add(taptweak)
taproot_pubkey_b = taproot_pubkey.get_bytes()
# Derive the bech32 address
# Use program_to_witness(version_int, pubkey_bytes)
address = program_to_witness_testnet(0x01, taproot_pubkey_b)
hrp = "bc"
witver, witprog = decode(hrp, address)
if witver is None:
hrp = "tb"
witver, witprog = decode(hrp, address)
scriptpubkey = segwit_scriptpubkey(witver, witprog)
print("the scriptPublicKey is: {}".format(scriptpubkey.hex()))
# Create a spending transaction
spending_tx = create_spending_transaction_nonode(txid='3f75f413db8ba76d7e26a9e93b9fe94990d58922cd0aaba09eac4b46b74437c2', scriptpubkey=scriptpubkey, amount_c=0.00032134)
print("Spending transaction:\n{}".format(spending_tx))
# Sign transaction with tweaked private key
# Method: TaprootSignatureHash(tx, output_list, hash_type=int, input_index=int, scriptpath=bool)
txout1 = CTxOut(nValue=36134, scriptPubKey=scriptpubkey)
sighash = TaprootSignatureHash(spending_tx, [txout1], SIGHASH_ALL, 0, False)
sig = sendPrivKeyParsed1.sign_schnorr(sighash)
# Add witness to transaction
spending_tx.wit.vtxinwit.append(CTxInWitness([sig]))
print(spending_tx.serialize().hex())
print("Success!")
This is the resulting transaction printed to my terminal:
01000000000101c23744b7464bac9ea0ab0acd2289d59049e99f3be9a9267e6da78bdb13f4753f00000000000000000001867d000000000000225120008687eb983919ac17053f6360be3c796b14d086e34e9e9fab43b42a4fcf6d200140237156dd9c8aa251e3c0576fb26bae749c7f5a18d21c0b1d3cf1cd4965c28e5d6410bedae6a11f799b011396cca9ce2afa4973e9cb1dc332dc6cf6469258ded400000000
I got this error when trying to broadcast the transaction:
sendrawtransaction RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Invalid Schnorr signature)"}
So what am I doing wrong?
Answer:
Fixed it, the main problem is that:
# Sending wallet creation
sendPrivKeyParsed1 = ECKey().set(int('SENDING PRIVATE KEY IN INTEGER'))
sendPubKeyObj = sendPrivKeyParsed1.get_pubkey()
recPubKeyBytes = sendPubKeyObj.get_bytes()
Was supposed to be this:
# Sending wallet creation
sendPrivKeyParsed1 = ECKey().set(int('SENDING PRIVATE KEY IN INTEGER'))
sendPubKeyObj = sendPrivKeyParsed1.get_pubkey()
sendPubKeyBytes = sendPubKeyObj.get_bytes()
The scriptPubKey needs to be calculated for both the sending and receiving addresses. In my original code, I did this to calculate the receiving address's scriptPubKey:
hrp = "bc"
witver, witprog = decode(hrp, address)
if witver is None:
hrp = "tb"
witver, witprog = decode(hrp, address)
scriptpubkey = segwit_scriptpubkey(witver, witprog)
print("the scriptPublicKey is: {}".format(scriptpubkey.hex()))
So instead of scriptpubkey
, you have scriptpubkeyRec
and scriptpubkeySend
. Maybe put the above code into a function that returns scriptpubkey
and run it once for the sending address and once for the receiving address. If you write such a function called get_scriptPubKey()
, the scriptPubKey generation would look like this:
# Derive the bech32 address
# Use program_to_witness(version_int, pubkey_bytes)
addressSend = program_to_witness_testnet(0x01, sendPubKeyBytes)
addressRec = program_to_witness_testnet(0x01, taproot_pubkey_b)
scriptpubkeySend = get_scriptPubKey1(addressSend)
scriptpubkeyRec = get_scriptPubKey1(addressRec)
Use scriptpubkeyRec
here:
# Create a spending transaction
spending_tx = create_spending_transaction_nonode(txid='3f75f413db8ba76d7e26a9e93b9fe94990d58922cd0aaba09eac4b46b74437c2', scriptpubkey=scriptpubkeyRec, amount_c=0.00032134)
Use scriptpubkeySend
here:
txout1 = CTxOut(nValue=36134, scriptPubKey=scriptpubkeySend)
The last problem is that you need to sign with the sending key like this:
sig = sendPrivKeyParsed1.sign_schnorr(sighash)
After I made these changes the transaction worked https://blockstream.info/testnet/tx/55d8281e28240aa84f23026f335128d0c4e0c8868617cb65bc92a1ddc9c1f322