subcoin_script/
signature_checker.rs

1use crate::num::ScriptNum;
2use crate::{EcdsaSignature, SchnorrSignature, ScriptExecutionData, SigVersion};
3use bitcoin::hashes::Hash;
4use bitcoin::locktime::absolute::LockTime as AbsoluteLockTime;
5use bitcoin::locktime::relative::LockTime as RelativeLockTime;
6use bitcoin::opcodes::all::OP_CODESEPARATOR;
7use bitcoin::script::Instruction;
8use bitcoin::secp256k1::{self, All, Message, Secp256k1};
9use bitcoin::sighash::{Annex, Prevouts, SegwitV0Sighash, SighashCache, TaprootError};
10use bitcoin::transaction::Version;
11use bitcoin::{
12    Amount, EcdsaSighashType, LegacySighash, PublicKey, Script, ScriptBuf, Sequence, Transaction,
13    TxOut, XOnlyPublicKey,
14};
15use std::sync::LazyLock;
16
17pub(crate) static SECP: LazyLock<Secp256k1<All>> = LazyLock::new(Secp256k1::new);
18
19/// Used for signature hash for invalid use of SIGHASH_SINGLE.
20#[rustfmt::skip]
21const UINT256_ONE: [u8; 32] = [
22    1, 0, 0, 0, 0, 0, 0, 0,
23    0, 0, 0, 0, 0, 0, 0, 0,
24    0, 0, 0, 0, 0, 0, 0, 0,
25    0, 0, 0, 0, 0, 0, 0, 0
26];
27
28/// Error types related to signature verification.
29#[derive(Debug, Eq, PartialEq, thiserror::Error)]
30pub enum SignatureError {
31    #[error("Invalid signature version")]
32    InvalidSignatureVersion,
33    #[error("ecdsa error: {0:?}")]
34    Ecdsa(secp256k1::Error),
35    #[error("Failed to parse ECDSA signature: {0:?}")]
36    ParseEcdsaSignature(bitcoin::ecdsa::Error),
37    #[error("schnorr error: {0:?}")]
38    Schnorr(secp256k1::Error),
39    #[error("Ecdsa sighash error: {0:?}")]
40    EcdsaSignatureHash(bitcoin::blockdata::transaction::InputsIndexError),
41    #[error("Taproot sighash error: {0:?}")]
42    TaprootSignatureHash(TaprootError),
43}
44
45/// Trait for verifying Bitcoin transaction signatures.
46pub trait SignatureChecker {
47    /// Verifies an ECDSA signature against a message and public key.
48    ///
49    /// # Arguments
50    /// * `sig` - The ECDSA signature to verify.
51    /// * `msg` - The message that was signed.
52    /// * `pk` - The public key corresponding to the signature.
53    ///
54    /// # Returns
55    /// - `true` if the signature is valid.
56    /// - `false` if the signature is invalid or an error occurs.
57    fn verify_ecdsa_signature(&self, sig: &EcdsaSignature, msg: &Message, pk: &PublicKey) -> bool {
58        SECP.verify_ecdsa(msg, &sig.signature, &pk.inner)
59            .inspect_err(|err| {
60                tracing::trace!(
61                    ?err,
62                    "[verify_ecdsa_signature] Failed to verify ecdsa signature"
63                );
64            })
65            .is_ok()
66    }
67
68    /// Checks an ECDSA signature in the context of a Bitcoin transaction.
69    ///
70    /// # Arguments
71    /// * `sig` - The ECDSA signature to check.
72    /// * `pk` - The public key corresponding to the signature.
73    /// * `script_code` - The script code for the transaction input.
74    /// * `sig_version` - The signature version (e.g., [`SigVersion::Base`] or [`SigVersion::WitnessV0`]).
75    ///
76    /// # Returns
77    /// - `Ok(true)` if the signature is valid.
78    /// - `Ok(false)` if the signature is invalid.
79    /// - `Err(SignatureError)` if an error occurs.
80    ///
81    /// # Notes
82    /// In the context of multisignature transactions, it is expected that not all signatures may be valid.
83    /// An invalid signature may be considered legitimate as long as the multisig conditions are met.
84    fn check_ecdsa_signature(
85        &mut self,
86        sig: &EcdsaSignature,
87        pk: &PublicKey,
88        script_code: &Script,
89        sig_version: SigVersion,
90    ) -> Result<bool, SignatureError>;
91
92    /// Verifies a Schnorr signature against a message and public key.
93    ///
94    /// # Arguments
95    /// * `sig` - The Schnorr signature to verify.
96    /// * `msg` - The message that was signed.
97    /// * `pk` - The public key corresponding to the signature.
98    ///
99    /// # Returns
100    /// - `true` if the signature is valid.
101    /// - `false` if the signature is invalid or an error occurs.
102    fn verify_schnorr_signature(
103        &self,
104        sig: &SchnorrSignature,
105        msg: &Message,
106        pk: &XOnlyPublicKey,
107    ) -> bool {
108        SECP.verify_schnorr(&sig.signature, msg, pk)
109            .inspect_err(|err| {
110                tracing::trace!(
111                    ?err,
112                    "[verify_schnorr_signature] Failed to verify schnorr signature"
113                );
114            })
115            .is_ok()
116    }
117
118    /// Checks a Schnorr signature in the context of a Bitcoin transaction.
119    ///
120    /// # Arguments
121    /// * `sig` - The Schnorr signature to check.
122    /// * `pk` - The public key corresponding to the signature.
123    /// * `sig_version` - The signature version (e.g., `SigVersion::Taproot`).
124    /// * `exec_data` - Additional execution data for Taproot scripts.
125    ///
126    /// # Returns
127    /// - `Ok(true)` if the signature is valid.
128    /// - `Ok(false)` if the signature is invalid.
129    /// - `Err(SignatureError)` if an error occurs.
130    fn check_schnorr_signature(
131        &mut self,
132        sig: &SchnorrSignature,
133        pk: &XOnlyPublicKey,
134        sig_version: SigVersion,
135        exec_data: &ScriptExecutionData,
136    ) -> Result<bool, SignatureError>;
137
138    /// Checks whether the absolute time lock (`lock_time`) in a transaction is satisfied.
139    fn check_lock_time(&self, lock_time: ScriptNum) -> bool;
140
141    /// Checks whether the relative time lock (`sequence`) for a specific input
142    /// in a transaction is satisfied.
143    fn check_sequence(&self, sequence: ScriptNum) -> bool;
144}
145
146/// Verify the signature against the public key.
147pub fn check_ecdsa_signature(
148    sig: &[u8],
149    public_key: &[u8],
150    checker: &mut impl SignatureChecker,
151    subscript: &Script,
152    sig_version: SigVersion,
153) -> bool {
154    tracing::trace!(
155        "[check_ecdsa_signature] sig: {}, public_key: {}, subscript: {}",
156        hex::encode(sig),
157        hex::encode(public_key),
158        hex::encode(subscript.as_bytes())
159    );
160
161    let sig = match EcdsaSignature::parse_der_lax(sig) {
162        Ok(sig) => sig,
163        Err(err) => {
164            tracing::trace!(
165                ?err,
166                "Failed to parse ecdsa signature from {}",
167                hex::encode(sig)
168            );
169            return false;
170        }
171    };
172
173    match PublicKey::from_slice(public_key) {
174        Ok(key) => checker
175            .check_ecdsa_signature(&sig, &key, subscript, sig_version)
176            .unwrap_or(false),
177        Err(_) => false,
178    }
179}
180
181/// A [`SignatureChecker`] implementation that skips all signature checks.
182pub struct NoSignatureCheck;
183
184impl SignatureChecker for NoSignatureCheck {
185    fn check_ecdsa_signature(
186        &mut self,
187        _sig: &EcdsaSignature,
188        _pk: &PublicKey,
189        _script_code: &Script,
190        _sig_version: SigVersion,
191    ) -> Result<bool, SignatureError> {
192        Ok(true)
193    }
194
195    fn check_schnorr_signature(
196        &mut self,
197        _sig: &SchnorrSignature,
198        _pk: &XOnlyPublicKey,
199        _sig_version: SigVersion,
200        _exec_data: &ScriptExecutionData,
201    ) -> Result<bool, SignatureError> {
202        Ok(true)
203    }
204
205    fn check_lock_time(&self, _lock_time: ScriptNum) -> bool {
206        true
207    }
208
209    fn check_sequence(&self, _sequence: ScriptNum) -> bool {
210        true
211    }
212}
213
214/// A [`SignatureChecker`] implementation for transactions.
215pub struct TransactionSignatureChecker<'a> {
216    tx: &'a Transaction,
217    input_index: usize,
218    input_amount: u64,
219    prev_outs: Vec<TxOut>,
220    sighash_cache: SighashCache<&'a Transaction>,
221}
222
223impl<'a> TransactionSignatureChecker<'a> {
224    /// Constructs a new instance of [`TransactionSignatureChecker`].
225    pub fn new(tx: &'a Transaction, input_index: usize, input_amount: u64) -> Self {
226        let sighash_cache = SighashCache::new(tx);
227        Self {
228            tx,
229            input_index,
230            input_amount,
231            prev_outs: Vec::new(),
232            sighash_cache,
233        }
234    }
235}
236
237/// Represents a script processed for base signature hash calculation,
238/// which may either be the original unmodified script or a sanitized version
239/// with all OP_CODESEPARATOR opcodes removed.
240#[derive(Debug)]
241enum BaseSighashScript<'a> {
242    /// The original script contains no OP_CODESEPARATOR opcodes.
243    Original(&'a Script),
244    /// A modified version of the script with all OP_CODESEPARATOR opcodes removed.
245    Sanitized(ScriptBuf),
246}
247
248impl BaseSighashScript<'_> {
249    /// Returns a reference to the processed script suitable for sighash computation
250    fn as_script(&self) -> &Script {
251        match self {
252            Self::Original(script) => script,
253            Self::Sanitized(script) => script,
254        }
255    }
256}
257
258fn remove_op_codeseparator(script: &Script) -> BaseSighashScript<'_> {
259    let has_code_separators = script.instructions().any(|instruction| {
260        instruction
261            .expect("Parsing script must not fail in signature verification")
262            .opcode()
263            .map(|op| op == OP_CODESEPARATOR)
264            .unwrap_or(false)
265    });
266
267    if !has_code_separators {
268        return BaseSighashScript::Original(script);
269    }
270
271    let original_bytes = script.as_bytes();
272
273    let mut sanitized_script = Vec::with_capacity(original_bytes.len());
274    let mut last_pos = 0;
275
276    for instruction in script.instruction_indices() {
277        let (pos, instruction) =
278            instruction.expect("Parsing script must not fail in signature verification");
279
280        match instruction {
281            Instruction::Op(OP_CODESEPARATOR) => {
282                // Skip the OP_CODESEPARATOR byte
283                last_pos = pos + 1;
284            }
285            Instruction::Op(_) => {
286                // Copy single-byte opcode
287                sanitized_script.push(original_bytes[pos]);
288                last_pos = pos + 1;
289            }
290            Instruction::PushBytes(data) => {
291                // Copy push opcode and its data
292                let data_end = pos + 1 + data.len();
293                sanitized_script.extend_from_slice(&original_bytes[pos..data_end]);
294                last_pos = data_end;
295            }
296        }
297    }
298
299    // Copy any remaining bytes after last parsed instruction
300    sanitized_script.extend_from_slice(&original_bytes[last_pos..]);
301
302    BaseSighashScript::Sanitized(ScriptBuf::from(sanitized_script))
303}
304
305impl SignatureChecker for TransactionSignatureChecker<'_> {
306    fn check_ecdsa_signature(
307        &mut self,
308        sig: &EcdsaSignature,
309        pk: &PublicKey,
310        script_pubkey: &Script,
311        sig_version: SigVersion,
312    ) -> Result<bool, SignatureError> {
313        let msg: Message = match sig_version {
314            SigVersion::Base => {
315                let base_sighash_script = remove_op_codeseparator(script_pubkey);
316
317                // SIGHASH_MASK defines the number of bits of the hash type which is used to
318                // identify which outpus are signed.
319                const SIGHASH_MASK: u32 = 0x1f;
320
321                // TODO: legacy_signature_hash() does not handle the sighash_single_bug properly.
322                //
323                // https://github.com/rust-bitcoin/rust-bitcoin/issues/4112
324                let is_sighash_single_bug = sig.sighash_type & SIGHASH_MASK
325                    == EcdsaSighashType::Single as u32
326                    && self.input_index >= self.tx.output.len();
327
328                if is_sighash_single_bug {
329                    LegacySighash::from_byte_array(UINT256_ONE).into()
330                } else {
331                    self.sighash_cache
332                        .legacy_signature_hash(
333                            self.input_index,
334                            base_sighash_script.as_script(),
335                            sig.sighash_type,
336                        )
337                        .map_err(SignatureError::EcdsaSignatureHash)?
338                        .into()
339                }
340            }
341            SigVersion::WitnessV0 => {
342                let p2wsh_signature_hash = {
343                    // TODO: https://github.com/rust-bitcoin/rust-bitcoin/issues/4133
344                    let mut enc = SegwitV0Sighash::engine();
345                    self.sighash_cache
346                        .segwit_v0_encode_signing_data_to(
347                            &mut enc,
348                            self.input_index,
349                            script_pubkey,
350                            Amount::from_sat(self.input_amount),
351                            EcdsaSighashType::from_consensus(sig.sighash_type),
352                            sig.sighash_type,
353                        )
354                        .unwrap();
355                    SegwitV0Sighash::from_engine(enc)
356                };
357
358                p2wsh_signature_hash.into()
359            }
360            _ => return Err(SignatureError::InvalidSignatureVersion),
361        };
362
363        let is_valid_signature = self.verify_ecdsa_signature(sig, &msg, pk);
364
365        if !is_valid_signature {
366            tracing::debug!(
367                ?sig,
368                %pk,
369                input_amount = self.input_amount,
370                script_pubkey = %hex::encode(script_pubkey.as_bytes()),
371                ?sig_version,
372                signed_msg = %msg,
373                "[check_ecdsa_signature] Invalid ECDSA signature"
374            );
375        }
376
377        Ok(is_valid_signature)
378    }
379
380    fn check_schnorr_signature(
381        &mut self,
382        sig: &SchnorrSignature,
383        pk: &XOnlyPublicKey,
384        sig_version: SigVersion,
385        exec_data: &ScriptExecutionData,
386    ) -> Result<bool, SignatureError> {
387        if !matches!(sig_version, SigVersion::Taproot | SigVersion::Tapscript) {
388            return Err(SignatureError::InvalidSignatureVersion);
389        }
390
391        let last_codeseparator_pos = if exec_data.codeseparator_pos_init {
392            Some(exec_data.codeseparator_pos)
393        } else {
394            None
395        };
396
397        let leaf_hash = exec_data.tapleaf_hash;
398        let annex = exec_data.annex.as_ref().map(|a| {
399            Annex::new(a).expect("Annex must be valid as it was checked on initialization; qed")
400        });
401
402        let sighash = self
403            .sighash_cache
404            .taproot_signature_hash(
405                self.input_index,
406                &Prevouts::All(&self.prev_outs),
407                annex,
408                Some((leaf_hash, last_codeseparator_pos.unwrap_or(u32::MAX))),
409                sig.sighash_type,
410            )
411            .map_err(SignatureError::TaprootSignatureHash)?;
412
413        let msg: Message = sighash.into();
414
415        Ok(self.verify_schnorr_signature(sig, &msg, pk))
416    }
417
418    /// This function verifies that the transaction's `nLockTime` field meets the conditions specified
419    /// by the `lock_time` parameter.
420    ///
421    /// The `lock_time` can represent either a block height or a Unix timestamp, depending on its value:
422    /// - If `lock_time < 500,000,000`, it is interpreted as a block height.
423    /// - If `lock_time >= 500,000,000`, it is interpreted as a Unix timestamp.
424    ///
425    /// The lock is satisfied if:
426    /// 1. The `lock_time` is of the same type (block height or timestamp) as the transaction's `nLockTime`.
427    /// 2. The `lock_time` is less than or equal to the transaction's `nLockTime`.
428    /// 3. The transaction's `nSequence` field for the input is not set to the maximum value (`0xFFFFFFFF`),
429    ///    which would disable the time lock.
430    ///
431    /// # Arguments
432    /// * `lock_time` - The absolute time lock to check, represented as a [`ScriptNum`].
433    ///
434    /// # Returns
435    /// - `true` if the absolute time lock is satisfied.
436    /// - `false` otherwise.
437    fn check_lock_time(&self, lock_time: ScriptNum) -> bool {
438        let Ok(lock_time) = u32::try_from(lock_time.value()).map(AbsoluteLockTime::from_consensus)
439        else {
440            return false;
441        };
442
443        // There are two kinds of nLockTime: lock-by-blockheight
444        // and lock-by-blocktime, distinguished by whether
445        // nLockTime < LOCKTIME_THRESHOLD.
446        //
447        // We want to compare apples to apples, so fail the script
448        // unless the type of nLockTime being tested is the same as
449        // the nLockTime in the transaction.
450        //
451        // Now that we know we're comparing apples-to-apples, the
452        // comparison is a simple numeric one.
453        match (lock_time, self.tx.lock_time) {
454            (AbsoluteLockTime::Blocks(h1), AbsoluteLockTime::Blocks(h2)) if h1 > h2 => {
455                return false;
456            }
457            (AbsoluteLockTime::Seconds(t1), AbsoluteLockTime::Seconds(t2)) if t1 > t2 => {
458                return false;
459            }
460            (AbsoluteLockTime::Blocks(_), AbsoluteLockTime::Seconds(_)) => return false,
461            (AbsoluteLockTime::Seconds(_), AbsoluteLockTime::Blocks(_)) => return false,
462            _ => {}
463        }
464
465        const SEQUENCE_FINAL: Sequence = Sequence::MAX;
466
467        // Finally the nLockTime feature can be disabled and thus
468        // CHECKLOCKTIMEVERIFY bypassed if every txin has been
469        // finalized by setting nSequence to maxint. The
470        // transaction would be allowed into the blockchain, making
471        // the opcode ineffective.
472        //
473        // Testing if this vin is not final is sufficient to
474        // prevent this condition. Alternatively we could test all
475        // inputs, but testing just this input minimizes the data
476        // required to prove correct CHECKLOCKTIMEVERIFY execution.
477        self.tx.input[self.input_index].sequence != SEQUENCE_FINAL
478    }
479
480    /// The lock is satisfied if:
481    /// 1. The transaction's version is at least 2 (BIP 68).
482    /// 2. The `sequence` is of the same type (blocks or seconds) as the input's `nSequence`.
483    /// 3. The `sequence` is less than or equal to the input's `nSequence`.
484    ///
485    /// # Arguments
486    /// * `sequence` - The relative time lock to check, represented as a [`ScriptNum`].
487    ///
488    /// # Returns
489    /// - `true` if the relative time lock is satisfied.
490    /// - `false` otherwise.
491    fn check_sequence(&self, sequence: ScriptNum) -> bool {
492        // Fail if the transaction's version number is not set high
493        // enough to trigger BIP 68 rules.
494        if self.tx.version < Version::TWO {
495            return false;
496        }
497
498        // Relative lock times are supported by comparing the passed
499        // in operand to the sequence number of the input.
500        let Some(input_lock_time) = self.tx.input[self.input_index]
501            .sequence
502            .to_relative_lock_time()
503        else {
504            return false;
505        };
506
507        let Some(lock_time) = u32::try_from(sequence.value())
508            .ok()
509            .and_then(|seq| RelativeLockTime::from_consensus(seq).ok())
510        else {
511            return false;
512        };
513
514        match (lock_time, input_lock_time) {
515            (RelativeLockTime::Blocks(h1), RelativeLockTime::Blocks(h2)) if h1 > h2 => {
516                return false;
517            }
518            (RelativeLockTime::Time(t1), RelativeLockTime::Time(t2)) if t1 > t2 => return false,
519            (RelativeLockTime::Blocks(_), RelativeLockTime::Time(_)) => return false,
520            (RelativeLockTime::Time(_), RelativeLockTime::Blocks(_)) => return false,
521            _ => {}
522        }
523
524        true
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531    use bitcoin::consensus::encode::deserialize_hex;
532    use bitcoin::hashes::Hash;
533    use bitcoin::sighash::SighashCache;
534    use bitcoin::{LegacySighash, Script, Transaction};
535
536    #[test]
537    fn test_remove_op_codeseparator() {
538        // https://www.blockchain.com/explorer/transactions/btc/5fec539b26083b26d9d77014402e5942566a3c8c6e1b2b0c9cd245d51a0a5c61
539        let tx = "01000000016aaa18f4ab91fab80ecda666c4def68b8b75cc6bb1169ecd81716eab03ff14d007000000fd8701483045022100ac4319cf798ab10d864ad5f206cd405b7a15957eef2b0094ab24ffcf2c28fbfb022012053c8142d9e4f832d85c6ce7dba82d44d011c7713fb584771fb8770da97c0c012102c8662aaa171b5c98fef66c02138165f600c7c5743380686958e395edf8eb36bf47304402202feedc3b54cd87868406e93ee650742b61ce39162d70b6fde5a805fd40a56c900220015970a2fc874c32edfcd6341981d35e5b019a14b17662e00f49e363db72b93c014cd22102fb6827937707bf432d85b094bc180ab93394ee013b3ecaafa04b9135e3ab6e50ad74926404162c5658b15167762103db22e387923ad0552e1c4a4355324313af85926d4266c0eaa86f02eb1e01b2d28763ac67762102c8662aaa171b5c98fef66c02138165f600c7c5743380686958e395edf8eb36bf886e6b6b0064ab05636f6e643175ac687664756c6c6e6b6bab05636f6e643275ac687664756c6c6e6b6bab05636f6e643375ac687664756c6c6e6b6bab05636f6e643475ac687664756c6c6e6b6bab05636f6e643575ac686868ffffffff01204e0000000000001976a914648a4310b84426f426398ef27e3388a4d2c05a2888ac342c5658";
540        let tx: Transaction = deserialize_hex(tx).unwrap();
541
542        let input_index = 0;
543        let sighash_type = 1;
544        let pkscript = hex::decode("2102fb6827937707bf432d85b094bc180ab93394ee013b3ecaafa04b9135e3ab6e50ad74926404162c5658b15167762103db22e387923ad0552e1c4a4355324313af85926d4266c0eaa86f02eb1e01b2d28763ac67762102c8662aaa171b5c98fef66c02138165f600c7c5743380686958e395edf8eb36bf886e6b6b0064ab05636f6e643175ac687664756c6c6e6b6bab05636f6e643275ac687664756c6c6e6b6bab05636f6e643375ac687664756c6c6e6b6bab05636f6e643475ac687664756c6c6e6b6bab05636f6e643575ac686868").unwrap();
545        let script_pubkey = Script::from_bytes(&pkscript);
546
547        let sighash_cache = SighashCache::new(&tx);
548        // legacy_signature_hash() does not take care of the removal of OP_CODESEPARATOR, we must
549        // remove all OP_CODESEPARATOR first.
550        let cleaned_script = remove_op_codeseparator(script_pubkey);
551        let sighash = sighash_cache
552            .legacy_signature_hash(input_index, cleaned_script.as_script(), sighash_type)
553            .unwrap();
554
555        let data = hex::decode("1d893b45c5d005bf6a20a0ab1ad19c16e92da602e9984180d947e2798aef1e41")
556            .unwrap();
557        let expected_sighash = LegacySighash::from_slice(&data).unwrap();
558
559        assert_eq!(expected_sighash, sighash);
560    }
561}