I am experimenting with creating and sending Taproot transactions programmatically and encountered a difficulty with the Schnorr signature. I am making an attempt to ship easy transaction with one V1_P2TR enter and one V1_P2TR output, utilizing what I perceive to be a key path spend strategy, with none scripts. Nevertheless, after I try to ship the transaction, my node rejects it with the error:
mandatory-script-verify-flag-failed (Invalid Schnorr signature)
I am utilizing the next dependencies in my Rust challenge:
bitcoin = { model = "0.30.1", options = ["rand"] }
ord-bitcoincore-rpc = "0.17.1" # (a forked model of bitcoincore-rpc, although I consider this element just isn't essential to the difficulty).
This is the related a part of my code:
fn create_and_send_tmp_tx(consumer: &Shopper, utxo: &ListUnspentResultEntry, fee_rate: f64, key_pair: &UntweakedKeyPair, address_to: &Tackle) -> Outcome {
let secp256k1 = Secp256k1::new();
// Verifying that UTXO could be spent by supplied key pair
let (public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair);
let handle = Tackle::p2tr(&secp256k1, public_key, None, Community::Bitcoin);
let address_script_pubkey = handle.script_pubkey();
let utxo_script_pubkey = utxo.script_pub_key.clone();
if ! (address_script_pubkey == utxo_script_pubkey) {
bail!("Cannot spend utxo");
}
let mut tx = Transaction {
enter: 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,
model: 2,
};
tx.enter[0].witness.push(
Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE])
.unwrap()
.to_vec(),
);
let payment = Quantity::from_sat((fee_rate * tx.vsize()).spherical() as u64);
data!("Price: {:?}", payment.to_sat());
tx.output[0].worth = utxo.quantity.to_sat() - payment.to_sat();
tx.enter[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,
).count on("Did not compute sighash");
let msg = secp256k1::Message::from_slice(sighash.as_ref()).count on("needs to be cryptographically safe hash");
let sig = secp256k1.sign_schnorr(&msg, &key_pair);
tx.enter[0].witness.push(
Signature {
sig,
hash_ty: TapSighashType::Default,
}
.to_vec(),
);
let signed_tx_bytes = consensus::encode::serialize(&tx);
// let signed_tx_bytes = consumer.sign_raw_transaction_with_wallet(&tx, None, None)?.hex;
let txid = match consumer.send_raw_transaction(&signed_tx_bytes) {
Okay(txid) => txid,
Err(err) => {
return Err(anyhow!("Did not ship transaction: {err}n"))
}
};
data!("Tx despatched: {:?}", txid);
Okay(txid)
}
The pockets I am utilizing is related to my node. If I exploit the
consumer.sign_raw_transaction_with_wallet(&tx, None, None)?.hex; perform, which permits the node to switch my witness with an accurate one, the transaction is accepted. This means that my inputs and outputs are constructed appropriately, and the difficulty possible lies with how I am producing the signature.
I’ve efficiently despatched transactions utilizing a script path spend with an analogous strategy, utilizing sighash_cache.taproot_script_spend_signature_hash, with out the node’s intervention in signing.
Might somebody assist me establish what I am doing unsuitable?
