use bitcoin::{
secp256k1::{ecdsa::Signature as EcdsaSignature, schnorr::Signature as SchnorrSignature},
sighash::{EcdsaSighashType, TapSighashType},
};
use crate::TxFeatureFlags;
const MIN_ECDSA_SIGNATURE_BYTES: usize = 9;
const MAX_ECDSA_SIGNATURE_BYTES: usize = 73;
pub(super) fn scan_ecdsa_signature(bytes: &[u8], flags: &mut TxFeatureFlags) {
if !is_ecdsa_signature_candidate(bytes) {
return;
}
let (sighash_byte, der) = bytes.split_last().unwrap();
let Ok(sighash_type) = EcdsaSighashType::from_standard(u32::from(*sighash_byte)) else {
return;
};
let candidate_flags = ecdsa_feature_flags(sighash_type);
if flags.contains_all(candidate_flags) {
return;
}
if EcdsaSignature::from_der(der).is_err() {
return;
}
flags.insert(candidate_flags);
}
pub(super) fn scan_taproot_signature(bytes: &[u8], flags: &mut TxFeatureFlags) {
let (sighash_type, signature) = match bytes.len() {
64 => (TapSighashType::Default, bytes),
65 => {
let Ok(sighash_type) = TapSighashType::from_consensus_u8(bytes[64]) else {
return;
};
(sighash_type, &bytes[..64])
}
_ => return,
};
let candidate_flags = taproot_feature_flags(sighash_type);
if flags.contains_all(candidate_flags) {
return;
}
if SchnorrSignature::from_slice(signature).is_err() {
return;
}
flags.insert(candidate_flags);
}
pub(super) fn record_validated_ecdsa_sighash(bytes: &[u8], flags: &mut TxFeatureFlags) {
let Ok(sighash_type) = EcdsaSighashType::from_standard(u32::from(*bytes.last().unwrap()))
else {
return;
};
flags.insert(ecdsa_feature_flags(sighash_type));
}
pub(super) fn record_validated_taproot_sighash(bytes: &[u8], flags: &mut TxFeatureFlags) {
let sighash_type = match bytes.len() {
64 => TapSighashType::Default,
65 => {
let Ok(sighash_type) = TapSighashType::from_consensus_u8(bytes[64]) else {
return;
};
sighash_type
}
_ => return,
};
flags.insert(taproot_feature_flags(sighash_type));
}
fn ecdsa_feature_flags(sighash_type: EcdsaSighashType) -> u32 {
let mut flags = match sighash_type {
EcdsaSighashType::All | EcdsaSighashType::AllPlusAnyoneCanPay => {
TxFeatureFlags::SIGHASH_ALL
}
EcdsaSighashType::None | EcdsaSighashType::NonePlusAnyoneCanPay => {
TxFeatureFlags::SIGHASH_NONE
}
EcdsaSighashType::Single | EcdsaSighashType::SinglePlusAnyoneCanPay => {
TxFeatureFlags::SIGHASH_SINGLE
}
};
if sighash_type.to_u32() & 0x80 != 0 {
flags |= TxFeatureFlags::SIGHASH_ANYONE_CAN_PAY;
}
flags
}
#[inline]
fn is_ecdsa_signature_candidate(bytes: &[u8]) -> bool {
(MIN_ECDSA_SIGNATURE_BYTES..=MAX_ECDSA_SIGNATURE_BYTES).contains(&bytes.len())
&& bytes.first() == Some(&0x30)
}
fn taproot_feature_flags(sighash_type: TapSighashType) -> u32 {
let mut flags = match sighash_type {
TapSighashType::Default => TxFeatureFlags::SIGHASH_DEFAULT,
TapSighashType::All | TapSighashType::AllPlusAnyoneCanPay => TxFeatureFlags::SIGHASH_ALL,
TapSighashType::None | TapSighashType::NonePlusAnyoneCanPay => TxFeatureFlags::SIGHASH_NONE,
TapSighashType::Single | TapSighashType::SinglePlusAnyoneCanPay => {
TxFeatureFlags::SIGHASH_SINGLE
}
};
if matches!(
sighash_type,
TapSighashType::AllPlusAnyoneCanPay
| TapSighashType::NonePlusAnyoneCanPay
| TapSighashType::SinglePlusAnyoneCanPay
) {
flags |= TxFeatureFlags::SIGHASH_ANYONE_CAN_PAY;
}
flags
}
#[cfg(test)]
mod tests {
use super::is_ecdsa_signature_candidate;
#[test]
fn rejects_impossible_ecdsa_signature_encodings() {
assert!(!is_ecdsa_signature_candidate(&[0x30; 8]));
assert!(is_ecdsa_signature_candidate(&[0x30; 9]));
assert!(is_ecdsa_signature_candidate(&[0x30; 73]));
assert!(!is_ecdsa_signature_candidate(&[0x30; 74]));
assert!(!is_ecdsa_signature_candidate(&[0x02; 33]));
}
}