1#![allow(clippy::arithmetic_side_effects)]
2extern crate alloc;
3extern crate core;
4pub mod caches;
5pub mod covenants;
6mod data_stack;
7pub mod error;
8pub mod hex;
9pub mod opcodes;
10pub mod result;
11pub mod script_builder;
12pub mod script_class;
13pub mod standard;
14#[cfg(feature = "wasm32-sdk")]
15pub mod wasm;
16pub mod zk_precompiles;
17
18pub mod runtime_resource_meter;
19
20pub mod zk_deps {
25 pub use ark_bn254;
26 pub use ark_ec;
27 pub use ark_groth16;
28 pub use ark_relations;
29 pub use ark_serialize;
30 pub use ark_snark;
31 pub use risc0_binfmt;
32 pub use risc0_circuit_recursion;
33 pub use risc0_core;
34 pub use risc0_zkp;
35}
36
37use std::io::Write;
38use std::ops::Deref;
39
40use crate::caches::Cache;
41use crate::covenants::CovenantsContext;
42use crate::data_stack::{Stack, StackEntry};
43use crate::opcodes::{OpCodeImplementation, deserialize_next_opcode};
44use crate::zk_precompiles::compute_zk_cost;
45use crate::zk_precompiles::tags::ZkTag;
46use itertools::Itertools;
47use kaspa_consensus_core::hashing::sighash::{
48 SigHashReusedValues, SigHashReusedValuesUnsync, calc_ecdsa_signature_hash, calc_schnorr_signature_hash,
49};
50use kaspa_consensus_core::hashing::sighash_type::SigHashType;
51use kaspa_consensus_core::mass::{Gram, ScriptUnits};
52use kaspa_consensus_core::tx::{PopulatedTransaction, ScriptPublicKey, TransactionInput, UtxoEntry, VerifiableTransaction};
53use kaspa_hashes::Hash;
54use kaspa_txscript_errors::TxScriptError;
55use kaspa_utils::hex::ToHex;
56use log::trace;
57use opcodes::codes::OpReturn;
58use opcodes::{OpCond, codes, to_small_int};
59use script_class::ScriptClass;
60
61pub mod prelude {
62 pub use super::standard::*;
63}
64pub use crate::data_stack::{deserialize_i64, serialize_i64};
65use crate::runtime_resource_meter::RuntimeResourceMeter;
66pub use crate::seq_commit_accessor::SeqCommitAccessor;
67pub use standard::*;
68
69pub mod seq_commit_accessor;
70
71pub mod engine_context;
72
73pub(crate) use engine_context::EngineContext;
74pub use engine_context::{EngineCtx, EngineCtxSync, EngineCtxUnsync};
75
76pub const MAX_SCRIPT_PUBLIC_KEY_VERSION: u16 = 0;
77pub const MAX_STACK_SIZE: usize = 244;
78pub const MAX_SCRIPTS_SIZE: usize = 1_000_000;
79pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 1_000_000;
80pub const MAX_OPS_PER_SCRIPT: i32 = 1_000_000;
81pub const MAX_TX_IN_SEQUENCE_NUM: u64 = u64::MAX;
82pub const SEQUENCE_LOCK_TIME_DISABLED: u64 = 1 << 63;
83pub const SEQUENCE_LOCK_TIME_MASK: u64 = 0x00000000ffffffff;
84pub const LOCK_TIME_THRESHOLD: u64 = 500_000_000_000;
85pub const MAX_PUB_KEYS_PER_MUTLTISIG: i32 = 20;
86
87const STANDARD_SCRIPT_PUB_KEY_MAX_SIZE: usize = 35;
88
89pub const NO_COST_OPCODE: u8 = 0x60;
92
93pub const ZERO_SIG: &[u8] = &[0u8; 64];
94
95pub type DynOpcodeImplementation<Tx, Reused> = Box<dyn OpCodeImplementation<Tx, Reused>>;
96
97#[derive(Clone, Hash, PartialEq, Eq)]
98enum Signature {
99 Secp256k1(secp256k1::schnorr::Signature),
100 Ecdsa(secp256k1::ecdsa::Signature),
101}
102
103#[derive(Clone, Hash, PartialEq, Eq)]
104enum PublicKey {
105 Schnorr(secp256k1::XOnlyPublicKey),
106 Ecdsa(secp256k1::PublicKey),
107}
108
109#[derive(Clone, Hash, PartialEq, Eq)]
111pub struct SigCacheKey {
112 signature: Signature,
113 pub_key: PublicKey,
114 message: secp256k1::Message,
115}
116
117enum ScriptSource<'a, T: VerifiableTransaction> {
118 TxInput { tx: &'a T, input: &'a TransactionInput, idx: usize, utxo_entry: &'a UtxoEntry, is_p2sh: bool },
119 StandAloneScripts(Vec<&'a [u8]>),
120}
121
122#[derive(Copy, Clone)]
123pub struct EngineFlags {
124 pub sigop_script_units: ScriptUnits,
125}
126
127impl Default for EngineFlags {
128 fn default() -> Self {
129 Self { sigop_script_units: Gram(1000).into() }
130 }
131}
132
133impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> Deref for TxScriptEngine<'a, T, Reused> {
134 type Target = EngineContext<'a, Reused>;
135 fn deref(&self) -> &Self::Target {
136 &self.ctx
137 }
138}
139
140pub struct TxScriptEngine<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> {
141 dstack: Stack,
142 astack: Stack,
143
144 script_source: ScriptSource<'a, T>,
145
146 ctx: EngineContext<'a, Reused>,
148
149 cond_stack: Vec<OpCond>, num_ops: i32,
152 runtime_resource_meter: RuntimeResourceMeter,
153 opcode_execution_log_buffer: Option<&'a mut dyn Write>,
154}
155
156pub struct ExecutionStacks {
158 pub dstack: Vec<StackEntry>,
160 pub astack: Vec<StackEntry>,
162}
163
164pub struct ExecutionStacksView<'a> {
166 pub dstack: &'a [StackEntry],
167 pub astack: &'a [StackEntry],
168}
169
170pub fn parse_script<T: VerifiableTransaction, Reused: SigHashReusedValues>(
171 script: &[u8],
172) -> impl Iterator<Item = Result<DynOpcodeImplementation<T, Reused>, TxScriptError>> + '_ {
173 script.iter().batching(|it| deserialize_next_opcode(it))
174}
175
176pub fn script_to_str(script: &[u8]) -> Result<String, TxScriptError> {
177 parse_script::<PopulatedTransaction<'_>, SigHashReusedValuesUnsync>(script)
178 .map(|op| op.map(|opcode| opcode.to_string()))
179 .collect::<Result<Vec<_>, _>>()
180 .map(|opcodes| opcodes.join(" "))
181}
182
183pub fn get_sig_op_count<T: VerifiableTransaction>(
215 tx: &T,
216 input_idx: usize,
217 covenants_ctx: &CovenantsContext,
218 seq_commit_accessor: Option<&dyn SeqCommitAccessor>,
219) -> Result<u16, TxScriptError> {
220 let sig_cache = Cache::new(0);
221 let reused_values = SigHashReusedValuesUnsync::new();
222 let ctx = EngineCtx::new(&sig_cache)
223 .with_reused(&reused_values)
224 .with_covenants_ctx(covenants_ctx)
225 .with_seq_commit_accessor_opt(seq_commit_accessor);
226 let mut vm = TxScriptEngine::from_transaction_input(
227 tx,
228 &tx.inputs()[input_idx],
229 input_idx,
230 tx.utxo(input_idx).ok_or_else(|| TxScriptError::InvalidInputIndex(input_idx as i32, tx.inputs().len()))?,
231 ctx,
232 Default::default(),
233 );
234 vm.execute()?;
235 Ok(vm.used_sig_ops())
236}
237
238pub fn p2sh_sig_scanner(signature_script: &[u8], spk: &ScriptPublicKey) -> u64 {
243 if !ScriptClass::is_pay_to_script_hash(spk.script()) {
244 return 0;
245 }
246
247 let Ok(signature_script_ops) =
248 parse_script::<PopulatedTransaction, SigHashReusedValuesUnsync>(signature_script).collect::<Result<Vec<_>, _>>()
249 else {
250 return 0;
251 };
252 let Some(p2sh_script_op) = signature_script_ops.last() else {
253 return 0;
254 };
255 let p2sh_script = p2sh_script_op.get_data();
256 let mut sigops = 0u64;
257 let mut prev_opcode_multisig_count = None;
258 for op in parse_script::<PopulatedTransaction, SigHashReusedValuesUnsync>(p2sh_script) {
259 let Ok(op) = op else {
260 return 0;
261 };
262 let opcode_value = op.value();
263 let multisig_count = prev_opcode_multisig_count.take();
264 match opcode_value {
265 codes::OpCheckSig
267 | codes::OpCheckSigVerify
268 | codes::OpCheckSigECDSA
269 | codes::OpCheckSigFromStack
270 | codes::OpCheckSigFromStackECDSA => sigops = sigops.saturating_add(1),
271 codes::OpCheckMultiSig | codes::OpCheckMultiSigVerify | codes::OpCheckMultiSigECDSA => {
272 sigops = sigops.saturating_add(multisig_count.unwrap_or(MAX_PUB_KEYS_PER_MUTLTISIG as u64));
273 }
274 codes::Op1..=codes::Op16 => {
275 prev_opcode_multisig_count = Some((opcode_value - codes::Op1 + 1) as u64);
276 }
277 ..=codes::OpPushData4 => {
278 prev_opcode_multisig_count = deserialize_i64(op.get_data())
279 .ok()
280 .filter(|count| (1..=MAX_PUB_KEYS_PER_MUTLTISIG as i64).contains(count))
281 .map(|count| count as u64);
282 }
283 _ => {}
284 }
285 }
286
287 sigops
288}
289
290#[must_use]
307pub fn get_sig_op_count_upper_bound<T: VerifiableTransaction, Reused: SigHashReusedValues>(
308 signature_script: &[u8],
309 prev_script_public_key: &ScriptPublicKey,
310) -> u64 {
311 let is_p2sh = ScriptClass::is_pay_to_script_hash(prev_script_public_key.script());
312 let script_pub_key_ops = parse_script::<T, Reused>(prev_script_public_key.script()).collect_vec();
313 if !is_p2sh {
314 return get_sig_op_count_by_opcodes(&script_pub_key_ops);
315 }
316
317 let signature_script_ops = parse_script::<T, Reused>(signature_script).collect_vec();
320 if signature_script_ops.is_empty() || signature_script_ops.iter().any(|op| op.is_err()) {
321 return 0;
322 }
323
324 let p2sh_script = signature_script_ops.last().expect("checked if empty above").as_ref().expect("checked if err above").get_data();
325 let p2sh_ops = parse_script::<T, Reused>(p2sh_script).collect_vec();
326
327 get_sig_op_count_by_opcodes(&p2sh_ops)
328}
329
330pub fn estimate_script_units_upper_bound<T: VerifiableTransaction, Reused: SigHashReusedValues>(
331 signature_script: &[u8],
332 prev_script_public_key: &ScriptPublicKey,
333 sigop_script_units: u64,
334) -> ScriptUnits {
335 let sig_op_upper_bound = get_sig_op_count_upper_bound::<T, Reused>(signature_script, prev_script_public_key);
336 let sig_op_units = ScriptUnits(sig_op_upper_bound.saturating_mul(sigop_script_units));
337 let total_script_len = (signature_script.len() + prev_script_public_key.script().len()) as u64;
338 let zk_units = get_zk_script_units_upper_bound::<T, Reused>(signature_script, prev_script_public_key);
339
340 sig_op_units.saturating_add((total_script_len * 100).into()) .saturating_add(zk_units)
342}
343
344pub fn get_zk_script_units_upper_bound<T: VerifiableTransaction, Reused: SigHashReusedValues>(
345 signature_script: &[u8],
346 prev_script_public_key: &ScriptPublicKey,
347) -> ScriptUnits {
348 let is_p2sh = ScriptClass::is_pay_to_script_hash(prev_script_public_key.script());
349 let script_pub_key_ops = parse_script::<T, Reused>(prev_script_public_key.script()).collect_vec();
350 if !is_p2sh {
351 return get_zk_script_units_upper_bound_by_opcodes(&script_pub_key_ops);
352 }
353
354 let signature_script_ops = parse_script::<T, Reused>(signature_script).collect_vec();
357 if signature_script_ops.is_empty() || signature_script_ops.iter().any(|op| op.is_err()) {
358 return ScriptUnits(0);
359 }
360
361 let p2sh_script = signature_script_ops.last().expect("checked if empty above").as_ref().expect("checked if err above").get_data();
362 let p2sh_ops = parse_script::<T, Reused>(p2sh_script).collect_vec();
363
364 get_zk_script_units_upper_bound_by_opcodes(&p2sh_ops)
365}
366
367fn get_zk_script_units_upper_bound_by_opcodes<T: VerifiableTransaction, Reused: SigHashReusedValues>(
368 opcodes: &[Result<DynOpcodeImplementation<T, Reused>, TxScriptError>],
369) -> ScriptUnits {
370 let mut zk_units: ScriptUnits = ScriptUnits(0);
371
372 for (i, op) in opcodes.iter().enumerate() {
373 match op {
374 Ok(op) => {
375 if op.value() == codes::OpZkPrecompile {
376 let cost = if i == 0 {
377 ZkTag::max_cost()
378 } else {
379 let prev_opcode = opcodes[i - 1].as_ref().expect("checked above");
380 let data = prev_opcode.get_data();
381 data.first().copied().map_or_else(ZkTag::max_cost, compute_zk_cost)
382 };
383 zk_units = zk_units.saturating_add(cost);
384 }
385 }
386 Err(_) => return zk_units, }
388 }
389
390 zk_units
391}
392
393fn get_sig_op_count_by_opcodes<T: VerifiableTransaction, Reused: SigHashReusedValues>(
394 opcodes: &[Result<DynOpcodeImplementation<T, Reused>, TxScriptError>],
395) -> u64 {
396 let mut num_sigs: u64 = 0;
398 for (i, op) in opcodes.iter().enumerate() {
399 match op {
400 Ok(op) => {
401 match op.value() {
402 codes::OpCheckSig | codes::OpCheckSigVerify | codes::OpCheckSigECDSA => num_sigs += 1,
403 codes::OpCheckMultiSig | codes::OpCheckMultiSigVerify | codes::OpCheckMultiSigECDSA => {
404 if i == 0 {
405 num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
406 continue;
407 }
408
409 let prev_opcode = opcodes[i - 1].as_ref().expect("checked above");
410 if prev_opcode.value() >= codes::OpTrue && prev_opcode.value() <= codes::Op16 {
411 num_sigs += to_small_int(prev_opcode) as u64;
412 } else {
413 num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
414 }
415 }
416 _ => {} }
418 }
419 Err(_) => return num_sigs,
420 }
421 }
422 num_sigs
423}
424
425pub fn is_unspendable<T: VerifiableTransaction, Reused: SigHashReusedValues>(script: &[u8]) -> bool {
429 parse_script::<T, Reused>(script).enumerate().any(|(index, op)| op.is_err() || (index == 0 && op.unwrap().value() == OpReturn))
430}
431
432enum ScriptExecutionOutput {
433 Executed,
434 AcceptedUnknownVersion,
435}
436
437impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<'a, T, Reused> {
438 pub fn new(ctx: EngineContext<'a, Reused>, flags: EngineFlags) -> Self {
439 let runtime_resource_meter = RuntimeResourceMeter::new_script_units(flags.sigop_script_units, ScriptUnits(u64::MAX));
440 Self {
441 dstack: Self::new_stack(),
442 astack: Self::new_stack(),
443 script_source: ScriptSource::StandAloneScripts(vec![]),
444 ctx,
445 cond_stack: vec![],
446 num_ops: 0,
447 runtime_resource_meter,
448 opcode_execution_log_buffer: None,
449 }
450 }
451
452 fn new_stack() -> Stack {
453 Stack::new(vec![])
454 }
455
456 pub fn used_sig_ops(&self) -> u16 {
458 self.runtime_resource_meter.used_sig_ops()
459 }
460
461 pub fn used_script_units(&self) -> ScriptUnits {
463 self.runtime_resource_meter.used_script_units()
464 }
465
466 fn pop_pushed_bytes(&mut self) -> u64 {
468 self.dstack.pop_pushed_bytes().saturating_add(self.astack.pop_pushed_bytes())
469 }
470
471 pub fn with_opcode_execution_log_buffer(mut self, buffer: &'a mut dyn Write) -> Self {
472 self.opcode_execution_log_buffer = Some(buffer);
473 self
474 }
475
476 pub fn stacks(&self) -> ExecutionStacksView<'_> {
478 ExecutionStacksView { dstack: &self.dstack, astack: &self.astack }
479 }
480
481 pub fn from_transaction_input(
498 tx: &'a T,
499 input: &'a TransactionInput,
500 input_idx: usize,
501 utxo_entry: &'a UtxoEntry,
502 ctx: EngineContext<'a, Reused>,
503 flags: EngineFlags,
504 ) -> Self {
505 Self::from_transaction_input_with_script_units_limit(tx, input, input_idx, utxo_entry, ctx, flags, ScriptUnits(u64::MAX))
506 }
507
508 pub fn from_transaction_input_with_script_units_limit(
509 tx: &'a T,
510 input: &'a TransactionInput,
511 input_idx: usize,
512 utxo_entry: &'a UtxoEntry,
513 ctx: EngineContext<'a, Reused>,
514 flags: EngineFlags,
515 script_units_limit: ScriptUnits,
516 ) -> Self {
517 let runtime_resource_meter = RuntimeResourceMeter::new_script_units(flags.sigop_script_units, script_units_limit);
518 let script_public_key = utxo_entry.script_public_key.script();
519 let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key);
522 assert!(input_idx < tx.tx().inputs.len());
523 Self {
524 dstack: Self::new_stack(),
525 astack: Self::new_stack(),
526 script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh },
527 ctx,
528 cond_stack: Default::default(),
529 num_ops: 0,
530 runtime_resource_meter,
531 opcode_execution_log_buffer: None,
532 }
533 }
534
535 pub fn from_script(
536 script: &'a [u8],
537 reused_values: &'a Reused,
538 sig_cache: &'a Cache<SigCacheKey, bool>,
539 flags: EngineFlags,
540 ) -> Self {
541 Self::from_script_with_script_units_limit(script, reused_values, sig_cache, ScriptUnits(u64::MAX), flags)
542 }
543
544 pub fn from_script_with_script_units_limit(
545 script: &'a [u8],
546 reused_values: &'a Reused,
547 sig_cache: &'a Cache<SigCacheKey, bool>,
548 script_units_limit: ScriptUnits,
549 flags: EngineFlags,
550 ) -> Self {
551 let runtime_resource_meter = RuntimeResourceMeter::new_script_units(flags.sigop_script_units, script_units_limit);
552 Self {
553 dstack: Self::new_stack(),
554 astack: Self::new_stack(),
555 script_source: ScriptSource::StandAloneScripts(vec![script]),
556 ctx: EngineCtx::new(sig_cache).with_reused(reused_values),
557 cond_stack: Default::default(),
558 num_ops: 0,
559 runtime_resource_meter,
560 opcode_execution_log_buffer: None,
561 }
562 }
563
564 fn consume_script_units(&mut self, units: ScriptUnits) -> Result<(), TxScriptError> {
565 self.runtime_resource_meter.consume_script_units(units)
566 }
567
568 fn consume_sig_op_cost(&mut self, count: u16) -> Result<(), TxScriptError> {
569 self.runtime_resource_meter.consume_sig_op_cost(count)
570 }
571
572 #[inline]
573 pub fn is_executing(&self) -> bool {
574 self.cond_stack.is_empty() || *self.cond_stack.last().expect("Checked not empty") == OpCond::True
575 }
576
577 pub fn execute_opcode(&mut self, opcode: DynOpcodeImplementation<T, Reused>) -> Result<(), TxScriptError> {
578 self.print_opcode_execution(&opcode);
579
580 if !opcode.is_push_opcode() {
583 self.num_ops += 1;
584 if self.num_ops > MAX_OPS_PER_SCRIPT {
585 return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
586 }
587 } else if opcode.len() > MAX_SCRIPT_ELEMENT_SIZE {
588 return Err(TxScriptError::ElementTooBig(opcode.len(), MAX_SCRIPT_ELEMENT_SIZE));
589 }
590
591 if self.is_executing() || opcode.is_conditional() {
592 opcode.execute(self)?;
593 let pushed_bytes = self.pop_pushed_bytes();
594 self.runtime_resource_meter.charge_newly_pushed_bytes(pushed_bytes)?;
595 Ok(())
596 } else {
597 Ok(())
598 }
599 }
600
601 fn print_opcode_execution(&mut self, opcode: &DynOpcodeImplementation<T, Reused>) {
602 let Some(buffer) = self.opcode_execution_log_buffer.as_mut() else {
603 return;
604 };
605
606 let format_stack = |stack: &Stack| stack.iter().map(|element| format!("0x{}", element.to_hex())).collect::<Vec<_>>();
607
608 writeln!(
609 buffer,
610 "Executing opcode: {}, astack: {:?}, dstack: {:?}",
611 opcode,
612 format_stack(&self.astack),
613 format_stack(&self.dstack)
614 )
615 .unwrap();
616 }
617
618 fn execute_script(&mut self, script: &[u8], verify_only_push: bool) -> Result<(), TxScriptError> {
619 let script_result = parse_script(script).try_for_each(|opcode| {
620 let opcode = opcode?;
621 if opcode.is_disabled() {
622 return Err(TxScriptError::OpcodeDisabled(format!("{:?}", opcode)));
623 }
624
625 if opcode.always_illegal() {
626 return Err(TxScriptError::OpcodeReserved(format!("{:?}", opcode)));
627 }
628
629 if verify_only_push && !opcode.is_push_opcode() {
630 return Err(TxScriptError::SignatureScriptNotPushOnly);
631 }
632
633 self.execute_opcode(opcode)?;
634
635 let combined_size = self.astack.len() + self.dstack.len();
636 if combined_size > MAX_STACK_SIZE {
637 return Err(TxScriptError::StackSizeExceeded(combined_size, MAX_STACK_SIZE));
638 }
639 Ok(())
640 });
641
642 if script_result.is_ok() && !self.cond_stack.is_empty() {
644 return Err(TxScriptError::ErrUnbalancedConditional);
645 }
646
647 self.astack.clear();
649 self.num_ops = 0; script_result
652 }
653
654 fn execute_inner(&mut self) -> Result<ScriptExecutionOutput, TxScriptError> {
655 let (scripts, is_p2sh, utxo_spk_script_units) = match &self.script_source {
656 ScriptSource::TxInput { input, utxo_entry, is_p2sh, .. } => {
657 if utxo_entry.script_public_key.version() > MAX_SCRIPT_PUBLIC_KEY_VERSION {
658 trace!("The version of the scriptPublicKey is higher than the known version - the Execute function returns true.");
659 return Ok(ScriptExecutionOutput::AcceptedUnknownVersion);
660 }
661
662 let utxo_spk_script_grams =
666 utxo_entry.script_public_key.script().len().saturating_sub(STANDARD_SCRIPT_PUB_KEY_MAX_SIZE);
667
668 (
669 vec![input.signature_script.as_slice(), utxo_entry.script_public_key.script()],
670 *is_p2sh,
671 Gram(utxo_spk_script_grams as u64).into(),
672 )
673 }
674 ScriptSource::StandAloneScripts(scripts) => (scripts.clone(), false, 0.into()),
675 };
676
677 self.consume_script_units(utxo_spk_script_units)?;
678
679 if scripts.is_empty() {
685 return Err(TxScriptError::NoScripts);
686 }
687
688 if scripts.iter().all(|e| e.is_empty()) {
689 return Err(TxScriptError::EvalFalse);
690 }
691 if let Some(s) = scripts.iter().find(|e| e.len() > MAX_SCRIPTS_SIZE) {
692 return Err(TxScriptError::ScriptSize(s.len(), MAX_SCRIPTS_SIZE));
693 }
694
695 let mut saved_stack: Option<Stack> = None;
696 scripts.iter().enumerate().filter(|(_, s)| !s.is_empty()).try_for_each(|(idx, s)| {
699 let verify_only_push = idx == 0 && matches!(self.script_source, ScriptSource::TxInput { .. });
700 if is_p2sh && idx == 1 {
702 saved_stack = Some(self.dstack.clone());
703 }
704 self.execute_script(s, verify_only_push)
705 })?;
706
707 if is_p2sh {
708 self.check_error_condition(false)?;
709 self.dstack = saved_stack.ok_or(TxScriptError::EmptyStack)?;
710 let script = self.dstack.pop()?;
711 self.execute_script(script.as_slice(), false)?
712 }
713 Ok(ScriptExecutionOutput::Executed)
714 }
715
716 pub fn execute(&mut self) -> Result<(), TxScriptError> {
717 match self.execute_inner()? {
718 ScriptExecutionOutput::Executed => self.check_error_condition(true),
719 ScriptExecutionOutput::AcceptedUnknownVersion => Ok(()),
721 }
722 }
723
724 pub fn execute_and_return_stacks(mut self) -> Result<ExecutionStacks, TxScriptError> {
726 let _ = self.execute_inner()?;
727 Ok(ExecutionStacks { dstack: self.dstack.into(), astack: self.astack.into() })
728 }
729
730 #[inline]
735 fn check_error_condition(&mut self, final_script: bool) -> Result<(), TxScriptError> {
736 if final_script {
737 if self.dstack.len() > 1 {
738 return Err(TxScriptError::CleanStack(self.dstack.len() - 1));
739 } else if self.dstack.is_empty() {
740 return Err(TxScriptError::EmptyStack);
741 }
742 }
743
744 let [v]: [bool; 1] = self.dstack.pop_items()?;
745 match v {
746 true => Ok(()),
747 false => Err(TxScriptError::EvalFalse),
748 }
749 }
750
751 fn check_pub_key_encoding_ecdsa(pub_key: &[u8]) -> Result<(), TxScriptError> {
754 match pub_key.len() {
755 33 => Ok(()),
756 _ => Err(TxScriptError::PubKeyFormat),
757 }
758 }
759
760 fn op_check_multisig_schnorr_or_ecdsa(&mut self, ecdsa: bool) -> Result<(), TxScriptError> {
761 let [num_keys]: [i32; 1] = self.dstack.pop_items()?;
762 if num_keys < 0 {
763 return Err(TxScriptError::InvalidPubKeyCount(format!("number of pubkeys {num_keys} is negative")));
764 } else if num_keys > MAX_PUB_KEYS_PER_MUTLTISIG {
765 return Err(TxScriptError::InvalidPubKeyCount(format!("too many pubkeys {num_keys} > {MAX_PUB_KEYS_PER_MUTLTISIG}")));
766 }
767 let num_keys_usize = num_keys as usize;
768
769 self.num_ops += num_keys;
770 if self.num_ops > MAX_OPS_PER_SCRIPT {
771 return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
772 }
773
774 let pub_keys = match self.dstack.len() >= num_keys_usize {
775 true => self.dstack.split_off(self.dstack.len() - num_keys_usize),
776 false => return Err(TxScriptError::InvalidStackOperation(num_keys_usize, self.dstack.len())),
777 };
778
779 let [num_sigs]: [i32; 1] = self.dstack.pop_items()?;
780 if num_sigs < 0 {
781 return Err(TxScriptError::InvalidSignatureCount(format!("number of signatures {num_sigs} is negative")));
782 } else if num_sigs > num_keys {
783 return Err(TxScriptError::InvalidSignatureCount(format!("more signatures than pubkeys {num_sigs} > {num_keys}")));
784 }
785 let num_sigs = num_sigs as usize;
786
787 let signatures = match self.dstack.len() >= num_sigs {
788 true => self.dstack.split_off(self.dstack.len() - num_sigs),
789 false => return Err(TxScriptError::InvalidStackOperation(num_sigs, self.dstack.len())),
790 };
791
792 let mut failed = false;
793 let mut pub_key_iter = pub_keys.iter();
794 'outer: for (sig_idx, signature) in signatures.iter().enumerate() {
795 if signature.is_empty() {
796 failed = true;
797 break;
798 }
799
800 let typ = *signature.last().expect("checked that is not empty");
801 let signature = &signature[..signature.len() - 1];
802 let hash_type = SigHashType::from_u8(typ).map_err(|_| TxScriptError::InvalidSigHashType(typ))?;
803
804 loop {
807 if pub_key_iter.len() < num_sigs - sig_idx {
808 failed = true;
812 break 'outer; }
814 let pub_key = pub_key_iter.next().unwrap();
816
817 let check_signature_result = if ecdsa {
819 self.check_ecdsa_signature(hash_type, pub_key.as_slice(), signature)
820 } else {
821 self.check_schnorr_signature(hash_type, pub_key.as_slice(), signature)
822 };
823
824 match check_signature_result {
825 Ok(valid) => {
826 if valid {
827 break;
829 }
830 }
831 Err(e) => {
832 return Err(e);
833 }
834 }
835 }
836 }
837
838 if failed && signatures.iter().any(|sig| !sig.is_empty()) {
839 return Err(TxScriptError::NullFail);
840 }
841
842 self.dstack.push_item(!failed)?;
843 Ok(())
844 }
845
846 #[inline]
847 fn check_schnorr_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
848 match self.script_source {
849 ScriptSource::TxInput { tx, idx, .. } => self.check_schnorr_signature_with_msg_hash(key, sig, |reused_values| {
850 calc_schnorr_signature_hash(tx, idx, hash_type, reused_values)
851 }),
852 _ => Err(TxScriptError::NotATransactionInput),
853 }
854 }
855
856 fn check_schnorr_signature_with_msg_hash<F>(&mut self, key: &[u8], sig: &[u8], msg_hash: F) -> Result<bool, TxScriptError>
857 where
858 F: FnOnce(&Reused) -> Hash,
859 {
860 self.consume_sig_op_cost(1)?;
861 let pk = secp256k1::XOnlyPublicKey::from_slice(key).map_err(TxScriptError::InvalidPubkey)?;
862 let sig = secp256k1::schnorr::Signature::from_slice(sig).map_err(TxScriptError::InvalidSignature)?;
863 let msg_hash = msg_hash(self.reused_values);
864 let secp_msg = secp256k1::Message::from_digest(msg_hash.into());
865 let sig_cache_key = SigCacheKey { signature: Signature::Secp256k1(sig), pub_key: PublicKey::Schnorr(pk), message: secp_msg };
866
867 if let Some(valid) = self.sig_cache.get(&sig_cache_key) {
868 Ok(valid)
869 } else {
870 let valid = sig.verify(&secp_msg, &pk).is_ok();
871 self.sig_cache.insert(sig_cache_key, valid);
872 Ok(valid)
873 }
874 }
875
876 fn check_ecdsa_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
877 match self.script_source {
878 ScriptSource::TxInput { tx, idx, .. } => self.check_ecdsa_signature_with_msg_hash(key, sig, |reused_values| {
879 calc_ecdsa_signature_hash(tx, idx, hash_type, reused_values)
880 }),
881 _ => Err(TxScriptError::NotATransactionInput),
882 }
883 }
884
885 fn check_ecdsa_signature_with_msg_hash<F>(&mut self, key: &[u8], sig: &[u8], msg_hash: F) -> Result<bool, TxScriptError>
886 where
887 F: FnOnce(&Reused) -> Hash,
888 {
889 self.consume_sig_op_cost(1)?;
890 Self::check_pub_key_encoding_ecdsa(key)?;
891 let pk = secp256k1::PublicKey::from_slice(key).map_err(TxScriptError::InvalidPubkey)?;
892 let sig = secp256k1::ecdsa::Signature::from_compact(sig).map_err(TxScriptError::InvalidSignature)?;
893 let msg_hash = msg_hash(self.reused_values);
894 let secp_msg = secp256k1::Message::from_digest(msg_hash.into());
895 let sig_cache_key = SigCacheKey { signature: Signature::Ecdsa(sig), pub_key: PublicKey::Ecdsa(pk), message: secp_msg };
896
897 if let Some(valid) = self.sig_cache.get(&sig_cache_key) {
898 Ok(valid)
899 } else {
900 let valid = sig.verify(&secp_msg, &pk).is_ok();
901 self.sig_cache.insert(sig_cache_key, valid);
902 Ok(valid)
903 }
904 }
905}
906
907trait SpkEncoding {
908 fn to_bytes(&self) -> Vec<u8>;
909}
910
911impl SpkEncoding for ScriptPublicKey {
912 fn to_bytes(&self) -> Vec<u8> {
913 self.version.to_be_bytes().into_iter().chain(self.script().iter().copied()).collect()
914 }
915}
916
917#[cfg(test)]
918mod tests {
919 use std::iter::once;
920
921 use crate::opcodes::codes::{
922 OpBlake2b, OpBlake2bWithKey, OpBlake3, OpBlake3WithKey, OpCheckMultiSig, OpCheckSig, OpCheckSigECDSA, OpCheckSigFromStack,
923 OpCheckSigFromStackECDSA, OpCheckSigVerify, OpData1, OpData2, OpData32, OpDrop, OpDup, OpEndIf, OpEqual, OpFalse, OpIf, OpNop,
924 OpPushData1, OpSHA256, OpTrue, OpVerify,
925 };
926
927 use super::*;
928 use crate::script_builder::{ScriptBuilder, ScriptBuilderResult};
929 use kaspa_addresses::{Address, Prefix, Version};
930 use kaspa_consensus_core::config::params::MAINNET_PARAMS;
931 use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync;
932 use kaspa_consensus_core::hashing::sighash_type::SIG_HASH_ALL;
933 use kaspa_consensus_core::mass::{
934 ComputeBudget, SCRIPT_UNITS_PER_COMPUTE_BUDGET_UNIT, SCRIPT_UNITS_PER_GRAM, ScriptUnits, SigopCount,
935 };
936 use kaspa_consensus_core::tx::{
937 ComputeCommit, MutableTransaction, PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionInput,
938 TransactionOutpoint, TransactionOutput,
939 };
940 use kaspa_core::assert_match;
941 use kaspa_utils::hex::FromHex;
942 use smallvec::SmallVec;
943
944 struct ScriptTestCase {
945 script: &'static [u8],
946 expected_result: Result<(), TxScriptError>,
947 }
948
949 struct KeyTestCase {
950 name: &'static str,
951 key: &'static [u8],
952 is_valid: bool,
953 }
954
955 struct VerifiableTransactionMock {}
956
957 impl VerifiableTransaction for VerifiableTransactionMock {
958 fn tx(&self) -> &Transaction {
959 unimplemented!()
960 }
961
962 fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) {
963 unimplemented!()
964 }
965
966 fn utxo(&self, _index: usize) -> Option<&UtxoEntry> {
967 unimplemented!()
968 }
969 }
970
971 fn run_test_script_cases(test_cases: Vec<ScriptTestCase>) {
972 let sig_cache = Cache::new(10_000);
973 let reused_values = SigHashReusedValuesUnsync::new();
974
975 for test in test_cases {
976 let input = TransactionInput {
978 previous_outpoint: TransactionOutpoint {
979 transaction_id: TransactionId::from_bytes([
980 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x41, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2,
981 0x0b, 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04,
982 ]),
983 index: 0,
984 },
985 signature_script: vec![],
986 sequence: 4294967295,
987 compute_commit: ComputeBudget(0).into(),
988 };
989 let output = TransactionOutput {
990 value: 1000000000,
991 script_public_key: ScriptPublicKey::new(0, test.script.into()),
992 covenant: None,
993 };
994
995 let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]);
996 let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase(), None);
997
998 let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
999
1000 let mut vm = TxScriptEngine::from_transaction_input(
1001 &populated_tx,
1002 &input,
1003 0,
1004 &utxo_entry,
1005 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1006 Default::default(),
1007 );
1008 assert_eq!(vm.execute(), test.expected_result);
1009 }
1010 }
1011
1012 #[test]
1013 fn test_push_units_budget_enforced() {
1014 let sig_cache = Cache::new(10_000);
1015 let reused_values = SigHashReusedValuesUnsync::new();
1016 let script = ScriptBuilder::new().add_data(&[1u8, 2u8, 3u8]).unwrap().add_op(OpDup).unwrap().add_op(OpDrop).unwrap().drain();
1017 let tight_budget = 2.into();
1018 let exact_budget = 3.into();
1019
1020 let mut vm_tight_budget =
1021 TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1022 &script,
1023 &reused_values,
1024 &sig_cache,
1025 tight_budget,
1026 Default::default(),
1027 );
1028 assert_eq!(vm_tight_budget.execute(), Err(TxScriptError::ExceededCommittedScriptUnits { used: 3, limit: 2 }));
1029
1030 let mut vm_exact_budget =
1031 TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1032 &script,
1033 &reused_values,
1034 &sig_cache,
1035 exact_budget,
1036 Default::default(),
1037 );
1038 assert!(vm_exact_budget.execute().is_ok());
1039 assert_eq!(vm_exact_budget.used_script_units(), exact_budget);
1040 }
1041
1042 #[test]
1043 fn test_literal_data_pushes_do_not_consume_script_units() {
1044 let sig_cache = Cache::new(10_000);
1045 let reused_values = SigHashReusedValuesUnsync::new();
1046 let script = ScriptBuilder::new().add_data(&[1u8, 2u8, 3u8]).unwrap().drain();
1047
1048 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1049 &script,
1050 &reused_values,
1051 &sig_cache,
1052 0.into(),
1053 Default::default(),
1054 );
1055 assert!(vm.execute().is_ok());
1056 assert_eq!(vm.used_script_units(), 0.into());
1057 }
1058
1059 #[test]
1060 fn test_literal_number_pushes_do_not_consume_script_units() {
1061 let sig_cache = Cache::new(10_000);
1062 let reused_values = SigHashReusedValuesUnsync::new();
1063 let script = ScriptBuilder::new().add_op(OpTrue).unwrap().drain();
1064
1065 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1066 &script,
1067 &reused_values,
1068 &sig_cache,
1069 0.into(),
1070 Default::default(),
1071 );
1072 assert!(vm.execute().is_ok());
1073 assert_eq!(vm.used_script_units(), 0.into());
1074 }
1075
1076 #[test]
1077 fn test_direct_push_above_element_limit_is_rejected() {
1078 let sig_cache = Cache::new(10_000);
1079 let reused_values = SigHashReusedValuesUnsync::new();
1080 let element_size = MAX_SCRIPT_ELEMENT_SIZE + 1;
1081 let data = vec![0u8; element_size];
1082 let oversized_push = ScriptBuilder::new().add_data_unchecked(&data).drain();
1083 let non_executed_branch_script = {
1084 let mut builder = ScriptBuilder::new();
1085 builder.add_op(OpFalse).unwrap().add_op(OpIf).unwrap();
1086 builder.script_mut().extend_from_slice(&oversized_push);
1087 builder.script_mut().push(OpEndIf);
1088 builder.script_mut().push(OpTrue);
1089 builder.drain()
1090 };
1091 let too_many_ops_script = vec![OpNop; MAX_OPS_PER_SCRIPT as usize + 1];
1092 let non_executed_too_many_ops_script = {
1093 let mut builder = ScriptBuilder::new();
1094 builder.add_op(OpFalse).unwrap().add_op(OpIf).unwrap();
1095 builder.script_mut().extend(std::iter::repeat_n(OpNop, MAX_OPS_PER_SCRIPT as usize + 1));
1096 builder.script_mut().push(OpEndIf);
1097 builder.script_mut().push(OpTrue);
1098 builder.drain()
1099 };
1100 let oversized_script = vec![OpTrue; MAX_SCRIPTS_SIZE + 1];
1101
1102 for script in
1103 [oversized_push, non_executed_branch_script, too_many_ops_script, non_executed_too_many_ops_script, oversized_script]
1104 {
1105 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
1106 &script,
1107 &reused_values,
1108 &sig_cache,
1109 Default::default(),
1110 );
1111
1112 assert_eq!(vm.execute(), Err(TxScriptError::ScriptSize(script.len(), MAX_SCRIPTS_SIZE)));
1118 }
1119 }
1120
1121 #[test]
1122 fn test_used_script_units_can_drive_compute_budget_selection() {
1123 let sig_cache = Cache::new(10_000);
1124 let reused_values = SigHashReusedValuesUnsync::new();
1125 let flags = EngineFlags { sigop_script_units: 0.into() };
1126 let script = ScriptBuilder::with_flags(flags)
1127 .add_data(&vec![42u8; SCRIPT_UNITS_PER_COMPUTE_BUDGET_UNIT as usize])
1128 .unwrap()
1129 .add_op(OpDup)
1130 .unwrap()
1131 .add_op(OpDrop)
1132 .unwrap()
1133 .drain();
1134
1135 let mut unrestricted_vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
1136 &script,
1137 &reused_values,
1138 &sig_cache,
1139 flags,
1140 );
1141 assert!(unrestricted_vm.execute().is_ok());
1142 assert_eq!(unrestricted_vm.used_script_units(), SCRIPT_UNITS_PER_COMPUTE_BUDGET_UNIT.into());
1143
1144 let required_units = unrestricted_vm.used_script_units();
1145 let budget = ComputeBudget::checked_covering_script_units(required_units).expect("expected budget to fit");
1146 assert_eq!(budget, ComputeBudget(1));
1147
1148 let mut exact_vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1149 &script,
1150 &reused_values,
1151 &sig_cache,
1152 ComputeCommit::from(ComputeBudget(1)).allowed_script_units(),
1153 flags,
1154 );
1155 assert!(exact_vm.execute().is_ok());
1156
1157 let mut underbudget_vm =
1158 TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1159 &script,
1160 &reused_values,
1161 &sig_cache,
1162 ComputeCommit::from(ComputeBudget(0)).allowed_script_units(),
1163 flags,
1164 );
1165 assert_eq!(
1166 underbudget_vm.execute(),
1167 Err(TxScriptError::ExceededCommittedScriptUnits {
1168 used: SCRIPT_UNITS_PER_COMPUTE_BUDGET_UNIT,
1169 limit: SCRIPT_UNITS_PER_COMPUTE_BUDGET_UNIT - 1
1170 })
1171 );
1172 }
1173
1174 #[test]
1175 fn test_hash_opcodes_charge_hashed_data_bytes() {
1176 let sig_cache = Cache::new(10_000);
1177 let reused_values = SigHashReusedValuesUnsync::new();
1178 let data = vec![42u8; 11];
1179 let blake_key = vec![24u8; blake2b_simd::KEYBYTES];
1180 let blake3_key = vec![36u8; blake3::KEY_LEN];
1181
1182 let test_cases = [
1183 (
1184 "blake2b",
1185 ScriptBuilder::new().add_data(&data).unwrap().add_op(OpBlake2b).unwrap().drain(),
1186 ScriptUnits(data.len() as u64 * 2 + 32),
1187 ),
1188 (
1189 "blake2b_with_key",
1190 ScriptBuilder::new().add_data(&data).unwrap().add_data(&blake_key).unwrap().add_op(OpBlake2bWithKey).unwrap().drain(),
1191 ScriptUnits(data.len() as u64 * 2 + 32),
1192 ),
1193 (
1194 "blake3",
1195 ScriptBuilder::new().add_data(&data).unwrap().add_op(OpBlake3).unwrap().drain(),
1196 ScriptUnits(data.len() as u64 + 32),
1197 ),
1198 (
1199 "blake3_with_key",
1200 ScriptBuilder::new().add_data(&data).unwrap().add_data(&blake3_key).unwrap().add_op(OpBlake3WithKey).unwrap().drain(),
1201 ScriptUnits(data.len() as u64 + 32),
1202 ),
1203 (
1204 "sha256",
1205 ScriptBuilder::new().add_data(&data).unwrap().add_op(OpSHA256).unwrap().drain(),
1206 ScriptUnits(data.len() as u64 + 32),
1207 ),
1208 ];
1209
1210 for (name, script, expected_units) in test_cases {
1211 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1212 &script,
1213 &reused_values,
1214 &sig_cache,
1215 expected_units,
1216 Default::default(),
1217 );
1218 assert!(vm.execute().is_ok(), "{name}");
1219 assert_eq!(vm.used_script_units(), expected_units, "{name}");
1220
1221 let mut underbudget_vm =
1222 TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script_with_script_units_limit(
1223 &script,
1224 &reused_values,
1225 &sig_cache,
1226 expected_units.saturating_sub(ScriptUnits(1)),
1227 Default::default(),
1228 );
1229 assert!(matches!(underbudget_vm.execute(), Err(TxScriptError::ExceededCommittedScriptUnits { .. })), "{name}");
1230 }
1231 }
1232
1233 #[test]
1234 fn test_non_standard_utxo_script_pub_key_size_counts_toward_used_script_units() {
1235 let sig_cache = Cache::new(10_000);
1236 let reused_values = SigHashReusedValuesUnsync::new();
1237
1238 let tests = [
1239 (10usize, 11usize, 0u64),
1240 (33usize, 34usize, 0u64),
1241 (34usize, 35usize, 0u64),
1242 (35usize, 36usize, 100u64),
1243 (100usize, 102usize, 6_700u64),
1244 ];
1245
1246 for (pushed_bytes, expected_script_len, expected_used_units) in tests {
1247 let utxo_script = ScriptBuilder::new().add_data(&vec![1u8; pushed_bytes]).unwrap().drain();
1248 assert_eq!(utxo_script.len(), expected_script_len, "unexpected encoded script length for {pushed_bytes} pushed bytes");
1249
1250 let mass_per_byte = MAINNET_PARAMS.mass_per_tx_byte;
1251 assert_eq!(mass_per_byte, 1);
1252
1253 let script_units_per_byte = SCRIPT_UNITS_PER_GRAM * mass_per_byte;
1254
1255 assert_eq!(
1257 expected_used_units,
1258 (expected_script_len.saturating_sub(STANDARD_SCRIPT_PUB_KEY_MAX_SIZE)) as u64 * script_units_per_byte
1259 );
1260
1261 let input = TransactionInput {
1262 previous_outpoint: TransactionOutpoint {
1263 transaction_id: TransactionId::from_bytes([pushed_bytes as u8; 32]),
1264 index: 0,
1265 },
1266 signature_script: vec![],
1267 sequence: 0,
1268 compute_commit: SigopCount(0).into(),
1269 };
1270 let output =
1271 TransactionOutput { value: 1, script_public_key: ScriptPublicKey::new(0, vec![OpTrue].into()), covenant: None };
1272 let tx = Transaction::new(0, vec![input.clone()], vec![output], 0, Default::default(), 0, vec![]);
1273 let utxo_entry = UtxoEntry::new(1, ScriptPublicKey::new(0, utxo_script.into()), 0, false, None);
1274 let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
1275
1276 let mut vm = TxScriptEngine::from_transaction_input(
1277 &populated_tx,
1278 &input,
1279 0,
1280 &utxo_entry,
1281 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1282 EngineFlags { sigop_script_units: 0.into() },
1283 );
1284
1285 assert_eq!(vm.execute(), Ok(()), "execution failed for SPK_LEN={expected_script_len}");
1286 assert_eq!(
1287 vm.used_script_units(),
1288 ScriptUnits(expected_used_units),
1289 "wrong used script units for SPK_LEN={expected_script_len}"
1290 );
1291 }
1292 }
1293
1294 #[test]
1295 fn test_v1_sigop_budget_enforced_with_mass_per_sigop() {
1296 let sig_cache = Cache::new(10_000);
1297 let reused_values = SigHashReusedValuesUnsync::new();
1298
1299 let input = TransactionInput {
1300 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([7u8; 32]), index: 0 },
1301 signature_script: vec![OpTrue, OpTrue],
1302 sequence: 0,
1303 compute_commit: ComputeBudget(0).into(),
1304 };
1305
1306 let output =
1307 TransactionOutput { value: 1, script_public_key: ScriptPublicKey::new(0, vec![OpCheckSig].into()), covenant: None };
1308
1309 let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]);
1310 let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, false, None);
1311 let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
1312
1313 let flags: EngineFlags = Default::default();
1314
1315 let too_tight_budget = flags.sigop_script_units - ScriptUnits(1);
1316 let mut vm_too_tight = TxScriptEngine::from_transaction_input_with_script_units_limit(
1317 &populated_tx,
1318 &input,
1319 0,
1320 &utxo_entry,
1321 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1322 Default::default(),
1323 too_tight_budget,
1324 );
1325
1326 assert_eq!(
1327 vm_too_tight.execute(),
1328 Err(TxScriptError::ExceededCommittedScriptUnits { used: 100_000, limit: too_tight_budget.0 })
1329 );
1330 let exact_budget = flags.sigop_script_units;
1331 let mut vm_exact = TxScriptEngine::from_transaction_input_with_script_units_limit(
1332 &populated_tx,
1333 &input,
1334 0,
1335 &utxo_entry,
1336 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1337 Default::default(),
1338 exact_budget,
1339 );
1340
1341 assert_match!(vm_exact.execute(), Err(TxScriptError::InvalidPubkey(_)));
1342 }
1343
1344 #[test]
1345 fn test_sigop_budget_enforced_via_script_units() {
1346 let sig_cache = Cache::new(10_000);
1347 let reused_values = SigHashReusedValuesUnsync::new();
1348
1349 let sigop_script_units = 10_000.into();
1350 let budget_allows_one_sigop_only = 15_000.into();
1351
1352 let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &[7u8; 32]).unwrap();
1353 let (x_only_pubkey, _) = schnorr_key.x_only_public_key();
1354 let x_only_pubkey = x_only_pubkey.serialize();
1355
1356 let two_sigops_script = ScriptBuilder::new()
1357 .add_op(OpDup)
1358 .unwrap()
1359 .add_data(&x_only_pubkey)
1360 .unwrap()
1361 .add_op(OpCheckSig)
1362 .unwrap()
1363 .add_op(OpDrop)
1364 .unwrap()
1365 .add_data(&x_only_pubkey)
1366 .unwrap()
1367 .add_op(OpCheckSig)
1368 .unwrap()
1369 .drain();
1370
1371 let input = TransactionInput {
1372 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([9u8; 32]), index: 0 },
1373 signature_script: vec![],
1374 sequence: 0,
1375 compute_commit: ComputeBudget(0).into(), };
1377
1378 let output = TransactionOutput { value: 1, script_public_key: ScriptPublicKey::new(0, vec![OpTrue].into()), covenant: None };
1379
1380 let tx = Transaction::new(1, vec![input], vec![output], 0, Default::default(), 0, vec![]);
1381 let utxo_entry = UtxoEntry::new(1, ScriptPublicKey::new(0, two_sigops_script.clone().into()), 0, false, None);
1382 let mut mutable_tx = MutableTransaction::with_entries(tx, vec![utxo_entry]);
1383 let sighash_reused = SigHashReusedValuesUnsync::new();
1384 let sig_hash = kaspa_consensus_core::hashing::sighash::calc_schnorr_signature_hash(
1385 &mutable_tx.as_verifiable(),
1386 0,
1387 SIG_HASH_ALL,
1388 &sighash_reused,
1389 );
1390 let message = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
1391 let signature: [u8; 64] = *schnorr_key.sign_schnorr(message).as_ref();
1392 let sig_with_hash_type: Vec<u8> = signature.into_iter().chain([SIG_HASH_ALL.to_u8()]).collect();
1393 mutable_tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&sig_with_hash_type).unwrap().drain();
1394
1395 let verifiable_tx = mutable_tx.as_verifiable();
1396 let mut vm = TxScriptEngine::from_transaction_input_with_script_units_limit(
1397 &verifiable_tx,
1398 &verifiable_tx.inputs()[0],
1399 0,
1400 verifiable_tx.utxo(0).unwrap(),
1401 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1402 EngineFlags { sigop_script_units },
1403 budget_allows_one_sigop_only,
1404 );
1405 assert_match!(
1406 vm.execute(),
1407 Err(TxScriptError::ExceededCommittedScriptUnits { .. }),
1408 "expected sigop budget enforcement for tx"
1409 );
1410
1411 let mut vm_with_doubled_budget = TxScriptEngine::from_transaction_input_with_script_units_limit(
1412 &verifiable_tx,
1413 &verifiable_tx.inputs()[0],
1414 0,
1415 verifiable_tx.utxo(0).unwrap(),
1416 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1417 EngineFlags { sigop_script_units },
1418 (budget_allows_one_sigop_only.0 * 2).into(),
1419 );
1420 assert_eq!(vm_with_doubled_budget.execute(), Ok(()), "expected tx to pass when budget is doubled");
1421 }
1422
1423 #[test]
1424 fn test_check_error_condition() {
1425 let test_cases = vec![
1426 ScriptTestCase {
1427 script: b"\x51", expected_result: Ok(()),
1429 },
1430 ScriptTestCase {
1431 script: b"\x61", expected_result: Err(TxScriptError::EmptyStack),
1433 },
1434 ScriptTestCase {
1435 script: b"\x51\x51", expected_result: Err(TxScriptError::CleanStack(1)),
1437 },
1438 ScriptTestCase {
1439 script: b"\x00", expected_result: Err(TxScriptError::EvalFalse),
1441 },
1442 ];
1443
1444 run_test_script_cases(test_cases)
1445 }
1446
1447 #[test]
1448 fn test_unknown_script_public_key_version_skips_final_stack_check() {
1449 let sig_cache = Cache::new(10_000);
1450 let reused_values = SigHashReusedValuesUnsync::new();
1451
1452 let input = TransactionInput {
1453 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([1u8; 32]), index: 0 },
1454 signature_script: vec![],
1455 sequence: 0,
1456 compute_commit: ComputeBudget(0).into(),
1457 };
1458 let output = TransactionOutput { value: 1, script_public_key: ScriptPublicKey::new(0, vec![OpTrue].into()), covenant: None };
1459 let tx = Transaction::new(1, vec![input.clone()], vec![output], 0, Default::default(), 0, vec![]);
1460 let unknown_version_spk = ScriptPublicKey::new(MAX_SCRIPT_PUBLIC_KEY_VERSION + 1, vec![OpFalse].into());
1461 let utxo_entry = UtxoEntry::new(1, unknown_version_spk, 0, false, None);
1462 let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
1463
1464 let mut vm = TxScriptEngine::from_transaction_input(
1465 &populated_tx,
1466 &input,
1467 0,
1468 &utxo_entry,
1469 EngineCtx::new(&sig_cache).with_reused(&reused_values),
1470 Default::default(),
1471 );
1472
1473 assert_eq!(vm.execute(), Ok(()));
1474 }
1475
1476 #[test]
1477 fn test_opcode_execution_log_buffer_trace_output() {
1478 let sig_cache = Cache::new(10_000);
1479 let reused_values = SigHashReusedValuesUnsync::new();
1480 let mut output = Vec::new();
1481
1482 let mut vm =
1483 TxScriptEngine::<VerifiableTransactionMock, _>::from_script(b"\x51", &reused_values, &sig_cache, EngineFlags::default())
1484 .with_opcode_execution_log_buffer(&mut output);
1485
1486 assert_eq!(vm.execute(), Ok(()));
1487 assert_eq!(
1488 String::from_utf8(output).expect("trace output should be valid UTF-8"),
1489 "Executing opcode: OpTrue, astack: [], dstack: []\n"
1490 );
1491 }
1492
1493 #[test]
1494 fn test_check_opif() {
1495 let test_cases = vec![
1496 ScriptTestCase {
1497 script: b"\x63", expected_result: Err(TxScriptError::InvalidStackOperation(1, 0)),
1499 },
1500 ScriptTestCase {
1501 script: b"\x52\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1503 },
1504 ScriptTestCase {
1505 script: b"\x51\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1507 },
1508 ScriptTestCase {
1509 script: b"\x00\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1511 },
1512 ScriptTestCase {
1513 script: b"\x51\x63\x51\x68", expected_result: Ok(()),
1515 },
1516 ScriptTestCase {
1517 script: b"\x00\x63\x51\x68", expected_result: Err(TxScriptError::EmptyStack),
1519 },
1520 ];
1521
1522 run_test_script_cases(test_cases)
1523 }
1524
1525 #[test]
1526 fn test_check_opelse() {
1527 let test_cases = vec![
1528 ScriptTestCase {
1529 script: b"\x67", expected_result: Err(TxScriptError::InvalidState("condition stack empty".to_string())),
1531 },
1532 ScriptTestCase {
1533 script: b"\x51\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1535 },
1536 ScriptTestCase {
1537 script: b"\x00\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1539 },
1540 ScriptTestCase {
1541 script: b"\x51\x63\x51\x67\x68", expected_result: Ok(()),
1543 },
1544 ScriptTestCase {
1545 script: b"\x00\x63\x67\x51\x68", expected_result: Ok(()),
1547 },
1548 ];
1549
1550 run_test_script_cases(test_cases)
1551 }
1552
1553 #[test]
1554 fn test_check_opnotif() {
1555 let test_cases = vec![
1556 ScriptTestCase {
1557 script: b"\x64", expected_result: Err(TxScriptError::InvalidStackOperation(1, 0)),
1559 },
1560 ScriptTestCase {
1561 script: b"\x51\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1563 },
1564 ScriptTestCase {
1565 script: b"\x00\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1567 },
1568 ScriptTestCase {
1569 script: b"\x51\x64\x67\x51\x68", expected_result: Ok(()),
1571 },
1572 ScriptTestCase {
1573 script: b"\x51\x64\x51\x67\x00\x68", expected_result: Err(TxScriptError::EvalFalse),
1575 },
1576 ScriptTestCase {
1577 script: b"\x00\x64\x51\x68", expected_result: Ok(()),
1579 },
1580 ];
1581
1582 run_test_script_cases(test_cases)
1583 }
1584
1585 #[test]
1586 fn test_check_nestedif() {
1587 let test_cases = vec![
1588 ScriptTestCase {
1589 script: b"\x51\x63\x00\x67\x51\x63\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
1592 },
1593 ScriptTestCase {
1594 script: b"\x51\x63\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
1597 },
1598 ScriptTestCase {
1599 script: b"\x51\x64\x00\x67\x51\x63\x51\x68\x68", expected_result: Ok(()),
1602 },
1603 ScriptTestCase {
1604 script: b"\x51\x64\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Ok(()),
1607 },
1608 ScriptTestCase {
1609 script: b"\x51\x64\x00\x67\x00\x64\x00\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
1612 },
1613 ScriptTestCase {
1614 script: b"\x51\x00\x63\x63\x00\x68\x68", expected_result: Ok(()),
1616 },
1617 ScriptTestCase {
1618 script: b"\x51\x00\x63\x63\x63\x00\x67\x00\x68\x68\x68", expected_result: Ok(()),
1620 },
1621 ScriptTestCase {
1622 script: b"\x51\x00\x63\x63\x63\x63\x00\x67\x00\x68\x68\x68\x68", expected_result: Ok(()),
1624 },
1625 ];
1626
1627 run_test_script_cases(test_cases)
1628 }
1629
1630 #[test]
1631 fn test_check_pub_key_encode() {
1632 let test_cases = vec![
1633 KeyTestCase {
1634 name: "uncompressed - invalid",
1635 key: &[
1636 0x04u8, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6,
1637 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd,
1638 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f,
1639 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3,
1640 ],
1641 is_valid: false,
1642 },
1643 KeyTestCase {
1644 name: "compressed - invalid",
1645 key: &[
1646 0x02, 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, 0x2e, 0x9c,
1647 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d,
1648 ],
1649 is_valid: false,
1650 },
1651 KeyTestCase {
1652 name: "compressed - invalid",
1653 key: &[
1654 0x03, 0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21,
1655 0x88, 0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
1656 ],
1657 is_valid: false,
1658 },
1659 KeyTestCase {
1660 name: "hybrid - invalid",
1661 key: &[
1662 0x06, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b,
1663 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, 0x48, 0x3a, 0xda, 0x77, 0x26,
1664 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19,
1665 0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8,
1666 ],
1667 is_valid: false,
1668 },
1669 KeyTestCase {
1670 name: "32 bytes pubkey - Ok",
1671 key: &[
1672 0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21, 0x88,
1673 0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
1674 ],
1675 is_valid: true,
1676 },
1677 KeyTestCase { name: "empty", key: &[], is_valid: false },
1678 ];
1679
1680 for test in test_cases {
1681 let check = secp256k1::XOnlyPublicKey::from_slice(test.key).is_ok();
1682 if test.is_valid {
1683 assert!(check, "checkSignatureLength test '{}' failed when it should have succeeded: {:?}", test.name, check)
1684 } else {
1685 assert!(!check, "checkSignatureEncoding test '{}' succeeded or failed on wrong format ({:?})", test.name, check)
1686 }
1687 }
1688 }
1689
1690 #[test]
1691 fn test_standard_script_pub_key_sizes() {
1692 let p2pk = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKey, &[0u8; 32]));
1693 let p2pk_ecdsa = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::PubKeyECDSA, &[0u8; 33]));
1694 let p2sh = pay_to_address_script(&Address::new(Prefix::Mainnet, Version::ScriptHash, &[0u8; 32]));
1695
1696 let tests =
1697 [("p2pk", p2pk.script().len(), 34), ("p2pk_ecdsa", p2pk_ecdsa.script().len(), 35), ("p2sh", p2sh.script().len(), 35)];
1698
1699 for (name, len, expected_len) in tests {
1700 assert_eq!(len, expected_len, "{name} length changed");
1701 assert!(
1702 len <= STANDARD_SCRIPT_PUB_KEY_MAX_SIZE,
1703 "{name} length {len} exceeds STANDARD_SCRIPT_PUB_KEY_MAX_SIZE ({STANDARD_SCRIPT_PUB_KEY_MAX_SIZE})"
1704 );
1705 }
1706 }
1707
1708 #[test]
1709 fn test_get_sig_op_count() {
1710 struct TestVector<'a> {
1711 name: &'a str,
1712 signature_script: &'a [u8],
1713 expected_sig_ops: u64,
1714 prev_script_public_key: ScriptPublicKey,
1715 }
1716
1717 let script_hash = Vec::from_hex("433ec2ac1ffa1b7b7d027f564529c57197f9ae88").unwrap();
1718 let prev_script_pubkey_p2sh_script =
1719 [OpBlake2b, OpData32].iter().copied().chain(script_hash.iter().copied()).chain(once(OpEqual));
1720 let prev_script_pubkey_p2sh = ScriptPublicKey::new(0, SmallVec::from_iter(prev_script_pubkey_p2sh_script));
1721
1722 let tests = [
1723 TestVector {
1724 name: "scriptSig doesn't parse",
1725 signature_script: &[OpPushData1, 0x02],
1726 expected_sig_ops: 0,
1727 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
1728 },
1729 TestVector {
1730 name: "scriptSig isn't push only",
1731 signature_script: &[OpTrue, OpDup],
1732 expected_sig_ops: 0,
1733 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
1734 },
1735 TestVector {
1736 name: "scriptSig length 0",
1737 signature_script: &[],
1738 expected_sig_ops: 0,
1739 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
1740 },
1741 TestVector {
1742 name: "No script at the end",
1743 signature_script: &[OpTrue, OpTrue],
1744 expected_sig_ops: 0,
1745 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
1746 }, TestVector {
1748 name: "pushed script doesn't parse",
1749 signature_script: &[OpData2, OpPushData1, 0x02],
1750 expected_sig_ops: 0,
1751 prev_script_public_key: prev_script_pubkey_p2sh,
1752 },
1753 TestVector {
1754 name: "mainnet multisig transaction 487f94ffa63106f72644068765b9dc629bb63e481210f382667d4a93b69af412",
1755 signature_script: &Vec::from_hex("41eb577889fa28283709201ef5b056745c6cf0546dd31666cecd41c40a581b256e885d941b86b14d44efacec12d614e7fcabf7b341660f95bab16b71d766ab010501411c0eeef117ca485d34e4bc0cf6d5b578aa250c5d13ebff0882a7e2eeea1f31e8ecb6755696d194b1b0fcb853afab28b61f3f7cec487bd611df7e57252802f535014c875220ab64c7691713a32ea6dfced9155c5c26e8186426f0697af0db7a4b1340f992d12041ae738d66fe3d21105483e5851778ad73c5cddf0819c5e8fd8a589260d967e72065120722c36d3fac19646258481dd3661fa767da151304af514cb30af5cb5692203cd7690ecb67cbbe6cafad00a7c9133da535298ab164549e0cce2658f7b3032754ae").unwrap(),
1756 prev_script_public_key: ScriptPublicKey::new(
1757 0,
1758 SmallVec::from_hex("aa20f38031f61ca23d70844f63a477d07f0b2c2decab907c2e096e548b0e08721c7987").unwrap(),
1759 ),
1760 expected_sig_ops: 4,
1761 },
1762 TestVector {
1763 name: "a partially parseable script public key",
1764 signature_script: &[],
1765 prev_script_public_key: ScriptPublicKey::new(
1766 0,
1767 SmallVec::from_slice(&[OpCheckSig,OpCheckSig, OpData1]),
1768 ),
1769 expected_sig_ops: 2,
1770 },
1771 TestVector {
1772 name: "p2pk",
1773 signature_script: &Vec::from_hex("416db0c0ce824a6d076c8e73aae9987416933df768e07760829cb0685dc0a2bbb11e2c0ced0cab806e111a11cbda19784098fd25db176b6a9d7c93e5747674d32301").unwrap(),
1774 prev_script_public_key: ScriptPublicKey::new(
1775 0,
1776 SmallVec::from_hex("208a457ca74ade0492c44c440da1cab5b008d8449150fe2794f0d8f4cce7e8aa27ac").unwrap(),
1777 ),
1778 expected_sig_ops: 1,
1779 },
1780 ];
1781
1782 for test in tests {
1783 assert_eq!(
1784 get_sig_op_count_upper_bound::<VerifiableTransactionMock, SigHashReusedValuesUnsync>(
1785 test.signature_script,
1786 &test.prev_script_public_key
1787 ),
1788 test.expected_sig_ops,
1789 "failed for '{}'",
1790 test.name
1791 );
1792 }
1793 }
1794
1795 #[test]
1796 fn test_p2sh_sig_scanner() {
1797 struct Test {
1798 name: String,
1799 redeem_script: Vec<u8>,
1800 signature_prefix: Vec<u8>,
1801 expected_sig_ops: u64,
1802 }
1803
1804 let flags = EngineFlags { sigop_script_units: 0.into() };
1805 let malformed_push = [OpPushData1, 2, 1];
1807
1808 let mut tests = vec![
1809 Test {
1810 name: "from-stack sigops are counted".to_string(),
1811 redeem_script: ScriptBuilder::new()
1812 .add_op(OpCheckSigFromStack)
1813 .unwrap()
1814 .add_op(OpCheckSigFromStackECDSA)
1815 .unwrap()
1816 .drain(),
1817 signature_prefix: vec![],
1818 expected_sig_ops: 2,
1819 },
1820 Test {
1821 name: "canonical multisig count".to_string(),
1822 redeem_script: ScriptBuilder::new().add_op(codes::Op2).unwrap().add_op(OpCheckMultiSig).unwrap().drain(),
1823 signature_prefix: vec![],
1824 expected_sig_ops: 2,
1825 },
1826 Test {
1827 name: "missing multisig count falls back to max".to_string(),
1828 redeem_script: ScriptBuilder::new().add_op(OpCheckMultiSig).unwrap().drain(),
1829 signature_prefix: vec![],
1830 expected_sig_ops: MAX_PUB_KEYS_PER_MUTLTISIG as u64,
1831 },
1832 Test {
1833 name: "signature prefix is ignored".to_string(),
1834 redeem_script: ScriptBuilder::new().add_op(OpCheckSig).unwrap().drain(),
1835 signature_prefix: ScriptBuilder::new().add_op(OpTrue).unwrap().drain(),
1836 expected_sig_ops: 1,
1837 },
1838 Test {
1839 name: "malformed redeem script returns zero".to_string(),
1840 redeem_script: [OpCheckSig].into_iter().chain(malformed_push).collect(),
1841 signature_prefix: vec![],
1842 expected_sig_ops: 0,
1843 },
1844 ];
1845
1846 for count in 1u8..=MAX_PUB_KEYS_PER_MUTLTISIG as u8 + 1 {
1847 let expected_sig_ops = u64::from(count).min(MAX_PUB_KEYS_PER_MUTLTISIG as u64);
1848
1849 let canonical_redeem_script = ScriptBuilder::new().add_data(&[count]).unwrap().add_op(OpCheckMultiSig).unwrap().drain();
1850 tests.push(Test {
1851 name: format!("canonical multisig count {count}"),
1852 redeem_script: canonical_redeem_script,
1853 signature_prefix: vec![],
1854 expected_sig_ops,
1855 });
1856
1857 let explicit_redeem_script =
1858 ScriptBuilder::with_flags(flags).add_data_with_push_opcode(&[count]).unwrap().add_op(OpCheckMultiSig).unwrap().drain();
1859 tests.push(Test {
1860 name: format!("explicit push multisig count {count}"),
1861 redeem_script: explicit_redeem_script,
1862 signature_prefix: vec![],
1863 expected_sig_ops,
1864 });
1865 }
1866
1867 for test in tests {
1868 let spk = pay_to_script_hash_script(&test.redeem_script);
1869 let signature_script = pay_to_script_hash_signature_script_with_flags(test.redeem_script, test.signature_prefix, flags)
1870 .expect("p2sh script build");
1871 assert_eq!(p2sh_sig_scanner(&signature_script, &spk), test.expected_sig_ops, "{}", test.name);
1872 }
1873
1874 let non_p2sh = ScriptPublicKey::new(0, SmallVec::from_slice(&[OpTrue]));
1875 assert_eq!(p2sh_sig_scanner(&[], &non_p2sh), 0, "non-p2sh spk");
1876
1877 let redeem_script = ScriptBuilder::new().add_op(OpCheckSig).unwrap().drain();
1878 let p2sh = pay_to_script_hash_script(&redeem_script);
1879 assert_eq!(p2sh_sig_scanner(&malformed_push, &p2sh), 0, "malformed sigscript");
1880 }
1881
1882 #[test]
1883 fn test_is_unspendable() {
1884 struct Test<'a> {
1885 name: &'a str,
1886 script_public_key: &'a [u8],
1887 expected: bool,
1888 }
1889 let tests = vec![
1890 Test { name: "unspendable", script_public_key: &[0x6a, 0x04, 0x74, 0x65, 0x73, 0x74], expected: true },
1891 Test {
1892 name: "spendable",
1893 script_public_key: &[
1894 0x76, 0xa9, 0x14, 0x29, 0x95, 0xa0, 0xfe, 0x68, 0x43, 0xfa, 0x9b, 0x95, 0x45, 0x97, 0xf0, 0xdc, 0xa7, 0xa4, 0x4d,
1895 0xf6, 0xfa, 0x0b, 0x5c, 0x88, 0xac,
1896 ],
1897 expected: false,
1898 },
1899 ];
1900
1901 for test in tests {
1902 assert_eq!(
1903 is_unspendable::<VerifiableTransactionMock, SigHashReusedValuesUnsync>(test.script_public_key),
1904 test.expected,
1905 "failed for '{}'",
1906 test.name
1907 );
1908 }
1909 }
1910
1911 #[derive(Clone)]
1912 struct SignatureData {
1913 signature: Vec<u8>,
1914 public_key: Vec<u8>,
1915 }
1916
1917 enum SignatureScriptBuilder {
1919 Multisig(Vec<SignatureData>),
1921
1922 Single(SignatureData),
1924
1925 Mixed(Vec<SignatureData>),
1927
1928 None,
1930 }
1931
1932 type SigBuilder = Box<dyn Fn(&MutableTransaction<Transaction>, &SigHashReusedValuesUnsync) -> SignatureScriptBuilder>;
1933 type ScriptBuilderFn = Box<dyn Fn(&mut ScriptBuilder) -> ScriptBuilderResult<&mut ScriptBuilder>>;
1934
1935 struct TestCase {
1936 name: &'static str,
1937 script_builder: ScriptBuilderFn,
1938 sig_builder: SigBuilder,
1939 expected_sig_ops: u16,
1940 sig_op_limit: u8,
1941 should_pass: bool,
1942 }
1943
1944 impl SignatureScriptBuilder {
1945 fn build(self, script: &[u8]) -> ScriptBuilderResult<Vec<u8>> {
1946 let mut builder = ScriptBuilder::new();
1947
1948 match self {
1949 SignatureScriptBuilder::Single(sig_data) => {
1950 builder.add_data(&sig_data.signature)?;
1951 builder.add_data(&sig_data.public_key)?;
1952 }
1953 SignatureScriptBuilder::Multisig(sig_data_vec) => {
1954 for sig_data in sig_data_vec {
1955 builder.add_data(&sig_data.signature)?;
1956 }
1957 }
1958 SignatureScriptBuilder::Mixed(sig_data_vec) => {
1959 for sig_data in sig_data_vec {
1960 builder.add_data(&sig_data.signature)?;
1961 builder.add_data(&sig_data.public_key)?;
1962 }
1963 }
1964 SignatureScriptBuilder::None => {}
1965 }
1966
1967 builder.add_data(script)?;
1968 Ok(builder.drain())
1969 }
1970 }
1971
1972 #[test]
1973 fn test_runtime_sig_op_count() -> ScriptBuilderResult<()> {
1974 let secp = secp256k1::Secp256k1::new();
1976 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
1977 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
1978
1979 let sig_cache = Cache::new(10_000);
1980 let reused_values = SigHashReusedValuesUnsync::new();
1981
1982 let create_schnorr_signature = move |tx: &MutableTransaction<Transaction>, reused: &SigHashReusedValuesUnsync| {
1984 let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, reused);
1985 let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap();
1986 let sig = keypair.sign_schnorr(msg);
1987 let mut signature = sig.as_ref().to_vec();
1988 signature.push(SIG_HASH_ALL.to_u8());
1989 SignatureData { signature, public_key: keypair.x_only_public_key().0.serialize().to_vec() }
1990 };
1991
1992 let create_ecdsa_signature = move |tx: &MutableTransaction<Transaction>, reused: &SigHashReusedValuesUnsync| {
1993 let hash = calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, reused);
1994 let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap();
1995 let sig = keypair.secret_key().sign_ecdsa(msg);
1996 let mut signature = sig.serialize_compact().to_vec();
1997 signature.push(SIG_HASH_ALL.to_u8());
1998 SignatureData { signature, public_key: keypair.public_key().serialize().to_vec() }
1999 };
2000
2001 let test_cases = vec![
2002 TestCase {
2004 name: "Basic Schnorr CheckSig - Single signature",
2005 script_builder: Box::new(|sb| sb.add_op(OpCheckSig)),
2006 sig_builder: Box::new(move |tx, reused| SignatureScriptBuilder::Single(create_schnorr_signature(tx, reused))),
2007 expected_sig_ops: 1,
2008 sig_op_limit: 1,
2009 should_pass: true,
2010 },
2011 TestCase {
2013 name: "Basic ECDSA CheckSig - Single signature",
2014 script_builder: Box::new(|sb| sb.add_op(OpCheckSigECDSA)),
2015 sig_builder: Box::new(move |tx, reused| SignatureScriptBuilder::Single(create_ecdsa_signature(tx, reused))),
2016 expected_sig_ops: 1,
2017 sig_op_limit: 1,
2018 should_pass: true,
2019 },
2020 TestCase {
2022 name: "Mixed Schnorr and ECDSA - Within limit",
2023 script_builder: Box::new(|sb| sb.add_op(OpCheckSigVerify)?.add_op(OpCheckSigECDSA)),
2024 sig_builder: Box::new(move |tx, reused| {
2025 SignatureScriptBuilder::Mixed(vec![create_ecdsa_signature(tx, reused), create_schnorr_signature(tx, reused)])
2026 }),
2027 expected_sig_ops: 2,
2028 sig_op_limit: 2,
2029 should_pass: true,
2030 },
2031 TestCase {
2033 name: "2-of-3 MultiSig - Basic validation",
2034 script_builder: Box::new(move |sb| {
2035 sb.add_i64(2)?
2036 .add_data(&keypair.x_only_public_key().0.serialize())?
2037 .add_data(&keypair.x_only_public_key().0.serialize())?
2038 .add_data(&keypair.x_only_public_key().0.serialize())?
2039 .add_i64(3)?
2040 .add_op(OpCheckMultiSig)
2041 }),
2042 sig_builder: Box::new(move |tx, reused| {
2043 let sig = create_schnorr_signature(tx, reused);
2044 SignatureScriptBuilder::Multisig(vec![sig.clone(), sig])
2045 }),
2046 expected_sig_ops: 2,
2047 sig_op_limit: 2,
2048 should_pass: true,
2049 },
2050 TestCase {
2051 name: "Mixed Schnorr and ECDSA - Exceeds limit",
2052 script_builder: Box::new(|sb| sb.add_op(OpCheckSigVerify)?.add_op(OpCheckSigECDSA)),
2053 sig_builder: Box::new(move |tx, reused| {
2054 SignatureScriptBuilder::Mixed(vec![create_ecdsa_signature(tx, reused), create_schnorr_signature(tx, reused)])
2055 }),
2056 expected_sig_ops: 2,
2057 sig_op_limit: 1,
2058 should_pass: false,
2059 },
2060 TestCase {
2062 name: "Conditional sig ops - True branch execution",
2063 script_builder: Box::new(|sb| sb.add_op(OpTrue)?.add_op(OpIf)?.add_op(OpCheckSigECDSA)?.add_op(OpEndIf)),
2064 sig_builder: Box::new(move |tx, reused| SignatureScriptBuilder::Single(create_ecdsa_signature(tx, reused))),
2065 expected_sig_ops: 1,
2066 sig_op_limit: 1,
2067 should_pass: true,
2068 },
2069 TestCase {
2071 name: "Conditional sig ops - False branch skips validation",
2072 script_builder: Box::new(|sb| {
2073 sb.add_op(OpFalse)?.add_op(OpIf)?.add_op(OpCheckSigECDSA)?.add_op(OpVerify)?.add_op(OpEndIf)?.add_op(OpTrue)
2074 }),
2075 sig_builder: Box::new(move |_tx, _reused| SignatureScriptBuilder::None),
2076 expected_sig_ops: 0,
2077 sig_op_limit: 0,
2078 should_pass: true,
2079 },
2080 ];
2081
2082 for test in test_cases {
2083 let mut script_builder = ScriptBuilder::new();
2085 (test.script_builder)(&mut script_builder)?;
2086 let script = script_builder.drain();
2087
2088 let script_pub_key = pay_to_script_hash_script(&script);
2089 let utxo_entry = UtxoEntry::new(1000, script_pub_key.clone(), 0, false, None);
2090
2091 let tx = Transaction::new(
2093 0,
2094 vec![TransactionInput {
2095 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::default(), index: 0 },
2096 signature_script: vec![],
2097 sequence: 0,
2098 compute_commit: SigopCount(test.sig_op_limit).into(),
2099 }],
2100 vec![],
2101 0,
2102 Default::default(),
2103 0,
2104 vec![],
2105 );
2106
2107 let mut tx = MutableTransaction::new(tx);
2108 tx.entries = vec![Some(utxo_entry.clone())];
2109
2110 let signature_script = (test.sig_builder)(&tx, &reused_values).build(&script)?;
2112 tx.tx.inputs[0].signature_script = signature_script;
2113
2114 let tx = tx.as_verifiable();
2116 let script_units_limit = tx.inputs()[0].compute_commit.allowed_script_units();
2117 let mut vm = TxScriptEngine::from_transaction_input_with_script_units_limit(
2118 &tx,
2119 &tx.inputs()[0],
2120 0,
2121 &utxo_entry,
2122 EngineCtx::new(&sig_cache).with_reused(&reused_values),
2123 Default::default(),
2124 script_units_limit,
2125 );
2126
2127 let result = vm.execute().map(|_| vm.used_sig_ops());
2128
2129 match (result, test.should_pass) {
2130 (Ok(count), true) => {
2131 assert_eq!(
2132 count, test.expected_sig_ops,
2133 "{} failed: Expected {} sig ops, got {}",
2134 test.name, test.expected_sig_ops, count
2135 );
2136 }
2137 (Ok(_), false) => {
2138 panic!("{} should have failed but succeeded", test.name);
2139 }
2140 (Err(err), true) => {
2141 panic!("{} failed but should have succeeded with err: {}", test.name, err);
2142 }
2143 (Err(_), false) => {
2144 }
2146 }
2147 }
2148
2149 Ok(())
2150 }
2151
2152 #[test]
2154 fn test_checksig_and_checksigfromstack_match_for_schnorr() -> ScriptBuilderResult<()> {
2155 let secp = secp256k1::Secp256k1::new();
2156 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2157 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2158 let reused_values = SigHashReusedValuesUnsync::new();
2159 let xonly_pub_key = keypair.x_only_public_key().0.serialize();
2160
2161 let mut script_pub_key_builder = ScriptBuilder::new();
2162 script_pub_key_builder
2163 .add_data(&xonly_pub_key)?
2164 .add_op(OpCheckSigVerify)?
2165 .add_data(&xonly_pub_key)?
2166 .add_op(OpCheckSigFromStack)?;
2167 let script_pub_key_script = script_pub_key_builder.drain();
2168 let script_pub_key = ScriptPublicKey::new(0, script_pub_key_script.clone().into());
2169 let utxo_entry = UtxoEntry::new(1000, script_pub_key, 0, false, None);
2170
2171 let tx = Transaction::new(
2172 1,
2173 vec![TransactionInput {
2174 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::default(), index: 0 },
2175 signature_script: vec![],
2176 sequence: 0,
2177 compute_commit: SigopCount(2).into(),
2178 }],
2179 vec![],
2180 0,
2181 Default::default(),
2182 0,
2183 vec![],
2184 );
2185 let mut tx = MutableTransaction::new(tx);
2186 tx.entries = vec![Some(utxo_entry.clone())];
2187
2188 let sig_hash = calc_schnorr_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values);
2189 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
2190 let schnorr_sig = keypair.sign_schnorr(msg);
2191 let schnorr_sig_raw = schnorr_sig.as_ref().to_vec();
2192 let mut schnorr_sig_with_hash_type = schnorr_sig_raw.clone();
2193 schnorr_sig_with_hash_type.push(SIG_HASH_ALL.to_u8());
2194
2195 let mut signature_script_builder = ScriptBuilder::new();
2196 signature_script_builder
2197 .add_data(&schnorr_sig_raw)?
2198 .add_data(sig_hash.as_bytes().as_slice())?
2199 .add_data(&schnorr_sig_with_hash_type)?;
2200 tx.tx.inputs[0].signature_script = signature_script_builder.drain();
2201
2202 let tx = tx.as_verifiable();
2203 let sig_cache = Cache::new(10_000);
2204 let result = TxScriptEngine::from_transaction_input(
2205 &tx,
2206 &tx.inputs()[0],
2207 0,
2208 &utxo_entry,
2209 EngineCtx::new(&sig_cache).with_reused(&reused_values),
2210 EngineFlags { sigop_script_units: 0.into() },
2211 )
2212 .execute();
2213 assert_eq!(result, Ok(()));
2214 Ok(())
2215 }
2216
2217 #[test]
2218 fn test_invalid_schnorr_signature_cache_stays_invalid() {
2219 let secp = secp256k1::Secp256k1::new();
2220 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2221 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2222 let public_key = keypair.x_only_public_key().0.serialize();
2223 let valid_msg = secp256k1::Message::from_digest(Hash::from_bytes([1; 32]).into());
2224 let invalid_msg_hash = Hash::from_bytes([2; 32]);
2225 let signature = keypair.sign_schnorr(valid_msg);
2226 let sig_cache = Cache::new(10_000);
2227 let reused_values = SigHashReusedValuesUnsync::new();
2228 let flags = EngineFlags { sigop_script_units: 0.into() };
2229 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
2230 &[],
2231 &reused_values,
2232 &sig_cache,
2233 flags,
2234 );
2235
2236 assert_eq!(vm.check_schnorr_signature_with_msg_hash(&public_key, signature.as_ref(), |_| invalid_msg_hash), Ok(false));
2237 assert_eq!(vm.check_schnorr_signature_with_msg_hash(&public_key, signature.as_ref(), |_| invalid_msg_hash), Ok(false));
2238 }
2239
2240 #[test]
2241 fn test_valid_schnorr_signature_cache_stays_valid() {
2242 let secp = secp256k1::Secp256k1::new();
2243 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2244 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2245 let public_key = keypair.x_only_public_key().0.serialize();
2246 let valid_msg_hash = Hash::from_bytes([1; 32]);
2247 let valid_msg = secp256k1::Message::from_digest(valid_msg_hash.into());
2248 let signature = keypair.sign_schnorr(valid_msg);
2249 let sig_cache = Cache::new(10_000);
2250 let reused_values = SigHashReusedValuesUnsync::new();
2251 let flags = EngineFlags { sigop_script_units: 0.into() };
2252 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
2253 &[],
2254 &reused_values,
2255 &sig_cache,
2256 flags,
2257 );
2258
2259 assert_eq!(vm.check_schnorr_signature_with_msg_hash(&public_key, signature.as_ref(), |_| valid_msg_hash), Ok(true));
2260 assert_eq!(vm.check_schnorr_signature_with_msg_hash(&public_key, signature.as_ref(), |_| valid_msg_hash), Ok(true));
2261 }
2262
2263 #[test]
2265 fn test_checksig_and_checksigfromstack_match_for_ecdsa() -> ScriptBuilderResult<()> {
2266 let secp = secp256k1::Secp256k1::new();
2267 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2268 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2269 let reused_values = SigHashReusedValuesUnsync::new();
2270 let ecdsa_pub_key = keypair.public_key().serialize();
2271
2272 let mut script_pub_key_builder = ScriptBuilder::new();
2273 script_pub_key_builder
2274 .add_data(&ecdsa_pub_key)?
2275 .add_op(OpCheckSigECDSA)?
2276 .add_op(OpVerify)?
2277 .add_data(&ecdsa_pub_key)?
2278 .add_op(OpCheckSigFromStackECDSA)?;
2279 let script_pub_key_script = script_pub_key_builder.drain();
2280 let script_pub_key = ScriptPublicKey::new(0, script_pub_key_script.clone().into());
2281 let utxo_entry = UtxoEntry::new(1000, script_pub_key, 0, false, None);
2282
2283 let tx = Transaction::new(
2284 1,
2285 vec![TransactionInput {
2286 previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::default(), index: 0 },
2287 signature_script: vec![],
2288 sequence: 0,
2289 compute_commit: SigopCount(3).into(),
2290 }],
2291 vec![],
2292 0,
2293 Default::default(),
2294 0,
2295 vec![],
2296 );
2297 let mut tx = MutableTransaction::new(tx);
2298 tx.entries = vec![Some(utxo_entry.clone())];
2299
2300 let sig_hash = calc_ecdsa_signature_hash(&tx.as_verifiable(), 0, SIG_HASH_ALL, &reused_values);
2301 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
2302 let ecdsa_sig = keypair.secret_key().sign_ecdsa(msg);
2303 let ecdsa_sig_raw = ecdsa_sig.serialize_compact().to_vec();
2304 let mut ecdsa_sig_with_hash_type = ecdsa_sig_raw.clone();
2305 ecdsa_sig_with_hash_type.push(SIG_HASH_ALL.to_u8());
2306
2307 let mut signature_script_builder = ScriptBuilder::new();
2308 signature_script_builder
2309 .add_data(&ecdsa_sig_raw)?
2310 .add_data(sig_hash.as_bytes().as_slice())?
2311 .add_data(&ecdsa_sig_with_hash_type)?;
2312 tx.tx.inputs[0].signature_script = signature_script_builder.drain();
2313
2314 let tx = tx.as_verifiable();
2315 let sig_cache = Cache::new(10_000);
2316 let result = TxScriptEngine::from_transaction_input(
2317 &tx,
2318 &tx.inputs()[0],
2319 0,
2320 &utxo_entry,
2321 EngineCtx::new(&sig_cache).with_reused(&reused_values),
2322 EngineFlags { sigop_script_units: 0.into() },
2323 )
2324 .execute();
2325 assert_eq!(result, Ok(()));
2326 Ok(())
2327 }
2328
2329 #[test]
2330 fn test_invalid_ecdsa_signature_cache_stays_invalid() {
2331 let secp = secp256k1::Secp256k1::new();
2332 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2333 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2334 let public_key = keypair.public_key().serialize();
2335 let valid_msg = secp256k1::Message::from_digest(Hash::from_bytes([1; 32]).into());
2336 let invalid_msg_hash = Hash::from_bytes([2; 32]);
2337 let signature = keypair.secret_key().sign_ecdsa(valid_msg).serialize_compact();
2338 let sig_cache = Cache::new(10_000);
2339 let reused_values = SigHashReusedValuesUnsync::new();
2340 let flags = EngineFlags { sigop_script_units: 0.into() };
2341 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
2342 &[],
2343 &reused_values,
2344 &sig_cache,
2345 flags,
2346 );
2347
2348 assert_eq!(vm.check_ecdsa_signature_with_msg_hash(&public_key, &signature, |_| invalid_msg_hash), Ok(false));
2349 assert_eq!(vm.check_ecdsa_signature_with_msg_hash(&public_key, &signature, |_| invalid_msg_hash), Ok(false));
2350 }
2351
2352 #[test]
2353 fn test_valid_ecdsa_signature_cache_stays_valid() {
2354 let secp = secp256k1::Secp256k1::new();
2355 let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng());
2356 let keypair = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap();
2357 let public_key = keypair.public_key().serialize();
2358 let valid_msg_hash = Hash::from_bytes([1; 32]);
2359 let valid_msg = secp256k1::Message::from_digest(valid_msg_hash.into());
2360 let signature = keypair.secret_key().sign_ecdsa(valid_msg).serialize_compact();
2361 let sig_cache = Cache::new(10_000);
2362 let reused_values = SigHashReusedValuesUnsync::new();
2363 let flags = EngineFlags { sigop_script_units: 0.into() };
2364 let mut vm = TxScriptEngine::<VerifiableTransactionMock, SigHashReusedValuesUnsync>::from_script(
2365 &[],
2366 &reused_values,
2367 &sig_cache,
2368 flags,
2369 );
2370
2371 assert_eq!(vm.check_ecdsa_signature_with_msg_hash(&public_key, &signature, |_| valid_msg_hash), Ok(true));
2372 assert_eq!(vm.check_ecdsa_signature_with_msg_hash(&public_key, &signature, |_| valid_msg_hash), Ok(true));
2373 }
2374}
2375
2376#[cfg(test)]
2377mod bitcoind_tests {
2378 use serde::Deserialize;
2380 use std::fs::File;
2381 use std::io::BufReader;
2382 use std::path::Path;
2383
2384 use super::*;
2385 use crate::script_builder::ScriptBuilderError;
2386 use kaspa_consensus_core::constants::MAX_TX_IN_SEQUENCE_NUM;
2387 use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync;
2388 use kaspa_consensus_core::tx::{
2389 PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput,
2390 };
2391 use kaspa_hashes::Hash;
2392
2393 #[derive(PartialEq, Eq, Debug, Clone)]
2394 enum UnifiedError {
2395 TxScriptError(TxScriptError),
2396 ScriptBuilderError(ScriptBuilderError),
2397 }
2398
2399 #[derive(PartialEq, Eq, Debug, Clone)]
2400 struct TestError {
2401 expected_result: String,
2402 result: Result<(), UnifiedError>,
2403 }
2404
2405 #[allow(dead_code)]
2406 #[derive(Deserialize, Debug, Clone)]
2407 #[serde(untagged)]
2408 enum JsonTestRow {
2409 Test(String, String, String, String),
2410 TestWithComment(String, String, String, String, String),
2411 Comment((String,)),
2412 }
2413
2414 fn create_spending_transaction(sig_script: Vec<u8>, script_public_key: ScriptPublicKey) -> Transaction {
2415 let coinbase = Transaction::new(
2416 1,
2417 vec![TransactionInput::new(
2418 TransactionOutpoint::new(TransactionId::default(), 0xffffffffu32),
2419 vec![0, 0],
2420 MAX_TX_IN_SEQUENCE_NUM,
2421 MAX_PUB_KEYS_PER_MUTLTISIG as u8,
2422 )],
2423 vec![TransactionOutput::new(0, script_public_key)],
2424 Default::default(),
2425 Default::default(),
2426 Default::default(),
2427 Default::default(),
2428 );
2429
2430 Transaction::new(
2431 1,
2432 vec![TransactionInput::new(
2433 TransactionOutpoint::new(coinbase.id(), 0u32),
2434 sig_script,
2435 MAX_TX_IN_SEQUENCE_NUM,
2436 MAX_PUB_KEYS_PER_MUTLTISIG as u8,
2437 )],
2438 vec![TransactionOutput::new(0, Default::default())],
2439 Default::default(),
2440 Default::default(),
2441 Default::default(),
2442 Default::default(),
2443 )
2444 }
2445
2446 impl JsonTestRow {
2447 fn test_row(&self, flags: EngineFlags) -> Result<(), TestError> {
2448 let (sig_script, script_pub_key, expected_result) = match self.clone() {
2450 JsonTestRow::Test(sig_script, sig_pub_key, _, expected_result) => (sig_script, sig_pub_key, expected_result),
2451 JsonTestRow::TestWithComment(sig_script, sig_pub_key, _, expected_result, _) => {
2452 (sig_script, sig_pub_key, expected_result)
2453 }
2454 JsonTestRow::Comment(_) => {
2455 return Ok(());
2456 }
2457 };
2458
2459 let result = Self::run_test(sig_script, script_pub_key, flags);
2460
2461 match Self::result_name(result.clone()).contains(&expected_result.as_str()) {
2462 true => Ok(()),
2463 false => Err(TestError { expected_result, result }),
2464 }
2465 }
2466
2467 fn run_test(sig_script: String, script_pub_key: String, flags: EngineFlags) -> Result<(), UnifiedError> {
2468 let script_sig = opcodes::parse_short_form_with_flags(sig_script, flags).map_err(UnifiedError::ScriptBuilderError)?;
2469 let script_pub_key = ScriptPublicKey::from_vec(
2470 0,
2471 opcodes::parse_short_form_with_flags(script_pub_key, flags).map_err(UnifiedError::ScriptBuilderError)?,
2472 );
2473
2474 let tx = create_spending_transaction(script_sig, script_pub_key.clone());
2476 let entry = UtxoEntry::new(0, script_pub_key.clone(), 0, true, None);
2477 let populated_tx = PopulatedTransaction::new(&tx, vec![entry]);
2478
2479 let sig_cache = Cache::new(10_000);
2481 let reused_values = SigHashReusedValuesUnsync::new();
2482
2483 struct MockSeqCommitAccessor;
2484 const EXPECTED_INPUT_BLOCK_HASH: [u8; 32] = {
2485 let mut block = [b'f'; 32];
2486 let input = b"input_block";
2487 let mut i = 0;
2488 while i < input.len() {
2489 block[i] = input[i];
2490 i += 1;
2491 }
2492 block
2493 };
2494
2495 const EXPECTED_OUTPUT_ROOT_HASH: [u8; 32] = {
2496 let mut block = [b'f'; 32];
2497 let input = b"output_root_hash";
2498 let mut i = 0;
2499 while i < input.len() {
2500 block[i] = input[i];
2501 i += 1;
2502 }
2503 block
2504 };
2505 impl SeqCommitAccessor for MockSeqCommitAccessor {
2506 fn is_chain_ancestor_from_pov(&self, block_hash: Hash) -> Option<bool> {
2507 (block_hash == Hash::from(EXPECTED_INPUT_BLOCK_HASH)).then_some(true)
2508 }
2509
2510 fn seq_commitment_within_depth(&self, block_hash: Hash) -> Option<Hash> {
2511 (block_hash == Hash::from(EXPECTED_INPUT_BLOCK_HASH)).then_some(Hash::from(EXPECTED_OUTPUT_ROOT_HASH))
2512 }
2513 }
2514
2515 let mut vm = TxScriptEngine::from_transaction_input(
2516 &populated_tx,
2517 &populated_tx.tx().inputs[0],
2518 0,
2519 &populated_tx.entries[0],
2520 EngineCtx::new(&sig_cache).with_reused(&reused_values).with_seq_commit_accessor_opt(Some(&MockSeqCommitAccessor)),
2521 flags,
2522 );
2523 vm.execute().map_err(UnifiedError::TxScriptError)
2524 }
2525
2526 fn result_name(result: Result<(), UnifiedError>) -> Vec<&'static str> {
2550 match result {
2551 Ok(_) => vec!["OK"],
2552 Err(ue) => match ue {
2553 UnifiedError::TxScriptError(e) => match e {
2554 TxScriptError::NumberTooBig(_) => vec!["UNKNOWN_ERROR"],
2555 TxScriptError::Serialization(_) => vec!["UNKNOWN_ERROR"],
2556 TxScriptError::PubKeyFormat => vec!["PUBKEYFORMAT"],
2557 TxScriptError::EvalFalse => vec!["EVAL_FALSE"],
2558 TxScriptError::EmptyStack => {
2559 vec!["EMPTY_STACK", "EVAL_FALSE", "UNBALANCED_CONDITIONAL", "INVALID_ALTSTACK_OPERATION"]
2560 }
2561 TxScriptError::NullFail => vec!["NULLFAIL"],
2562 TxScriptError::InvalidSigHashType(_) => vec!["SIG_HASHTYPE"],
2564 TxScriptError::SignatureScriptNotPushOnly => vec!["SIG_PUSHONLY"],
2565 TxScriptError::CleanStack(_) => vec!["CLEANSTACK"],
2566 TxScriptError::OpcodeReserved(_) => vec!["BAD_OPCODE"],
2567 TxScriptError::MalformedPush(_, _) => vec!["BAD_OPCODE"],
2568 TxScriptError::InvalidOpcode(_) => vec!["BAD_OPCODE"],
2569 TxScriptError::ErrUnbalancedConditional => vec!["UNBALANCED_CONDITIONAL"],
2570 TxScriptError::InvalidState(s) if s == "condition stack empty" => vec!["UNBALANCED_CONDITIONAL"],
2571 TxScriptError::EarlyReturn => vec!["OP_RETURN"],
2573 TxScriptError::VerifyError => vec!["VERIFY", "EQUALVERIFY"],
2574 TxScriptError::InvalidStackOperation(_, _) => vec!["INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION"],
2575 TxScriptError::InvalidState(s) if s == "pick at an invalid location" => vec!["INVALID_STACK_OPERATION"],
2576 TxScriptError::InvalidState(s) if s == "roll at an invalid location" => vec!["INVALID_STACK_OPERATION"],
2577 TxScriptError::OutOfBoundsSubstring(_, _, _) => vec!["UNKNOWN_ERROR"],
2578 TxScriptError::InvalidIndex(_) => vec!["UNKNOWN_ERROR"],
2579 TxScriptError::OpcodeDisabled(_) => vec!["DISABLED_OPCODE"],
2580 TxScriptError::ElementTooBig(_, _) => vec!["PUSH_SIZE"],
2581 TxScriptError::TooManyOperations(_) => vec!["OP_COUNT"],
2582 TxScriptError::StackSizeExceeded(_, _) => vec!["STACK_SIZE"],
2583 TxScriptError::InvalidPubKeyCount(_) => vec!["PUBKEY_COUNT"],
2584 TxScriptError::InvalidSignatureCount(_) => vec!["SIG_COUNT"],
2585 TxScriptError::NotMinimalData(_) => vec!["MINIMALDATA", "UNKNOWN_ERROR"],
2586 TxScriptError::UnsatisfiedLockTime(_) => vec!["UNSATISFIED_LOCKTIME"],
2588 TxScriptError::InvalidState(s) if s == "expected boolean" => vec!["MINIMALIF"],
2589 TxScriptError::InvalidState(_) => vec!["UNKNOWN_ERROR"],
2590 TxScriptError::ScriptSize(_, _) => vec!["SCRIPT_SIZE"],
2591 TxScriptError::CovenantsError(_) => vec!["UNKNOWN_ERROR"],
2592 TxScriptError::InvalidSignature(_) => vec!["INVALID_SIG"],
2593 TxScriptError::InvalidPubkey(_) => vec!["PUBKEYFORMAT"],
2594 _ => vec![],
2595 },
2596 UnifiedError::ScriptBuilderError(e) => match e {
2597 ScriptBuilderError::ElementExceedsMaxSize(_, _) => vec!["PUSH_SIZE"],
2598 _ => vec![],
2599 },
2600 },
2601 }
2602 }
2603 }
2604
2605 fn run_json_test_file(file_name: &str, flags: EngineFlags) {
2606 let file =
2607 File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file");
2608 let reader = BufReader::new(file);
2609
2610 let tests: Vec<JsonTestRow> = serde_json::from_reader(reader).expect("Failed Parsing {:?}");
2612 for row in tests {
2613 if let Err(error) = row.test_row(flags) {
2614 panic!("Test: {:?} failed for {}: {:?}", row.clone(), file_name, error);
2615 }
2616 }
2617 }
2618
2619 #[test]
2620 fn test_bitcoind_tests() {
2621 run_json_test_file("script_tests.json", Default::default());
2622 }
2623
2624 #[test]
2625 fn test_script_pub_keys_from_json_roundtrip_through_string_format() {
2626 let file_name = "script_tests.json";
2627 let file =
2628 File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file");
2629 let reader = BufReader::new(file);
2630 let tests: Vec<JsonTestRow> = serde_json::from_reader(reader).expect("Failed Parsing {:?}");
2631
2632 for row in tests {
2633 let script_pub_key = match row.clone() {
2634 JsonTestRow::Test(_, script_pub_key, _, _) => script_pub_key,
2635 JsonTestRow::TestWithComment(_, script_pub_key, _, _, _) => script_pub_key,
2636 JsonTestRow::Comment(_) => continue,
2637 };
2638
2639 let Ok(script) = opcodes::parse_short_form(script_pub_key.clone()) else {
2640 continue; };
2642
2643 let is_parseable = parse_script::<PopulatedTransaction<'_>, SigHashReusedValuesUnsync>(&script).all(|op| op.is_ok());
2644 if !is_parseable {
2645 continue;
2646 }
2647
2648 let str_script = script_to_str(&script).unwrap();
2649 let reparsed = opcodes::parse_short_form(str_script.clone()).unwrap_or_else(|error| {
2650 panic!(
2651 "failed to reparse stringified scriptPubKey from {}: {:?}; original={}, stringified={}",
2652 file_name, error, script_pub_key, str_script
2653 )
2654 });
2655 if reparsed != script {
2656 continue;
2657 }
2658
2659 assert_eq!(reparsed, script, "scriptPubKey roundtrip mismatch in {} for {:?}", file_name, row);
2660 }
2661 }
2662}