bitcoin: remove `From<Message>` for `TapSighash`
What changed, and why it matters
This commit removes a convenience conversion that let developers accidentally treat a Taproot transaction digest as an ECDSA-style message. It is a defensive API cleanup: it makes the library's types more precise so users cannot pass a Taproot sighash through an ECDSA-specific wrapper. There is no direct bug being fixed, but the change prevents a class of future misuse where a Taproot signature could be computed over a wrongly-wrapped hash.
Review downstream code that may have relied on `From<Message>` for `TapSighash` or on `Psbt::sighash_taproot` returning a `Message`; update to use `TapSighash` and `to_byte_array()`. No urgent security deployment is required, but include in the next regular release to improve API safety.
Security signals we found
API misuse prevention: removes conversion of Taproot sighash into ECDSA-specific Message type
Type narrowing: TapSighash no longer implements From<Message>
All Taproot signing call sites now pass raw 32-byte sighash to Schnorr signing functions
No functional change to signature hash computation or verification logic
Evidence from the diff
The patch removes impl_message_from_hash!(TapSighash) and updates all call sites to pass sighash.to_byte_array() directly to sign_schnorr* instead of converting the sighash to a secp256k1::Message first. secp256k1::Message is semantically an ECDSA message (a tagged/structured 32-byte hash), whereas a Taproot sighash is a plain 32-byte transaction digest consumed directly by Schnorr signing. The Psbt::sighash_taproot return type changes from Result<(Message, TapSighashType), SignError> to Result<(TapSighash, TapSighashType), SignError>. Examples and tests are updated accordingly. This is a type-safety/API-correctness change rather than a patch for an active vulnerability.
Changed components
bitcoin/src/crypto/sighash.rsbitcoin/src/psbt/mod.rsbitcoin/examples/sign-tx-taproot.rsbitcoin/examples/taproot-psbt.rsInspect captured patch +14 / −19
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index 7f1ab505..5b7c83a4 100644
--- a/bitcoin/examples/sign-tx-taproot.rs
+++ b/bitcoin/examples/sign-tx-taproot.rs
@@ -5,7 +5,7 @@
use bitcoin::ext::*;
use bitcoin::key::{Keypair, TapTweak, TweakedKeypair, UntweakedPublicKey};
use bitcoin::locktime::absolute;
-use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification};
+use bitcoin::secp256k1::{rand, Secp256k1, SecretKey, Signing, Verification};
use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
use bitcoin::{
transaction, Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
@@ -69,8 +69,7 @@ fn main() {
// Sign the sighash using the secp256k1 library (exported by rust-bitcoin).
let tweaked: TweakedKeypair = keypair.tap_tweak(&secp, None);
- let msg = Message::from(sighash);
- let signature = secp.sign_schnorr(msg.as_ref(), tweaked.as_keypair());
+ let signature = secp.sign_schnorr(&sighash.to_byte_array(), tweaked.as_keypair());
// Update the witness stack.
let signature = bitcoin::taproot::Signature { signature, sighash_type };
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index c2aa11a7..88a9df79 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -750,8 +750,7 @@ fn sign_psbt_taproot(
Some(_) => keypair, // no tweak for script spend
};
- let msg = secp256k1::Message::from(hash);
- let signature = secp.sign_schnorr(msg.as_ref(), &keypair);
+ let signature = secp.sign_schnorr(&hash.to_byte_array(), &keypair);
let final_signature = taproot::Signature { signature, sighash_type };
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 8ffeb730..a5179963 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -91,8 +91,6 @@ hashes::impl_hex_for_newtype!(TapSighash);
#[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(TapSighash);
-impl_message_from_hash!(TapSighash);
-
/// Efficiently calculates signature hash message for legacy, SegWit and Taproot inputs.
#[derive(Debug)]
pub struct SighashCache<T: Borrow<Transaction>> {
@@ -2026,9 +2024,8 @@ mod tests {
.taproot_signature_hash(tx_ind, &Prevouts::All(&utxos), None, None, hash_ty)
.unwrap();
- let msg = secp256k1::Message::from(sighash);
let key_spend_sig =
- secp.sign_schnorr_with_aux_rand(msg.as_ref(), &tweaked_keypair, &[0u8; 32]);
+ secp.sign_schnorr_with_aux_rand(&sighash.to_byte_array(), &tweaked_keypair, &[0u8; 32]);
assert_eq!(expected.internal_pubkey, internal_key);
assert_eq!(expected.tweak, tweak);
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 3ba09f28..ee64956c 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -29,7 +29,7 @@ use crate::prelude::{btree_map, BTreeMap, BTreeSet, Borrow, Box, Vec};
use crate::script::ScriptExt as _;
use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache};
use crate::transaction::{self, Transaction, TransactionExt as _, TxOut};
-use crate::{Amount, FeeRate, TapLeafHash, TapSighashType};
+use crate::{Amount, FeeRate, TapLeafHash, TapSighash, TapSighashType};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
@@ -439,15 +439,15 @@ impl Psbt {
// Based on input.tap_internal_key.is_some() alone, it is not sufficient to determine whether it is a key path spend.
// According to BIP 371, we also need to consider the condition leaf_hashes.is_empty() for a more accurate determination.
if internal_key == xonly && leaf_hashes.is_empty() && input.tap_key_sig.is_none() {
- let (msg, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
+ let (sighash, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
let key_pair = Keypair::from_secret_key(secp, &sk.inner)
.tap_tweak(secp, input.tap_merkle_root)
.to_keypair();
#[cfg(feature = "rand-std")]
- let signature = secp.sign_schnorr(msg.as_ref(), &key_pair);
+ let signature = secp.sign_schnorr(&sighash.to_byte_array(), &key_pair);
#[cfg(not(feature = "rand-std"))]
- let signature = secp.sign_schnorr_no_aux_rand(msg.as_ref(), &key_pair);
+ let signature = secp.sign_schnorr_no_aux_rand(&sighash.to_byte_array(), &key_pair);
let signature = taproot::Signature { signature, sighash_type };
input.tap_key_sig = Some(signature);
@@ -468,13 +468,13 @@ impl Psbt {
let key_pair = Keypair::from_secret_key(secp, &sk.inner);
for lh in leaf_hashes {
- let (msg, sighash_type) =
+ let (sighash, sighash_type) =
self.sighash_taproot(input_index, cache, Some(lh))?;
#[cfg(feature = "rand-std")]
- let signature = secp.sign_schnorr(msg.as_ref(), &key_pair);
+ let signature = secp.sign_schnorr(&sighash.to_byte_array(), &key_pair);
#[cfg(not(feature = "rand-std"))]
- let signature = secp.sign_schnorr_no_aux_rand(msg.as_ref(), &key_pair);
+ let signature = secp.sign_schnorr_no_aux_rand(&sighash.to_byte_array(), &key_pair);
let signature = taproot::Signature { signature, sighash_type };
input.tap_script_sigs.insert((xonly, lh), signature);
@@ -552,7 +552,7 @@ impl Psbt {
}
}
- /// Returns the sighash message to sign an SCHNORR input along with the sighash type.
+ /// Returns the sighash to sign a Taproot input along with the sighash type.
///
/// Uses the [`TapSighashType`] from this input if one is specified. If no sighash type is
/// specified uses [`TapSighashType::Default`].
@@ -561,7 +561,7 @@ impl Psbt {
input_index: usize,
cache: &mut SighashCache<T>,
leaf_hash: Option<TapLeafHash>,
- ) -> Result<(Message, TapSighashType), SignError> {
+ ) -> Result<(TapSighash, TapSighashType), SignError> {
use OutputType::*;
if self.signing_algorithm(input_index)? != SigningAlgorithm::Schnorr {
@@ -606,7 +606,7 @@ impl Psbt {
} else {
cache.taproot_key_spend_signature_hash(input_index, &prev_outs, hash_ty)?
};
- Ok((Message::from(sighash), hash_ty))
+ Ok((sighash, hash_ty))
}
_ => Err(SignError::Unsupported),
}
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.