use zebra_chain::constants::{ LOCKTIME_THRESHOLD, SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG, }; use zebra_keys::{Message, Public, Signature}; use {Num, Script, SighashCache, TransactionInputSigner}; /// Checks transaction signature pub trait SignatureChecker { fn verify_signature(&self, signature: &Signature, public: &Public, hash: &Message) -> bool; fn check_signature( &mut self, signature: &Signature, public: &Public, script_code: &Script, sighashtype: u32, ) -> bool; fn check_lock_time(&self, lock_time: Num) -> bool; fn check_sequence(&self, sequence: Num) -> bool; } pub struct NoopSignatureChecker; impl SignatureChecker for NoopSignatureChecker { fn verify_signature(&self, signature: &Signature, public: &Public, hash: &Message) -> bool { public.verify(hash, signature).unwrap_or(false) } fn check_signature(&mut self, _: &Signature, _: &Public, _: &Script, _: u32) -> bool { false } fn check_lock_time(&self, _: Num) -> bool { false } fn check_sequence(&self, _: Num) -> bool { false } } #[derive(Debug)] pub struct TransactionSignatureChecker { pub signer: TransactionInputSigner, pub input_index: usize, pub input_amount: u64, pub consensus_branch_id: u32, pub cache: SighashCache, } impl SignatureChecker for TransactionSignatureChecker { fn verify_signature(&self, signature: &Signature, public: &Public, hash: &Message) -> bool { public.verify(hash, signature).unwrap_or(false) } fn check_signature( &mut self, signature: &Signature, public: &Public, script_code: &Script, sighashtype: u32, ) -> bool { let hash = self.signer.signature_hash( &mut self.cache, Some(self.input_index), self.input_amount, script_code, sighashtype, self.consensus_branch_id, ); self.verify_signature(signature, public, &hash) } fn check_lock_time(&self, lock_time: Num) -> bool { // There are two kinds of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether // nLockTime < LOCKTIME_THRESHOLD. // // We want to compare apples to apples, so fail the script // unless the type of nLockTime being tested is the same as // the nLockTime in the transaction. let lock_time_u32: u32 = lock_time.into(); if !((self.signer.lock_time < LOCKTIME_THRESHOLD && lock_time_u32 < LOCKTIME_THRESHOLD) || (self.signer.lock_time >= LOCKTIME_THRESHOLD && lock_time_u32 >= LOCKTIME_THRESHOLD)) { return false; } // Now that we know we're comparing apples-to-apples, the // comparison is a simple numeric one. if i64::from(lock_time) > self.signer.lock_time as i64 { return false; } // Finally the nLockTime feature can be disabled and thus // CHECKLOCKTIMEVERIFY bypassed if every txin has been // finalized by setting nSequence to maxint. The // transaction would be allowed into the blockchain, making // the opcode ineffective. // // Testing if this vin is not final is sufficient to // prevent this condition. Alternatively we could test all // inputs, but testing just this input minimizes the data // required to prove correct CHECKLOCKTIMEVERIFY execution. SEQUENCE_FINAL != self.signer.inputs[self.input_index].sequence } fn check_sequence(&self, sequence: Num) -> bool { // Relative lock times are supported by comparing the passed // in operand to the sequence number of the input. let to_sequence: i64 = self.signer.inputs[self.input_index].sequence as i64; // Fail if the transaction's version number is not set high // enough to trigger BIP 68 rules. if (self.signer.version as u32) < 2 { return false; } // Sequence numbers with their most significant bit set are not // consensus constrained. Testing that the transaction's sequence // number do not have this bit set prevents using this property // to get around a CHECKSEQUENCEVERIFY check. if to_sequence & SEQUENCE_LOCKTIME_DISABLE_FLAG as i64 != 0 { return false; } // Mask off any bits that do not have consensus-enforced meaning // before doing the integer comparisons let locktime_mask: u32 = SEQUENCE_LOCKTIME_TYPE_FLAG | SEQUENCE_LOCKTIME_MASK; let to_sequence_masked: i64 = to_sequence & locktime_mask as i64; let sequence_masked: i64 = i64::from(sequence) & locktime_mask as i64; // There are two kinds of nSequence: lock-by-blockheight // and lock-by-blocktime, distinguished by whether // nSequenceMasked < CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG. // We want to compare apples to apples, so fail the script // unless the type of nSequenceMasked being tested is the same as // the nSequenceMasked in the transaction. if !((to_sequence_masked < SEQUENCE_LOCKTIME_TYPE_FLAG as i64 && sequence_masked < SEQUENCE_LOCKTIME_TYPE_FLAG as i64) || (to_sequence_masked >= SEQUENCE_LOCKTIME_TYPE_FLAG as i64 && sequence_masked >= SEQUENCE_LOCKTIME_TYPE_FLAG as i64)) { return false; } // Now that we know we're comparing apples-to-apples, the // comparison is a simple numeric one. sequence_masked <= to_sequence_masked } }