Merge rust-bitcoin/rust-bitcoin#6766: bitcoin: make legacy sighash takes `EcdsaSighashType`
What changed, and why it matters
This is a routine API cleanup in a Bitcoin library written in Rust. It changes the legacy signature-hash functions so they accept a dedicated EcdsaSighashType value instead of a raw u32 number. The change is type-system only: callers must now pass a typed value, but the library still supports unusual/non-standard sighash values through a special variant. There is no direct security bug being fixed here; it is a code-quality and type-safety improvement.
No security action required. Treat as a normal API refactor. Downstream users updating to this version will need to change legacy sighash calls from raw u32 values to EcdsaSighashType values (e.g., EcdsaSighashType::All or EcdsaSighashType::from_consensus(...)).
Security signals we found
Type narrowing: replaces raw u32 sighash_type parameter with strongly typed EcdsaSighashType
Non-standard sighash values explicitly retained via EcdsaSighashType::NonStandard
No change to hashing logic or consensus rules in the diff
No mention of vulnerability, CVE, bug bounty, or security advisory in commit or PR description
Evidence from the diff
The commit refactors SighashCache::legacy_encode_signing_data_to and SighashCache::legacy_signature_hash to take EcdsaSighashType directly rather than a generic Into
Changed components
bitcoin/src/crypto/sighash.rsbitcoin/examples/sighash.rsInspect captured patch +17 / −20
### bitcoin/examples/sighash.rs
@@ -91,7 +91,7 @@ fn compute_sighash_legacy(raw_tx: &[u8], inp_idx: usize, script_pubkey_bytes_opt
let sig = ecdsa::Signature::from_slice(instr.unwrap().push_bytes().unwrap().as_bytes())
.expect("failed to parse sig");
let sighash = cache
- .legacy_signature_hash(inp_idx, script_code, sig.sighash_type.to_u32())
+ .legacy_signature_hash(inp_idx, script_code, sig.sighash_type)
.expect("failed to compute sighash");
println!("Legacy sighash: {:x} (sighash flag {})", sighash, sig.sighash_type);
}
### bitcoin/src/crypto/sighash.rs
@@ -679,7 +679,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// [`EcdsaSighashType`] appended to the resulting sig, and a script written around this, but
/// this is the general (and hard) part.
///
- /// The `sighash_type` supports an arbitrary `u32` value, instead of just [`EcdsaSighashType`],
+ /// The `sighash_type` supports arbitrary values via [`EcdsaSighashType::NonStandard`],
/// because internally 4 bytes are being hashed, even though only the lowest byte is appended to
/// signature in a transaction.
///
@@ -694,18 +694,17 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
///
/// This function can't handle the SIGHASH_SINGLE bug internally, so it returns [`EncodeSigningDataResult`]
/// that must be handled by the caller (see [`EncodeSigningDataResult::is_sighash_single_bug`]).
- pub fn legacy_encode_signing_data_to<W: Write, U: Into<u32>, T: ScriptHashableTag>(
+ pub fn legacy_encode_signing_data_to<W: Write, T: ScriptHashableTag>(
&self,
mut writer: W,
input_index: usize,
script_pubkey: &crate::script::Script<T>,
- sighash_type: U,
+ sighash_type: EcdsaSighashType,
) -> EncodeSigningDataResult<SigningDataError<transaction::InputsIndexError>> {
// Validate input_index.
if let Err(e) = self.tx.borrow().tx_in(input_index) {
return EncodeSigningDataResult::WriteResult(Err(SigningDataError::Sighash(e)));
}
- let sighash_type: u32 = sighash_type.into();
if is_invalid_use_of_sighash_single(
sighash_type,
@@ -724,10 +723,9 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
mut writer: W,
input_index: usize,
script_pubkey: &crate::script::Script<T>,
- sighash_type: u32,
+ sighash_type: EcdsaSighashType,
) -> Result<(), io::Error> {
- let (sighash, anyone_can_pay) =
- EcdsaSighashType::from_consensus(sighash_type).split_anyonecanpay_flag();
+ let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
io::encode_to_writer(&self_.version, &mut writer)?;
// Add all inputs necessary..
@@ -782,7 +780,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
_ => unreachable!(),
};
io::encode_to_writer(&self_.lock_time, &mut writer)?;
- writer.write_all(&sighash_type.to_le_bytes())?;
+ writer.write_all(&sighash_type.to_u32().to_le_bytes())?;
Ok(())
}
@@ -804,7 +802,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// [`EcdsaSighashType`] appended to the resulting sig, and a script written around this, but
/// this is the general (and hard) part.
///
- /// The `sighash_type` supports an arbitrary `u32` value, instead of just [`EcdsaSighashType`],
+ /// The `sighash_type` supports arbitrary values via [`EcdsaSighashType::NonStandard`],
/// because internally 4 bytes are being hashed, even though only the lowest byte is appended to
/// signature in a transaction.
///
@@ -821,7 +819,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
&self,
input_index: usize,
script_pubkey: &crate::script::Script<T>,
- sighash_type: u32,
+ sighash_type: EcdsaSighashType,
) -> Result<LegacySighash, transaction::InputsIndexError> {
let mut engine = LegacySighash::engine();
match self
@@ -949,9 +947,8 @@ impl<'a> Annex<'a> {
pub fn as_bytes(&self) -> &[u8] { self.0 }
}
-fn is_invalid_use_of_sighash_single(sighash: u32, input_index: usize, outputs_len: usize) -> bool {
- let ty = EcdsaSighashType::from_consensus(sighash);
- ty.is_single() && input_index >= outputs_len
+fn is_invalid_use_of_sighash_single(sighash: EcdsaSighashType, input_index: usize, outputs_len: usize) -> bool {
+ sighash.is_single() && input_index >= outputs_len
}
/// Result of [`SighashCache::legacy_encode_signing_data_to`].
@@ -986,12 +983,12 @@ impl<E> EncodeSigningDataResult<E> {
/// # let mut writer = sha256d::Hash::engine();
/// # let input_index = 0;
/// # let script_pubkey = bitcoin::ScriptPubKeyBuf::new();
- /// # let sighash_u32 = 0u32;
+ /// # let sighash_type = bitcoin::EcdsaSighashType::All;
/// # const SOME_TX: &'static str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
/// # let raw_tx = hex::decode_to_vec(SOME_TX).unwrap();
/// # let tx: Transaction = decode_from_slice(&raw_tx).unwrap();
/// let cache = SighashCache::new(&tx);
- /// if cache.legacy_encode_signing_data_to(&mut writer, input_index, &script_pubkey, sighash_u32)
+ /// if cache.legacy_encode_signing_data_to(&mut writer, input_index, &script_pubkey, sighash_type)
/// .is_sighash_single_bug()
/// .expect("writer can't fail") {
/// // use a hash value of "1", instead of computing the actual hash due to SIGHASH_SINGLE bug
@@ -1382,13 +1379,13 @@ mod tests {
let script = ScriptPubKeyBuf::new();
let cache = SighashCache::new(&tx);
- let sighash_single = 3;
+ let sighash_single = EcdsaSighashType::Single;
let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash");
let want = LegacySighash::from_byte_array(UINT256_ONE);
assert_eq!(got, want);
// https://github.com/rust-bitcoin/rust-bitcoin/issues/4112
- let sighash_single = 131;
+ let sighash_single = EcdsaSighashType::SinglePlusAnyoneCanPay;
let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash");
let want = LegacySighash::from_byte_array(UINT256_ONE);
assert_eq!(got, want);
@@ -1416,7 +1413,7 @@ mod tests {
let want = LegacySighash::from_byte_array(bytes);
let cache = SighashCache::new(&tx);
- let got = cache.legacy_signature_hash(input_index, &script, hash_type as u32).unwrap();
+ let got = cache.legacy_signature_hash(input_index, &script, EcdsaSighashType::from_consensus(hash_type as u32)).unwrap();
assert_eq!(got, want);
}
@@ -1616,7 +1613,7 @@ mod tests {
}))
);
assert_eq!(
- c.legacy_signature_hash(10, ScriptPubKey::new(), 0u32),
+ c.legacy_signature_hash(10, ScriptPubKey::new(), EcdsaSighashType::All),
Err(InputsIndexError(IndexOutOfBoundsError {
index: 10,
length: 1Why this scored 19/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.