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#[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#[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
45pub trait SignatureChecker {
47 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 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 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 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 fn check_lock_time(&self, lock_time: ScriptNum) -> bool;
140
141 fn check_sequence(&self, sequence: ScriptNum) -> bool;
144}
145
146pub 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
181pub 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
214pub 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 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#[derive(Debug)]
241enum BaseSighashScript<'a> {
242 Original(&'a Script),
244 Sanitized(ScriptBuf),
246}
247
248impl BaseSighashScript<'_> {
249 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 last_pos = pos + 1;
284 }
285 Instruction::Op(_) => {
286 sanitized_script.push(original_bytes[pos]);
288 last_pos = pos + 1;
289 }
290 Instruction::PushBytes(data) => {
291 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 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 const SIGHASH_MASK: u32 = 0x1f;
320
321 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 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 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 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 self.tx.input[self.input_index].sequence != SEQUENCE_FINAL
478 }
479
480 fn check_sequence(&self, sequence: ScriptNum) -> bool {
492 if self.tx.version < Version::TWO {
495 return false;
496 }
497
498 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 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 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}