Skip to main content

kaspa_txscript/
lib.rs

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
20/// Consensus-pinned ZK verifier dependencies used by txscript.
21///
22/// Client applications can import these re-exports to use the same verifier
23/// versions and consensus-compatible behavior as the txscript engine.
24pub 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
89// The last opcode that does not count toward operations.
90// Note that this includes OP_RESERVED which counts as a push operation.
91pub 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// TODO: Make it pub(crate)
110#[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    // Engine context for outer caches and various inner contexts
147    ctx: EngineContext<'a, Reused>,
148
149    cond_stack: Vec<OpCond>, // Following if stacks, and whether it is running
150
151    num_ops: i32,
152    runtime_resource_meter: RuntimeResourceMeter,
153    opcode_execution_log_buffer: Option<&'a mut dyn Write>,
154}
155
156/// Captures the engine stacks after execution
157pub struct ExecutionStacks {
158    /// data stack snapshot
159    pub dstack: Vec<StackEntry>,
160    /// alt stack snapshot
161    pub astack: Vec<StackEntry>,
162}
163
164/// A read-only view of the execution stacks
165pub 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
183/// Determines the exact number of signature operations executed in a transaction input
184/// by simulating the script execution. Takes into account conditional branches and only
185/// counts signature operations that are actually executed.
186///
187/// Example of how counts differ:
188/// ```text
189/// IF
190///     CHECKSIG        // 1 sig op if true branch taken
191/// ELSE
192///     CHECKSIG        // 3 sig ops if false branch taken
193///     CHECKSIG
194///     CHECKSIG
195/// ENDIF
196/// ```
197/// `get_sig_op_upper_bound` would return 4, while this function returns 1 or 3
198/// depending on which branch is actually executed.
199///
200/// This function should be used:
201/// - After the runtime signature operation counting hardfork activation
202/// - When exact sig op counts are needed for fee calculation
203/// - For accurate validation of sig op limits
204/// - When working with scripts that have conditional logic
205///
206/// # Arguments
207/// * `tx` - The transaction containing the input to analyze
208/// * `input_idx` - Index of the input to analyze
209/// * `kip10_enabled` - Whether KIP-10 features are enabled
210///
211/// # Returns
212/// * `Ok(u8)` - The exact number of signature operations executed
213/// * `Err(TxScriptError)` - If script execution fails or input index is invalid
214pub 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
238/// Counts sigops in a p2sh redeem script.
239///
240/// This includes from-stack signature opcodes and non-minimal data pushes
241/// used as multisig pubkey counts. Malformed scripts return zero.
242pub 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            // p2sh sigop scanning includes from-stack variants.
266            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/// Calculates an upper bound of signature operations in a script without executing it.
291/// This is faster than `get_sig_op_count` but may overestimate the count in scripts
292/// with conditional logic.
293///
294/// This function should be used:
295/// - Before the runtime signature operation counting hardfork activation
296/// - When you need a conservative upper bound for validation
297/// - When fast static analysis is preferred over exact counting
298/// - For preliminary transaction size and fee estimation
299///
300/// # Arguments
301/// * `signature_script` - The signature script to analyze
302/// * `prev_script_public_key` - The previous output's script public key
303///
304/// # Returns
305/// * `u64` - Upper bound of possible signature operations in the script
306#[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    // For P2SH scripts, the signature script must be non-empty;
318    // otherwise there is no redeem script candidate and the conservative upper bound is zero.
319    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()) // Multiplying by 100 is a heuristic to estimate how costly the script is going to be.
341    .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    // For P2SH scripts, the signature script must be non-empty;
355    // otherwise there is no redeem script candidate and the conservative upper bound is zero.
356    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, // If there's an error in parsing an opcode, the script won't consume any more cost from this point.
387        }
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    // TODO: Check for overflows
397    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                    _ => {} // If the opcode is not sigop, no need to increase the count
417                }
418            }
419            Err(_) => return num_sigs,
420        }
421    }
422    num_sigs
423}
424
425/// Returns whether the passed public key script is unspendable, or guaranteed to fail at execution.
426///
427/// This allows inputs to be pruned instantly when entering the UTXO set.
428pub 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    /// Returns the number of signature operations used in script execution.
457    pub fn used_sig_ops(&self) -> u16 {
458        self.runtime_resource_meter.used_sig_ops()
459    }
460
461    /// Returns the total script units consumed so far.
462    pub fn used_script_units(&self) -> ScriptUnits {
463        self.runtime_resource_meter.used_script_units()
464    }
465
466    /// Returns and resets the currently tracked pushed bytes across both stacks.
467    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    /// Returns a read-only view of the execution stacks
477    pub fn stacks(&self) -> ExecutionStacksView<'_> {
478        ExecutionStacksView { dstack: &self.dstack, astack: &self.astack }
479    }
480
481    /// Creates a new Script Engine for validating transaction input.
482    ///
483    /// # Arguments
484    /// * `tx` - The transaction being validated
485    /// * `input` - The input being validated
486    /// * `input_idx` - Index of the input in the transaction
487    /// * `utxo_entry` - UTXO entry being spent
488    /// * `reused_values` - Reused values for signature hashing
489    /// * `sig_cache` - Cache for signature verification
490    /// * `kip10_enabled` - Whether KIP-10 transaction introspection opcodes are enabled
491    ///
492    /// # Panics
493    /// * When input_idx >= number of inputs in transaction (malformed input)
494    ///
495    /// # Returns
496    /// Script engine instance configured for the given input
497    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        // The script_public_key in P2SH is just validating the hash on the OpMultiSig script
520        // the user provides
521        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        // Different from kaspad: Illegal and disabled opcode are checked on execute instead
581        // Note that this includes OP_RESERVED which counts as a push operation.
582        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        // Moving between scripts - we can't be inside an if
643        if script_result.is_ok() && !self.cond_stack.is_empty() {
644            return Err(TxScriptError::ErrUnbalancedConditional);
645        }
646
647        // Alt stack doesn't persist
648        self.astack.clear();
649        self.num_ops = 0; // number of ops is per script.
650
651        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                // To avoid breaking the old pricing, we only charge for the part of the script public key
663                // that exceeds the standard maximum size. Therefore, the only transactions that are affected
664                // by this change are those with non-standard script public keys.
665                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        // TODO: run all in same iterator?
680        // When both the signature script and public key script are empty the
681        // result is necessarily an error since the stack would end up being
682        // empty which is equivalent to a false top element. Thus, just return
683        // the relevant error now as an optimization.
684        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        // try_for_each quits only if an error occurred. So, we always run over all scripts if
697        // each is successful
698        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            // Save script in p2sh
701            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            // Unknown script versions are accepted without execution, indepedently of the stack state. There's no need to check the error condition.
720            ScriptExecutionOutput::AcceptedUnknownVersion => Ok(()),
721        }
722    }
723
724    /// Executes the scripts without the final error condition checks and returns both stacks in raw vector form.
725    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    // check_error_condition is called whenever we finish a chunk of the scripts
731    // (all original scripts, all scripts including p2sh, and maybe future extensions)
732    // returns Ok(()) if the running script has ended and was successful, leaving a true boolean
733    // on the stack. An error otherwise.
734    #[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    // *** SIGNATURE SPECIFIC CODE **
752
753    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            // Advance through the pub_keys iterator.
805            // Note every check consumes the public key
806            loop {
807                if pub_key_iter.len() < num_sigs - sig_idx {
808                    // When there are more signatures than public keys remaining,
809                    // there is no way to succeed since too many signatures are
810                    // invalid, so exit early.
811                    failed = true;
812                    break 'outer; // Break the outer signature loop
813                }
814                // SAFETY: we just checked the len
815                let pub_key = pub_key_iter.next().unwrap();
816
817                // We don't pass 'enforce_nullfail=true' because we manually enforce it below
818                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                            // Current sig is valid, we can break the inner loop and continue to next sig
828                            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            // Ensure encapsulation of variables (no leaking between tests)
977            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            // Since the script size and element size limits are both 1M, a direct
1113            // push above the element limit necessarily exceeds the script size limit as well.
1114            // Similarly, MAX_OPS_PER_SCRIPT + 1 one-byte opcodes exceed the
1115            // script size limit. We cannot reach these execution checks directly here, but
1116            // we still want to verify that such scripts are rejected by the engine.
1117            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            // The script engine assumes mass_per_byte=1, so this assertion should fail if that ever changes.
1256            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(), // We set the allowed units directly in the engine, so we skip setting sig_op_count and compute_budget.
1376        };
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", // opcodes::codes::OpTrue{data: ""}
1428                expected_result: Ok(()),
1429            },
1430            ScriptTestCase {
1431                script: b"\x61", // opcodes::codes::OpNop{data: ""}
1432                expected_result: Err(TxScriptError::EmptyStack),
1433            },
1434            ScriptTestCase {
1435                script: b"\x51\x51", // opcodes::codes::OpTrue, opcodes::codes::OpTrue,
1436                expected_result: Err(TxScriptError::CleanStack(1)),
1437            },
1438            ScriptTestCase {
1439                script: b"\x00", // opcodes::codes::OpFalse{data: ""},
1440                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", // OpIf
1498                expected_result: Err(TxScriptError::InvalidStackOperation(1, 0)),
1499            },
1500            ScriptTestCase {
1501                script: b"\x52\x63", // Op2, OpIf - any non-zero value is truthy, but the OpIf is left unclosed.
1502                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1503            },
1504            ScriptTestCase {
1505                script: b"\x51\x63", // OpTrue, OpIf
1506                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1507            },
1508            ScriptTestCase {
1509                script: b"\x00\x63", // OpFalse, OpIf
1510                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1511            },
1512            ScriptTestCase {
1513                script: b"\x51\x63\x51\x68", // OpTrue, OpIf, OpTrue, OpEndIf
1514                expected_result: Ok(()),
1515            },
1516            ScriptTestCase {
1517                script: b"\x00\x63\x51\x68", // OpFalse, OpIf, OpTrue, OpEndIf
1518                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", // OpElse
1530                expected_result: Err(TxScriptError::InvalidState("condition stack empty".to_string())),
1531            },
1532            ScriptTestCase {
1533                script: b"\x51\x63\x67", // OpTrue, OpIf, OpElse
1534                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1535            },
1536            ScriptTestCase {
1537                script: b"\x00\x63\x67", // OpFalse, OpIf, OpElse
1538                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1539            },
1540            ScriptTestCase {
1541                script: b"\x51\x63\x51\x67\x68", // OpTrue, OpIf, OpTrue, OpElse, OpEndIf
1542                expected_result: Ok(()),
1543            },
1544            ScriptTestCase {
1545                script: b"\x00\x63\x67\x51\x68", // OpFalse, OpIf, OpElse, OpTrue, OpEndIf
1546                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", // OpNotIf
1558                expected_result: Err(TxScriptError::InvalidStackOperation(1, 0)),
1559            },
1560            ScriptTestCase {
1561                script: b"\x51\x64", // OpTrue, OpNotIf
1562                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1563            },
1564            ScriptTestCase {
1565                script: b"\x00\x64", // OpFalse, OpNotIf
1566                expected_result: Err(TxScriptError::ErrUnbalancedConditional),
1567            },
1568            ScriptTestCase {
1569                script: b"\x51\x64\x67\x51\x68", // OpTrue, OpNotIf, OpElse, OpTrue, OpEndIf
1570                expected_result: Ok(()),
1571            },
1572            ScriptTestCase {
1573                script: b"\x51\x64\x51\x67\x00\x68", // OpTrue, OpNotIf, OpTrue, OpElse, OpFalse, OpEndIf
1574                expected_result: Err(TxScriptError::EvalFalse),
1575            },
1576            ScriptTestCase {
1577                script: b"\x00\x64\x51\x68", // OpFalse, OpIf, OpTrue, OpEndIf
1578                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", // OpTrue, OpIf, OpFalse, OpElse, OpTrue, OpIf,
1590                // OpTrue, OpEndIf, OpEndIf
1591                expected_result: Err(TxScriptError::EvalFalse),
1592            },
1593            ScriptTestCase {
1594                script: b"\x51\x63\x00\x67\x00\x63\x67\x51\x68\x68", // OpTrue, OpIf, OpFalse, OpElse, OpFalse, OpIf,
1595                // OpElse, OpTrue, OpEndIf, OpEndIf
1596                expected_result: Err(TxScriptError::EvalFalse),
1597            },
1598            ScriptTestCase {
1599                script: b"\x51\x64\x00\x67\x51\x63\x51\x68\x68", // OpTrue, OpNotIf, OpFalse, OpElse, OpTrue, OpIf,
1600                // OpTrue, OpEndIf, OpEndIf
1601                expected_result: Ok(()),
1602            },
1603            ScriptTestCase {
1604                script: b"\x51\x64\x00\x67\x00\x63\x67\x51\x68\x68", // OpTrue, OpNotIf, OpFalse, OpElse, OpFalse, OpIf,
1605                // OpTrue, OpEndIf, OpEndIf
1606                expected_result: Ok(()),
1607            },
1608            ScriptTestCase {
1609                script: b"\x51\x64\x00\x67\x00\x64\x00\x67\x51\x68\x68", // OpTrue, OpNotIf, OpFalse, OpElse, OpFalse, OpNotIf,
1610                // OpFalse, OpElse, OpTrue, OpEndIf, OpEndIf
1611                expected_result: Err(TxScriptError::EvalFalse),
1612            },
1613            ScriptTestCase {
1614                script: b"\x51\x00\x63\x63\x00\x68\x68", // OpTrue, OpFalse, OpIf, OpIf  OpFalse, OpEndIf, OpEndIf
1615                expected_result: Ok(()),
1616            },
1617            ScriptTestCase {
1618                script: b"\x51\x00\x63\x63\x63\x00\x67\x00\x68\x68\x68", // OpTrue, OpFalse, OpIf, OpIf  OpFalse, OpEndIf, OpEndIf
1619                expected_result: Ok(()),
1620            },
1621            ScriptTestCase {
1622                script: b"\x51\x00\x63\x63\x63\x63\x00\x67\x00\x68\x68\x68\x68", // OpTrue, OpFalse, OpIf, OpIf  OpFalse, OpEndIf, OpEndIf
1623                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            }, // No script at end but still push only.
1747            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        // OpPushData1 declares a two-byte payload, but only one payload byte follows the length byte.
1806        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    /// Builder for constructing signature scripts with different signature types and combinations.
1918    enum SignatureScriptBuilder {
1919        /// Multisignature script that requires multiple signatures to be valid.
1920        Multisig(Vec<SignatureData>),
1921
1922        /// Single signature script with one signature and its corresponding public key.
1923        Single(SignatureData),
1924
1925        /// Mixed signature script that mix different signature types (e.g., ECDSA and Schnorr)
1926        Mixed(Vec<SignatureData>),
1927
1928        /// Empty signature script builder
1929        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        // Setup keys and test environment
1975        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        // Helper functions for creating signatures
1983        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            // Basic Schnorr CheckSig
2003            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            // Basic ECDSA CheckSig
2012            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            // Mixed Schnorr and ECDSA
2021            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            // 2-of-3 MultiSig test case
2032            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            // Conditional execution with sig ops
2061            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            // Conditional execution with sig ops
2070            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            // Create script
2084            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            // Create transaction
2092            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            // Build signature script
2111            let signature_script = (test.sig_builder)(&tx, &reused_values).build(&script)?;
2112            tx.tx.inputs[0].signature_script = signature_script;
2113
2114            // Execute script
2115            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                    // Test correctly failed
2145                }
2146            }
2147        }
2148
2149        Ok(())
2150    }
2151
2152    // This test checks that valid signatures for OpCheckSig are also valid signatures for OpCheckSigFromStack
2153    #[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    // This test checks that valid signatures for OpCheckSigECDSA are also valid signatures for OpCheckSigFromStackECDSA
2264    #[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    // Bitcoind tests
2379    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            // Parse test to objects
2449            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            // Create transaction
2475            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            // Run transaction
2480            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        /*
2527
2528        // At this point an error was expected so ensure the result of
2529        // the execution matches it.
2530        success := false
2531        for _, code := range allowedErrorCodes {
2532            if IsErrorCode(err, code) {
2533                success = true
2534                break
2535            }
2536        }
2537        if !success {
2538            var scriptErr Error
2539            if ok := errors.As(err, &scriptErr); ok {
2540                t.Errorf("%s: want error codes %v, got %v", name,
2541                    allowedErrorCodes, scriptErr.ErrorCode)
2542                continue
2543            }
2544            t.Errorf("%s: want error codes %v, got err: %v (%T)",
2545                name, allowedErrorCodes, err, err)
2546            continue
2547        }*/
2548
2549        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                        //SIG_HIGH_S
2563                        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                        //ErrInvalidStackOperation
2572                        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                        //ErrNegativeLockTime
2587                        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        // Read the JSON contents of the file as an instance of `User`.
2611        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; // Bitcoind tests include some non-parseable scriptPubKeys which we skip here since the test is about roundtripping parseable ones.
2641            };
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}