Bitcoin

Issue configuring correct Schnorr signature for Taproot transactions

I’m experimenting with creating and sending Taproot transactions programmatically, and I’m running into problems with Schnorr signatures. I’m trying to send a simple transaction with one V1_P2TR input and one V1_P2TR output using what I understand to be a critical path spend approach without a script. However, when I try to send a transaction, my node rejects the transaction with the following error:
mandatory-script-verify-flag-failed (Invalid Schnorr signature)

I’m using the following dependencies in my Rust project:

bitcoin =  version = "0.30.1", features = ("rand") 
ord-bitcoincore-rpc = "0.17.1"   # (a forked version of bitcoincore-rpc, though I believe this detail is not crucial to the issue).

Here is the relevant part of my code:

fn create_and_send_tmp_tx(client: &Client, utxo: &ListUnspentResultEntry, fee_rate: f64, key_pair: &UntweakedKeyPair, address_to: &Address) -> Result<Txid> 

    let secp256k1 = Secp256k1::new();

    // Verifying that UTXO can be spent by provided key pair
    let (public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair);
    let address = Address::p2tr(&secp256k1, public_key, None, Network::Bitcoin);
    let address_script_pubkey = address.script_pubkey();
    let utxo_script_pubkey = utxo.script_pub_key.clone();
    if ! (address_script_pubkey == utxo_script_pubkey) 
        bail!("Can't spend utxo");
    

    let mut tx = Transaction 
        input: vec!(TxIn 
            previous_output: OutPoint 
                txid: utxo.txid,
                vout: utxo.vout,
            ,
            script_sig: Builder::new().into_script(),
            witness: Witness::new(),
            sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
        ),
        output: vec!(TxOut 
            script_pubkey: address_to.script_pubkey(),
            value: 0,   // tmp value for estimation
        ),
        lock_time: LockTime::ZERO,
        version: 2,
    ;

    tx.input(0).witness.push(
        Signature::from_slice(&(0; SCHNORR_SIGNATURE_SIZE))
            .unwrap()
            .to_vec(),
    );

    let fee = Amount::from_sat((fee_rate * tx.vsize()).round() as u64);
    info!("Fee: :?", fee.to_sat());
    tx.output(0).value = utxo.amount.to_sat() - fee.to_sat();
    tx.input(0).witness.clear();

    let prevouts = vec!(TxOut script_pubkey: utxo.script_pub_key.clone(), value: utxo.amount.to_sat());

    let mut sighash_cache = SighashCache::new(&tx);
    let sighash = sighash_cache.taproot_key_spend_signature_hash(
        0,
        &Prevouts::All(&prevouts),
        TapSighashType::Default,
    ).expect("Failed to compute sighash");

    let msg = secp256k1::Message::from_slice(sighash.as_ref()).expect("should be cryptographically secure hash");
    let sig = secp256k1.sign_schnorr(&msg, &key_pair);

    tx.input(0).witness.push(
        Signature 
            sig,
            hash_ty: TapSighashType::Default,
        
            .to_vec(),
    );

    let signed_tx_bytes = consensus::encode::serialize(&tx);
    // let signed_tx_bytes = client.sign_raw_transaction_with_wallet(&tx, None, None)?.hex;

    let txid = match client.send_raw_transaction(&signed_tx_bytes) 
        Ok(txid) => txid,
        Err(err) => 
            return Err(anyhow!("Failed to send transaction: err\n"))
        
    ;
    info!("Tx sent: :?", txid);
    Ok(txid)

The wallet I am using is connected to my node. If I use
client.sign_raw_transaction_with_wallet(&tx, None, None)?.hex; When a node uses a function that allows it to replace my witness with the correct witness, the transaction is approved. This means that your inputs and outputs are configured correctly, the problem is most likely in how you generate the signature.

I have successfully sent transactions using a script route spend using a similar approach. sighash_cache.taproot_script_spend_signature_hashWithout node involvement in signing.

Can someone help me identify what I am doing wrong?

Related Articles

Back to top button