From 5d38073afb4845f9806deaaa73b0b7ae0430ebb2 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 25 Feb 2025 07:40:46 +0800 Subject: [PATCH 1/2] Fix `is_invalid_use_of_sighash_single()` incompatibility with Bitcoin Core --- bitcoin/src/crypto/sighash.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 5498211a04..32854bb85d 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -429,6 +429,16 @@ impl EcdsaSighashType { } } + /// Checks if the sighash type is [`Self::Single`] or [`Self::SinglePlusAnyoneCanPay`]. + /// + /// This matches Bitcoin Core's behavior where SIGHASH_SINGLE bug check is based on the base + /// type (after masking with 0x1f), regardless of the ANYONECANPAY flag. + /// + /// See: + pub fn is_single(&self) -> bool { + matches!(self, Self::Single | Self::SinglePlusAnyoneCanPay) + } + /// Constructs a new [`EcdsaSighashType`] from a raw `u32`. /// /// **Note**: this replicates consensus behaviour, for current standardness rules correctness @@ -1360,7 +1370,7 @@ impl std::error::Error for AnnexError { fn is_invalid_use_of_sighash_single(sighash: u32, input_index: usize, outputs_len: usize) -> bool { let ty = EcdsaSighashType::from_consensus(sighash); - ty == EcdsaSighashType::Single && input_index >= outputs_len + ty.is_single() && input_index >= outputs_len } /// Result of [`SighashCache::legacy_encode_signing_data_to`]. From 7ab2f5be4076c22e60eefcaa943444808eae3e3f Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 25 Feb 2025 07:38:36 +0800 Subject: [PATCH 2/2] Add test for sighash_single_bug incompatility fix --- bitcoin/src/crypto/sighash.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 32854bb85d..7167f64f09 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1545,8 +1545,6 @@ mod tests { #[test] fn sighash_single_bug() { - const SIGHASH_SINGLE: u32 = 3; - // We need a tx with more inputs than outputs. let tx = Transaction { version: transaction::Version::ONE, @@ -1557,10 +1555,16 @@ mod tests { let script = ScriptBuf::new(); let cache = SighashCache::new(&tx); - let got = cache.legacy_signature_hash(1, &script, SIGHASH_SINGLE).expect("sighash"); + let sighash_single = 3; + let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash"); let want = LegacySighash::from_byte_array(UINT256_ONE); + assert_eq!(got, want); - assert_eq!(got, want) + // https://github.com/rust-bitcoin/rust-bitcoin/issues/4112 + let sighash_single = 131; + let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash"); + let want = LegacySighash::from_byte_array(UINT256_ONE); + assert_eq!(got, want); } #[test]