Search code examples
rustbitcoin

Issue with Signing a Taproot P2TR Input in Bitcoin


Issue with Signing a Taproot P2TR Input in Bitcoin

I am trying to sign a Bitcoin transaction spending a Taproot (P2TR) input but keep encountering an error: mandatory-script-verify-flag-failed (Invalid Schnorr signature) when submitting the transaction to the network. Here's the situation:

Deriving the Address

I derived the Taproot address using the following code:

let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(
    &hex::decode(env::var("BITCOIN_FEES_ACCOUNT_PRIV_KEY").expect("BITCOIN_FEES_ACCOUNT_PRIV_KEY must be set"))
        .expect("Invalid hex"),
)
.expect("Invalid private key");
let keypair = bitcoin::secp256k1::Keypair::from_secret_key(&secp, &secret_key);
let (x_only_public_key, _parity) = XOnlyPublicKey::from_keypair(&keypair);
let address = bitcoin::Address::p2tr(&secp, x_only_public_key, None, Network::Bitcoin);
println!("Taproot address: {}", address);

This generated the address:

bc1pclr7kl28zzvdkfv8xsa6kv4vtqc6q3fetfsgzmgpmhe66f85t73qz62933

I sent 0.001 BTC to this address on mainnet in a single input.


Transaction Creation and Signing

I created the spending transaction as follows:

let mut unsigned_tx = Transaction {
    version: Version(2),
    lock_time: LockTime::ZERO,
    input: vec![TxIn {
        previous_output: OutPoint {
            txid: Txid::from_str(
                "a693543ba39b5709359f5eb309c9322b5733680a8a21d6b52eb9977ec1aec7b6",
            )
            .unwrap(),
            vout: 0,
        },
        script_sig: ScriptBuf::new(),
        sequence: Sequence::MAX,
        witness: Witness::new(),
    }],
    output: vec![TxOut {
        value: Amount::from_sat(98894),
        script_pubkey: ScriptBuf::from(
            hex::decode("5120c7c7eb7d471098db2587343bab32ac5831a045395a60816d01ddf3ad24f45fa2")
                .expect("Invalid hex string"),
        ),
    }],
};

let sighash_type = TapSighashType::AllPlusAnyoneCanPay;
let binding = [unsigned_tx.output[0].clone()];
let prevouts = Prevouts::All(&binding[..]);

let mut sighasher = SighashCache::new(&mut unsigned_tx);
let sighash = sighasher
    .taproot_key_spend_signature_hash(0, &prevouts, sighash_type)
    .expect("Failed to create sighash");

let tweaked: TweakedKeypair = keypair.tap_tweak(&secp, None);
let msg = Message::from(sighash);
let signature = secp.sign_schnorr(&msg, &tweaked.to_inner());

let taproot_signature = bitcoin::taproot::Signature {
    signature,
    sighash_type,
};

let witness = Witness::p2tr_key_spend(&taproot_signature);
*sighasher.witness_mut(0).unwrap() = witness;

let tx = sighasher.into_transaction();
let raw_tx = serialize_hex(&tx);
println!("Raw transaction hex: '[\"{}\"]'", raw_tx);

The Problem

The signed transaction outputs the following hex:

02000000000101b6c7aec17e97b92eb5d6218a0a6833572b32c909b35e9f3509579ba33b5493a60000000000ffffffff014e82010000000000225120c7c7eb7d471098db2587343bab32ac5831a045395a60816d01ddf3ad24f45fa2014122378e554918f9ed96d9cd49c25f9987a224102112d7c1b8ec26abc889104fdb15c18070e202e9dde7a659d66171bf3c35cf0753560f8308c095aa3c037eb4fa8100000000

However, when I try submitting the transaction with bitcoin-cli:

bitcoin-cli testmempoolaccept '["02000000000101b6c7aec17e97b92eb5d6218a0a6833572b32c909b35e9f3509579ba33b5493a60000000000ffffffff014e82010000000000225120c7c7eb7d471098db2587343bab32ac5831a045395a60816d01ddf3ad24f45fa2014122378e554918f9ed96d9cd49c25f9987a224102112d7c1b8ec26abc889104fdb15c18070e202e9dde7a659d66171bf3c35cf0753560f8308c095aa3c037eb4fa8100000000"]'

I get:

{
  "txid": "9c7da7c85517749eb6ba931bfaa43bcaef4f39ff91819ec9058e353fbdcd7a56",
  "wtxid": "e4376ab2fbee06da3fcb669fd7fb74496b14d8c9ef12cba76f12e8ce22e4e65d",
  "allowed": false,
  "reject-reason": "mandatory-script-verify-flag-failed (Invalid Schnorr signature)"
}

Troubleshooting Attempts

  1. II also tried signing without tweaking the keypair:
let signature = secp.sign_schnorr(&msg, &keypair);

But the same error occurred.


Question

What could I be doing wrong in signing this transaction? Is there an issue with my signature, tweaking, or transaction structure? Any help is greatly appreciated!


Solution

  • Reposting Andrew Poelstra answer from https://github.com/rust-bitcoin/rust-bitcoin/issues/3969

    It looks like you're setting your prevout to the 0th output of the transaction you're signing. But it needs to be the 0th output of the other transaction, a6935. BTW, I make this mistake constantly. I wonder if there's a good way to solve it in the API.