1extern crate alloc;
2extern crate core;
3
4pub mod caches;
5mod data_stack;
6pub mod error;
7pub mod opcodes;
8pub mod result;
9pub mod script_builder;
10pub mod script_class;
11pub mod standard;
12#[cfg(feature = "wasm32-sdk")]
13pub mod wasm;
14
15use crate::caches::Cache;
16use crate::data_stack::{DataStack, Stack};
17use crate::opcodes::{deserialize_next_opcode, OpCodeImplementation};
18use itertools::Itertools;
19use kaspa_consensus_core::hashing::sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValues};
20use kaspa_consensus_core::hashing::sighash_type::SigHashType;
21use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionInput, UtxoEntry, VerifiableTransaction};
22use kaspa_txscript_errors::TxScriptError;
23use log::trace;
24use opcodes::codes::OpReturn;
25use opcodes::{codes, to_small_int, OpCond};
26use script_class::ScriptClass;
27
28pub mod prelude {
29 pub use super::standard::*;
30}
31pub use standard::*;
32
33pub const MAX_SCRIPT_PUBLIC_KEY_VERSION: u16 = 0;
34pub const MAX_STACK_SIZE: usize = 244;
35pub const MAX_SCRIPTS_SIZE: usize = 10_000;
36pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520;
37pub const MAX_OPS_PER_SCRIPT: i32 = 201;
38pub const MAX_TX_IN_SEQUENCE_NUM: u64 = u64::MAX;
39pub const SEQUENCE_LOCK_TIME_DISABLED: u64 = 1 << 63;
40pub const SEQUENCE_LOCK_TIME_MASK: u64 = 0x00000000ffffffff;
41pub const LOCK_TIME_THRESHOLD: u64 = 500_000_000_000;
42pub const MAX_PUB_KEYS_PER_MUTLTISIG: i32 = 20;
43
44pub const NO_COST_OPCODE: u8 = 0x60;
47
48#[derive(Clone, Hash, PartialEq, Eq)]
49enum Signature {
50 Secp256k1(secp256k1::schnorr::Signature),
51 Ecdsa(secp256k1::ecdsa::Signature),
52}
53
54#[derive(Clone, Hash, PartialEq, Eq)]
55enum PublicKey {
56 Schnorr(secp256k1::XOnlyPublicKey),
57 Ecdsa(secp256k1::PublicKey),
58}
59
60#[derive(Clone, Hash, PartialEq, Eq)]
62pub struct SigCacheKey {
63 signature: Signature,
64 pub_key: PublicKey,
65 message: secp256k1::Message,
66}
67
68enum ScriptSource<'a, T: VerifiableTransaction> {
69 TxInput { tx: &'a T, input: &'a TransactionInput, id: usize, utxo_entry: &'a UtxoEntry, is_p2sh: bool },
70 StandAloneScripts(Vec<&'a [u8]>),
71}
72
73pub struct TxScriptEngine<'a, T: VerifiableTransaction> {
74 dstack: Stack,
75 astack: Stack,
76
77 script_source: ScriptSource<'a, T>,
78
79 reused_values: &'a mut SigHashReusedValues,
82 sig_cache: &'a Cache<SigCacheKey, bool>,
83
84 cond_stack: Vec<OpCond>, num_ops: i32,
87}
88
89fn parse_script<T: VerifiableTransaction>(
90 script: &[u8],
91) -> impl Iterator<Item = Result<Box<dyn OpCodeImplementation<T>>, TxScriptError>> + '_ {
92 script.iter().batching(|it| deserialize_next_opcode(it))
93}
94
95pub fn get_sig_op_count<T: VerifiableTransaction>(signature_script: &[u8], prev_script_public_key: &ScriptPublicKey) -> u64 {
96 let is_p2sh = ScriptClass::is_pay_to_script_hash(prev_script_public_key.script());
97 let script_pub_key_ops = parse_script::<T>(prev_script_public_key.script()).collect_vec();
98 if !is_p2sh {
99 return get_sig_op_count_by_opcodes(&script_pub_key_ops);
100 }
101
102 let signature_script_ops = parse_script::<T>(signature_script).collect_vec();
103 if signature_script_ops.is_empty() || signature_script_ops.iter().any(|op| op.is_err() || !op.as_ref().unwrap().is_push_opcode()) {
104 return 0;
105 }
106
107 let p2sh_script = signature_script_ops.last().expect("checked if empty above").as_ref().expect("checked if err above").get_data();
108 let p2sh_ops = parse_script::<T>(p2sh_script).collect_vec();
109 get_sig_op_count_by_opcodes(&p2sh_ops)
110}
111
112fn get_sig_op_count_by_opcodes<T: VerifiableTransaction>(opcodes: &[Result<Box<dyn OpCodeImplementation<T>>, TxScriptError>]) -> u64 {
113 let mut num_sigs: u64 = 0;
115 for (i, op) in opcodes.iter().enumerate() {
116 match op {
117 Ok(op) => {
118 match op.value() {
119 codes::OpCheckSig | codes::OpCheckSigVerify | codes::OpCheckSigECDSA => num_sigs += 1,
120 codes::OpCheckMultiSig | codes::OpCheckMultiSigVerify | codes::OpCheckMultiSigECDSA => {
121 if i == 0 {
122 num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
123 continue;
124 }
125
126 let prev_opcode = opcodes[i - 1].as_ref().expect("they were checked before");
127 if prev_opcode.value() >= codes::OpTrue && prev_opcode.value() <= codes::Op16 {
128 num_sigs += to_small_int(prev_opcode) as u64;
129 } else {
130 num_sigs += MAX_PUB_KEYS_PER_MUTLTISIG as u64;
131 }
132 }
133 _ => {} }
135 }
136 Err(_) => return num_sigs,
137 }
138 }
139 num_sigs
140}
141
142pub fn is_unspendable<T: VerifiableTransaction>(script: &[u8]) -> bool {
146 parse_script::<T>(script).enumerate().any(|(index, op)| op.is_err() || (index == 0 && op.unwrap().value() == OpReturn))
147}
148
149impl<'a, T: VerifiableTransaction> TxScriptEngine<'a, T> {
150 pub fn new(reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache<SigCacheKey, bool>) -> Self {
151 Self {
152 dstack: vec![],
153 astack: vec![],
154 script_source: ScriptSource::StandAloneScripts(vec![]),
155 reused_values,
156 sig_cache,
157 cond_stack: vec![],
158 num_ops: 0,
159 }
160 }
161
162 pub fn from_transaction_input(
163 tx: &'a T,
164 input: &'a TransactionInput,
165 input_idx: usize,
166 utxo_entry: &'a UtxoEntry,
167 reused_values: &'a mut SigHashReusedValues,
168 sig_cache: &'a Cache<SigCacheKey, bool>,
169 ) -> Result<Self, TxScriptError> {
170 let script_public_key = utxo_entry.script_public_key.script();
171 let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key);
174 match input_idx < tx.tx().inputs.len() {
175 true => Ok(Self {
176 dstack: Default::default(),
177 astack: Default::default(),
178 script_source: ScriptSource::TxInput { tx, input, id: input_idx, utxo_entry, is_p2sh },
179 reused_values,
180 sig_cache,
181 cond_stack: Default::default(),
182 num_ops: 0,
183 }),
184 false => Err(TxScriptError::InvalidIndex(input_idx, tx.tx().inputs.len())),
185 }
186 }
187
188 pub fn from_script(script: &'a [u8], reused_values: &'a mut SigHashReusedValues, sig_cache: &'a Cache<SigCacheKey, bool>) -> Self {
189 Self {
190 dstack: Default::default(),
191 astack: Default::default(),
192 script_source: ScriptSource::StandAloneScripts(vec![script]),
193 reused_values,
194 sig_cache,
195 cond_stack: Default::default(),
196 num_ops: 0,
197 }
198 }
199
200 #[inline]
201 pub fn is_executing(&self) -> bool {
202 return self.cond_stack.is_empty() || *self.cond_stack.last().expect("Checked not empty") == OpCond::True;
203 }
204
205 fn execute_opcode(&mut self, opcode: Box<dyn OpCodeImplementation<T>>) -> Result<(), TxScriptError> {
206 if !opcode.is_push_opcode() {
209 self.num_ops += 1;
210 if self.num_ops > MAX_OPS_PER_SCRIPT {
211 return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
212 }
213 } else if opcode.len() > MAX_SCRIPT_ELEMENT_SIZE {
214 return Err(TxScriptError::ElementTooBig(opcode.len(), MAX_SCRIPT_ELEMENT_SIZE));
215 }
216
217 if self.is_executing() || opcode.is_conditional() {
218 if opcode.value() > 0 && opcode.value() <= 0x4e {
219 opcode.check_minimal_data_push()?;
220 }
221 opcode.execute(self)
222 } else {
223 Ok(())
224 }
225 }
226
227 fn execute_script(&mut self, script: &[u8], verify_only_push: bool) -> Result<(), TxScriptError> {
228 let script_result = parse_script(script).try_for_each(|opcode| {
229 let opcode = opcode?;
230 if opcode.is_disabled() {
231 return Err(TxScriptError::OpcodeDisabled(format!("{:?}", opcode)));
232 }
233
234 if opcode.always_illegal() {
235 return Err(TxScriptError::OpcodeReserved(format!("{:?}", opcode)));
236 }
237
238 if verify_only_push && !opcode.is_push_opcode() {
239 return Err(TxScriptError::SignatureScriptNotPushOnly);
240 }
241
242 self.execute_opcode(opcode)?;
243
244 let combined_size = self.astack.len() + self.dstack.len();
245 if combined_size > MAX_STACK_SIZE {
246 return Err(TxScriptError::StackSizeExceeded(combined_size, MAX_STACK_SIZE));
247 }
248 Ok(())
249 });
250
251 if script_result.is_ok() && !self.cond_stack.is_empty() {
253 return Err(TxScriptError::ErrUnbalancedConditional);
254 }
255
256 self.astack.clear();
258 self.num_ops = 0; script_result
261 }
262
263 pub fn execute(&mut self) -> Result<(), TxScriptError> {
264 let (scripts, is_p2sh) = match &self.script_source {
265 ScriptSource::TxInput { input, utxo_entry, is_p2sh, .. } => {
266 if utxo_entry.script_public_key.version() > MAX_SCRIPT_PUBLIC_KEY_VERSION {
267 trace!("The version of the scriptPublicKey is higher than the known version - the Execute function returns true.");
268 return Ok(());
269 }
270 (vec![input.signature_script.as_slice(), utxo_entry.script_public_key.script()], *is_p2sh)
271 }
272 ScriptSource::StandAloneScripts(scripts) => (scripts.clone(), false),
273 };
274
275 if scripts.is_empty() {
281 return Err(TxScriptError::NoScripts);
282 }
283
284 if scripts.iter().all(|e| e.is_empty()) {
285 return Err(TxScriptError::EvalFalse);
286 }
287 if let Some(s) = scripts.iter().find(|e| e.len() > MAX_SCRIPTS_SIZE) {
288 return Err(TxScriptError::ScriptSize(s.len(), MAX_SCRIPTS_SIZE));
289 }
290
291 let mut saved_stack: Option<Vec<Vec<u8>>> = None;
292 scripts.iter().enumerate().filter(|(_, s)| !s.is_empty()).try_for_each(|(idx, s)| {
295 let verify_only_push =
296 idx == 0 && matches!(self.script_source, ScriptSource::TxInput { tx: _, input: _, id: _, utxo_entry: _, is_p2sh: _ });
297 if is_p2sh && idx == 1 {
299 saved_stack = Some(self.dstack.clone());
300 }
301 self.execute_script(s, verify_only_push)
302 })?;
303
304 if is_p2sh {
305 self.check_error_condition(false)?;
306 self.dstack = saved_stack.ok_or(TxScriptError::EmptyStack)?;
307 let script = self.dstack.pop().ok_or(TxScriptError::EmptyStack)?;
308 self.execute_script(script.as_slice(), false)?
309 }
310
311 self.check_error_condition(true)?;
312 Ok(())
313 }
314
315 #[inline]
320 fn check_error_condition(&mut self, final_script: bool) -> Result<(), TxScriptError> {
321 if final_script {
322 if self.dstack.len() > 1 {
323 return Err(TxScriptError::CleanStack(self.dstack.len() - 1));
324 } else if self.dstack.is_empty() {
325 return Err(TxScriptError::EmptyStack);
326 }
327 }
328
329 let [v]: [bool; 1] = self.dstack.pop_items()?;
330 match v {
331 true => Ok(()),
332 false => Err(TxScriptError::EvalFalse),
333 }
334 }
335
336 fn check_pub_key_encoding(pub_key: &[u8]) -> Result<(), TxScriptError> {
339 match pub_key.len() {
340 32 => Ok(()),
341 _ => Err(TxScriptError::PubKeyFormat),
342 }
343 }
344
345 fn check_pub_key_encoding_ecdsa(pub_key: &[u8]) -> Result<(), TxScriptError> {
346 match pub_key.len() {
347 33 => Ok(()),
348 _ => Err(TxScriptError::PubKeyFormat),
349 }
350 }
351
352 fn op_check_multisig_schnorr_or_ecdsa(&mut self, ecdsa: bool) -> Result<(), TxScriptError> {
353 let [num_keys]: [i32; 1] = self.dstack.pop_items()?;
354 if num_keys < 0 {
355 return Err(TxScriptError::InvalidPubKeyCount(format!("number of pubkeys {num_keys} is negative")));
356 } else if num_keys > MAX_PUB_KEYS_PER_MUTLTISIG {
357 return Err(TxScriptError::InvalidPubKeyCount(format!("too many pubkeys {num_keys} > {MAX_PUB_KEYS_PER_MUTLTISIG}")));
358 }
359 let num_keys_usize = num_keys as usize;
360
361 self.num_ops += num_keys;
362 if self.num_ops > MAX_OPS_PER_SCRIPT {
363 return Err(TxScriptError::TooManyOperations(MAX_OPS_PER_SCRIPT));
364 }
365
366 let pub_keys = match self.dstack.len() >= num_keys_usize {
367 true => self.dstack.split_off(self.dstack.len() - num_keys_usize),
368 false => return Err(TxScriptError::InvalidStackOperation(num_keys_usize, self.dstack.len())),
369 };
370
371 let [num_sigs]: [i32; 1] = self.dstack.pop_items()?;
372 if num_sigs < 0 {
373 return Err(TxScriptError::InvalidSignatureCount(format!("number of signatures {num_sigs} is negative")));
374 } else if num_sigs > num_keys {
375 return Err(TxScriptError::InvalidSignatureCount(format!("more signatures than pubkeys {num_sigs} > {num_keys}")));
376 }
377 let num_sigs = num_sigs as usize;
378
379 let signatures = match self.dstack.len() >= num_sigs {
380 true => self.dstack.split_off(self.dstack.len() - num_sigs),
381 false => return Err(TxScriptError::InvalidStackOperation(num_sigs, self.dstack.len())),
382 };
383
384 let mut failed = false;
385 let mut pub_key_iter = pub_keys.iter();
386 'outer: for (sig_idx, signature) in signatures.iter().enumerate() {
387 if signature.is_empty() {
388 failed = true;
389 break;
390 }
391
392 let typ = *signature.last().expect("checked that is not empty");
393 let signature = &signature[..signature.len() - 1];
394 let hash_type = SigHashType::from_u8(typ).map_err(|_| TxScriptError::InvalidSigHashType(typ))?;
395
396 loop {
399 if pub_key_iter.len() < num_sigs - sig_idx {
400 failed = true;
404 break 'outer; }
406 let pub_key = pub_key_iter.next().unwrap();
408
409 let check_signature_result = if ecdsa {
410 self.check_ecdsa_signature(hash_type, pub_key.as_slice(), signature)
411 } else {
412 self.check_schnorr_signature(hash_type, pub_key.as_slice(), signature)
413 };
414
415 match check_signature_result {
416 Ok(valid) => {
417 if valid {
418 break;
420 }
421 }
422 Err(e) => {
423 return Err(e);
424 }
425 }
426 }
427 }
428
429 if failed && signatures.iter().any(|sig| !sig.is_empty()) {
430 return Err(TxScriptError::NullFail);
431 }
432
433 self.dstack.push_item(!failed);
434 Ok(())
435 }
436
437 #[inline]
438 fn check_schnorr_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
439 match self.script_source {
440 ScriptSource::TxInput { tx, id, .. } => {
441 if sig.len() != 64 {
442 return Err(TxScriptError::SigLength(sig.len()));
443 }
444 Self::check_pub_key_encoding(key)?;
445 let pk = secp256k1::XOnlyPublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?;
446 let sig = secp256k1::schnorr::Signature::from_slice(sig).map_err(TxScriptError::InvalidSignature)?;
447 let sig_hash = calc_schnorr_signature_hash(tx, id, hash_type, self.reused_values);
448 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
449 let sig_cache_key =
450 SigCacheKey { signature: Signature::Secp256k1(sig), pub_key: PublicKey::Schnorr(pk), message: msg };
451
452 match self.sig_cache.get(&sig_cache_key) {
453 Some(valid) => Ok(valid),
454 None => {
455 match sig.verify(&msg, &pk) {
457 Ok(()) => {
458 self.sig_cache.insert(sig_cache_key, true);
459 Ok(true)
460 }
461 Err(_) => {
462 self.sig_cache.insert(sig_cache_key, false);
463 Ok(false)
464 }
465 }
466 }
467 }
468 }
469 _ => Err(TxScriptError::NotATransactionInput),
470 }
471 }
472
473 fn check_ecdsa_signature(&mut self, hash_type: SigHashType, key: &[u8], sig: &[u8]) -> Result<bool, TxScriptError> {
474 match self.script_source {
475 ScriptSource::TxInput { tx, id, .. } => {
476 if sig.len() != 64 {
477 return Err(TxScriptError::SigLength(sig.len()));
478 }
479 Self::check_pub_key_encoding_ecdsa(key)?;
480 let pk = secp256k1::PublicKey::from_slice(key).map_err(TxScriptError::InvalidSignature)?;
481 let sig = secp256k1::ecdsa::Signature::from_compact(sig).map_err(TxScriptError::InvalidSignature)?;
482 let sig_hash = calc_ecdsa_signature_hash(tx, id, hash_type, self.reused_values);
483 let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
484 let sig_cache_key = SigCacheKey { signature: Signature::Ecdsa(sig), pub_key: PublicKey::Ecdsa(pk), message: msg };
485
486 match self.sig_cache.get(&sig_cache_key) {
487 Some(valid) => Ok(valid),
488 None => {
489 match sig.verify(&msg, &pk) {
491 Ok(()) => {
492 self.sig_cache.insert(sig_cache_key, true);
493 Ok(true)
494 }
495 Err(_) => {
496 self.sig_cache.insert(sig_cache_key, false);
497 Ok(false)
498 }
499 }
500 }
501 }
502 }
503 _ => Err(TxScriptError::NotATransactionInput),
504 }
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use std::iter::once;
511
512 use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue};
513
514 use super::*;
515 use kaspa_consensus_core::tx::{
516 PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput,
517 };
518 use smallvec::SmallVec;
519
520 struct ScriptTestCase {
521 script: &'static [u8],
522 expected_result: Result<(), TxScriptError>,
523 }
524
525 struct KeyTestCase {
526 name: &'static str,
527 key: &'static [u8],
528 is_valid: bool,
529 }
530
531 struct VerifiableTransactionMock {}
532
533 impl VerifiableTransaction for VerifiableTransactionMock {
534 fn tx(&self) -> &Transaction {
535 unimplemented!()
536 }
537
538 fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) {
539 unimplemented!()
540 }
541 }
542
543 fn run_test_script_cases(test_cases: Vec<ScriptTestCase>) {
544 let sig_cache = Cache::new(10_000);
545 let mut reused_values = SigHashReusedValues::new();
546
547 for test in test_cases {
548 let input = TransactionInput {
550 previous_outpoint: TransactionOutpoint {
551 transaction_id: TransactionId::from_bytes([
552 0xc9, 0x97, 0xa5, 0xe5, 0x6e, 0x10, 0x41, 0x02, 0xfa, 0x20, 0x9c, 0x6a, 0x85, 0x2d, 0xd9, 0x06, 0x60, 0xa2,
553 0x0b, 0x2d, 0x9c, 0x35, 0x24, 0x23, 0xed, 0xce, 0x25, 0x85, 0x7f, 0xcd, 0x37, 0x04,
554 ]),
555 index: 0,
556 },
557 signature_script: vec![],
558 sequence: 4294967295,
559 sig_op_count: 0,
560 };
561 let output = TransactionOutput { value: 1000000000, script_public_key: ScriptPublicKey::new(0, test.script.into()) };
562
563 let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]);
564 let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase());
565
566 let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]);
567
568 let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &mut reused_values, &sig_cache)
569 .expect("Script creation failed");
570 assert_eq!(vm.execute(), test.expected_result);
571 }
572 }
573
574 #[test]
575 fn test_check_error_condition() {
576 let test_cases = vec![
577 ScriptTestCase {
578 script: b"\x51", expected_result: Ok(()),
580 },
581 ScriptTestCase {
582 script: b"\x61", expected_result: Err(TxScriptError::EmptyStack),
584 },
585 ScriptTestCase {
586 script: b"\x51\x51", expected_result: Err(TxScriptError::CleanStack(1)),
588 },
589 ScriptTestCase {
590 script: b"\x00", expected_result: Err(TxScriptError::EvalFalse),
592 },
593 ];
594
595 run_test_script_cases(test_cases)
596 }
597
598 #[test]
599 fn test_check_opif() {
600 let test_cases = vec![
601 ScriptTestCase {
602 script: b"\x63", expected_result: Err(TxScriptError::EmptyStack),
604 },
605 ScriptTestCase {
606 script: b"\x52\x63", expected_result: Err(TxScriptError::InvalidState("expected boolean".to_string())),
608 },
609 ScriptTestCase {
610 script: b"\x51\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
612 },
613 ScriptTestCase {
614 script: b"\x00\x63", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
616 },
617 ScriptTestCase {
618 script: b"\x51\x63\x51\x68", expected_result: Ok(()),
620 },
621 ScriptTestCase {
622 script: b"\x00\x63\x51\x68", expected_result: Err(TxScriptError::EmptyStack),
624 },
625 ];
626
627 run_test_script_cases(test_cases)
628 }
629
630 #[test]
631 fn test_check_opelse() {
632 let test_cases = vec![
633 ScriptTestCase {
634 script: b"\x67", expected_result: Err(TxScriptError::InvalidState("condition stack empty".to_string())),
636 },
637 ScriptTestCase {
638 script: b"\x51\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
640 },
641 ScriptTestCase {
642 script: b"\x00\x63\x67", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
644 },
645 ScriptTestCase {
646 script: b"\x51\x63\x51\x67\x68", expected_result: Ok(()),
648 },
649 ScriptTestCase {
650 script: b"\x00\x63\x67\x51\x68", expected_result: Ok(()),
652 },
653 ];
654
655 run_test_script_cases(test_cases)
656 }
657
658 #[test]
659 fn test_check_opnotif() {
660 let test_cases = vec![
661 ScriptTestCase {
662 script: b"\x64", expected_result: Err(TxScriptError::EmptyStack),
664 },
665 ScriptTestCase {
666 script: b"\x51\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
668 },
669 ScriptTestCase {
670 script: b"\x00\x64", expected_result: Err(TxScriptError::ErrUnbalancedConditional),
672 },
673 ScriptTestCase {
674 script: b"\x51\x64\x67\x51\x68", expected_result: Ok(()),
676 },
677 ScriptTestCase {
678 script: b"\x51\x64\x51\x67\x00\x68", expected_result: Err(TxScriptError::EvalFalse),
680 },
681 ScriptTestCase {
682 script: b"\x00\x64\x51\x68", expected_result: Ok(()),
684 },
685 ];
686
687 run_test_script_cases(test_cases)
688 }
689
690 #[test]
691 fn test_check_nestedif() {
692 let test_cases = vec![
693 ScriptTestCase {
694 script: b"\x51\x63\x00\x67\x51\x63\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
697 },
698 ScriptTestCase {
699 script: b"\x51\x63\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
702 },
703 ScriptTestCase {
704 script: b"\x51\x64\x00\x67\x51\x63\x51\x68\x68", expected_result: Ok(()),
707 },
708 ScriptTestCase {
709 script: b"\x51\x64\x00\x67\x00\x63\x67\x51\x68\x68", expected_result: Ok(()),
712 },
713 ScriptTestCase {
714 script: b"\x51\x64\x00\x67\x00\x64\x00\x67\x51\x68\x68", expected_result: Err(TxScriptError::EvalFalse),
717 },
718 ScriptTestCase {
719 script: b"\x51\x00\x63\x63\x00\x68\x68", expected_result: Ok(()),
721 },
722 ScriptTestCase {
723 script: b"\x51\x00\x63\x63\x63\x00\x67\x00\x68\x68\x68", expected_result: Ok(()),
725 },
726 ScriptTestCase {
727 script: b"\x51\x00\x63\x63\x63\x63\x00\x67\x00\x68\x68\x68\x68", expected_result: Ok(()),
729 },
730 ];
731
732 run_test_script_cases(test_cases)
733 }
734
735 #[test]
736 fn test_check_pub_key_encode() {
737 let test_cases = vec![
738 KeyTestCase {
739 name: "uncompressed - invalid",
740 key: &[
741 0x04u8, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6,
742 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd,
743 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f,
744 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3,
745 ],
746 is_valid: false,
747 },
748 KeyTestCase {
749 name: "compressed - invalid",
750 key: &[
751 0x02, 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, 0x2e, 0x9c,
752 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d,
753 ],
754 is_valid: false,
755 },
756 KeyTestCase {
757 name: "compressed - invalid",
758 key: &[
759 0x03, 0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21,
760 0x88, 0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
761 ],
762 is_valid: false,
763 },
764 KeyTestCase {
765 name: "hybrid - invalid",
766 key: &[
767 0x06, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b,
768 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, 0x48, 0x3a, 0xda, 0x77, 0x26,
769 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19,
770 0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8,
771 ],
772 is_valid: false,
773 },
774 KeyTestCase {
775 name: "32 bytes pubkey - Ok",
776 key: &[
777 0x26, 0x89, 0xc7, 0xc2, 0xda, 0xb1, 0x33, 0x09, 0xfb, 0x14, 0x3e, 0x0e, 0x8f, 0xe3, 0x96, 0x34, 0x25, 0x21, 0x88,
778 0x7e, 0x97, 0x66, 0x90, 0xb6, 0xb4, 0x7f, 0x5b, 0x2a, 0x4b, 0x7d, 0x44, 0x8e,
779 ],
780 is_valid: true,
781 },
782 KeyTestCase { name: "empty", key: &[], is_valid: false },
783 ];
784
785 for test in test_cases {
786 let check = TxScriptEngine::<PopulatedTransaction>::check_pub_key_encoding(test.key);
787 if test.is_valid {
788 assert_eq!(
789 check,
790 Ok(()),
791 "checkSignatureLength test '{}' failed when it should have succeeded: {:?}",
792 test.name,
793 check
794 )
795 } else {
796 assert_eq!(
797 check,
798 Err(TxScriptError::PubKeyFormat),
799 "checkSignatureEncoding test '{}' succeeded or failed on wrong format ({:?})",
800 test.name,
801 check
802 )
803 }
804 }
805 }
806
807 #[test]
808 fn test_get_sig_op_count() {
809 struct TestVector<'a> {
810 name: &'a str,
811 signature_script: &'a [u8],
812 expected_sig_ops: u64,
813 prev_script_public_key: ScriptPublicKey,
814 }
815
816 let script_hash = hex::decode("433ec2ac1ffa1b7b7d027f564529c57197f9ae88").unwrap();
817 let prev_script_pubkey_p2sh_script =
818 [OpBlake2b, OpData32].iter().copied().chain(script_hash.iter().copied()).chain(once(OpEqual));
819 let prev_script_pubkey_p2sh = ScriptPublicKey::new(0, SmallVec::from_iter(prev_script_pubkey_p2sh_script));
820
821 let tests = [
822 TestVector {
823 name: "scriptSig doesn't parse",
824 signature_script: &[OpPushData1, 0x02],
825 expected_sig_ops: 0,
826 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
827 },
828 TestVector {
829 name: "scriptSig isn't push only",
830 signature_script: &[OpTrue, OpDup],
831 expected_sig_ops: 0,
832 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
833 },
834 TestVector {
835 name: "scriptSig length 0",
836 signature_script: &[],
837 expected_sig_ops: 0,
838 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
839 },
840 TestVector {
841 name: "No script at the end",
842 signature_script: &[OpTrue, OpTrue],
843 expected_sig_ops: 0,
844 prev_script_public_key: prev_script_pubkey_p2sh.clone(),
845 }, TestVector {
847 name: "pushed script doesn't parse",
848 signature_script: &[OpData2, OpPushData1, 0x02],
849 expected_sig_ops: 0,
850 prev_script_public_key: prev_script_pubkey_p2sh,
851 },
852 TestVector {
853 name: "mainnet multisig transaction 487f94ffa63106f72644068765b9dc629bb63e481210f382667d4a93b69af412",
854 signature_script: &hex::decode("41eb577889fa28283709201ef5b056745c6cf0546dd31666cecd41c40a581b256e885d941b86b14d44efacec12d614e7fcabf7b341660f95bab16b71d766ab010501411c0eeef117ca485d34e4bc0cf6d5b578aa250c5d13ebff0882a7e2eeea1f31e8ecb6755696d194b1b0fcb853afab28b61f3f7cec487bd611df7e57252802f535014c875220ab64c7691713a32ea6dfced9155c5c26e8186426f0697af0db7a4b1340f992d12041ae738d66fe3d21105483e5851778ad73c5cddf0819c5e8fd8a589260d967e72065120722c36d3fac19646258481dd3661fa767da151304af514cb30af5cb5692203cd7690ecb67cbbe6cafad00a7c9133da535298ab164549e0cce2658f7b3032754ae").unwrap(),
855 prev_script_public_key: ScriptPublicKey::new(
856 0,
857 SmallVec::from_slice(&hex::decode("aa20f38031f61ca23d70844f63a477d07f0b2c2decab907c2e096e548b0e08721c7987").unwrap()),
858 ),
859 expected_sig_ops: 4,
860 },
861 TestVector {
862 name: "a partially parseable script public key",
863 signature_script: &[],
864 prev_script_public_key: ScriptPublicKey::new(
865 0,
866 SmallVec::from_slice(&[OpCheckSig,OpCheckSig, OpData1]),
867 ),
868 expected_sig_ops: 2,
869 },
870 TestVector {
871 name: "p2pk",
872 signature_script: &hex::decode("416db0c0ce824a6d076c8e73aae9987416933df768e07760829cb0685dc0a2bbb11e2c0ced0cab806e111a11cbda19784098fd25db176b6a9d7c93e5747674d32301").unwrap(),
873 prev_script_public_key: ScriptPublicKey::new(
874 0,
875 SmallVec::from_slice(&hex::decode("208a457ca74ade0492c44c440da1cab5b008d8449150fe2794f0d8f4cce7e8aa27ac").unwrap()),
876 ),
877 expected_sig_ops: 1,
878 },
879 ];
880
881 for test in tests {
882 assert_eq!(
883 get_sig_op_count::<VerifiableTransactionMock>(test.signature_script, &test.prev_script_public_key),
884 test.expected_sig_ops,
885 "failed for '{}'",
886 test.name
887 );
888 }
889 }
890
891 #[test]
892 fn test_is_unspendable() {
893 struct Test<'a> {
894 name: &'a str,
895 script_public_key: &'a [u8],
896 expected: bool,
897 }
898 let tests = vec![
899 Test { name: "unspendable", script_public_key: &[0x6a, 0x04, 0x74, 0x65, 0x73, 0x74], expected: true },
900 Test {
901 name: "spendable",
902 script_public_key: &[
903 0x76, 0xa9, 0x14, 0x29, 0x95, 0xa0, 0xfe, 0x68, 0x43, 0xfa, 0x9b, 0x95, 0x45, 0x97, 0xf0, 0xdc, 0xa7, 0xa4, 0x4d,
904 0xf6, 0xfa, 0x0b, 0x5c, 0x88, 0xac,
905 ],
906 expected: false,
907 },
908 ];
909
910 for test in tests {
911 assert_eq!(
912 is_unspendable::<VerifiableTransactionMock>(test.script_public_key),
913 test.expected,
914 "failed for '{}'",
915 test.name
916 );
917 }
918 }
919}
920
921#[cfg(test)]
922mod bitcoind_tests {
923 use serde::Deserialize;
925 use std::fs::File;
926 use std::io::BufReader;
927 use std::path::Path;
928
929 use super::*;
930 use crate::script_builder::ScriptBuilderError;
931 use kaspa_consensus_core::constants::MAX_TX_IN_SEQUENCE_NUM;
932 use kaspa_consensus_core::tx::{
933 PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput,
934 };
935
936 #[derive(PartialEq, Eq, Debug, Clone)]
937 enum UnifiedError {
938 TxScriptError(TxScriptError),
939 ScriptBuilderError(ScriptBuilderError),
940 }
941
942 #[derive(PartialEq, Eq, Debug, Clone)]
943 struct TestError {
944 expected_result: String,
945 result: Result<(), UnifiedError>,
946 }
947
948 #[allow(dead_code)]
949 #[derive(Deserialize, Debug, Clone)]
950 #[serde(untagged)]
951 enum JsonTestRow {
952 Test(String, String, String, String),
953 TestWithComment(String, String, String, String, String),
954 Comment((String,)),
955 }
956
957 fn create_spending_transaction(sig_script: Vec<u8>, script_public_key: ScriptPublicKey) -> Transaction {
958 let coinbase = Transaction::new(
959 1,
960 vec![TransactionInput::new(
961 TransactionOutpoint::new(TransactionId::default(), 0xffffffffu32),
962 vec![0, 0],
963 MAX_TX_IN_SEQUENCE_NUM,
964 Default::default(),
965 )],
966 vec![TransactionOutput::new(0, script_public_key)],
967 Default::default(),
968 Default::default(),
969 Default::default(),
970 Default::default(),
971 );
972
973 Transaction::new(
974 1,
975 vec![TransactionInput::new(
976 TransactionOutpoint::new(coinbase.id(), 0u32),
977 sig_script,
978 MAX_TX_IN_SEQUENCE_NUM,
979 Default::default(),
980 )],
981 vec![TransactionOutput::new(0, Default::default())],
982 Default::default(),
983 Default::default(),
984 Default::default(),
985 Default::default(),
986 )
987 }
988
989 impl JsonTestRow {
990 fn test_row(&self) -> Result<(), TestError> {
991 let (sig_script, script_pub_key, expected_result) = match self.clone() {
993 JsonTestRow::Test(sig_script, sig_pub_key, _, expected_result) => (sig_script, sig_pub_key, expected_result),
994 JsonTestRow::TestWithComment(sig_script, sig_pub_key, _, expected_result, _) => {
995 (sig_script, sig_pub_key, expected_result)
996 }
997 JsonTestRow::Comment(_) => {
998 return Ok(());
999 }
1000 };
1001
1002 let result = Self::run_test(sig_script, script_pub_key);
1003
1004 match Self::result_name(result.clone()).contains(&expected_result.as_str()) {
1005 true => Ok(()),
1006 false => Err(TestError { expected_result, result }),
1007 }
1008 }
1009
1010 fn run_test(sig_script: String, script_pub_key: String) -> Result<(), UnifiedError> {
1011 let script_sig = opcodes::parse_short_form(sig_script).map_err(UnifiedError::ScriptBuilderError)?;
1012 let script_pub_key =
1013 ScriptPublicKey::from_vec(0, opcodes::parse_short_form(script_pub_key).map_err(UnifiedError::ScriptBuilderError)?);
1014
1015 let tx = create_spending_transaction(script_sig, script_pub_key.clone());
1017 let entry = UtxoEntry::new(0, script_pub_key.clone(), 0, true);
1018 let populated_tx = PopulatedTransaction::new(&tx, vec![entry]);
1019
1020 let sig_cache = Cache::new(10_000);
1022 let mut reused_values = SigHashReusedValues::new();
1023 let mut vm = TxScriptEngine::from_transaction_input(
1024 &populated_tx,
1025 &populated_tx.tx().inputs[0],
1026 0,
1027 &populated_tx.entries[0],
1028 &mut reused_values,
1029 &sig_cache,
1030 )
1031 .map_err(UnifiedError::TxScriptError)?;
1032 vm.execute().map_err(UnifiedError::TxScriptError)
1033 }
1034
1035 fn result_name(result: Result<(), UnifiedError>) -> Vec<&'static str> {
1059 match result {
1060 Ok(_) => vec!["OK"],
1061 Err(ue) => match ue {
1062 UnifiedError::TxScriptError(e) => match e {
1063 TxScriptError::NumberTooBig(_) => vec!["UNKNOWN_ERROR"],
1064 TxScriptError::PubKeyFormat => vec!["PUBKEYFORMAT"],
1065 TxScriptError::EvalFalse => vec!["EVAL_FALSE"],
1066 TxScriptError::EmptyStack => {
1067 vec!["EMPTY_STACK", "EVAL_FALSE", "UNBALANCED_CONDITIONAL", "INVALID_ALTSTACK_OPERATION"]
1068 }
1069 TxScriptError::NullFail => vec!["NULLFAIL"],
1070 TxScriptError::SigLength(_) => vec!["NULLFAIL"],
1071 TxScriptError::InvalidSigHashType(_) => vec!["SIG_HASHTYPE"],
1073 TxScriptError::SignatureScriptNotPushOnly => vec!["SIG_PUSHONLY"],
1074 TxScriptError::CleanStack(_) => vec!["CLEANSTACK"],
1075 TxScriptError::OpcodeReserved(_) => vec!["BAD_OPCODE"],
1076 TxScriptError::MalformedPush(_, _) => vec!["BAD_OPCODE"],
1077 TxScriptError::InvalidOpcode(_) => vec!["BAD_OPCODE"],
1078 TxScriptError::ErrUnbalancedConditional => vec!["UNBALANCED_CONDITIONAL"],
1079 TxScriptError::InvalidState(s) if s == "condition stack empty" => vec!["UNBALANCED_CONDITIONAL"],
1080 TxScriptError::EarlyReturn => vec!["OP_RETURN"],
1082 TxScriptError::VerifyError => vec!["VERIFY", "EQUALVERIFY"],
1083 TxScriptError::InvalidStackOperation(_, _) => vec!["INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION"],
1084 TxScriptError::InvalidState(s) if s == "pick at an invalid location" => vec!["INVALID_STACK_OPERATION"],
1085 TxScriptError::InvalidState(s) if s == "roll at an invalid location" => vec!["INVALID_STACK_OPERATION"],
1086 TxScriptError::OpcodeDisabled(_) => vec!["DISABLED_OPCODE"],
1087 TxScriptError::ElementTooBig(_, _) => vec!["PUSH_SIZE"],
1088 TxScriptError::TooManyOperations(_) => vec!["OP_COUNT"],
1089 TxScriptError::StackSizeExceeded(_, _) => vec!["STACK_SIZE"],
1090 TxScriptError::InvalidPubKeyCount(_) => vec!["PUBKEY_COUNT"],
1091 TxScriptError::InvalidSignatureCount(_) => vec!["SIG_COUNT"],
1092 TxScriptError::NotMinimalData(_) => vec!["MINIMALDATA", "UNKNOWN_ERROR"],
1093 TxScriptError::UnsatisfiedLockTime(_) => vec!["UNSATISFIED_LOCKTIME"],
1095 TxScriptError::InvalidState(s) if s == "expected boolean" => vec!["MINIMALIF"],
1096 TxScriptError::ScriptSize(_, _) => vec!["SCRIPT_SIZE"],
1097 _ => vec![],
1098 },
1099 UnifiedError::ScriptBuilderError(e) => match e {
1100 ScriptBuilderError::ElementExceedsMaxSize(_) => vec!["PUSH_SIZE"],
1101 _ => vec![],
1102 },
1103 },
1104 }
1105 }
1106 }
1107
1108 #[test]
1109 fn test_bitcoind_tests() {
1110 let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join("script_tests.json"))
1111 .expect("Could not find test file");
1112 let reader = BufReader::new(file);
1113
1114 let tests: Vec<JsonTestRow> = serde_json::from_reader(reader).expect("Failed Parsing {:?}");
1116 let mut had_errors = 0;
1117 let total_tests = tests.len();
1118 for row in tests {
1119 if let Err(error) = row.test_row() {
1120 println!("Test: {:?} failed: {:?}", row.clone(), error);
1121 had_errors += 1;
1122 }
1123 }
1124 if had_errors > 0 {
1125 panic!("{}/{} json tests failed", had_errors, total_tests)
1126 }
1127 }
1128}