diff --git a/consensus/benches/check_scripts.rs b/consensus/benches/check_scripts.rs index 42804bc328..629f4ad797 100644 --- a/consensus/benches/check_scripts.rs +++ b/consensus/benches/check_scripts.rs @@ -78,6 +78,7 @@ fn mock_tx_with_payload(inputs_count: usize, non_uniq_signatures: usize, payload } fn benchmark_check_scripts(c: &mut Criterion) { + let flags = Default::default(); for inputs_count in [100, 50, 25, 10, 5, 2] { for non_uniq_signatures in [0, inputs_count / 2] { let (tx, utxos) = mock_tx_with_payload(inputs_count, non_uniq_signatures, 0); @@ -89,7 +90,7 @@ fn benchmark_check_scripts(c: &mut Criterion) { let cache = Cache::new(inputs_count as u64); b.iter(|| { cache.clear(); - check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable())).unwrap(); + check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable()), flags).unwrap(); }) }); @@ -98,7 +99,7 @@ fn benchmark_check_scripts(c: &mut Criterion) { let cache = Cache::new(inputs_count as u64); b.iter(|| { cache.clear(); - check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable())).unwrap(); + check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), flags).unwrap(); }) }); @@ -110,7 +111,8 @@ fn benchmark_check_scripts(c: &mut Criterion) { let cache = Cache::new(inputs_count as u64); b.iter(|| { cache.clear(); - check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool)).unwrap(); + check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool), flags) + .unwrap(); }) }); } @@ -146,7 +148,7 @@ fn benchmark_check_scripts_with_payload(c: &mut Criterion) { let cache = Cache::new(inputs_count as u64); b.iter(|| { cache.clear(); - check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable())).unwrap(); + check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), Default::default()).unwrap(); }) }); } diff --git a/consensus/client/src/serializable/numeric.rs b/consensus/client/src/serializable/numeric.rs index 6c24db634a..2fbcbe94ae 100644 --- a/consensus/client/src/serializable/numeric.rs +++ b/consensus/client/src/serializable/numeric.rs @@ -271,7 +271,7 @@ impl SerializableTransaction { outputs: outputs.into_iter().map(Into::into).collect(), version: transaction.version, lock_time: transaction.lock_time, - subnetwork_id: transaction.subnetwork_id.clone(), + subnetwork_id: transaction.subnetwork_id, gas: transaction.gas, mass: transaction.mass(), payload: transaction.payload.clone(), @@ -290,7 +290,7 @@ impl SerializableTransaction { outputs, version: inner.version, lock_time: inner.lock_time, - subnetwork_id: inner.subnetwork_id.clone(), + subnetwork_id: inner.subnetwork_id, gas: inner.gas, payload: inner.payload.clone(), mass: inner.mass, @@ -319,7 +319,7 @@ impl SerializableTransaction { inputs, outputs, lock_time: transaction.lock_time, - subnetwork_id: transaction.subnetwork_id.clone(), + subnetwork_id: transaction.subnetwork_id, gas: transaction.gas, mass: transaction.mass(), payload: transaction.payload.clone(), diff --git a/consensus/client/src/serializable/string.rs b/consensus/client/src/serializable/string.rs index 35c7907b29..a7fff11165 100644 --- a/consensus/client/src/serializable/string.rs +++ b/consensus/client/src/serializable/string.rs @@ -263,7 +263,7 @@ impl SerializableTransaction { version: transaction.version, outputs: outputs.into_iter().map(Into::into).collect(), lock_time: transaction.lock_time.to_string(), - subnetwork_id: transaction.subnetwork_id.clone(), + subnetwork_id: transaction.subnetwork_id, gas: transaction.gas.to_string(), mass: transaction.mass().to_string(), payload: transaction.payload.clone(), @@ -281,7 +281,7 @@ impl SerializableTransaction { outputs, version: inner.version, lock_time: inner.lock_time.to_string(), - subnetwork_id: inner.subnetwork_id.clone(), + subnetwork_id: inner.subnetwork_id, gas: inner.gas.to_string(), mass: inner.mass.to_string(), payload: inner.payload.clone(), @@ -310,7 +310,7 @@ impl SerializableTransaction { inputs, outputs, lock_time: transaction.lock_time.to_string(), - subnetwork_id: transaction.subnetwork_id.clone(), + subnetwork_id: transaction.subnetwork_id, gas: transaction.gas.to_string(), mass: transaction.mass().to_string(), payload: transaction.payload.clone(), diff --git a/consensus/client/src/transaction.rs b/consensus/client/src/transaction.rs index 4026ac1ebc..a1cc899a05 100644 --- a/consensus/client/src/transaction.rs +++ b/consensus/client/src/transaction.rs @@ -351,16 +351,8 @@ impl From<&Transaction> for cctx::Transaction { inner.inputs.clone().into_iter().map(|input| input.as_ref().into()).collect::>(); let outputs: Vec = inner.outputs.clone().into_iter().map(|output| output.as_ref().into()).collect::>(); - cctx::Transaction::new( - inner.version, - inputs, - outputs, - inner.lock_time, - inner.subnetwork_id.clone(), - inner.gas, - inner.payload.clone(), - ) - .with_mass(inner.mass) + cctx::Transaction::new(inner.version, inputs, outputs, inner.lock_time, inner.subnetwork_id, inner.gas, inner.payload.clone()) + .with_mass(inner.mass) } } @@ -392,7 +384,7 @@ impl Transaction { gas: tx.gas, payload: tx.payload.clone(), mass: tx.mass(), - subnetwork_id: tx.subnetwork_id.clone(), + subnetwork_id: tx.subnetwork_id, }) } @@ -415,7 +407,7 @@ impl Transaction { inputs, outputs, inner.lock_time, - inner.subnetwork_id.clone(), + inner.subnetwork_id, inner.gas, inner.payload.clone(), ) diff --git a/consensus/core/src/config/params.rs b/consensus/core/src/config/params.rs index a0eb463562..8e9ddf9dbc 100644 --- a/consensus/core/src/config/params.rs +++ b/consensus/core/src/config/params.rs @@ -229,6 +229,8 @@ pub struct OverrideParams { /// Crescendo activation DAA score pub crescendo_activation: Option, + + pub covenants_activation: Option, } impl From for OverrideParams { @@ -257,6 +259,7 @@ impl From for OverrideParams { pruning_proof_m: Some(p.pruning_proof_m), blockrate: Some(p.blockrate), crescendo_activation: Some(p.crescendo_activation), + covenants_activation: Some(p.covenants_activation), } } } @@ -321,6 +324,8 @@ pub struct Params { /// Crescendo activation DAA score pub crescendo_activation: ForkActivation, + + pub covenants_activation: ForkActivation, } impl Params { @@ -489,6 +494,7 @@ impl Params { .unwrap_or(self.pre_crescendo_target_time_per_block), crescendo_activation: overrides.crescendo_activation.unwrap_or(self.crescendo_activation), + covenants_activation: overrides.covenants_activation.unwrap_or(self.covenants_activation), } } } @@ -599,6 +605,7 @@ pub const MAINNET_PARAMS: Params = Params { // Roughly 2025-05-05 1500 UTC crescendo_activation: ForkActivation::new(110_165_000), + covenants_activation: ForkActivation::never(), }; pub const TESTNET_PARAMS: Params = Params { @@ -654,6 +661,7 @@ pub const TESTNET_PARAMS: Params = Params { // 18:30 UTC, March 6, 2025 crescendo_activation: ForkActivation::new(88_657_000), + covenants_activation: ForkActivation::never(), }; pub const SIMNET_PARAMS: Params = Params { @@ -694,6 +702,7 @@ pub const SIMNET_PARAMS: Params = Params { pre_crescendo_target_time_per_block: TenBps::target_time_per_block(), crescendo_activation: ForkActivation::always(), + covenants_activation: ForkActivation::never(), }; pub const DEVNET_PARAMS: Params = Params { @@ -732,4 +741,5 @@ pub const DEVNET_PARAMS: Params = Params { pre_crescendo_target_time_per_block: 1000, crescendo_activation: ForkActivation::always(), + covenants_activation: ForkActivation::never(), }; diff --git a/consensus/core/src/hashing/sighash.rs b/consensus/core/src/hashing/sighash.rs index 2c8006f75d..f7d17c460b 100644 --- a/consensus/core/src/hashing/sighash.rs +++ b/consensus/core/src/hashing/sighash.rs @@ -257,7 +257,7 @@ pub fn calc_schnorr_signature_hash( .write_u8(input.0.sig_op_count) .update(outputs_hash(tx, hash_type, reused_values, input_index)) .write_u64(tx.lock_time) - .update(&tx.subnetwork_id) + .update(tx.subnetwork_id) .write_u64(tx.gas) .update(payload_hash(tx, reused_values)) .write_u8(hash_type.to_u8()); diff --git a/consensus/core/src/hashing/tx.rs b/consensus/core/src/hashing/tx.rs index b5d3b966fb..5624ce313d 100644 --- a/consensus/core/src/hashing/tx.rs +++ b/consensus/core/src/hashing/tx.rs @@ -1,6 +1,9 @@ use super::HasherExtensions; -use crate::tx::{Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput}; -use kaspa_hashes::{Hash, Hasher}; +use crate::{ + mass::transaction_estimated_serialized_size, + tx::{Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput}, +}; +use kaspa_hashes::{Hash, HasherBase}; bitflags::bitflags! { /// A bitmask defining which transaction fields we want to encode and which to ignore. @@ -28,21 +31,19 @@ pub fn hash_pre_crescendo(tx: &Transaction) -> Hash { /// Not intended for direct use by clients. Instead use `tx.id()` pub(crate) fn id(tx: &Transaction) -> TransactionId { - // Encode the transaction, replace signature script with an empty array, skip - // sigop counts and mass commitment and hash the result. - - let encoding_flags = if tx.is_coinbase() { - TxEncodingFlags::FULL - } else { - TxEncodingFlags::EXCLUDE_SIGNATURE_SCRIPT | TxEncodingFlags::EXCLUDE_MASS_COMMIT - }; let mut hasher = kaspa_hashes::TransactionID::new(); - write_transaction(&mut hasher, tx, encoding_flags); + write_transaction_for_transaction_id(&mut hasher, tx); hasher.finalize() } +fn write_transaction_for_transaction_id(hasher: &mut T, tx: &Transaction) { + // Encode the transaction, replace signature script with an empty array, skip + // sigop counts and mass commitment and hash the result. + write_transaction(hasher, tx, TxEncodingFlags::EXCLUDE_SIGNATURE_SCRIPT | TxEncodingFlags::EXCLUDE_MASS_COMMIT) +} + /// Write the transaction into the provided hasher according to the encoding flags -fn write_transaction(hasher: &mut T, tx: &Transaction, encoding_flags: TxEncodingFlags) { +fn write_transaction(hasher: &mut T, tx: &Transaction, encoding_flags: TxEncodingFlags) { hasher.update(tx.version.to_le_bytes()).write_len(tx.inputs.len()); for input in tx.inputs.iter() { // Write the tx input @@ -55,7 +56,7 @@ fn write_transaction(hasher: &mut T, tx: &Transaction, encoding_flags write_output(hasher, output); } - hasher.update(tx.lock_time.to_le_bytes()).update(&tx.subnetwork_id).update(tx.gas.to_le_bytes()).write_var_bytes(&tx.payload); + hasher.update(tx.lock_time.to_le_bytes()).update(tx.subnetwork_id).update(tx.gas.to_le_bytes()).write_var_bytes(&tx.payload); /* Design principles (mostly related to the new mass commitment field; see KIP-0009): @@ -83,7 +84,7 @@ fn write_transaction(hasher: &mut T, tx: &Transaction, encoding_flags } #[inline(always)] -fn write_input(hasher: &mut T, input: &TransactionInput, encoding_flags: TxEncodingFlags) { +fn write_input(hasher: &mut T, input: &TransactionInput, encoding_flags: TxEncodingFlags) { write_outpoint(hasher, &input.previous_outpoint); if !encoding_flags.contains(TxEncodingFlags::EXCLUDE_SIGNATURE_SCRIPT) { hasher.write_var_bytes(input.signature_script.as_slice()).update([input.sig_op_count]); @@ -94,18 +95,35 @@ fn write_input(hasher: &mut T, input: &TransactionInput, encoding_fla } #[inline(always)] -fn write_outpoint(hasher: &mut T, outpoint: &TransactionOutpoint) { +fn write_outpoint(hasher: &mut T, outpoint: &TransactionOutpoint) { hasher.update(outpoint.transaction_id).update(outpoint.index.to_le_bytes()); } #[inline(always)] -fn write_output(hasher: &mut T, output: &TransactionOutput) { +fn write_output(hasher: &mut T, output: &TransactionOutput) { hasher .update(output.value.to_le_bytes()) .update(output.script_public_key.version().to_le_bytes()) .write_var_bytes(output.script_public_key.script()); } +struct PreimageHasher { + buff: Vec, +} + +impl HasherBase for PreimageHasher { + fn update>(&mut self, data: A) -> &mut Self { + self.buff.extend_from_slice(data.as_ref()); + self + } +} + +pub fn transaction_id_preimage(tx: &Transaction) -> Vec { + let mut hasher = PreimageHasher { buff: Vec::with_capacity(transaction_estimated_serialized_size(tx) as usize) }; + write_transaction_for_transaction_id(&mut hasher, tx); + hasher.buff +} + #[cfg(test)] mod tests { use super::*; @@ -173,9 +191,10 @@ mod tests { // Test #6 tests.push(Test { - tx: Transaction::new(2, inputs.clone(), outputs.clone(), 54, subnets::SUBNETWORK_ID_COINBASE, 3, Vec::new()), - expected_id: "3fad809b11bd5a4af027aa4ac3fbde97e40624fd40965ba3ee1ee1b57521ad10", - expected_hash: "b4eb5f0cab5060bf336af5dcfdeb2198cc088b693b35c87309bd3dda04f1cfb9", + // Valid coinbase transactions have no inputs. + tx: Transaction::new(2, vec![], outputs.clone(), 54, subnets::SUBNETWORK_ID_COINBASE, 3, Vec::new()), + expected_id: "f16306e20f6a28576e526092979b2bf3fc53b933fa6482c71b7a06c489495910", + expected_hash: "968b9effa67001baa5a3016449211bf59a8db3721314bd8a64723eac2cff4552", }); // Test #7 @@ -195,6 +214,12 @@ mod tests { for (i, test) in tests.iter().enumerate() { assert_eq!(test.tx.id(), Hash::from_str(test.expected_id).unwrap(), "transaction id failed for test {}", i + 1); assert_eq!(hash(&test.tx), Hash::from_str(test.expected_hash).unwrap(), "transaction hash failed for test {}", i + 1); + + let preimage = transaction_id_preimage(&test.tx); + let mut hasher = kaspa_hashes::TransactionID::new(); + hasher.update(&preimage); + let preimage_hash = hasher.finalize(); + assert_eq!(preimage_hash, test.tx.id(), "transaction id preimage failed for test {}", i + 1); } // Avoid compiler warnings on the last clone diff --git a/consensus/core/src/subnets.rs b/consensus/core/src/subnets.rs index 756c4d40a8..2b66b586db 100644 --- a/consensus/core/src/subnets.rs +++ b/consensus/core/src/subnets.rs @@ -10,7 +10,7 @@ use thiserror::Error; pub const SUBNETWORK_ID_SIZE: usize = 20; /// The domain representation of a Subnetwork ID -#[derive(Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, BorshSerialize, BorshDeserialize, Copy)] pub struct SubnetworkId([u8; SUBNETWORK_ID_SIZE]); impl Debug for SubnetworkId { @@ -40,6 +40,12 @@ impl From<[u8; SUBNETWORK_ID_SIZE]> for SubnetworkId { } } +impl From for Vec { + fn from(id: SubnetworkId) -> Self { + id.0.into() + } +} + impl SubnetworkId { pub const fn from_byte(b: u8) -> SubnetworkId { let mut bytes = [0u8; SUBNETWORK_ID_SIZE]; diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index b2270acad0..63daea93bb 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -569,8 +569,9 @@ pub enum TransactionQueryResult { #[cfg(test)] mod tests { + use crate::subnets::SUBNETWORK_ID_NATIVE; + use super::*; - use consensus_core::subnets::SUBNETWORK_ID_COINBASE; use smallvec::smallvec; fn test_transaction() -> Transaction { @@ -620,7 +621,7 @@ mod tests { TransactionOutput { value: 7, script_public_key }, ], 8, - SUBNETWORK_ID_COINBASE, + SUBNETWORK_ID_NATIVE, 9, vec![ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, @@ -649,13 +650,13 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, 117, 220, 118, 217, 0, 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, 117, 220, 118, 217, 0, - 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0, 0, 0, 0, 0, - 0, 0, 0, 69, 146, 193, 64, 98, 49, 45, 0, 77, 32, 25, 122, 77, 15, 211, 252, 61, 210, 82, 177, 39, 153, 127, 33, 188, 172, - 138, 38, 67, 75, 241, 176, + 0, 0, 0, 68, 15, 100, 245, 228, 237, 168, 194, 55, 73, 105, 105, 205, 253, 74, 234, 87, 244, 163, 12, 201, 68, 207, 194, + 9, 181, 172, 185, 10, 11, 239, 13, ]; assert_eq!(expected_bts, bts); assert_eq!(tx, bincode::deserialize(&bts).unwrap()); @@ -698,11 +699,11 @@ mod tests { } ], "lockTime": 8, - "subnetworkId": "0100000000000000000000000000000000000000", + "subnetworkId": "0000000000000000000000000000000000000000", "gas": 9, "payload": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60616263", "mass": 0, - "id": "4592c14062312d004d20197a4d0fd3fc3dd252b127997f21bcac8a26434bf1b0" + "id": "440f64f5e4eda8c237496969cdfd4aea57f4a30cc944cfc209b5acb90a0bef0d" }"#; assert_eq!(expected_str, str); assert_eq!(tx, serde_json::from_str(&str).unwrap()); diff --git a/consensus/src/consensus/services.rs b/consensus/src/consensus/services.rs index 6409252c1b..89e5eee091 100644 --- a/consensus/src/consensus/services.rs +++ b/consensus/src/consensus/services.rs @@ -149,6 +149,7 @@ impl ConsensusServices { params.ghostdag_k(), tx_script_cache_counters, mass_calculator.clone(), + params.covenants_activation, ); let pruning_point_manager = PruningPointManager::new( diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index 4756333440..69c1dc3566 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -937,8 +937,13 @@ impl VirtualStateProcessor { virtual_state.daa_score, virtual_state.past_median_time, )?; - let ValidatedTransaction { calculated_fee, .. } = - self.validate_transaction_in_utxo_context(tx, utxo_view, virtual_state.daa_score, TxValidationFlags::Full)?; + let ValidatedTransaction { calculated_fee, .. } = self.validate_transaction_in_utxo_context( + tx, + utxo_view, + virtual_state.daa_score, + virtual_state.daa_score, + TxValidationFlags::Full, + )?; Ok(calculated_fee) } @@ -1180,6 +1185,7 @@ impl VirtualStateProcessor { &new_pruning_point_transactions, &virtual_read.utxo_set, new_pruning_point_header.daa_score, + new_pruning_point_header.daa_score, TxValidationFlags::Full, ); if validated_transactions.len() < new_pruning_point_transactions.len() - 1 { diff --git a/consensus/src/pipeline/virtual_processor/utxo_validation.rs b/consensus/src/pipeline/virtual_processor/utxo_validation.rs index e0beccceae..45c7aef7a7 100644 --- a/consensus/src/pipeline/virtual_processor/utxo_validation.rs +++ b/consensus/src/pipeline/virtual_processor/utxo_validation.rs @@ -138,8 +138,13 @@ impl VirtualStateProcessor { // No need to fully validate selected parent transactions since selected parent txs were already validated // as part of selected parent UTXO state verification with the exact same UTXO context. let validation_flags = if is_selected_parent { TxValidationFlags::SkipScriptChecks } else { TxValidationFlags::Full }; - let (validated_transactions, inner_multiset) = - self.validate_transactions_with_muhash_in_parallel(&txs, &composed_view, pov_daa_score, validation_flags); + let (validated_transactions, inner_multiset) = self.validate_transactions_with_muhash_in_parallel( + &txs, + &composed_view, + pov_daa_score, + self.headers_store.get_daa_score(merged_block).unwrap(), + validation_flags, + ); ctx.multiset_hash.combine(&inner_multiset); @@ -217,8 +222,13 @@ impl VirtualStateProcessor { // Verify all transactions are valid in context let current_utxo_view = selected_parent_utxo_view.compose(&ctx.mergeset_diff); - let validated_transactions = - self.validate_transactions_in_parallel(&txs, ¤t_utxo_view, header.daa_score, TxValidationFlags::Full); + let validated_transactions = self.validate_transactions_in_parallel( + &txs, + ¤t_utxo_view, + header.daa_score, + header.daa_score, + TxValidationFlags::Full, + ); if validated_transactions.len() < txs.len() - 1 { // Some non-coinbase transactions are invalid return Err(InvalidTransactionsInUtxoContext(txs.len() - 1 - validated_transactions.len(), txs.len() - 1)); @@ -268,6 +278,7 @@ impl VirtualStateProcessor { txs: &'a Vec, utxo_view: &V, pov_daa_score: u64, + block_daa_score: u64, flags: TxValidationFlags, ) -> Vec<(ValidatedTransaction<'a>, u32)> { self.thread_pool.install(|| { @@ -276,7 +287,7 @@ impl VirtualStateProcessor { // that all txs within each block are independent .enumerate() .skip(1) // Skip the coinbase tx. - .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score, flags).ok().map(|vtx| (vtx, i as u32))) + .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score,block_daa_score, flags).ok().map(|vtx| (vtx, i as u32))) .collect() }) } @@ -288,6 +299,7 @@ impl VirtualStateProcessor { txs: &'a Vec, utxo_view: &V, pov_daa_score: u64, + block_daa_score: u64, flags: TxValidationFlags, ) -> (SmallVec<[(ValidatedTransaction<'a>, u32); 2]>, MuHash) { self.thread_pool.install(|| { @@ -296,7 +308,7 @@ impl VirtualStateProcessor { // that all txs within each block are independent .enumerate() .skip(1) // Skip the coinbase tx. - .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score, flags).ok().map(|vtx| { + .filter_map(|(i, tx)| self.validate_transaction_in_utxo_context(tx, &utxo_view, pov_daa_score, block_daa_score, flags).ok().map(|vtx| { let mh = MuHash::from_transaction(&vtx, pov_daa_score); (smallvec![(vtx, i as u32)], mh) } @@ -318,6 +330,7 @@ impl VirtualStateProcessor { transaction: &'a Transaction, utxo_view: &impl UtxoView, pov_daa_score: u64, + block_daa_score: u64, flags: TxValidationFlags, ) -> TxResult> { let mut entries = Vec::with_capacity(transaction.inputs.len()); @@ -330,7 +343,13 @@ impl VirtualStateProcessor { } } let populated_tx = PopulatedTransaction::new(transaction, entries); - let res = self.transaction_validator.validate_populated_transaction_and_get_fee(&populated_tx, pov_daa_score, flags, None); + let res = self.transaction_validator.validate_populated_transaction_and_get_fee( + &populated_tx, + pov_daa_score, + block_daa_score, + flags, + None, + ); match res { Ok(calculated_fee) => Ok(ValidatedTransaction::new(populated_tx, calculated_fee)), Err(tx_rule_error) => { @@ -393,6 +412,7 @@ impl VirtualStateProcessor { let calculated_fee = self.transaction_validator.validate_populated_transaction_and_get_fee( &mutable_tx.as_verifiable(), pov_daa_score, + pov_daa_score, TxValidationFlags::SkipMassCheck, // we can skip the mass check since we just set it mass_and_feerate_threshold, )?; diff --git a/consensus/src/processes/transaction_validator/mod.rs b/consensus/src/processes/transaction_validator/mod.rs index dc1bed6260..bf9292b197 100644 --- a/consensus/src/processes/transaction_validator/mod.rs +++ b/consensus/src/processes/transaction_validator/mod.rs @@ -9,7 +9,7 @@ use kaspa_txscript::{ SigCacheKey, }; -use kaspa_consensus_core::{mass::MassCalculator, KType}; +use kaspa_consensus_core::{config::params::ForkActivation, mass::MassCalculator, KType}; #[derive(Clone)] pub struct TransactionValidator { @@ -21,6 +21,7 @@ pub struct TransactionValidator { coinbase_maturity: u64, ghostdag_k: KType, sig_cache: Cache, + covenants_activation: ForkActivation, pub(crate) mass_calculator: MassCalculator, } @@ -36,6 +37,7 @@ impl TransactionValidator { ghostdag_k: KType, counters: Arc, mass_calculator: MassCalculator, + covenants_activation: ForkActivation, ) -> Self { Self { max_tx_inputs, @@ -47,6 +49,7 @@ impl TransactionValidator { ghostdag_k, sig_cache: Cache::with_counters(10_000, counters), mass_calculator, + covenants_activation, } } @@ -70,6 +73,7 @@ impl TransactionValidator { ghostdag_k, sig_cache: Cache::with_counters(10_000, counters), mass_calculator: MassCalculator::new(0, 0, 0, 0), + covenants_activation: ForkActivation::never(), } } } diff --git a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs index fa26761ffb..f8c3a67c7c 100644 --- a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs +++ b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs @@ -157,7 +157,7 @@ fn check_transaction_subnetwork(tx: &Transaction) -> TxResult<()> { if tx.is_coinbase() || tx.subnetwork_id.is_native() { Ok(()) } else { - Err(TxRuleError::SubnetworksDisabled(tx.subnetwork_id.clone())) + Err(TxRuleError::SubnetworksDisabled(tx.subnetwork_id)) } } diff --git a/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs b/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs index 521a5a51e5..304a4e7d41 100644 --- a/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs +++ b/consensus/src/processes/transaction_validator/tx_validation_in_utxo_context.rs @@ -3,7 +3,7 @@ use kaspa_consensus_core::{ hashing::sighash::{SigHashReusedValuesSync, SigHashReusedValuesUnsync}, tx::{TransactionInput, VerifiableTransaction}, }; -use kaspa_txscript::{caches::Cache, get_sig_op_count_upper_bound, SigCacheKey, TxScriptEngine}; +use kaspa_txscript::{caches::Cache, get_sig_op_count_upper_bound, EngineFlags, SigCacheKey, TxScriptEngine}; use kaspa_txscript_errors::TxScriptError; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::ThreadPool; @@ -35,6 +35,7 @@ impl TransactionValidator { &self, tx: &(impl VerifiableTransaction + Sync), pov_daa_score: u64, + block_daa_score: u64, flags: TxValidationFlags, mass_and_feerate_threshold: Option<(u64, f64)>, ) -> TxResult { @@ -53,7 +54,7 @@ impl TransactionValidator { match flags { TxValidationFlags::Full | TxValidationFlags::SkipMassCheck => { - self.check_scripts(tx)?; + self.check_scripts(tx, block_daa_score)?; } TxValidationFlags::SkipScriptChecks => {} } @@ -165,34 +166,46 @@ impl TransactionValidator { Ok(()) } - pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { - check_scripts(&self.sig_cache, tx) + pub fn check_scripts(&self, tx: &(impl VerifiableTransaction + Sync), block_daa_score: u64) -> TxResult<()> { + check_scripts(&self.sig_cache, tx, EngineFlags { covenants_enabled: self.covenants_activation.is_active(block_daa_score) }) } } -pub fn check_scripts(sig_cache: &Cache, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { +pub fn check_scripts( + sig_cache: &Cache, + tx: &(impl VerifiableTransaction + Sync), + flags: EngineFlags, +) -> TxResult<()> { if tx.inputs().len() > CHECK_SCRIPTS_PARALLELISM_THRESHOLD { - check_scripts_par_iter(sig_cache, tx) + check_scripts_par_iter(sig_cache, tx, flags) } else { - check_scripts_sequential(sig_cache, tx) + check_scripts_sequential(sig_cache, tx, flags) } } -pub fn check_scripts_sequential(sig_cache: &Cache, tx: &impl VerifiableTransaction) -> TxResult<()> { +pub fn check_scripts_sequential( + sig_cache: &Cache, + tx: &impl VerifiableTransaction, + flags: EngineFlags, +) -> TxResult<()> { let reused_values = SigHashReusedValuesUnsync::new(); for (i, (input, entry)) in tx.populated_inputs().enumerate() { - TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache) + TxScriptEngine::from_transaction_input(tx, input, i, entry, &reused_values, sig_cache, flags) .execute() .map_err(|err| map_script_err(err, input))?; } Ok(()) } -pub fn check_scripts_par_iter(sig_cache: &Cache, tx: &(impl VerifiableTransaction + Sync)) -> TxResult<()> { +pub fn check_scripts_par_iter( + sig_cache: &Cache, + tx: &(impl VerifiableTransaction + Sync), + flags: EngineFlags, +) -> TxResult<()> { let reused_values = SigHashReusedValuesSync::new(); (0..tx.inputs().len()).into_par_iter().try_for_each(|idx| { let (input, utxo) = tx.populated_input(idx); - TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache) + TxScriptEngine::from_transaction_input(tx, input, idx, utxo, &reused_values, sig_cache, flags) .execute() .map_err(|err| map_script_err(err, input)) }) @@ -202,8 +215,9 @@ pub fn check_scripts_par_iter_pool( sig_cache: &Cache, tx: &(impl VerifiableTransaction + Sync), pool: &ThreadPool, + flags: EngineFlags, ) -> TxResult<()> { - pool.install(|| check_scripts_par_iter(sig_cache, tx)) + pool.install(|| check_scripts_par_iter(sig_cache, tx, flags)) } fn map_script_err(script_err: TxScriptError, input: &TransactionInput) -> TxRuleError { @@ -298,13 +312,14 @@ mod tests { }], ); - tv.check_scripts(&populated_tx).expect("Signature check failed"); + let flags = Default::default(); + tv.check_scripts(&populated_tx, flags).expect("Signature check failed"); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); // Duplicated sigs should fail due to wrong sighash assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse)) ); } @@ -368,11 +383,12 @@ mod tests { }], ); - assert!(tv.check_scripts(&populated_tx).is_err(), "Expecting signature check to fail"); + let flags = Default::default(); + assert!(tv.check_scripts(&populated_tx, flags).is_err(), "Expecting signature check to fail"); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)).expect_err("Expecting signature check to fail"); + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags).expect_err("Expecting signature check to fail"); // Verify we are correctly testing the parallelism case (applied here as sanity for all tests) assert!( @@ -441,13 +457,15 @@ mod tests { is_coinbase: false, }], ); - tv.check_scripts(&populated_tx).expect("Signature check failed"); + + let flags = Default::default(); + tv.check_scripts(&populated_tx, flags).expect("Signature check failed"); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); // Duplicated sigs should fail due to wrong sighash assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) ); } @@ -512,12 +530,13 @@ mod tests { }], ); - assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + let flags = Default::default(); + assert_eq!(tv.check_scripts(&populated_tx, flags), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) ); } @@ -582,12 +601,13 @@ mod tests { }], ); - assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); + let flags = Default::default(); + assert_eq!(tv.check_scripts(&populated_tx, flags), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail))); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::NullFail)) ); } @@ -652,12 +672,13 @@ mod tests { }], ); - assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse))); + let flags = Default::default(); + assert_eq!(tv.check_scripts(&populated_tx, flags), Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse))); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::EvalFalse)) ); } @@ -713,12 +734,16 @@ mod tests { }], ); - assert_eq!(tv.check_scripts(&populated_tx), Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly))); + let flags = Default::default(); + assert_eq!( + tv.check_scripts(&populated_tx, flags), + Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly)) + ); // Test a tx with 2 inputs to cover parallelism split points in inner script checking code let (tx2, entries2) = duplicate_input(&tx, &populated_tx.entries); assert_eq!( - tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2)), + tv.check_scripts(&PopulatedTransaction::new(&tx2, entries2), flags), Err(TxRuleError::SignatureInvalid(TxScriptError::SignatureScriptNotPushOnly)) ); } @@ -799,7 +824,7 @@ mod tests { let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap(); let signed_tx = sign(MutableTransaction::with_entries(unsigned_tx, entries), schnorr_key); let populated_tx = signed_tx.as_verifiable(); - assert_eq!(tv.check_scripts(&populated_tx), Ok(())); + assert_eq!(tv.check_scripts(&populated_tx, Default::default()), Ok(())); assert_eq!(TransactionValidator::check_sig_op_counts(&populated_tx), Ok(())); } } diff --git a/crypto/txscript/errors/src/lib.rs b/crypto/txscript/errors/src/lib.rs index 2f7fb020fc..397799fdc3 100644 --- a/crypto/txscript/errors/src/lib.rs +++ b/crypto/txscript/errors/src/lib.rs @@ -75,6 +75,10 @@ pub enum TxScriptError { Serialization(#[from] SerializationError), #[error("sig op count exceeds passed limit of {0}")] ExceededSigOpLimit(u8), + #[error("substring [{0}:{1}] is out of bounds for string of length {2}")] + OutOfBoundsSubstring(usize, usize, usize), + #[error("{0} cannot be used as an array index")] + InvalidIndex(i32), } #[derive(Error, PartialEq, Eq, Debug, Clone, Copy)] diff --git a/crypto/txscript/examples/covenants.rs b/crypto/txscript/examples/covenants.rs new file mode 100644 index 0000000000..14ebbcbba1 --- /dev/null +++ b/crypto/txscript/examples/covenants.rs @@ -0,0 +1,190 @@ +use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; +use kaspa_consensus_core::hashing::tx::transaction_id_preimage; +use kaspa_consensus_core::subnets::SubnetworkId; +use kaspa_consensus_core::tx::{ + PopulatedTransaction, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, +}; +use kaspa_hashes::Hash; +use kaspa_txscript::caches::Cache; +use kaspa_txscript::opcodes::codes::{ + Op1Add, OpBlake2bWithKey, OpCat, OpDup, OpEqual, OpEqualVerify, OpOutpointTxId, OpRot, OpTxInputIndex, OpTxInputSpk, + OpTxOutputCount, OpTxOutputSpk, OpTxPayloadLen, OpTxPayloadSubstr, +}; +use kaspa_txscript::pay_to_script_hash_script; +use kaspa_txscript::script_builder::{ScriptBuilder, ScriptBuilderResult}; +use kaspa_txscript::{EngineFlags, TxScriptEngine}; +use kaspa_txscript_errors::TxScriptError; + +fn main() -> ScriptBuilderResult<()> { + counter_example() +} + +/// Demonstrates a simple covenant that enforces a counter stored in the transaction payload. +/// Each spend must increment the counter and return funds to the same script public key. +/// A spend that does not increment the counter is rejected. +fn counter_example() -> ScriptBuilderResult<()> { + println!("[COVENANT] Counter payload covenant"); + let covenant_script = build_covenant_script()?; + let spk = pay_to_script_hash_script(&covenant_script); + + // Shared engine state + let sig_cache = Cache::new(10_000); + let reused_values = SigHashReusedValuesUnsync::new(); + let flags = EngineFlags { covenants_enabled: true }; + + // Create the initial UTXO with counter = 0 + let mut state = CovenantState::new(0, &spk); + + // Two valid increments + for next in [1u8, 2u8] { + println!("[COVENANT] Spending to counter {next}"); + let tx = build_spend_tx(&state, next, &spk, &covenant_script); + run_vm(&tx, &state.utxo_entry, &sig_cache, &reused_values, flags).expect("covenant spend should succeed"); + state = CovenantState::from_tx(tx, &spk, next); + } + + let counter_2_state = state.clone(); + let next = 3u8; + println!("[COVENANT] Spending to counter {next}"); + let tx = build_spend_tx(&state, next, &spk, &covenant_script); + run_vm(&tx, &state.utxo_entry, &sig_cache, &reused_values, flags).expect("covenant spend should succeed"); + state = CovenantState::from_tx(tx, &spk, next); + + println!("[COVENANT] Attempting invalid spend (no increment)"); + let bad_tx = build_spend_tx(&state, state.counter, &spk, &covenant_script); + let err = run_vm(&bad_tx, &state.utxo_entry, &sig_cache, &reused_values, flags).expect_err("non-incrementing spend must fail"); + println!("[COVENANT] Expected failure: {err:?}"); + + println!("[COVENANT] Attempting invalid spend (no increment and reuse previous state)"); + // We try to spend the last UTXO but provide the previous state with counter=2 + let bad_tx = build_spend_tx( + &CovenantState { utxo_outpoint: state.utxo_outpoint, ..counter_2_state }, + state.counter, + &spk, + &covenant_script, + ); + let err = run_vm(&bad_tx, &state.utxo_entry, &sig_cache, &reused_values, flags).expect_err("non-incrementing spend must fail"); + println!("[COVENANT] Expected failure: {err:?}"); + + println!("[COVENANT] Attempting invalid spend (increase by 2)"); + let bad_tx = build_spend_tx(&state, state.counter + 2, &spk, &covenant_script); + let err = run_vm(&bad_tx, &state.utxo_entry, &sig_cache, &reused_values, flags).expect_err("non-incrementing spend must fail"); + println!("[COVENANT] Expected failure: {err:?}"); + + println!("[COVENANT] Example complete"); + Ok(()) +} + +/// Holds the current covenant UTXO state. +#[derive(Clone)] +struct CovenantState { + prev_tx_rest: Vec, + prev_payload: Vec, + utxo_outpoint: TransactionOutpoint, + utxo_entry: UtxoEntry, + counter: u8, +} + +impl CovenantState { + fn new(counter: u8, spk: &kaspa_consensus_core::tx::ScriptPublicKey) -> Self { + let payload = encode_counter(counter); + let tx = genesis_tx(&payload, spk.clone()); + Self::from_tx(tx, spk, counter) + } + + fn from_tx(tx: Transaction, spk: &kaspa_consensus_core::tx::ScriptPublicKey, counter: u8) -> Self { + let preimage = transaction_id_preimage(&tx); + let payload_len = tx.payload.len(); + let (rest, payload) = preimage.split_at(preimage.len() - payload_len); + let outpoint = TransactionOutpoint::new(tx.id(), 0); + let utxo_entry = UtxoEntry::new(1_000_000, spk.clone(), 0, false); + Self { prev_tx_rest: rest.to_vec(), prev_payload: payload.to_vec(), utxo_outpoint: outpoint, utxo_entry, counter } + } +} + +/// Build the covenant script described in the docs. +fn build_covenant_script() -> ScriptBuilderResult> { + Ok(ScriptBuilder::new() + // Hash(prev_tx_rest || prev_tx_payload) with domain "TransactionID" and verify matches input outpoint txid + .add_op(OpDup)? + .add_op(OpRot)? + .add_op(OpRot)? + .add_op(OpCat)? + .add_data(b"TransactionID")? + .add_op(OpBlake2bWithKey)? + .add_op(OpTxInputIndex)? + .add_op(OpOutpointTxId)? + .add_op(OpEqualVerify)? + // Enforce payload increment: payload_of_tx == prev_payload + 1 + .add_op(Op1Add)? + .add_i64(0)? + .add_op(OpTxPayloadLen)? + .add_op(OpTxPayloadSubstr)? + .add_op(OpEqualVerify)? + // Enforce same script pub key and single-output spend + .add_op(OpTxInputIndex)? + .add_op(OpTxInputSpk)? + .add_i64(0)? + .add_op(OpTxOutputSpk)? + .add_op(OpEqualVerify)? + .add_op(OpTxOutputCount)? + .add_i64(1)? + .add_op(OpEqual)? + .drain()) +} + +/// Build the spend transaction for the next counter value. +fn build_spend_tx( + state: &CovenantState, + next_counter: u8, + spk: &kaspa_consensus_core::tx::ScriptPublicKey, + covenant_script: &[u8], +) -> Transaction { + let payload = encode_counter(next_counter); + let sig_script = ScriptBuilder::new() + .add_data(&state.prev_tx_rest) + .unwrap() + .add_data(&state.prev_payload) + .unwrap() + // For P2SH the redeem script must be the last stack item in the signature script + .add_data(covenant_script) + .unwrap() + .drain(); + + let input = TransactionInput::new(state.utxo_outpoint, sig_script, 0, 0); + let output = TransactionOutput::new(state.utxo_entry.amount, spk.clone()); + + let mut tx = Transaction::new(0, vec![input], vec![output], 0, SubnetworkId::default(), 0, payload); + tx.finalize(); + tx +} + +/// Run the VM for a single-input covenant spend. +fn run_vm( + tx: &Transaction, + utxo_entry: &UtxoEntry, + sig_cache: &Cache, + reused_values: &SigHashReusedValuesUnsync, + flags: EngineFlags, +) -> Result<(), TxScriptError> { + let populated = PopulatedTransaction::new(tx, vec![utxo_entry.clone()]); + let mut vm = TxScriptEngine::from_transaction_input(&populated, &tx.inputs[0], 0, utxo_entry, reused_values, sig_cache, flags); + vm.execute() +} + +/// Create a genesis-style transaction that seeds the first covenant UTXO. +fn genesis_tx(payload: &[u8], spk: kaspa_consensus_core::tx::ScriptPublicKey) -> Transaction { + let dummy_input = TransactionInput::new(TransactionOutpoint::new(Hash::from_u64_word(0), 0), vec![], 0, 0); + let output = TransactionOutput::new(1_000_000, spk); + let mut tx = Transaction::new(0, vec![dummy_input], vec![output], 0, SubnetworkId::default(), 0, payload.to_vec()); + tx.finalize(); + tx +} + +fn encode_counter(counter: u8) -> Vec { + if counter == 0 { + vec![] + } else { + vec![counter] + } +} diff --git a/crypto/txscript/examples/kip-10.rs b/crypto/txscript/examples/kip-10.rs index f1e80fd147..e08e936f60 100644 --- a/crypto/txscript/examples/kip-10.rs +++ b/crypto/txscript/examples/kip-10.rs @@ -126,7 +126,15 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Owner branch execution successful"); } @@ -136,7 +144,15 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { println!("[STANDARD] Checking borrower branch"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[STANDARD] Borrower branch execution successful"); } @@ -147,7 +163,15 @@ fn threshold_scenario() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[STANDARD] Borrower branch with threshold not reached failed as expected"); } @@ -295,7 +319,15 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Owner branch execution successful"); } @@ -305,7 +337,15 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { println!("[ONE-TIME] Checking borrower branch"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[ONE-TIME] Borrower branch execution successful"); } @@ -316,7 +356,15 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[ONE-TIME] Borrower branch with threshold not reached failed as expected"); } @@ -338,8 +386,15 @@ fn threshold_scenario_limited_one_time() -> ScriptBuilderResult<()> { wrong_tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&script)?.drain(); let wrong_tx = PopulatedTransaction::new(&wrong_tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&wrong_tx, &wrong_tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &wrong_tx, + &wrong_tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(VerifyError)); println!("[ONE-TIME] Borrower branch with output going to wrong address failed as expected"); } @@ -448,7 +503,15 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Owner branch execution successful"); } @@ -458,7 +521,15 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { println!("[TWO-TIMES] Checking borrower branch (first borrowing)"); tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[TWO-TIMES] Borrower branch (first borrowing) execution successful"); } @@ -469,7 +540,15 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { // Less than threshold tx.outputs[0].value -= 1; let tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(EvalFalse)); println!("[TWO-TIMES] Borrower branch with threshold not reached failed as expected"); } @@ -491,8 +570,15 @@ fn threshold_scenario_limited_2_times() -> ScriptBuilderResult<()> { wrong_tx.inputs[0].signature_script = ScriptBuilder::new().add_op(OpFalse)?.add_data(&two_times_script)?.drain(); let wrong_tx = PopulatedTransaction::new(&wrong_tx, vec![utxo_entry.clone()]); - let mut vm = - TxScriptEngine::from_transaction_input(&wrong_tx, &wrong_tx.tx.inputs[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &wrong_tx, + &wrong_tx.tx.inputs[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(VerifyError)); println!("[TWO-TIMES] Borrower branch with output going to wrong address failed as expected"); } @@ -603,7 +689,15 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Owner branch execution successful"); } @@ -621,7 +715,15 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); println!("[SHARED-SECRET] Borrower branch with correct shared secret execution successful"); } @@ -639,7 +741,15 @@ fn shared_secret_scenario() -> ScriptBuilderResult<()> { } let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(VerifyError)); println!("[SHARED-SECRET] Borrower branch with incorrect secret failed as expected"); } diff --git a/crypto/txscript/src/data_stack.rs b/crypto/txscript/src/data_stack.rs index 9c5b2d1116..e39d298ed3 100644 --- a/crypto/txscript/src/data_stack.rs +++ b/crypto/txscript/src/data_stack.rs @@ -4,7 +4,7 @@ use core::iter; use kaspa_txscript_errors::SerializationError; use std::cmp::Ordering; use std::num::TryFromIntError; -use std::ops::Deref; +use std::ops::{Deref, Index}; #[derive(PartialEq, Eq, Debug, Default, PartialOrd, Ord)] pub(crate) struct SizedEncodeInt(pub(crate) i64); @@ -55,26 +55,40 @@ impl From> for i64 { } } -pub(crate) type Stack = Vec>; +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Stack { + inner: Vec>, + max_element_size: usize, +} -pub(crate) trait DataStack { - fn pop_items(&mut self) -> Result<[T; SIZE], TxScriptError> - where - Vec: OpcodeData; - #[allow(dead_code)] - fn peek_items(&self) -> Result<[T; SIZE], TxScriptError> - where - Vec: OpcodeData; - fn pop_raw(&mut self) -> Result<[Vec; SIZE], TxScriptError>; - fn peek_raw(&self) -> Result<[Vec; SIZE], TxScriptError>; - fn push_item(&mut self, item: T) -> Result<(), TxScriptError> - where - Vec: OpcodeData; - fn drop_items(&mut self) -> Result<(), TxScriptError>; - fn dup_items(&mut self) -> Result<(), TxScriptError>; - fn over_items(&mut self) -> Result<(), TxScriptError>; - fn rot_items(&mut self) -> Result<(), TxScriptError>; - fn swap_items(&mut self) -> Result<(), TxScriptError>; +impl Deref for Stack { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Index for Stack { + type Output = Vec; + + fn index(&self, index: usize) -> &Self::Output { + &self.inner[index] + } +} + +#[cfg(test)] +impl From>> for Stack { + fn from(inner: Vec>) -> Self { + // TODO: Replace with the correct max element size after the fork. + Self { inner, max_element_size: usize::MAX } + } +} + +impl From for Vec> { + fn from(stack: Stack) -> Self { + stack.inner + } } pub(crate) trait OpcodeData { @@ -223,62 +237,70 @@ impl OpcodeData for Vec { } } -impl DataStack for Stack { +impl Stack { + pub(crate) fn new(inner: Vec>, max_element_size: usize) -> Self { + Self { inner, max_element_size } + } + #[inline] - fn pop_items(&mut self) -> Result<[T; SIZE], TxScriptError> - where - Vec: OpcodeData, - { - if self.len() < SIZE { - return Err(TxScriptError::InvalidStackOperation(SIZE, self.len())); + pub fn insert(&mut self, index: usize, element: Vec) -> Result<(), TxScriptError> { + if element.len() > self.max_element_size { + return Err(TxScriptError::ElementTooBig(element.len(), self.max_element_size)); } - Ok(<[T; SIZE]>::try_from(self.split_off(self.len() - SIZE).iter().map(|v| v.deserialize()).collect::, _>>()?) - .expect("Already exact item")) + self.inner.insert(index, element); + Ok(()) + } + + #[cfg(test)] + pub(crate) fn inner(&self) -> &[Vec] { + &self.inner } #[inline] - fn peek_items(&self) -> Result<[T; SIZE], TxScriptError> + pub fn pop_items(&mut self) -> Result<[T; SIZE], TxScriptError> where Vec: OpcodeData, { if self.len() < SIZE { return Err(TxScriptError::InvalidStackOperation(SIZE, self.len())); } - Ok(<[T; SIZE]>::try_from(self[self.len() - SIZE..].iter().map(|v| v.deserialize()).collect::, _>>()?) - .expect("Already exact item")) + Ok(<[T; SIZE]>::try_from( + self.inner.split_off(self.len() - SIZE).iter().map(|v| v.deserialize()).collect::, _>>()?, + ) + .expect("Already exact item")) } #[inline] - fn pop_raw(&mut self) -> Result<[Vec; SIZE], TxScriptError> { + pub fn pop_raw(&mut self) -> Result<[Vec; SIZE], TxScriptError> { if self.len() < SIZE { return Err(TxScriptError::InvalidStackOperation(SIZE, self.len())); } - Ok(<[Vec; SIZE]>::try_from(self.split_off(self.len() - SIZE)).expect("Already exact item")) + Ok(<[Vec; SIZE]>::try_from(self.inner.split_off(self.len() - SIZE)).expect("Already exact item")) } #[inline] - fn peek_raw(&self) -> Result<[Vec; SIZE], TxScriptError> { + pub fn peek_raw(&self) -> Result<[Vec; SIZE], TxScriptError> { if self.len() < SIZE { return Err(TxScriptError::InvalidStackOperation(SIZE, self.len())); } - Ok(<[Vec; SIZE]>::try_from(self[self.len() - SIZE..].to_vec()).expect("Already exact item")) + Ok(<[Vec; SIZE]>::try_from(self.inner[self.len() - SIZE..].to_vec()).expect("Already exact item")) } #[inline] - fn push_item(&mut self, item: T) -> Result<(), TxScriptError> + pub fn push_item(&mut self, item: T) -> Result<(), TxScriptError> where Vec: OpcodeData, { let v = OpcodeData::serialize(&item)?; - Vec::push(self, v); + Vec::push(&mut self.inner, v); Ok(()) } #[inline] - fn drop_items(&mut self) -> Result<(), TxScriptError> { + pub fn drop_items(&mut self) -> Result<(), TxScriptError> { match self.len() >= SIZE { true => { - self.truncate(self.len() - SIZE); + self.inner.truncate(self.len() - SIZE); Ok(()) } false => Err(TxScriptError::InvalidStackOperation(SIZE, self.len())), @@ -286,10 +308,10 @@ impl DataStack for Stack { } #[inline] - fn dup_items(&mut self) -> Result<(), TxScriptError> { + pub fn dup_items(&mut self) -> Result<(), TxScriptError> { match self.len() >= SIZE { true => { - self.extend_from_within(self.len() - SIZE..); + self.inner.extend_from_within(self.len() - SIZE..); Ok(()) } false => Err(TxScriptError::InvalidStackOperation(SIZE, self.len())), @@ -297,10 +319,10 @@ impl DataStack for Stack { } #[inline] - fn over_items(&mut self) -> Result<(), TxScriptError> { + pub fn over_items(&mut self) -> Result<(), TxScriptError> { match self.len() >= 2 * SIZE { true => { - self.extend_from_within(self.len() - 2 * SIZE..self.len() - SIZE); + self.inner.extend_from_within(self.len() - 2 * SIZE..self.len() - SIZE); Ok(()) } false => Err(TxScriptError::InvalidStackOperation(2 * SIZE, self.len())), @@ -308,11 +330,11 @@ impl DataStack for Stack { } #[inline] - fn rot_items(&mut self) -> Result<(), TxScriptError> { + pub fn rot_items(&mut self) -> Result<(), TxScriptError> { match self.len() >= 3 * SIZE { true => { - let drained = self.drain(self.len() - 3 * SIZE..self.len() - 2 * SIZE).collect::>>(); - self.extend(drained); + let drained = self.inner.drain(self.len() - 3 * SIZE..self.len() - 2 * SIZE).collect::>>(); + self.inner.extend(drained); Ok(()) } false => Err(TxScriptError::InvalidStackOperation(3 * SIZE, self.len())), @@ -320,16 +342,40 @@ impl DataStack for Stack { } #[inline] - fn swap_items(&mut self) -> Result<(), TxScriptError> { + pub fn swap_items(&mut self) -> Result<(), TxScriptError> { match self.len() >= 2 * SIZE { true => { - let drained = self.drain(self.len() - 2 * SIZE..self.len() - SIZE).collect::>>(); - self.extend(drained); + let drained = self.inner.drain(self.len() - 2 * SIZE..self.len() - SIZE).collect::>>(); + self.inner.extend(drained); Ok(()) } false => Err(TxScriptError::InvalidStackOperation(2 * SIZE, self.len())), } } + + pub fn clear(&mut self) { + self.inner.clear() + } + + pub fn pop(&mut self) -> Result, TxScriptError> { + self.inner.pop().ok_or(TxScriptError::EmptyStack) + } + + pub fn split_off(&mut self, at: usize) -> Vec> { + self.inner.split_off(at) + } + + pub fn push(&mut self, item: Vec) -> Result<(), TxScriptError> { + if item.len() > self.max_element_size { + return Err(TxScriptError::ElementTooBig(item.len(), self.max_element_size)); + } + self.inner.push(item); + Ok(()) + } + + pub fn remove(&mut self, index: usize) -> Vec { + self.inner.remove(index) + } } #[cfg(test)] diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index d19100940b..15b7f2167a 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -15,7 +15,7 @@ pub mod wasm; pub mod runtime_sig_op_counter; use crate::caches::Cache; -use crate::data_stack::{DataStack, Stack}; +use crate::data_stack::Stack; use crate::opcodes::{deserialize_next_opcode, OpCodeImplementation}; use itertools::Itertools; use kaspa_consensus_core::hashing::sighash::{ @@ -77,6 +77,11 @@ enum ScriptSource<'a, T: VerifiableTransaction> { StandAloneScripts(Vec<&'a [u8]>), } +#[derive(Default, Copy, Clone)] +pub struct EngineFlags { + pub covenants_enabled: bool, +} + pub struct TxScriptEngine<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> { dstack: Stack, astack: Stack, @@ -91,6 +96,7 @@ pub struct TxScriptEngine<'a, T: VerifiableTransaction, Reused: SigHashReusedVal num_ops: i32, runtime_sig_op_counter: RuntimeSigOpCounter, + flags: EngineFlags, } fn parse_script( @@ -140,6 +146,7 @@ pub fn get_sig_op_count(tx: &T, input_idx: usize) -> R tx.utxo(input_idx).ok_or_else(|| TxScriptError::InvalidInputIndex(input_idx as i32, tx.inputs().len()))?, &reused_values, &sig_cache, + Default::default(), ); vm.execute()?; Ok(vm.used_sig_ops()) @@ -222,19 +229,25 @@ pub fn is_unspendable(scr } impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<'a, T, Reused> { - pub fn new(reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { + pub fn new(reused_values: &'a Reused, sig_cache: &'a Cache, flags: EngineFlags) -> Self { Self { - dstack: vec![], - astack: vec![], + dstack: Self::new_stack(flags), + astack: Self::new_stack(flags), script_source: ScriptSource::StandAloneScripts(vec![]), reused_values, sig_cache, cond_stack: vec![], num_ops: 0, runtime_sig_op_counter: RuntimeSigOpCounter::new(u8::MAX), + flags, } } + fn new_stack(flags: EngineFlags) -> Stack { + let max_elem_size = if flags.covenants_enabled { MAX_SCRIPT_ELEMENT_SIZE } else { usize::MAX }; + Stack::new(vec![], max_elem_size) + } + /// Returns the number of signature operations used in script execution. pub fn used_sig_ops(&self) -> u8 { self.runtime_sig_op_counter.used_sig_ops() @@ -263,6 +276,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' utxo_entry: &'a UtxoEntry, reused_values: &'a Reused, sig_cache: &'a Cache, + flags: EngineFlags, ) -> Self { let script_public_key = utxo_entry.script_public_key.script(); // The script_public_key in P2SH is just validating the hash on the OpMultiSig script @@ -270,21 +284,27 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' let is_p2sh = ScriptClass::is_pay_to_script_hash(script_public_key); assert!(input_idx < tx.tx().inputs.len()); Self { - dstack: Default::default(), - astack: Default::default(), + dstack: Self::new_stack(flags), + astack: Self::new_stack(flags), script_source: ScriptSource::TxInput { tx, input, idx: input_idx, utxo_entry, is_p2sh }, reused_values, sig_cache, cond_stack: Default::default(), num_ops: 0, runtime_sig_op_counter: RuntimeSigOpCounter::new(input.sig_op_count), + flags, } } - pub fn from_script(script: &'a [u8], reused_values: &'a Reused, sig_cache: &'a Cache) -> Self { + pub fn from_script( + script: &'a [u8], + reused_values: &'a Reused, + sig_cache: &'a Cache, + flags: EngineFlags, + ) -> Self { Self { - dstack: Default::default(), - astack: Default::default(), + dstack: Self::new_stack(flags), + astack: Self::new_stack(flags), script_source: ScriptSource::StandAloneScripts(vec![script]), reused_values, sig_cache, @@ -292,6 +312,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' num_ops: 0, // Runtime sig op counting is not needed for standalone scripts, only inputs have sig op count value runtime_sig_op_counter: RuntimeSigOpCounter::new(u8::MAX), + flags, } } @@ -325,7 +346,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' fn execute_script(&mut self, script: &[u8], verify_only_push: bool) -> Result<(), TxScriptError> { let script_result = parse_script(script).try_for_each(|opcode| { let opcode = opcode?; - if opcode.is_disabled() { + if opcode.is_disabled(self.flags) { return Err(TxScriptError::OpcodeDisabled(format!("{:?}", opcode))); } @@ -386,7 +407,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' return Err(TxScriptError::ScriptSize(s.len(), MAX_SCRIPTS_SIZE)); } - let mut saved_stack: Option>> = None; + let mut saved_stack: Option = None; // try_for_each quits only if an error occurred. So, we always run over all scripts if // each is successful scripts.iter().enumerate().filter(|(_, s)| !s.is_empty()).try_for_each(|(idx, s)| { @@ -402,7 +423,7 @@ impl<'a, T: VerifiableTransaction, Reused: SigHashReusedValues> TxScriptEngine<' if is_p2sh { self.check_error_condition(false)?; self.dstack = saved_stack.ok_or(TxScriptError::EmptyStack)?; - let script = self.dstack.pop().ok_or(TxScriptError::EmptyStack)?; + let script = self.dstack.pop()?; self.execute_script(script.as_slice(), false)? } @@ -685,7 +706,15 @@ mod tests { let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); - let mut vm = TxScriptEngine::from_transaction_input(&populated_tx, &input, 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &populated_tx, + &input, + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), test.expected_result); } } @@ -1244,7 +1273,15 @@ mod tests { // Execute script let tx = tx.as_verifiable(); - let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, &utxo_entry, &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + &utxo_entry, + &reused_values, + &sig_cache, + Default::default(), + ); let result = vm.execute().map(|_| vm.used_sig_ops()); @@ -1342,7 +1379,7 @@ mod bitcoind_tests { } impl JsonTestRow { - fn test_row(&self) -> Result<(), TestError> { + fn test_row(&self, flags: EngineFlags) -> Result<(), TestError> { // Parse test to objects let (sig_script, script_pub_key, expected_result) = match self.clone() { JsonTestRow::Test(sig_script, sig_pub_key, _, expected_result) => (sig_script, sig_pub_key, expected_result), @@ -1354,7 +1391,13 @@ mod bitcoind_tests { } }; - let result = Self::run_test(sig_script, script_pub_key); + if let JsonTestRow::TestWithComment(_, _, _, _, comment) = self.clone() { + if comment.contains("OpTxSubnetId") { + eprintln!("debug"); + } + } + + let result = Self::run_test(sig_script, script_pub_key, flags); match Self::result_name(result.clone()).contains(&expected_result.as_str()) { true => Ok(()), @@ -1362,7 +1405,7 @@ mod bitcoind_tests { } } - fn run_test(sig_script: String, script_pub_key: String) -> Result<(), UnifiedError> { + fn run_test(sig_script: String, script_pub_key: String, flags: EngineFlags) -> Result<(), UnifiedError> { let script_sig = opcodes::parse_short_form(sig_script).map_err(UnifiedError::ScriptBuilderError)?; let script_pub_key = ScriptPublicKey::from_vec(0, opcodes::parse_short_form(script_pub_key).map_err(UnifiedError::ScriptBuilderError)?); @@ -1382,6 +1425,7 @@ mod bitcoind_tests { &populated_tx.entries[0], &reused_values, &sig_cache, + flags, ); vm.execute().map_err(UnifiedError::TxScriptError) } @@ -1438,6 +1482,8 @@ mod bitcoind_tests { TxScriptError::InvalidStackOperation(_, _) => vec!["INVALID_STACK_OPERATION", "INVALID_ALTSTACK_OPERATION"], TxScriptError::InvalidState(s) if s == "pick at an invalid location" => vec!["INVALID_STACK_OPERATION"], TxScriptError::InvalidState(s) if s == "roll at an invalid location" => vec!["INVALID_STACK_OPERATION"], + TxScriptError::OutOfBoundsSubstring(_, _, _) => vec!["UNKNOWN_ERROR"], + TxScriptError::InvalidIndex(_) => vec!["UNKNOWN_ERROR"], TxScriptError::OpcodeDisabled(_) => vec!["DISABLED_OPCODE"], TxScriptError::ElementTooBig(_, _) => vec!["PUSH_SIZE"], TxScriptError::TooManyOperations(_) => vec!["OP_COUNT"], @@ -1448,6 +1494,7 @@ mod bitcoind_tests { //ErrNegativeLockTime TxScriptError::UnsatisfiedLockTime(_) => vec!["UNSATISFIED_LOCKTIME"], TxScriptError::InvalidState(s) if s == "expected boolean" => vec!["MINIMALIF"], + TxScriptError::InvalidState(_) => vec!["UNKNOWN_ERROR"], TxScriptError::ScriptSize(_, _) => vec!["SCRIPT_SIZE"], _ => vec![], }, @@ -1460,31 +1507,7 @@ mod bitcoind_tests { } } - #[test] - fn test_bitcoind_tests() { - // Script test files are split into two versions to test behavior after KIP-10: - // - // - script_tests.json: Tests expanded functionality with KIP-10 enabled - // - // KIP-10 introduces two major changes: - // - // 1. Support for 8-byte integer arithmetic (previously limited to 4 bytes) - // This enables working with larger numbers in scripts and reduces artificial constraints - // - // 2. Transaction introspection opcodes: - // - OpTxInputCount (0xb3): Get number of inputs - // - OpTxOutputCount (0xb4): Get number of outputs - // - OpTxInputIndex (0xb9): Get current input index - // - OpTxInputAmount (0xbe): Get input amount - // - OpTxInputSpk (0xbf): Get input script public key - // - OpTxOutputAmount (0xc2): Get output amount - // - OpTxOutputSpk (0xc3): Get output script public key - // - // These changes were added to support mutual transactions and auto-compounding addresses. - // When KIP-10 is disabled (pre-activation), the new opcodes will return an InvalidOpcode error - // and arithmetic is limited to 4 bytes. When enabled, scripts gain full access to transaction - // data and 8-byte arithmetic capabilities. - let file_name = "script_tests.json"; + fn run_json_test_file(file_name: &str, flags: EngineFlags) { let file = File::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data").join(file_name)).expect("Could not find test file"); let reader = BufReader::new(file); @@ -1492,9 +1515,19 @@ mod bitcoind_tests { // Read the JSON contents of the file as an instance of `User`. let tests: Vec = serde_json::from_reader(reader).expect("Failed Parsing {:?}"); for row in tests { - if let Err(error) = row.test_row() { + if let Err(error) = row.test_row(flags) { panic!("Test: {:?} failed for {}: {:?}", row.clone(), file_name, error); } } } + + #[test] + fn test_pre_covenants_bitcoind_tests() { + run_json_test_file("script_tests.json", Default::default()); + } + + #[test] + fn test_covenants_bitcoind_tests() { + run_json_test_file("script_tests_covenants.json", EngineFlags { covenants_enabled: true }); + } } diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index c970841532..33d6f7bb7b 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -2,9 +2,8 @@ mod macros; use crate::{ - data_stack::{DataStack, OpcodeData}, - ScriptSource, SpkEncoding, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, - SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, + data_stack::OpcodeData, EngineFlags, ScriptSource, SpkEncoding, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD, + MAX_SCRIPT_ELEMENT_SIZE, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, }; use blake2b_simd::Params; use kaspa_consensus_core::hashing::sighash::SigHashReusedValues; @@ -66,7 +65,7 @@ pub trait OpCodeMetadata: Debug { // For push data- check if we can use shorter encoding fn check_minimal_data_push(&self) -> Result<(), TxScriptError>; - fn is_disabled(&self) -> bool; + fn is_disabled(&self, flags: EngineFlags) -> bool; fn always_illegal(&self) -> bool; fn is_push_opcode(&self) -> bool; fn get_data(&self) -> &[u8]; @@ -107,25 +106,29 @@ impl OpCodeMetadata for OpCode { CODE } - fn is_disabled(&self) -> bool { - matches!( - CODE, - codes::OpCat - | codes::OpSubStr - | codes::OpLeft - | codes::OpRight - | codes::OpInvert - | codes::OpAnd - | codes::OpOr - | codes::OpXor - | codes::Op2Mul - | codes::Op2Div - | codes::OpMul - | codes::OpDiv - | codes::OpMod - | codes::OpLShift - | codes::OpRShift - ) + fn is_disabled(&self, flags: EngineFlags) -> bool { + if flags.covenants_enabled { + matches!(CODE, codes::OpLeft | codes::OpRight | codes::Op2Mul | codes::Op2Div | codes::OpLShift | codes::OpRShift) + } else { + matches!( + CODE, + codes::OpCat + | codes::OpSubStr + | codes::OpLeft + | codes::OpRight + | codes::OpInvert + | codes::OpAnd + | codes::OpOr + | codes::OpXor + | codes::Op2Mul + | codes::Op2Div + | codes::OpMul + | codes::OpDiv + | codes::OpMod + | codes::OpLShift + | codes::OpRShift + ) + } } fn always_illegal(&self) -> bool { @@ -203,8 +206,7 @@ fn push_data( data: Vec, vm: &mut TxScriptEngine, ) -> OpCodeResult { - vm.dstack.push(data); - Ok(()) + vm.dstack.push(data) } #[inline] @@ -216,6 +218,17 @@ fn push_number( Ok(()) } +fn substring(data: &[u8], start: usize, end: usize) -> Result, TxScriptError> { + if end - start > MAX_SCRIPT_ELEMENT_SIZE { + return Err(TxScriptError::ElementTooBig(end - start, MAX_SCRIPT_ELEMENT_SIZE)); + } + data.get(start..end).map(|substr| substr.to_vec()).ok_or(TxScriptError::OutOfBoundsSubstring(start, end, data.len())) +} + +fn i32_to_usize(value: i32) -> Result { + value.try_into().map_err(|_| TxScriptError::InvalidIndex(value)) +} + /* The following is the implementation and metadata of all opcodes. Each opcode has unique number (and template system makes it impossible to use two opcodes), length specification, @@ -250,8 +263,7 @@ opcode_list! { // Data push opcodes. opcode |Op0| OpFalse<0x00, 1>(self , vm) { - vm.dstack.push(vec![]); - Ok(()) + vm.dstack.push(vec![]) } opcode OpData1<0x01, 2>(self, vm) push_data(self.data.clone(), vm) @@ -363,20 +375,15 @@ opcode_list! { if vm.is_executing() { // This code seems identical to pop_bool, but was written this way to preserve // the similar flow of go-kaspad - if let Some(mut cond_buf) = vm.dstack.pop() { - if cond_buf.len() > 1 { - return Err(TxScriptError::InvalidState("expected boolean".to_string())); - } - cond = match cond_buf.pop() { - Some(stack_cond) => match stack_cond { - 1 => OpCond::True, - _ => return Err(TxScriptError::InvalidState("expected boolean".to_string())), - } - None => OpCond::False, - } - } else { - return Err(TxScriptError::EmptyStack); + let mut cond_buf = vm.dstack.pop()?; + if cond_buf.len() > 1 { + return Err(TxScriptError::InvalidState("expected boolean".to_string())); } + cond = match cond_buf.pop() { + Some(1) => OpCond::True, + Some(_) => return Err(TxScriptError::InvalidState("expected boolean".to_string())), + None => OpCond::False, + }; } vm.cond_stack.push(cond); Ok(()) @@ -385,19 +392,14 @@ opcode_list! { opcode OpNotIf<0x64, 1>(self, vm) { let mut cond = OpCond::Skip; if vm.is_executing() { - if let Some(mut cond_buf) = vm.dstack.pop() { - if cond_buf.len() > 1 { - return Err(TxScriptError::InvalidState("expected boolean".to_string())); - } - cond = match cond_buf.pop() { - Some(stack_cond) => match stack_cond { - 1 => OpCond::False, - _ => return Err(TxScriptError::InvalidState("expected boolean".to_string())), - } - None => OpCond::True, - } - } else { - return Err(TxScriptError::EmptyStack); + let mut cond_buf = vm.dstack.pop()?; + if cond_buf.len() > 1 { + return Err(TxScriptError::InvalidState("expected boolean".to_string())); + } + cond = match cond_buf.pop() { + Some(1) => OpCond::False, + Some(_) => return Err(TxScriptError::InvalidState("expected boolean".to_string())), + None => OpCond::True, } } vm.cond_stack.push(cond); @@ -436,18 +438,12 @@ opcode_list! { // Stack opcodes. opcode OpToAltStack<0x6b, 1>(self, vm) { let [item] = vm.dstack.pop_raw()?; - vm.astack.push(item); - Ok(()) + vm.astack.push(item) } opcode OpFromAltStack<0x6c, 1>(self, vm) { - match vm.astack.pop() { - Some(last) => { - vm.dstack.push(last); - Ok(()) - }, - None => Err(TxScriptError::EmptyStack) - } + let last = vm.astack.pop()?; + vm.dstack.push(last) } opcode Op2Drop<0x6d, 1>(self, vm) vm.dstack.drop_items::<2>() @@ -460,7 +456,7 @@ opcode_list! { opcode OpIfDup<0x73, 1>(self, vm) { let [result] = vm.dstack.peek_raw()?; if as OpcodeData>::deserialize(&result)? { - vm.dstack.push(result); + vm.dstack.push(result)?; } Ok(()) } @@ -487,8 +483,7 @@ opcode_list! { if loc < 0 || loc as usize >= vm.dstack.len() { return Err(TxScriptError::InvalidState("pick at an invalid location".to_string())); } - vm.dstack.push(vm.dstack[vm.dstack.len()-(loc as usize)-1].clone()); - Ok(()) + vm.dstack.push(vm.dstack[vm.dstack.len()-(loc as usize)-1].clone()) } opcode OpRoll<0x7a, 1>(self, vm) { @@ -497,8 +492,7 @@ opcode_list! { return Err(TxScriptError::InvalidState("roll at an invalid location".to_string())); } let item = vm.dstack.remove(vm.dstack.len()-(loc as usize)-1); - vm.dstack.push(item); - Ok(()) + vm.dstack.push(item) } opcode OpRot<0x7b, 1>(self, vm) vm.dstack.rot_items::<1>() @@ -507,7 +501,7 @@ opcode_list! { opcode OpTuck<0x7d, 1>(self, vm) { match vm.dstack.len() >= 2 { true => { - vm.dstack.insert(vm.dstack.len()-2, vm.dstack.last().expect("We have at least two items").clone()); + vm.dstack.insert(vm.dstack.len()-2, vm.dstack.last().expect("We have at least two items").clone())?; Ok(()) } false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len())) @@ -515,8 +509,31 @@ opcode_list! { } // Splice opcodes. - opcode OpCat<0x7e, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpSubStr<0x7f, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpCat<0x7e, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let b = vm.dstack.pop()?; + let a = vm.dstack.pop()?; + let mut r = a; + r.extend_from_slice(&b); + vm.dstack.push(r) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpSubStr<0x7f, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let [start, end]: [i32; 2] = vm.dstack.pop_items()?; + let data = vm.dstack.pop()?; + let end = i32_to_usize(end)?; + let start = i32_to_usize(start)?; + let substr = substring(&data, start, end)?; + vm.dstack.push(substr) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + opcode OpLeft<0x80, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpRight<0x81, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) @@ -531,10 +548,57 @@ opcode_list! { } // Bitwise logic opcodes. - opcode OpInvert<0x83, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpAnd<0x84, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpOr<0x85, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpXor<0x86, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpInvert<0x83, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let data = vm.dstack.pop()?; + let r: Vec = data.into_iter().map(|b| !b).collect(); + vm.dstack.push(r) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpAnd<0x84, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let b = vm.dstack.pop()?; + let a = vm.dstack.pop()?; + if a.len() != b.len() { + return Err(TxScriptError::InvalidState("AND operands must be of equal length".to_string())); + } + let r: Vec = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte & b_byte).collect(); + vm.dstack.push(r) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpOr<0x85, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let b = vm.dstack.pop()?; + let a = vm.dstack.pop()?; + if a.len() != b.len() { + return Err(TxScriptError::InvalidState("OR operands must be of equal length".to_string())); + } + let r: Vec = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte | b_byte).collect(); + vm.dstack.push(r) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpXor<0x86, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let b = vm.dstack.pop()?; + let a = vm.dstack.pop()?; + if a.len() != b.len() { + return Err(TxScriptError::InvalidState("XOR operands must be of equal length".to_string())); + } + let r: Vec = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte ^ b_byte).collect(); + vm.dstack.push(r) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } opcode OpEqual<0x87, 1>(self, vm) { match vm.dstack.len() >= 2 { @@ -544,7 +608,6 @@ opcode_list! { true => vm.dstack.push(vec![1]), false => vm.dstack.push(vec![]), } - Ok(()) } false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len())) } @@ -626,9 +689,39 @@ opcode_list! { Ok(()) } - opcode OpMul<0x95, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpDiv<0x96, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) - opcode OpMod<0x97, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + opcode OpMul<0x95, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?; + let r = a.checked_mul(b).ok_or_else(|| TxScriptError::NumberTooBig("Product exceeds 64-bit signed integer range".to_string()))?; + vm.dstack.push_item(r)?; + Ok(()) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpDiv<0x96, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?; + let r = a.checked_div(b).ok_or_else(|| TxScriptError::InvalidState("Quotient exceeds 64-bit signed integer range, or there was a division by zero".to_string()))?; + vm.dstack.push_item(r)?; + Ok(()) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + + opcode OpMod<0x97, 1>(self, vm){ + if vm.flags.covenants_enabled{ + let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?; + let r = a.checked_rem(b).ok_or_else(|| TxScriptError::InvalidState("Illegal modulo by zero".to_string()))?; + vm.dstack.push_item(r)?; + Ok(()) + } else { + Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) + } + } + opcode OpLShift<0x98, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) opcode OpRShift<0x99, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}"))) @@ -719,15 +812,23 @@ opcode_list! { // Undefined opcodes. opcode OpUnknown166<0xa6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown167<0xa7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) // Crypto opcodes. + opcode OpBlake2bWithKey<0xa7, 1>(self, vm) { + if vm.flags.covenants_enabled { + let [data, key] = vm.dstack.pop_raw()?; + let hash = Params::new().hash_length(32).key(&key).to_state().update(&data).finalize(); + vm.dstack.push(hash.as_bytes().to_vec()) + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + opcode OpSHA256<0xa8, 1>(self, vm) { let [last] = vm.dstack.pop_raw()?; let mut hasher = Sha256::new(); hasher.update(last); - vm.dstack.push(hasher.finalize().to_vec()); - Ok(()) + vm.dstack.push(hasher.finalize().to_vec()) } opcode OpCheckMultiSigECDSA<0xa9, 1>(self, vm) { @@ -738,8 +839,7 @@ opcode_list! { let [last] = vm.dstack.pop_raw()?; //let hash = blake2b(last.as_slice()); let hash = Params::new().hash_length(32).to_state().update(&last).finalize(); - vm.dstack.push(hash.as_bytes().to_vec()); - Ok(()) + vm.dstack.push(hash.as_bytes().to_vec()) } opcode OpCheckSigECDSA<0xab, 1>(self, vm) { @@ -907,7 +1007,18 @@ opcode_list! { // Introspection opcodes // Transaction level opcodes (following Transaction struct field order) - opcode OpTxVersion<0xb2, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxVersion<0xb2, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.tx().version as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxVersion only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } opcode OpTxInputCount<0xb3, 1>(self, vm) { match vm.script_source { ScriptSource::TxInput{tx, ..} => { @@ -924,10 +1035,58 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("OpOutputCount only applies to transaction inputs".to_string())) } } - opcode OpTxLockTime<0xb5, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxSubnetId<0xb6, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxGas<0xb7, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxPayload<0xb8, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxLockTime<0xb5, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.tx().lock_time as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxLockTime only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpTxSubnetId<0xb6, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_data(tx.tx().subnetwork_id.into(), vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxSubnetId only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpTxGas<0xb7, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.tx().gas as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxGas only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpTxPayloadSubstr<0xb8, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [start, end]: [i32; 2] = vm.dstack.pop_items()?; + let end = i32_to_usize(end)?; + let start = i32_to_usize(start)?; + let substr = substring(&tx.tx().payload, start, end)?; + push_data(substr, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxPayloadSubstr only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } // Input related opcodes (following TransactionInput struct field order) opcode OpTxInputIndex<0xb9, 1>(self, vm) { match vm.script_source { @@ -937,10 +1096,70 @@ opcode_list! { _ => Err(TxScriptError::InvalidSource("OpInputIndex only applies to transaction inputs".to_string())) } } - opcode OpOutpointTxId<0xba, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpOutpointIndex<0xbb, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxInputScriptSig<0xbc, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxInputSeq<0xbd, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpOutpointTxId<0xba, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + push_data(input.previous_outpoint.transaction_id.as_bytes().into(), vm) + }, + _ => Err(TxScriptError::InvalidSource("OpOutpointTxId only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpOutpointIndex<0xbb, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + push_number(input.previous_outpoint.index as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpOutpointIndex only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpTxInputScriptSigSubStr<0xbc, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx, start, end]: [i32; 3] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let start = i32_to_usize(start)?; + let end = i32_to_usize(end)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + let substr = substring(&input.signature_script, start, end)?; + push_data(substr, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputScriptSigSubStr only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } + opcode OpTxInputSeq<0xbd, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + // sequence is used as a bitflag field, so push as raw bytes (minimal number encoding doesn't apply). See CheckSequenceVerify for more details. + push_data(input.sequence.to_le_bytes().into(), vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputSeq only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } // UTXO related opcodes (following UtxoEntry struct field order) opcode OpTxInputAmount<0xbe, 1>(self, vm) { match vm.script_source { @@ -961,14 +1180,27 @@ opcode_list! { let utxo = usize::try_from(idx).ok() .and_then(|idx| tx.utxo(idx)) .ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?; - vm.dstack.push(utxo.script_public_key.to_bytes()); - Ok(()) + vm.dstack.push(utxo.script_public_key.to_bytes()) }, _ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string())) } } opcode OpTxInputBlockDaaScore<0xc0, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) - opcode OpTxInputIsCoinbase<0xc1, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + opcode OpTxInputIsCoinbase<0xc1, 1>(self, vm){ + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + push_number(utxo.is_coinbase as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputIsCoinbase only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::OpcodeReserved(format!("{self:?}"))) + } + } // Output related opcodes (following TransactionOutput struct field order) opcode OpTxOutputAmount<0xc2, 1>(self, vm) { match vm.script_source { @@ -979,7 +1211,7 @@ opcode_list! { .ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; push_number(output.value.try_into().map_err(|e: TryFromIntError| TxScriptError::NumberTooBig(e.to_string()))?, vm) }, - _ => Err(TxScriptError::InvalidSource("OpOutputAmount only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpTxOutputAmount only applies to transaction inputs".to_string())) } } opcode OpTxOutputSpk<0xc3, 1>(self, vm) { @@ -989,20 +1221,137 @@ opcode_list! { let output = usize::try_from(idx).ok() .and_then(|idx| tx.outputs().get(idx)) .ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.inputs().len()))?; - vm.dstack.push(output.script_public_key.to_bytes()); - Ok(()) + vm.dstack.push(output.script_public_key.to_bytes()) }, - _ => Err(TxScriptError::InvalidSource("OpOutputSpk only applies to transaction inputs".to_string())) + _ => Err(TxScriptError::InvalidSource("OpTxOutputSpk only applies to transaction inputs".to_string())) + } + } + + opcode OpTxPayloadLen<0xc4, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + push_number(tx.tx().payload.len() as i64, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxPayloadLen only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + + opcode OpTxInputSpkLen<0xc5, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + // TODO: Consider adding a method to ScirptPublicKey for getting length directly, instead of converting to bytes first. + let len = utxo.script_public_key.to_bytes().len() as i64; + push_number(len, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputSpkLen only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + + opcode OpTxInputSpkSubstr<0xc6, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx, start, end]: [i32; 3] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let start = i32_to_usize(start)?; + let end = i32_to_usize(end)?; + let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + let spk_bytes = utxo.script_public_key.to_bytes(); + let substr = substring(&spk_bytes, start, end)?; + push_data(substr, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputSpkSubstr only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + + opcode OpTxOutputSpkLen<0xc7, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?; + // TODO: Consider adding a method to ScirptPublicKey for getting length directly, instead of converting to bytes first. + let len = output.script_public_key.to_bytes().len() as i64; + push_number(len, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxOutputSpkLen only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + + opcode OpTxOutputSpkSubstr<0xc8, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx, start, end]: [i32; 3] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let start = i32_to_usize(start)?; + let end = i32_to_usize(end)?; + let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?; + let spk_bytes = output.script_public_key.to_bytes(); + let substr = substring(&spk_bytes, start, end)?; + push_data(substr, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxOutputSpkSubstr only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + + opcode OpTxInputScriptSigLen<0xc9, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx]: [i32; 1] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + let len = input.signature_script.len() as i64; + push_number(len, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputScriptSigLen only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) + } + } + opcode OpTxInputScriptSigSubstr<0xca, 1>(self, vm) { + if vm.flags.covenants_enabled { + match vm.script_source { + ScriptSource::TxInput{tx, ..} => { + let [idx, start, end]: [i32; 3] = vm.dstack.pop_items()?; + let idx = i32_to_usize(idx)?; + let start = i32_to_usize(start)?; + let end = i32_to_usize(end)?; + let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?; + let substr = substring(&input.signature_script, start, end)?; + push_data(substr, vm) + }, + _ => Err(TxScriptError::InvalidSource("OpTxInputScriptSigSubstr only applies to transaction inputs".to_string())) + } + } else { + Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) } } + // Undefined opcodes - opcode OpUnknown196<0xc4, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown197<0xc5, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown198<0xc6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown199<0xc7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown200<0xc8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown201<0xc9, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) - opcode OpUnknown202<0xca, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown203<0xcb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown204<0xcc, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) opcode OpUnknown205<0xcd, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}"))) @@ -1087,13 +1436,13 @@ mod test { }; struct TestCase<'a> { - init: Stack, + init: Vec>, code: Box, SigHashReusedValuesUnsync>>, - dstack: Stack, + dstack: Vec>, } struct ErrorTestCase<'a> { - init: Stack, + init: Vec>, code: Box, SigHashReusedValuesUnsync>>, error: TxScriptError, } @@ -1102,10 +1451,12 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for TestCase { init, code, dstack } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let init: Stack = init.into(); + let dstack = dstack.into(); + let mut vm = TxScriptEngine::new(&reused_values, &cache, Default::default()); vm.dstack = init.clone(); code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value())); - assert_eq!(*vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); + assert_eq!(vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value()); } } @@ -1113,8 +1464,8 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); for ErrorTestCase { init, code, error } in tests { - let mut vm = TxScriptEngine::new(&reused_values, &cache); - vm.dstack.clone_from(&init); + let mut vm = TxScriptEngine::new(&reused_values, &cache, Default::default()); + vm.dstack = init.clone().into(); assert_eq!( code.execute(&mut vm) .expect_err(format!("Opcode {} should have errored (init: {:?})", code.value(), init.clone()).as_str()), @@ -1148,7 +1499,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, Default::default()); for pop in tests { match pop.execute(&mut vm) { @@ -1167,14 +1518,10 @@ mod test { opcodes::OpVerNotIf::empty().expect("Should accept empty"), opcodes::OpReserved1::empty().expect("Should accept empty"), opcodes::OpReserved2::empty().expect("Should accept empty"), - opcodes::OpTxVersion::empty().expect("Should accept empty"), - opcodes::OpTxLockTime::empty().expect("Should accept empty"), - opcodes::OpTxSubnetId::empty().expect("Should accept empty"), - opcodes::OpTxGas::empty().expect("Should accept empty"), - opcodes::OpTxPayload::empty().expect("Should accept empty"), + opcodes::OpTxPayloadSubstr::empty().expect("Should accept empty"), opcodes::OpOutpointTxId::empty().expect("Should accept empty"), opcodes::OpOutpointIndex::empty().expect("Should accept empty"), - opcodes::OpTxInputScriptSig::empty().expect("Should accept empty"), + opcodes::OpTxInputScriptSigSubStr::empty().expect("Should accept empty"), opcodes::OpTxInputSeq::empty().expect("Should accept empty"), opcodes::OpTxInputBlockDaaScore::empty().expect("Should accept empty"), opcodes::OpTxInputIsCoinbase::empty().expect("Should accept empty"), @@ -1182,7 +1529,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, Default::default()); for pop in tests { match pop.execute(&mut vm) { @@ -1196,15 +1543,15 @@ mod test { fn test_opcode_invalid() { let tests: Vec>> = vec![ opcodes::OpUnknown166::empty().expect("Should accept empty"), - opcodes::OpUnknown167::empty().expect("Should accept empty"), - opcodes::OpUnknown196::empty().expect("Should accept empty"), - opcodes::OpUnknown197::empty().expect("Should accept empty"), - opcodes::OpUnknown198::empty().expect("Should accept empty"), - opcodes::OpUnknown199::empty().expect("Should accept empty"), - opcodes::OpUnknown200::empty().expect("Should accept empty"), - opcodes::OpUnknown201::empty().expect("Should accept empty"), - opcodes::OpUnknown202::empty().expect("Should accept empty"), - opcodes::OpUnknown203::empty().expect("Should accept empty"), + opcodes::OpBlake2bWithKey::empty().expect("Should accept empty"), + opcodes::OpTxPayloadLen::empty().expect("Should accept empty"), + opcodes::OpTxInputSpkLen::empty().expect("Should accept empty"), + opcodes::OpTxInputSpkSubstr::empty().expect("Should accept empty"), + opcodes::OpTxOutputSpkLen::empty().expect("Should accept empty"), + opcodes::OpTxOutputSpkSubstr::empty().expect("Should accept empty"), + opcodes::OpTxInputScriptSigLen::empty().expect("Should accept empty"), + opcodes::OpTxInputScriptSigSubstr::empty().expect("Should accept empty"), + opcodes::OpBlake2bWithKey::empty().expect("Should accept empty"), opcodes::OpUnknown204::empty().expect("Should accept empty"), opcodes::OpUnknown205::empty().expect("Should accept empty"), opcodes::OpUnknown206::empty().expect("Should accept empty"), @@ -1255,7 +1602,7 @@ mod test { let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - let mut vm = TxScriptEngine::new(&reused_values, &cache); + let mut vm = TxScriptEngine::new(&reused_values, &cache, Default::default()); for pop in tests { match pop.execute(&mut vm) { @@ -2850,8 +3197,9 @@ mod test { ] { let mut tx = base_tx.clone(); tx.0.lock_time = tx_lock_time; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache); - vm.dstack = vec![lock_time.clone()]; + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, Default::default()); + vm.dstack = vec![lock_time.clone()].into(); match code.execute(&mut vm) { // Message is based on the should_fail values Ok(()) => assert!( @@ -2892,8 +3240,9 @@ mod test { ] { let mut input = base_input.clone(); input.sequence = tx_sequence; - let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache); - vm.dstack = vec![sequence.clone()]; + let mut vm = + TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, &reused_values, &sig_cache, Default::default()); + vm.dstack = vec![sequence.clone()].into(); match code.execute(&mut vm) { // Message is based on the should_fail values Ok(()) => { @@ -2998,7 +3347,7 @@ mod test { mod kip10 { use super::*; use crate::{ - data_stack::{DataStack, OpcodeData}, + data_stack::OpcodeData, opcodes::{codes::*, push_number}, pay_to_script_hash_script, script_builder::ScriptBuilder, @@ -3084,6 +3433,7 @@ mod test { tx.utxo(current_idx).unwrap(), &reused_values, &sig_cache, + Default::default(), ); // Check input index opcode first @@ -3116,10 +3466,10 @@ mod test { // Check the result matches expectations if let Some(ref expected_spk) = expected_result.expected_spk { - assert_eq!(vm.dstack, vec![expected_spk.clone()]); + assert_eq!(vm.dstack.inner(), vec![expected_spk.clone()]); } if let Some(ref expected_amount) = expected_result.expected_amount { - assert_eq!(vm.dstack, vec![expected_amount.clone()]); + assert_eq!(vm.dstack.inner(), vec![expected_amount.clone()]); } vm.dstack.clear(); } @@ -3317,6 +3667,7 @@ mod test { tx.utxo(0).unwrap(), &reused_values, &sig_cache, + Default::default(), ); let op_input_count = opcodes::OpTxInputCount::empty().expect("Should accept empty"); @@ -3325,7 +3676,7 @@ mod test { // Test input count op_input_count.execute(&mut vm).unwrap(); assert_eq!( - vm.dstack, + vm.dstack.inner(), vec![ as OpcodeData>::serialize(&(input_count as i64)).unwrap()], "Input count mismatch for {} inputs", input_count @@ -3335,7 +3686,7 @@ mod test { // Test output count op_output_count.execute(&mut vm).unwrap(); assert_eq!( - vm.dstack, + vm.dstack.inner(), vec![ as OpcodeData>::serialize(&(output_count as i64)).unwrap()], "Output count mismatch for {} outputs", output_count @@ -3376,8 +3727,15 @@ mod test { // Test success case { - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3393,8 +3751,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3426,8 +3791,15 @@ mod test { let mut tx = MutableTransaction::with_entries(tx, utxo_entries); tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3445,8 +3817,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3467,8 +3846,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); // OpInputSpk should push input's SPK onto stack, making it non-empty assert_eq!(vm.execute(), Ok(())); @@ -3491,8 +3877,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); // Should succeed because the SPKs are different assert_eq!(vm.execute(), Ok(())); @@ -3516,8 +3909,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); // Should succeed because both SPKs are identical assert_eq!(vm.execute(), Ok(())); @@ -3554,8 +3954,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3573,8 +3980,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3599,8 +4013,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3616,8 +4037,15 @@ mod test { tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); // Should fail because script expects index 0 but we're at index 1 assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); @@ -3655,8 +4083,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&input_count_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3667,8 +4102,15 @@ mod test { tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&output_count_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Ok(())); } @@ -3683,8 +4125,15 @@ mod test { tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&wrong_input_count_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[0], + 0, + tx.utxo(0).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } @@ -3698,11 +4147,299 @@ mod test { tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&wrong_output_count_script).unwrap().drain(); let tx = tx.as_verifiable(); - let mut vm = - TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), &reused_values, &sig_cache); + let mut vm = TxScriptEngine::from_transaction_input( + &tx, + &tx.inputs()[1], + 1, + tx.utxo(1).unwrap(), + &reused_values, + &sig_cache, + Default::default(), + ); assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse)); } } } + + #[cfg(test)] + mod introspection { + use super::*; + use crate::script_builder::{ScriptBuilder, ScriptBuilderResult}; + use crate::{opcodes::codes, EngineFlags, SpkEncoding}; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; + use kaspa_consensus_core::subnets::SubnetworkId; + use kaspa_consensus_core::tx::{ + PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, + }; + use kaspa_hashes::Hash; + + fn payload_bytes(len: usize) -> Vec { + (0..len).map(|i| (i % 256) as u8).collect() + } + + fn base_transaction(payload_len: usize) -> (Transaction, Vec) { + let version: u16 = 5; + let lock_time: u64 = 123; + let subnetwork_id = SubnetworkId::from_bytes([9u8; 20]); + let gas: u64 = 777; + let payload = payload_bytes(payload_len); + + let sig_script_0 = ScriptBuilder::new().add_data(&[0xaa, 0xbb, 0xcc]).expect("sig script build").drain(); + let sig_script_1 = ScriptBuilder::new().add_data(&[0x11]).expect("sig script build").drain(); + + let inputs = vec![ + TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), sig_script_0, 0x1122334455667788, 0), + TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), sig_script_1, 0x0fedcba987654321, 0), + ]; + + let output_spk_0 = ScriptBuilder::new().add_ops(&[codes::OpTrue, codes::Op2, codes::Op3]).expect("spk build").drain(); + let output_spk_1 = ScriptBuilder::new().add_ops(&[codes::Op4, codes::Op5]).expect("spk build").drain(); + + let outputs = vec![ + TransactionOutput::new(11, ScriptPublicKey::new(0, output_spk_0.into())), + TransactionOutput::new(22, ScriptPublicKey::new(0, output_spk_1.into())), + ]; + + let mut tx = Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload); + tx.finalize(); + + let utxo_spk_0 = ScriptBuilder::new().add_ops(&[codes::OpTrue, codes::Op1]).expect("spk build").drain(); + let utxo_spk_1 = ScriptBuilder::new().add_ops(&[codes::Op2, codes::Op3]).expect("spk build").drain(); + + let entries = vec![ + UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk_0.into()), 0, true), + UtxoEntry::new(2000, ScriptPublicKey::new(0, utxo_spk_1.into()), 0, false), + ]; + + (tx, entries) + } + + fn script(build: F) -> Vec + where + F: FnOnce(&mut ScriptBuilder) -> ScriptBuilderResult<&mut ScriptBuilder>, + { + let mut sb = ScriptBuilder::new(); + // Add a drop at the start to remove the initial sigscript data + sb.add_op(codes::OpDrop).expect("builder failure"); + build(&mut sb).expect("builder failure"); + sb.drain() + } + + fn run_script(tx: &Transaction, mut entries: Vec, idx: usize, script: Vec) -> Result<(), TxScriptError> { + entries[idx].script_public_key = ScriptPublicKey::new(0, script.into()); + let populated_tx = PopulatedTransaction::new(tx, entries); + let reused_values = SigHashReusedValuesUnsync::new(); + let sig_cache = Cache::new(10_000); + let mut vm = TxScriptEngine::from_transaction_input( + &populated_tx, + &populated_tx.tx.inputs[idx], + idx, + &populated_tx.entries[idx], + &reused_values, + &sig_cache, + EngineFlags { covenants_enabled: true }, + ); + vm.execute() + } + + #[test] + fn tx_level_introspection() { + let (tx, entries) = base_transaction(100); + let (tx_large, entries_large) = base_transaction(600); + let expected_subnet: Vec = tx.subnetwork_id.into(); + + let spk_version = script(|sb| sb.add_op(codes::OpTxVersion)?.add_i64(tx.version as i64)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_version).expect("tx version"); + + let spk_lock_time = script(|sb| sb.add_op(codes::OpTxLockTime)?.add_i64(tx.lock_time as i64)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_lock_time).expect("tx locktime"); + + let spk_subnet = script(|sb| sb.add_op(codes::OpTxSubnetId)?.add_data(&expected_subnet)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_subnet).expect("tx subnet id"); + + let spk_gas = script(|sb| sb.add_op(codes::OpTxGas)?.add_i64(tx.gas as i64)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_gas).expect("tx gas"); + + let spk_payload_len = + script(|sb| sb.add_op(codes::OpTxPayloadLen)?.add_i64(tx.payload.len() as i64)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_payload_len).expect("tx payload len"); + + let expected_payload_full = tx.payload.clone(); + let spk_payload_substr_full = script(|sb| { + sb.add_i64(0)? + .add_op(codes::OpTxPayloadLen)? + .add_op(codes::OpTxPayloadSubstr)? + .add_data(&expected_payload_full)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_payload_substr_full).expect("payload substr full"); + + let expected_substr = tx.payload[1..4].to_vec(); + let spk_payload_substr = script(|sb| { + sb.add_i64(1)?.add_i64(4)?.add_op(codes::OpTxPayloadSubstr)?.add_data(&expected_substr)?.add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_payload_substr).expect("payload substr ok"); + + let spk_payload_substr_oob = script(|sb| sb.add_i64(1000)?.add_i64(1002)?.add_op(codes::OpTxPayloadSubstr)); + let err = + run_script(&tx_large, entries_large.clone(), 0, spk_payload_substr_oob).expect_err("payload substr out of bounds"); + assert!(matches!(err, TxScriptError::OutOfBoundsSubstring(_, _, _))); + + let spk_payload_substr_too_long = script(|sb| sb.add_i64(0)?.add_i64(600)?.add_op(codes::OpTxPayloadSubstr)); + let err = run_script(&tx_large, entries_large.clone(), 0, spk_payload_substr_too_long).expect_err("payload substr >520"); + assert!(matches!(err, TxScriptError::ElementTooBig(_, _))); + } + + #[test] + fn input_input_output_introspection() { + let (tx, entries) = base_transaction(40); + let input_spk_bytes_1 = entries[1].script_public_key.to_bytes(); + let out_spk_bytes_0 = tx.outputs[0].script_public_key.to_bytes(); + let sig_script_0 = tx.inputs[0].signature_script.clone(); + + let spk_input_spk_len = script(|sb| { + sb.add_i64(1)?.add_op(codes::OpTxInputSpkLen)?.add_i64(input_spk_bytes_1.len() as i64)?.add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_spk_len).expect("input spk len"); + + let expected_spk_full = input_spk_bytes_1.clone(); + let spk_input_spk_substr_full = script(|sb| { + sb.add_i64(1)? + .add_i64(0)? + .add_i64(1)? + .add_op(codes::OpTxInputSpkLen)? + .add_op(codes::OpTxInputSpkSubstr)? + .add_data(&expected_spk_full)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_spk_substr_full).expect("input spk substr full"); + + let expected_spk_suffix = input_spk_bytes_1[1..].to_vec(); + let spk_input_spk_substr_suffix = script(|sb| { + sb.add_i64(1)? + .add_i64(1)? + .add_i64(1)? + .add_op(codes::OpTxInputSpkLen)? + .add_op(codes::OpTxInputSpkSubstr)? + .add_data(&expected_spk_suffix)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_spk_substr_suffix).expect("input spk substr suffix"); + + let spk_input_spk_substr_oob = script(|sb| sb.add_i64(1)?.add_i64(100)?.add_i64(101)?.add_op(codes::OpTxInputSpkSubstr)); + let err = run_script(&tx, entries.clone(), 0, spk_input_spk_substr_oob).expect_err("input spk substr oob"); + assert!(matches!(err, TxScriptError::OutOfBoundsSubstring(_, _, _))); + + let spk_output_spk_len = script(|sb| { + sb.add_i64(0)?.add_op(codes::OpTxOutputSpkLen)?.add_i64(out_spk_bytes_0.len() as i64)?.add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_output_spk_len).expect("output spk len"); + + let expected_out_spk_full = out_spk_bytes_0.clone(); + let spk_output_spk_substr_full = script(|sb| { + sb.add_i64(0)? + .add_i64(0)? + .add_i64(0)? + .add_op(codes::OpTxOutputSpkLen)? + .add_op(codes::OpTxOutputSpkSubstr)? + .add_data(&expected_out_spk_full)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_output_spk_substr_full).expect("output spk substr full"); + + let expected_out_spk_tail = out_spk_bytes_0[1..3].to_vec(); + let spk_output_spk_substr_tail = script(|sb| { + sb.add_i64(0)? + .add_i64(1)? + .add_i64(3)? + .add_op(codes::OpTxOutputSpkSubstr)? + .add_data(&expected_out_spk_tail)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_output_spk_substr_tail).expect("output spk substr tail"); + + let spk_input_sig_len = script(|sb| { + sb.add_i64(0)?.add_op(codes::OpTxInputScriptSigLen)?.add_i64(sig_script_0.len() as i64)?.add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_sig_len).expect("input sig len"); + + let expected_sig_full = sig_script_0.clone(); + let spk_input_sig_substr_full = script(|sb| { + sb.add_i64(0)? + .add_i64(0)? + .add_i64(0)? + .add_op(codes::OpTxInputScriptSigLen)? + .add_op(codes::OpTxInputScriptSigSubstr)? + .add_data(&expected_sig_full)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_sig_substr_full).expect("input sig substr full"); + + let expected_sig_substr = sig_script_0[1..3].to_vec(); + let spk_input_sig_substr = script(|sb| { + sb.add_i64(0)? + .add_i64(1)? + .add_i64(3)? + .add_op(codes::OpTxInputScriptSigSubstr)? + .add_data(&expected_sig_substr)? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_input_sig_substr).expect("input sig substr"); + + let spk_outpoint_txid = script(|sb| { + sb.add_i64(1)? + .add_op(codes::OpOutpointTxId)? + .add_data(&tx.inputs[1].previous_outpoint.transaction_id.as_bytes())? + .add_op(codes::OpEqual) + }); + run_script(&tx, entries.clone(), 0, spk_outpoint_txid).expect("outpoint txid"); + + let spk_outpoint_index = script(|sb| sb.add_i64(1)?.add_op(codes::OpOutpointIndex)?.add_i64(1)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_outpoint_index).expect("outpoint index"); + + let seq_bytes = tx.inputs[0].sequence.to_le_bytes(); + let spk_seq = script(|sb| sb.add_i64(0)?.add_op(codes::OpTxInputSeq)?.add_data(&seq_bytes)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_seq).expect("input seq"); + + let spk_is_coinbase_true = + script(|sb| sb.add_i64(0)?.add_op(codes::OpTxInputIsCoinbase)?.add_i64(1)?.add_op(codes::OpEqual)); + run_script(&tx, entries.clone(), 0, spk_is_coinbase_true).expect("is coinbase true"); + + let spk_is_coinbase_false = + script(|sb| sb.add_i64(1)?.add_op(codes::OpTxInputIsCoinbase)?.add_i64(0)?.add_op(codes::OpEqual)); + run_script(&tx, entries, 0, spk_is_coinbase_false).expect("is coinbase false"); + } + + #[test] + fn error_paths_for_indices_and_sizes() { + let (tx, entries) = base_transaction(600); + + let spk_bad_input_index = script(|sb| sb.add_i64(5)?.add_op(codes::OpTxInputSpkLen)); + let err = run_script(&tx, entries.clone(), 0, spk_bad_input_index).expect_err("invalid input index"); + assert!(matches!(err, TxScriptError::InvalidInputIndex(_, _))); + + let spk_bad_output_index = script(|sb| sb.add_i64(9)?.add_op(codes::OpTxOutputSpkLen)); + let err = run_script(&tx, entries.clone(), 0, spk_bad_output_index).expect_err("invalid output index"); + assert!(matches!(err, TxScriptError::InvalidOutputIndex(_, _))); + + // Large input SPK to trigger ElementTooBig via substring length + let mut large_entries = entries.clone(); + large_entries[1].script_public_key = ScriptPublicKey::new(0, vec![0u8; 600].into()); + let spk_large_spk_substr = script(|sb| sb.add_i64(1)?.add_i64(0)?.add_i64(600)?.add_op(codes::OpTxInputSpkSubstr)); + let err = run_script(&tx, large_entries.clone(), 0, spk_large_spk_substr).expect_err("input spk substr too long"); + assert!(matches!(err, TxScriptError::ElementTooBig(_, _))); + + // Large input signature script to trigger ElementTooBig via substring length + let mut tx_large_sig = tx.clone(); + let mut large_sig_script = Vec::with_capacity(1 + 2 + 600); + large_sig_script.push(codes::OpPushData2); + large_sig_script.extend_from_slice(&(600u16).to_le_bytes()); + large_sig_script.extend(std::iter::repeat_n(0u8, 600)); + tx_large_sig.inputs[0].signature_script = large_sig_script; + let spk_large_sig_substr = script(|sb| sb.add_i64(0)?.add_i64(0)?.add_i64(600)?.add_op(codes::OpTxInputScriptSigSubstr)); + let err = run_script(&tx_large_sig, entries.clone(), 0, spk_large_sig_substr).expect_err("sig substr too long"); + assert!(matches!(err, TxScriptError::ElementTooBig(_, _))); + } + } } diff --git a/crypto/txscript/src/standard/multisig.rs b/crypto/txscript/src/standard/multisig.rs index 550f7b2bf3..2b98564ee7 100644 --- a/crypto/txscript/src/standard/multisig.rs +++ b/crypto/txscript/src/standard/multisig.rs @@ -184,7 +184,7 @@ mod tests { let (input, entry) = tx.populated_inputs().next().unwrap(); let cache = Cache::new(10_000); - let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &reused_values, &cache); + let mut engine = TxScriptEngine::from_transaction_input(&tx, input, 0, entry, &reused_values, &cache, Default::default()); assert_eq!(engine.execute().is_ok(), is_ok); } #[test] diff --git a/crypto/txscript/test-data/script_tests_covenants.json b/crypto/txscript/test-data/script_tests_covenants.json new file mode 100644 index 0000000000..1e14da16fc --- /dev/null +++ b/crypto/txscript/test-data/script_tests_covenants.json @@ -0,0 +1,5456 @@ +[ + [ + "Format is: [[wit..., amount]?, scriptSig, scriptPubKey, flags, expected_scripterror, ... comments]" + ], + [ + "It is evaluated as if there was a crediting coinbase transaction with two 0" + ], + [ + "pushes as scriptSig, and one output of 0 satoshi and given scriptPubKey," + ], + [ + "followed by a spending transaction which spends this output as only input (and" + ], + [ + "correct prevout hash), using the given scriptSig. All nLockTimes are 0, all" + ], + [ + "nSequences are max." + ], + [ + "", + "DEPTH 0 EQUAL", + "", + "OK", + "Test the test: we should have an empty stack after scriptSig evaluation" + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK", + "and multiple spaces should not change that." + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK" + ], + [ + " ", + "DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK", + "Similarly whitespace around and between symbols" + ], + [ + "1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + " 1 2", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "1 2 ", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + " 1 2 ", + "2 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "1", + "", + "", + "OK" + ], + [ + "0x02 0x01 0x00", + "", + "", + "OK", + "all bytes are significant, not only the last one" + ], + [ + "0x09 0x00000000 0x00000000 0x10", + "", + "", + "OK", + "equals zero when cast to Int64" + ], + [ + "0x01 0x11", + "17 EQUAL", + "", + "OK", + "push 1 byte" + ], + [ + "0x02 0x417a", + "'Az' EQUAL", + "", + "OK" + ], + [ + "0x4b 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "push 75 bytes" + ], + [ + "0x4c 0x4c 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "0x4c is OP_PUSHDATA1 (push 76 bytes)" + ], + [ + "0x4d 0x0001 0x417a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a", + "'Azzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' EQUAL", + "", + "OK", + "0x4d is OP_PUSHDATA2" + ], + [ + "0x4f 1000", + "ADD 999 EQUAL", + "", + "OK" + ], + [ + "0", + "IF 0x50 ENDIF 1", + "", + "OK", + "0x50 is reserved (ok if not executed)" + ], + [ + "0x51", + "0x5f ADD 0x60 EQUAL", + "", + "OK", + "0x51 through 0x60 push 1 through 16 onto stack" + ], + [ + "1", + "NOP", + "", + "OK" + ], + [ + "0", + "IF VER ELSE 1 ENDIF", + "", + "OK", + "VER non-functional (ok if not executed)" + ], + [ + "0", + "IF RESERVED RESERVED1 RESERVED2 ELSE 1 ENDIF", + "", + "OK", + "RESERVED ok in un-executed IF" + ], + [ + "1", + "DUP IF ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ENDIF", + "", + "OK" + ], + [ + "1", + "DUP IF ELSE ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ELSE ENDIF", + "", + "OK" + ], + [ + "0", + "IF ELSE 1 ENDIF", + "", + "OK" + ], + [ + "1 1", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 1", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0 0", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 1", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "OK" + ], + [ + "1 0", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0 1", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0 ELSE 1 ELSE 0 ENDIF", + "", + "OK", + "Multiple ELSE's are valid and executed inverts on each ELSE encountered" + ], + [ + "1", + "IF 1 ELSE 0 ELSE ENDIF", + "", + "OK" + ], + [ + "1", + "IF ELSE 0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "1", + "IF 1 ELSE 0 ELSE 1 ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "'' 1", + "IF SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ENDIF 0x20 0x2c49a55fe0ca3e7a005420c19a527865df8f17e468d234f562ef238d4236a632 EQUAL", + "", + "OK" + ], + [ + "1", + "NOTIF 0 ELSE 1 ELSE 0 ENDIF", + "", + "OK", + "Multiple ELSE's are valid and execution inverts on each ELSE encountered" + ], + [ + "0", + "NOTIF 1 ELSE 0 ELSE ENDIF", + "", + "OK" + ], + [ + "0", + "NOTIF ELSE 0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "NOTIF 1 ELSE 0 ELSE 1 ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "'' 0", + "NOTIF SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ELSE ELSE SHA256 ENDIF 0x20 0x2c49a55fe0ca3e7a005420c19a527865df8f17e468d234f562ef238d4236a632 EQUAL", + "", + "OK" + ], + [ + "0", + "IF 1 IF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 1 IF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", + "", + "OK", + "Nested ELSE ELSE" + ], + [ + "1", + "NOTIF 0 NOTIF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 0 NOTIF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", + "", + "OK" + ], + [ + "0", + "IF RETURN ENDIF 1", + "", + "OK", + "RETURN only works if executed" + ], + [ + "1 1", + "VERIFY", + "", + "OK" + ], + [ + "1 0x05 0x01 0x00 0x00 0x00 0x00", + "VERIFY", + "", + "OK", + "values >4 bytes can be cast to boolean" + ], + [ + "0x01 0x80", + "VERIFY TRUE", + "", + "VERIFY", + "negative 0 is false" + ], + [ + "10 0 11", + "TOALTSTACK DROP FROMALTSTACK ADD 21 EQUAL", + "", + "OK" + ], + [ + "'gavin_was_here'", + "TOALTSTACK 11 FROMALTSTACK 'gavin_was_here' EQUALVERIFY 11 EQUAL", + "", + "OK" + ], + [ + "0", + "IFDUP DEPTH 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "1", + "IFDUP DEPTH 2 EQUALVERIFY 1 EQUALVERIFY 1 EQUAL", + "", + "OK" + ], + [ + "0x05 0x0100000000", + "IFDUP DEPTH 2 EQUALVERIFY 0x05 0x0100000000 EQUALVERIFY DROP TRUE", + "", + "OK", + "IFDUP dups non ints" + ], + [ + "0", + "DROP DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "0", + "DUP 1 ADD 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "0 1", + "NIP", + "", + "OK" + ], + [ + "1 0", + "OVER DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "0 PICK 20 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "1 PICK 21 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "2 PICK 22 EQUALVERIFY DEPTH 3 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT 22 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT DROP 20 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "22 21 20", + "ROT DROP DROP 21 EQUAL", + "", + "OK" + ], + [ + "22 21 20", + "ROT ROT 21 EQUAL 2DROP", + "", + "OK" + ], + [ + "22 21 20", + "ROT ROT ROT 20 EQUALVERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 24 EQUALVERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT DROP 25 EQUALVERIFY DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 20 EQUALVERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP DROP 21 EQUALVERIFY 2DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 2DROP 22 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2DROP 2DROP DROP 23 EQUALVERIFY TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2ROT 22 EQUALVERIFY 2DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "25 24 23 22 21 20", + "2ROT 2ROT 2ROT 20 EQUALVERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "SWAP 1 EQUALVERIFY 0 EQUAL", + "", + "OK" + ], + [ + "0 1", + "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP", + "", + "OK" + ], + [ + "13 14", + "2DUP ROT EQUALVERIFY EQUAL", + "", + "OK" + ], + [ + "-1 0 1 2", + "3DUP DEPTH 7 EQUALVERIFY ADD ADD 3 EQUALVERIFY 2DROP 0 EQUALVERIFY", + "", + "OK" + ], + [ + "1 2 3 5", + "2OVER ADD ADD 8 EQUALVERIFY ADD ADD 6 EQUAL", + "", + "OK" + ], + [ + "1 3 5 7", + "2SWAP ADD 4 EQUALVERIFY ADD 12 EQUAL", + "", + "OK" + ], + [ + "0", + "SIZE 0 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "1", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "127", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "128", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "32767", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "32768", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "8388607", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "8388608", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "2147483647", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "2147483648", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "549755813887", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "549755813888", + "SIZE 6 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "9223372036854775807", + "SIZE 8 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-1", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-127", + "SIZE 1 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-128", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-32767", + "SIZE 2 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-32768", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-8388607", + "SIZE 3 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-8388608", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-2147483647", + "SIZE 4 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-2147483648", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-549755813887", + "SIZE 5 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-549755813888", + "SIZE 6 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "-9223372036854775807", + "SIZE 8 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SIZE 26 EQUALVERIFY DROP TRUE", + "", + "OK" + ], + [ + "42", + "SIZE 1 EQUALVERIFY 42 EQUAL", + "", + "OK", + "SIZE does not consume argument" + ], + [ + "2 -2", + "ADD 0 EQUAL", + "", + "OK" + ], + [ + "2147483647 -2147483647", + "ADD 0 EQUAL", + "", + "OK" + ], + [ + "-1 -1", + "ADD -2 EQUAL", + "", + "OK" + ], + [ + "0 0", + "EQUAL", + "", + "OK" + ], + [ + "1 1", + "ADD 2 EQUAL", + "", + "OK" + ], + [ + "1", + "1ADD 2 EQUAL", + "", + "OK" + ], + [ + "111", + "1SUB 110 EQUAL", + "", + "OK" + ], + [ + "111 1", + "ADD 12 SUB 100 EQUAL", + "", + "OK" + ], + [ + "0", + "ABS 0 EQUAL", + "", + "OK" + ], + [ + "16", + "ABS 16 EQUAL", + "", + "OK" + ], + [ + "-16", + "ABS -16 NEGATE EQUAL", + "", + "OK" + ], + [ + "0", + "NOT NOP", + "", + "OK" + ], + [ + "1", + "NOT 0 EQUAL", + "", + "OK" + ], + [ + "11", + "NOT 0 EQUAL", + "", + "OK" + ], + [ + "0", + "0NOTEQUAL 0 EQUAL", + "", + "OK" + ], + [ + "1", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "111", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "-111", + "0NOTEQUAL 1 EQUAL", + "", + "OK" + ], + [ + "1 1", + "BOOLAND NOP", + "", + "OK" + ], + [ + "1 0", + "BOOLAND NOT", + "", + "OK" + ], + [ + "0 1", + "BOOLAND NOT", + "", + "OK" + ], + [ + "0 0", + "BOOLAND NOT", + "", + "OK" + ], + [ + "16 17", + "BOOLAND NOP", + "", + "OK" + ], + [ + "1 1", + "BOOLOR NOP", + "", + "OK" + ], + [ + "1 0", + "BOOLOR NOP", + "", + "OK" + ], + [ + "0 1", + "BOOLOR NOP", + "", + "OK" + ], + [ + "0 0", + "BOOLOR NOT", + "", + "OK" + ], + [ + "16 17", + "BOOLOR NOP", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMEQUAL", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMEQUALVERIFY 1", + "", + "OK" + ], + [ + "11 10 1", + "ADD NUMNOTEQUAL NOT", + "", + "OK" + ], + [ + "111 10 1", + "ADD NUMNOTEQUAL", + "", + "OK" + ], + [ + "11 10", + "LESSTHAN NOT", + "", + "OK" + ], + [ + "4 4", + "LESSTHAN NOT", + "", + "OK" + ], + [ + "10 11", + "LESSTHAN", + "", + "OK" + ], + [ + "-11 11", + "LESSTHAN", + "", + "OK" + ], + [ + "-11 -10", + "LESSTHAN", + "", + "OK" + ], + [ + "11 10", + "GREATERTHAN", + "", + "OK" + ], + [ + "4 4", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "10 11", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "-11 11", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "-11 -10", + "GREATERTHAN NOT", + "", + "OK" + ], + [ + "11 10", + "LESSTHANOREQUAL NOT", + "", + "OK" + ], + [ + "4 4", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "10 11", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "-11 11", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "-11 -10", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "11 10", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "4 4", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "10 11", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "-11 11", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "-11 -10", + "GREATERTHANOREQUAL NOT", + "", + "OK" + ], + [ + "1 0", + "MIN 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 1", + "MIN 0 NUMEQUAL", + "", + "OK" + ], + [ + "-1 0", + "MIN -1 NUMEQUAL", + "", + "OK" + ], + [ + "0 -2147483647", + "MIN -2147483647 NUMEQUAL", + "", + "OK" + ], + [ + "2147483647 0", + "MAX 2147483647 NUMEQUAL", + "", + "OK" + ], + [ + "0 100", + "MAX 100 NUMEQUAL", + "", + "OK" + ], + [ + "-100 0", + "MAX 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 -2147483647", + "MAX 0 NUMEQUAL", + "", + "OK" + ], + [ + "0 0 1", + "WITHIN", + "", + "OK" + ], + [ + "1 0 1", + "WITHIN NOT", + "", + "OK" + ], + [ + "0 -2147483647 2147483647", + "WITHIN", + "", + "OK" + ], + [ + "-1 -100 100", + "WITHIN", + "", + "OK" + ], + [ + "11 -100 100", + "WITHIN", + "", + "OK" + ], + [ + "-2147483647 -100 100", + "WITHIN NOT", + "", + "OK" + ], + [ + "2147483647 -100 100", + "WITHIN NOT", + "", + "OK" + ], + [ + "2147483647 2147483647", + "SUB 0 EQUAL", + "", + "OK" + ], + [ + "2147483647", + "DUP ADD 4294967294 EQUAL", + "", + "OK", + ">32 bit EQUAL is valid" + ], + [ + "2147483647", + "NEGATE DUP ADD -4294967294 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "SHA256 0x20 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 EQUAL", + "", + "OK" + ], + [ + "'a'", + "SHA256 0x20 0xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "SHA256 0x20 0x71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 EQUAL", + "", + "OK" + ], + [ + "''", + "NOP BLAKE2B 0x20 0x0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8 EQUAL", + "", + "OK" + ], + [ + "'a'", + "BLAKE2B NOP 0x20 0x8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4 EQUAL", + "", + "OK" + ], + [ + "'abcdefghijklmnopqrstuvwxyz'", + "NOP BLAKE2B 0x20 0x117ad6b940f5e8292c007d9c7e7350cd33cf85b5887e8da71c7957830f536e7c EQUAL", + "", + "OK", + "The NOP is added so the script won't be interpreted as P2SH" + ], + [ + "'a'", + "NOP BLAKE2B 0x20 0x8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4 EQUAL", + "", + "OK" + ], + [ + "'data' 'key'", + "BLAKE2B_WITH_KEY 0x20 0x0b05c30009bdbad5c1a1e7013a2e85ea700731cf9dda9d45eb12ea6ac755485a EQUAL", + "", + "OK", + "OpBlake2bWithKey hashes data with a key" + ], + [ + "'data' 'other'", + "BLAKE2B_WITH_KEY 0x20 0x2ccf4b06ec46954c7a594ae9fb799ddb06cf264560eaa75ef24a8cf6d4a59b4e EQUAL", + "", + "OK", + "OpBlake2bWithKey output depends on the key" + ], + [ + "0", + "IF 0xb2 ELSE 1 ENDIF", + "", + "OK", + "opcodes above OP_CHECKSEQUENCEVERIFY invalid if executed" + ], + [ + "0", + "IF 0xbd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xbe ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xbf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xc9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xca ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xce ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xcf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xd9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xda ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xde ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xdf ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xe9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xea ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xeb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xec ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xed ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xee ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xef ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf0 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf1 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf2 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf3 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf4 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf5 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf6 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf7 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf8 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xf9 ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfa ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfb ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfc ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfd ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xfe ELSE 1 ENDIF", + "", + "OK" + ], + [ + "0", + "IF 0xff ELSE 1 ENDIF", + "", + "OK" + ], + [ + "", + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", + "OK", + "520 byte push" + ], + [ + "1", + "0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "OK", + "201 opcodes executed. 0x61 is NOP" + ], + [ + "1 2 3 4 5", + "0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 1 2 3 4 5 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 0x6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d75", + "", + "OK", + "244 stack size (0x6f is 3DUP, 0x6d is 2DROP, and 0x75 is DROP)" + ], + [ + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP DROP 0x6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d 0x61616161", + "", + "OK", + "Max-size (10,000-byte), max-push(520 bytes), max-opcodes(201), max stack size(244 items). 0x6f is 3DUP, 0x61 is NOP, 0x6d is 2DROP" + ], + [ + "0", + "IF 0x5050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050 ENDIF 1", + "", + "OK", + ">201 opcodes, but RESERVED (0x50) doesn't count towards opcode limit." + ], + [ + "", + "1", + "", + "OK" + ], + [ + "127", + "0x01 0x7F EQUAL", + "", + "OK" + ], + [ + "128", + "0x02 0x8000 EQUAL", + "", + "OK", + "Leave room for the sign bit" + ], + [ + "32767", + "0x02 0xFF7F EQUAL", + "", + "OK" + ], + [ + "32768", + "0x03 0x008000 EQUAL", + "", + "OK" + ], + [ + "8388607", + "0x03 0xFFFF7F EQUAL", + "", + "OK" + ], + [ + "8388608", + "0x04 0x00008000 EQUAL", + "", + "OK" + ], + [ + "2147483647", + "0x04 0xFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "2147483648", + "0x05 0x0000008000 EQUAL", + "", + "OK" + ], + [ + "549755813887", + "0x05 0xFFFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "549755813888", + "0x06 0xFFFFFFFF7F EQUALVERIFY 2DROP TRUE", + "", + "OK" + ], + [ + "9223372036854775807", + "0x08 0xFFFFFFFFFFFFFF7F EQUAL", + "", + "OK" + ], + [ + "-2", + "0x01 0x82 EQUAL", + "", + "OK", + "Numbers are little-endian with the MSB being a sign bit" + ], + [ + "-127", + "0x01 0xFF EQUAL", + "", + "OK" + ], + [ + "-128", + "0x02 0x8080 EQUAL", + "", + "OK" + ], + [ + "-32767", + "0x02 0xFFFF EQUAL", + "", + "OK" + ], + [ + "-32768", + "0x03 0x008080 EQUAL", + "", + "OK" + ], + [ + "-8388607", + "0x03 0xFFFFFF EQUAL", + "", + "OK" + ], + [ + "-8388608", + "0x04 0x00008080 EQUAL", + "", + "OK" + ], + [ + "-2147483647", + "0x04 0xFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "-2147483648", + "0x05 0x0000008080 EQUAL", + "", + "OK" + ], + [ + "-4294967295", + "0x05 0xFFFFFFFF80 EQUAL", + "", + "OK" + ], + [ + "-549755813887", + "0x05 0xFFFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "-549755813888", + "0x06 0x000000008080 EQUAL", + "", + "OK" + ], + [ + "-9223372036854775807", + "0x08 0xFFFFFFFFFFFFFFFF EQUAL", + "", + "OK" + ], + [ + "2147483647", + "1ADD 2147483648 EQUAL", + "", + "OK", + "We can do math on 4-byte integers, and compare 5-byte ones" + ], + [ + "2147483647", + "1ADD DROP 1", + "", + "OK" + ], + [ + "-2147483647", + "1ADD DROP 1", + "", + "OK" + ], + [ + "1", + "0x02 0x0100 EQUAL NOT", + "", + "OK", + "Not the same byte array..." + ], + [ + "0", + "0x01 0x80 EQUAL NOT", + "", + "OK" + ], + [ + "", + "NOP 1", + "", + "OK", + "The following tests check the if(stack.size() < N) tests in each opcode" + ], + [ + "1", + "IF 1 ENDIF", + "", + "OK", + "They are here to catch copy-and-paste errors" + ], + [ + "0", + "NOTIF 1 ENDIF", + "", + "OK", + "Most of them are duplicated elsewhere," + ], + [ + "1", + "VERIFY 1", + "", + "OK", + "but, hey, more is always better, right?" + ], + [ + "0", + "TOALTSTACK 1", + "", + "OK" + ], + [ + "1", + "TOALTSTACK FROMALTSTACK", + "", + "OK" + ], + [ + "0 0", + "2DROP 1", + "", + "OK" + ], + [ + "0 1", + "2DUP VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 0 1", + "3DUP VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0", + "2OVER VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0 0 0", + "2ROT VERIFY DROP DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "0 1 0 0", + "2SWAP VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1", + "IFDUP VERIFY", + "", + "OK" + ], + [ + "", + "DEPTH 1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0", + "DROP 1", + "", + "OK" + ], + [ + "1", + "DUP VERIFY", + "", + "OK" + ], + [ + "0 1", + "NIP", + "", + "OK" + ], + [ + "1 0", + "OVER VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0 0 0 3", + "PICK VERIFY DROP DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "PICK VERIFY DROP TRUE", + "", + "OK" + ], + [ + "1 0 0 0 3", + "ROLL VERIFY DROP DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "ROLL", + "", + "OK" + ], + [ + "1 0 0", + "ROT VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1 0", + "SWAP VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0 1", + "TUCK VERIFY DROP DROP TRUE", + "", + "OK" + ], + [ + "1", + "SIZE VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0 0", + "EQUAL", + "", + "OK" + ], + [ + "0 0", + "EQUALVERIFY 1", + "", + "OK" + ], + [ + "0 0 1", + "EQUAL EQUAL", + "", + "OK", + "OP_0 and bools must have identical byte representations" + ], + [ + "0", + "1ADD", + "", + "OK" + ], + [ + "2", + "1SUB", + "", + "OK" + ], + [ + "-1", + "NEGATE", + "", + "OK" + ], + [ + "-1", + "ABS", + "", + "OK" + ], + [ + "0", + "NOT", + "", + "OK" + ], + [ + "-1", + "0NOTEQUAL", + "", + "OK" + ], + [ + "1 0", + "ADD", + "", + "OK" + ], + [ + "1 0", + "SUB", + "", + "OK" + ], + [ + "-1 -1", + "BOOLAND", + "", + "OK" + ], + [ + "-1 0", + "BOOLOR", + "", + "OK" + ], + [ + "0 0", + "NUMEQUAL", + "", + "OK" + ], + [ + "5 4", + "NUMEQUAL FALSE EQUAL", + "", + "OK" + ], + [ + "0 0", + "NUMEQUALVERIFY 1", + "", + "OK" + ], + [ + "-1 0", + "NUMNOTEQUAL", + "", + "OK" + ], + [ + "-1 0", + "LESSTHAN", + "", + "OK" + ], + [ + "1 0", + "GREATERTHAN", + "", + "OK" + ], + [ + "0 0", + "LESSTHANOREQUAL", + "", + "OK" + ], + [ + "0 0", + "GREATERTHANOREQUAL", + "", + "OK" + ], + [ + "-1 0", + "MIN", + "", + "OK" + ], + [ + "1 0", + "MAX", + "", + "OK" + ], + [ + "-1 -1 0", + "WITHIN", + "", + "OK" + ], + [ + "0", + "SHA256", + "", + "OK" + ], + [ + "0", + "BLAKE2B", + "", + "OK" + ], + [ + "", + "0 0 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "CHECKMULTISIG is allowed to have zero keys and/or sigs" + ], + [ + "", + "0 0 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 1 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Zero sigs means no sigs are checked" + ], + [ + "", + "0 0 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "CHECKMULTISIG is allowed to have zero keys and/or sigs" + ], + [ + "", + "0 0 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 0 1 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Zero sigs means no sigs are checked" + ], + [ + "", + "0 0 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 2 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK", + "Test from up to 20 pubkeys, all not checked" + ], + [ + "", + "0 'a' 'b' 'c' 3 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 4 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 5 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 6 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 7 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 8 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 9 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 10 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 11 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 12 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 13 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 14 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 15 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 16 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 17 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 18 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 19 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG VERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 1 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 2 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 3 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 4 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 5 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 6 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 7 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 8 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 9 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 10 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 11 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 12 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 13 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 14 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 15 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 16 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 17 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 18 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 19 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "", + "0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY DEPTH 0 EQUAL", + "", + "OK" + ], + [ + "1", + "0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY 0 0 CHECKMULTISIGVERIFY", + "", + "OK", + "nOpCount is incremented by the number of keys evaluated in addition to the usual one op per op. In this case we have zero keys, so we can execute 201 CHECKMULTISIGS" + ], + [ + "", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY DROP DROP DROP DROP DROP DROP DROP TRUE", + "", + "OK", + "Even though there are no signatures being checked nOpCount is incremented by the number of keys." + ], + [ + "1", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY", + "", + "OK" + ], + [ + "0x01 1", + "BLAKE2B 0x20 0xce57216285125006ec18197bd8184221cefa559bb0798410d99a5bba5b07cd1d EQUAL", + "", + "OK", + "Very basic P2SH" + ], + [ + "0x00", + "SIZE 0 EQUALVERIFY DROP TRUE", + "", + "OK", + "Basic OP_0 execution" + ], + [ + "Numeric pushes" + ], + [ + "-1", + "0x4f EQUAL", + "", + "OK", + "OP1_NEGATE pushes 0x81" + ], + [ + "1", + "0x51 EQUAL", + "", + "OK", + "OP_1 pushes 0x01" + ], + [ + "2", + "0x52 EQUAL", + "", + "OK", + "OP_2 pushes 0x02" + ], + [ + "3", + "0x53 EQUAL", + "", + "OK", + "OP_3 pushes 0x03" + ], + [ + "4", + "0x54 EQUAL", + "", + "OK", + "OP_4 pushes 0x04" + ], + [ + "5", + "0x55 EQUAL", + "", + "OK", + "OP_5 pushes 0x05" + ], + [ + "6", + "0x56 EQUAL", + "", + "OK", + "OP_6 pushes 0x06" + ], + [ + "7", + "0x57 EQUAL", + "", + "OK", + "OP_7 pushes 0x07" + ], + [ + "8", + "0x58 EQUAL", + "", + "OK", + "OP_8 pushes 0x08" + ], + [ + "9", + "0x59 EQUAL", + "", + "OK", + "OP_9 pushes 0x09" + ], + [ + "10", + "0x5a EQUAL", + "", + "OK", + "OP_10 pushes 0x0a" + ], + [ + "11", + "0x5b EQUAL", + "", + "OK", + "OP_11 pushes 0x0b" + ], + [ + "12", + "0x5c EQUAL", + "", + "OK", + "OP_12 pushes 0x0c" + ], + [ + "13", + "0x5d EQUAL", + "", + "OK", + "OP_13 pushes 0x0d" + ], + [ + "14", + "0x5e EQUAL", + "", + "OK", + "OP_14 pushes 0x0e" + ], + [ + "15", + "0x5f EQUAL", + "", + "OK", + "OP_15 pushes 0x0f" + ], + [ + "16", + "0x60 EQUAL", + "", + "OK", + "OP_16 pushes 0x10" + ], + [ + "Unevaluated non-minimal pushes are ignored" + ], + [ + "0", + "IF 0x4c 0x00 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA1 ignored" + ], + [ + "0", + "IF 0x4d 0x0000 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA2 ignored" + ], + [ + "0", + "IF 0x4c 0x00000000 ENDIF 1 ", + "", + "OK", + "non-minimal PUSHDATA4 ignored" + ], + [ + "0", + "IF 0x01 0x81 ENDIF 1 ", + "", + "OK", + "1NEGATE equiv" + ], + [ + "0", + "IF 0x01 0x01 ENDIF 1 ", + "", + "OK", + "OP_1 equiv" + ], + [ + "0", + "IF 0x01 0x02 ENDIF 1 ", + "", + "OK", + "OP_2 equiv" + ], + [ + "0", + "IF 0x01 0x03 ENDIF 1 ", + "", + "OK", + "OP_3 equiv" + ], + [ + "0", + "IF 0x01 0x04 ENDIF 1 ", + "", + "OK", + "OP_4 equiv" + ], + [ + "0", + "IF 0x01 0x05 ENDIF 1 ", + "", + "OK", + "OP_5 equiv" + ], + [ + "0", + "IF 0x01 0x06 ENDIF 1 ", + "", + "OK", + "OP_6 equiv" + ], + [ + "0", + "IF 0x01 0x07 ENDIF 1 ", + "", + "OK", + "OP_7 equiv" + ], + [ + "0", + "IF 0x01 0x08 ENDIF 1 ", + "", + "OK", + "OP_8 equiv" + ], + [ + "0", + "IF 0x01 0x09 ENDIF 1 ", + "", + "OK", + "OP_9 equiv" + ], + [ + "0", + "IF 0x01 0x0a ENDIF 1 ", + "", + "OK", + "OP_10 equiv" + ], + [ + "0", + "IF 0x01 0x0b ENDIF 1 ", + "", + "OK", + "OP_11 equiv" + ], + [ + "0", + "IF 0x01 0x0c ENDIF 1 ", + "", + "OK", + "OP_12 equiv" + ], + [ + "0", + "IF 0x01 0x0d ENDIF 1 ", + "", + "OK", + "OP_13 equiv" + ], + [ + "0", + "IF 0x01 0x0e ENDIF 1 ", + "", + "OK", + "OP_14 equiv" + ], + [ + "0", + "IF 0x01 0x0f ENDIF 1 ", + "", + "OK", + "OP_15 equiv" + ], + [ + "0", + "IF 0x01 0x10 ENDIF 1 ", + "", + "OK", + "OP_16 equiv" + ], + [ + "Numeric minimaldata rules are only applied when a stack item is numerically evaluated; the push itself is allowed" + ], + [ + "0x01 0x00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x01 0x80", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0180", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0100", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0200", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0300", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0400", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0500", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0600", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0700", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0800", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0900", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0a00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0b00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0c00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0d00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0e00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x0f00", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "0x02 0x1000", + "1 VERIFY DROP TRUE", + "", + "OK" + ], + [ + "While not really correctly DER encoded, the empty signature is allowed" + ], + [ + "to provide a compact way to provide a delibrately invalid signature." + ], + [ + "0", + "0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 CHECKSIG NOT", + "", + "OK" + ], + [ + "0", + "1 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 1 CHECKMULTISIG NOT", + "", + "OK" + ], + [ + "TRUE DATA_8 0x0000000000000080", + "CHECKSEQUENCEVERIFY", + "", + "OK", + "CSV passes if stack top bit 1 << 63 is set" + ], + [ + "", + "DEPTH", + "", + "EVAL_FALSE", + "Test the test: we should have an empty stack after scriptSig evaluation" + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE", + "and multiple spaces should not change that." + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + " ", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "DEPTH", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP", + "", + "EVAL_FALSE" + ], + [ + "", + "NOP DEPTH", + "", + "EVAL_FALSE" + ], + [ + "0x4c01", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA1 with not enough bytes" + ], + [ + "0x4d0200ff", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA2 with not enough bytes" + ], + [ + "0x4e03000000ffff", + "0x01 NOP", + "", + "BAD_OPCODE", + "PUSHDATA4 with not enough bytes" + ], + [ + "1", + "IF 0x50 ENDIF 1", + "", + "BAD_OPCODE", + "0x50 is reserved" + ], + [ + "0x52", + "0x5f ADD 0x60 EQUAL", + "", + "EVAL_FALSE", + "0x51 through 0x60 push 1 through 16 onto stack" + ], + [ + "0", + "NOP", + "", + "EVAL_FALSE", + "" + ], + [ + "1", + "IF VER ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VER non-functional" + ], + [ + "0", + "IF VERIF ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VERIF illegal everywhere" + ], + [ + "0", + "IF ELSE 1 ELSE VERIF ENDIF", + "", + "BAD_OPCODE", + "VERIF illegal everywhere" + ], + [ + "0", + "IF VERNOTIF ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "VERNOTIF illegal everywhere" + ], + [ + "0", + "IF ELSE 1 ELSE VERNOTIF ENDIF", + "", + "BAD_OPCODE", + "VERNOTIF illegal everywhere" + ], + [ + "0", + "DUP IF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "IF 1 ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "DUP IF ELSE ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "IF 1 ELSE ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0", + "NOTIF ELSE 1 ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "IF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1 0", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "IF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 1", + "NOTIF IF 1 ELSE 0 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1 1", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "0 0", + "NOTIF IF 1 ELSE 0 ENDIF ELSE IF 0 ELSE 1 ENDIF ENDIF", + "", + "EVAL_FALSE" + ], + [ + "1", + "IF RETURN ELSE ELSE 1 ENDIF", + "", + "OP_RETURN", + "Multiple ELSEs" + ], + [ + "1", + "IF 1 ELSE ELSE RETURN ENDIF", + "", + "OP_RETURN" + ], + [ + "1", + "ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "Malformed IF/ELSE/ENDIF sequence" + ], + [ + "1", + "ELSE ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "ENDIF ELSE", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "ENDIF ELSE IF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ENDIF ELSE", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ENDIF ELSE ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ENDIF ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "IF ELSE ELSE ENDIF ENDIF", + "", + "UNBALANCED_CONDITIONAL" + ], + [ + "1", + "RETURN", + "", + "OP_RETURN" + ], + [ + "1", + "DUP IF RETURN ENDIF", + "", + "OP_RETURN" + ], + [ + "1", + "RETURN 'data'", + "", + "OP_RETURN", + "canonical prunable txout format" + ], + [ + "0", + "VERIFY 1", + "", + "VERIFY" + ], + [ + "1", + "VERIFY", + "", + "EVAL_FALSE" + ], + [ + "1", + "VERIFY 0", + "", + "EVAL_FALSE" + ], + [ + "", + "IFDUP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DROP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DUP DEPTH 0 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "DUP 1 ADD 2 EQUALVERIFY 0 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "", + "NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 0 NIP", + "", + "EVAL_FALSE" + ], + [ + "", + "OVER 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "PICK 19 EQUALVERIFY DEPTH 2 EQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "0 PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "-1 PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "0 PICK 20 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "1 PICK 21 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "2 PICK 22 EQUALVERIFY DEPTH 3 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "", + "0 ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "-1 ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "19 20 21", + "0 ROLL 20 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "1 ROLL 21 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "19 20 21", + "2 ROLL 22 EQUALVERIFY DEPTH 2 EQUAL", + "", + "EQUALVERIFY" + ], + [ + "", + "ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1 2 ROT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0 1", + "SWAP 1 EQUALVERIFY", + "", + "EQUALVERIFY" + ], + [ + "", + "TUCK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "TUCK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 0", + "TUCK DEPTH 3 EQUALVERIFY SWAP 2DROP", + "", + "EVAL_FALSE" + ], + [ + "", + "2DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 2", + "3DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "2OVER 1 VERIFY DROP DROP DROP DROP TRUE", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2 3 2OVER 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "2SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2 3 2SWAP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "'ab' 'cd'", + "CAT 'abcd' EQUAL", + "", + "OK", + "OpCat concatenates byte strings" + ], + [ + "'aaaa'", + "DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT DUP CAT", + "", + "PUSH_SIZE", + "OpCat errors when result exceeds MAX_SCRIPT_ELEMENT_SIZE" + ], + [ + "'kaspa' 1 4", + "SUBSTR 'asp' EQUAL", + "", + "OK", + "OpSubstr returns inclusive start / exclusive end" + ], + [ + "'prefix' -1 2", + "SUBSTR", + "", + "UNKNOWN_ERROR", + "OpSubstr errors for out-of-bounds ranges" + ], + [ + "'short' 0 600", + "SUBSTR", + "", + "PUSH_SIZE", + "OpSubstr errors when requested length exceeds MAX_SCRIPT_ELEMENT_SIZE" + ], + [ + "'abc' 2 0", + "IF LEFT ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "LEFT disabled" + ], + [ + "'abc' 2 0", + "IF RIGHT ELSE 1 ENDIF", + "", + "DISABLED_OPCODE", + "RIGHT disabled" + ], + [ + "", + "SIZE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NOP", + "", + "EMPTY_STACK", + "Checks EMPTY_STACK error" + ], + [ + "0x03 0xf0f0f0", + "INVERT 0x03 0x0f0f0f EQUAL", + "", + "OK", + "OpInvert returns bitwise NOT" + ], + [ + "0x02 0x0f0f 0x02 0x3333", + "AND 0x02 0x0303 EQUAL", + "", + "OK", + "OpAnd computes bitwise AND" + ], + [ + "0x01 0xff 0x02 0x0000", + "AND", + "", + "UNKNOWN_ERROR", + "OpAnd errors when operand lengths differ" + ], + [ + "0x03 0xf0f0f0 0x03 0x0f0f0f", + "OR 0x03 0xffffff EQUAL", + "", + "OK", + "OpOr computes bitwise OR" + ], + [ + "0x01 0xaa 0x01 0xaa", + "OR 0x01 0xaa EQUAL", + "", + "OK", + "OpOr with identical operands returns the same bytes" + ], + [ + "0x01 0xaa 0x02 0xbbcc", + "OR", + "", + "UNKNOWN_ERROR", + "OpOr errors when operand lengths differ" + ], + [ + "0x02 0xf0f0 0x02 0x0f0f", + "XOR 0x02 0xffff EQUAL", + "", + "OK", + "OpXor computes bitwise XOR" + ], + [ + "0x02 0xf0f0 0x02 0x00ff", + "XOR 0x02 0xf00f EQUAL", + "", + "OK", + "OpXor example with 1 XOR 1 bits" + ], + [ + "0x01 0xaa 0x01 0xaa", + "XOR 0x01 0x00 EQUAL", + "", + "OK", + "OpXor with identical operands yields zero" + ], + [ + "0x01 0xff 0x03 0x000000", + "XOR", + "", + "UNKNOWN_ERROR", + "OpXor errors when operand lengths differ" + ], + [ + "2 0", + "IF 2MUL ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "2MUL disabled" + ], + [ + "2 0", + "IF 2DIV ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "2DIV disabled" + ], + [ + "1", + "IF 2 2 MUL 4 EQUAL ELSE 0 ENDIF", + "", + "OK", + "OpMul works when executed inside condition" + ], + [ + "1", + "IF 7 2 DIV 3 EQUAL ELSE 0 ENDIF", + "", + "OK", + "OpDiv works when executed inside condition" + ], + [ + "1", + "IF 7 3 MOD 1 EQUAL ELSE 0 ENDIF", + "", + "OK", + "OpMod works when executed inside condition" + ], + [ + "2 2 0", + "IF LSHIFT ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "LSHIFT disabled" + ], + [ + "2 2 0", + "IF RSHIFT ELSE 1 ENDIF NOP", + "", + "DISABLED_OPCODE", + "RSHIFT disabled" + ], + [ + "", + "EQUAL NOT", + "", + "INVALID_STACK_OPERATION", + "EQUAL must error when there are no stack items" + ], + [ + "0", + "EQUAL NOT", + "", + "INVALID_STACK_OPERATION", + "EQUAL must error when there are not 2 stack items" + ], + [ + "0 1", + "EQUAL", + "", + "EVAL_FALSE" + ], + [ + "1 1", + "ADD 0 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "11 1", + "ADD 12 SUB 11 EQUAL", + "", + "EVAL_FALSE" + ], + [ + "2147483648 0", + "ADD NOP", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" + ], + [ + "-2147483648 0", + "ADD NOP", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" + ], + [ + "-9223372036854775808 0", + "ADD NOP", + "", + "UNKNOWN_ERROR", + "" + ], + [ + "2147483647", + "DUP ADD 4294967294 NUMEQUAL", + "", + "OK", + "NUMEQUAL is in numeric range since kip10" + ], + [ + "'abcdef'", + "NOT 0 EQUAL", + "", + "OK", + "numbers up to 8 bytes are supported since kip10" + ], + [ + "'abcdefghi'", + "NOT 0 EQUAL", + "", + "UNKNOWN_ERROR", + "NOT is an arithmetic operand" + ], + [ + "2 3", + "MUL 6 EQUAL", + "", + "OK", + "OpMul multiplies two numbers" + ], + [ + "7 2", + "DIV 3 EQUAL", + "", + "OK", + "OpDiv performs integer division" + ], + [ + "2", + "2MUL 4 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2", + "2DIV 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "7 3", + "MOD 1 EQUAL", + "", + "OK", + "OpMod returns remainder" + ], + [ + "-7 3", + "MOD -1 EQUAL", + "", + "OK", + "OpMod keeps dividend sign (negative dividend, positive divisor)" + ], + [ + "7 -3", + "MOD 1 EQUAL", + "", + "OK", + "OpMod keeps dividend sign (positive dividend, negative divisor)" + ], + [ + "-7 -3", + "MOD -1 EQUAL", + "", + "OK", + "OpMod keeps dividend sign (negative dividend, negative divisor)" + ], + [ + "9223372036854775807 2", + "MUL", + "", + "UNKNOWN_ERROR", + "OpMul errors on signed 64-bit overflow" + ], + [ + "10 0", + "DIV", + "", + "UNKNOWN_ERROR", + "OpDiv errors on division by zero" + ], + [ + "10 0", + "MOD", + "", + "UNKNOWN_ERROR", + "OpMod errors on modulo by zero" + ], + [ + "2 2", + "LSHIFT 8 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "2 1", + "RSHIFT 1 EQUAL", + "", + "DISABLED_OPCODE", + "disabled" + ], + [ + "0x50", + "1", + "", + "BAD_OPCODE", + "opcode 0x50 is reserved" + ], + [ + "1", + "IF TXVERSION ELSE 1 ENDIF", + "", + "OK", + "OpTxVersion is enabled post covenants activation" + ], + [ + "1", + "IF 0xb3 ELSE 1 ENDIF", + "", + "OK", + "OpTxInputCount is enabled since kip10" + ], + [ + "1", + "IF 0xb4 ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputCount is enabled since kip10" + ], + [ + "1", + "IF TXLOCKTIME 0 EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxLockTime is enabled post covenants activation" + ], + [ + "1", + "IF TXSUBNETID 0x14 0x0000000000000000000000000000000000000000 EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxSubnetId is enabled post covenants activation" + ], + [ + "1", + "IF TXGAS 0 EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxGas is enabled post covenants activation" + ], + [ + "1", + "IF 0 0 TXPAYLOADSUBSTR '' EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxPayloadSubstr is enabled post covenants activation" + ], + [ + "1", + "IF 0xb9 0 NUMEQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxInputIndex is enabled since kip10" + ], + [ + "1", + "0 OUTPOINTTXID DROP", + "", + "OK", + "OpOutpointTxId is enabled post covenants activation" + ], + [ + "1", + "0 OUTPOINTINDEX DROP", + "", + "OK", + "OpOutpointIndex is enabled post covenants activation" + ], + [ + "1", + "0 0 1 TX_INPUT_SCRIPT_SIG_SUBSTR 0x01 0x51 EQUALVERIFY", + "", + "OK", + "OpTxInputScriptSigSubstr is enabled post covenants activation" + ], + [ + "1", + "0 TX_INPUT_SEQ DROP", + "", + "OK", + "OpTxInputSeq is enabled post covenants activation" + ], + [ + "0 1", + "IF 0xbe 0 NUMEQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxInputAmount is enabled since kip10" + ], + [ + "0 1", + "IF 0xbf ELSE 1 ENDIF", + "", + "OK", + "OpTxInputSpk is enabled since kip10" + ], + [ + "1", + "IF 0xc0 ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "OpTxInputBlockDaaScore is reserved" + ], + [ + "1", + "0 TX_INPUT_IS_COINBASE DROP", + "", + "OK", + "OpTxInputIsCoinbase is enabled post covenants activation" + ], + [ + "0 1", + "IF 0xc2 0 NUMEQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputAmount is enabled since kip10" + ], + [ + "0 1", + "IF 0xc3 0x02 0x0000 EQUAL ELSE 1 ENDIF", + "", + "OK", + "OpTxOutputSpk is enabled since kip10" + ], + [ + "TODO (before merge): Add tests for the missing range 0xc4 to 0xca" + ], + [ + "1", + "IF 0xcb ELSE 1 ENDIF", + "", + "BAD_OPCODE", + "opcodes above OpTxInputScriptSigSubstr invalid if executed" + ], + [ + "1", + "IF 0xcc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xce ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xcf ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xd9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xda ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xde ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xdf ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xe9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xea ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xeb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xec ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xed ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xee ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xef ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf0 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf1 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf2 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf3 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf4 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf5 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf6 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf7 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf8 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xf9 ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfa ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfb ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfc ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfd ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xfe ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "1", + "IF 0xff ELSE 1 ENDIF", + "", + "BAD_OPCODE" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", + "PUSH_SIZE", + ">520 byte push" + ], + [ + "0", + "IF 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' ENDIF 1", + "", + "PUSH_SIZE", + ">520 byte push in non-executed IF branch" + ], + [ + "1", + "0x61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "OP_COUNT", + ">201 opcodes executed. 0x61 is NOP" + ], + [ + "0", + "IF 0x6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161 ENDIF 1", + "", + "OP_COUNT", + ">201 opcodes including non-executed IF branch. 0x61 is NOP" + ], + [ + "", + "1 2 3 4 5 6 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", + "", + "STACK_SIZE", + ">244 stack size (0x6f is 3DUP)" + ], + [ + "", + "1 TOALTSTACK 2 TOALTSTACK 3 4 5 6 7 8 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f", + "", + "STACK_SIZE", + ">244 stack+altstack size" + ], + [ + "", + "0 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' 0x6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f 2DUP 0x616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "", + "SCRIPT_SIZE", + "10,001-byte scriptPubKey" + ], + [ + "1", + "VER", + "", + "BAD_OPCODE", + "OP_VER is reserved" + ], + [ + "1", + "VERIF", + "", + "BAD_OPCODE", + "OP_VERIF is reserved" + ], + [ + "1", + "VERNOTIF", + "", + "BAD_OPCODE", + "OP_VERNOTIF is reserved" + ], + [ + "1", + "RESERVED", + "", + "BAD_OPCODE", + "OP_RESERVED is reserved" + ], + [ + "1", + "RESERVED1", + "", + "BAD_OPCODE", + "OP_RESERVED1 is reserved" + ], + [ + "1", + "RESERVED2", + "", + "BAD_OPCODE", + "OP_RESERVED2 is reserved" + ], + [ + "2147483648", + "1ADD 2147483649 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648", + "NEGATE -2147483648 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "-2147483648", + "1ADD -2147483647 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483647", + "DUP 1ADD 1SUB NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648", + "1SUB 2147483647 NUMEQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648 1", + "BOOLOR 1 EQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "2147483648 1", + "BOOLAND 1 EQUAL", + "", + "OK", + "We can do math on 5-byte integers since kip10" + ], + [ + "-9223372036854775808", + "1ADD 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 9-byte integers" + ], + [ + "-9223372036854775808", + "NEGATE 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 9-byte integers" + ], + [ + "-9223372036854775808", + "1ADD 1", + "", + "UNKNOWN_ERROR", + "Because we use a sign bit, -9223372036854775808 is also 9 bytes" + ], + [ + "-9223372036854775808", + "1ADD 1SUB 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 9-byte integers, even if the result is 8-bytes" + ], + [ + "-9223372036854775808", + "1SUB 1", + "", + "UNKNOWN_ERROR", + "We cannot do math on 9-byte integers, even if the result is 8-bytes" + ], + [ + "-9223372036854775808 1", + "BOOLOR 1", + "", + "UNKNOWN_ERROR", + "We cannot do BOOLOR on 9-byte integers (but we can still do IF etc)" + ], + [ + "-9223372036854775808 1", + "BOOLAND 1", + "", + "UNKNOWN_ERROR", + "We cannot do BOOLAND on 9-byte integers" + ], + [ + "-9223372036854775807", + "1SUB", + "", + "UNKNOWN_ERROR", + "result of math operation can't exceed 8 bytes" + ], + [ + "1", + "1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "ENDIF without IF" + ], + [ + "1", + "IF 1", + "", + "UNBALANCED_CONDITIONAL", + "IF without ENDIF" + ], + [ + "", + "IF 1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "The following tests check the if(stack.size() < N) tests in each opcode" + ], + [ + "", + "NOTIF 1 ENDIF", + "", + "UNBALANCED_CONDITIONAL", + "They are here to catch copy-and-paste errors" + ], + [ + "", + "VERIFY 1", + "", + "INVALID_STACK_OPERATION", + "Most of them are duplicated elsewhere," + ], + [ + "", + "TOALTSTACK 1", + "", + "INVALID_STACK_OPERATION", + "but, hey, more is always better, right?" + ], + [ + "1", + "FROMALTSTACK", + "", + "INVALID_ALTSTACK_OPERATION" + ], + [ + "1", + "2DROP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "2DUP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "3DUP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1", + "2OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 1 1", + "2ROT", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1", + "2SWAP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "IFDUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DROP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "DUP 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NIP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "OVER", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 3", + "PICK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0", + "PICK 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1 1 3", + "ROLL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "0", + "ROLL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "ROT", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SWAP", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "TUCK", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SIZE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "EQUAL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "EQUALVERIFY 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1ADD 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "1SUB 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NEGATE 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "ABS 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "NOT 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "0NOTEQUAL 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "ADD", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "SUB", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "BOOLAND", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "BOOLOR", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMEQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMEQUALVERIFY 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "NUMNOTEQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "LESSTHAN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "GREATERTHAN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "LESSTHANOREQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "GREATERTHANOREQUAL", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "MIN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1", + "MAX", + "", + "INVALID_STACK_OPERATION" + ], + [ + "1 1", + "WITHIN", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "SHA256 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "", + "BLAKE2B 1", + "", + "INVALID_STACK_OPERATION" + ], + [ + "Increase CHECKSIG and CHECKMULTISIG negative test coverage" + ], + [ + "", + "CHECKSIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKSIG must error when there are no stack items" + ], + [ + "0", + "CHECKSIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKSIG must error when there are not 2 stack items" + ], + [ + "", + "CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are no stack items" + ], + [ + "", + "-1 CHECKMULTISIG NOT", + "", + "PUBKEY_COUNT", + "CHECKMULTISIG must error when the specified number of pubkeys is negative" + ], + [ + "", + "1 CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are not enough pubkeys on the stack" + ], + [ + "", + "-1 0 CHECKMULTISIG NOT", + "", + "SIG_COUNT", + "CHECKMULTISIG must error when the specified number of signatures is negative" + ], + [ + "", + "1 'pk1' 1 CHECKMULTISIG NOT", + "", + "INVALID_STACK_OPERATION", + "CHECKMULTISIG must error when there are not enough signatures on the stack" + ], + [ + "", + "0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG 0 0 CHECKMULTISIG", + "", + "OP_COUNT", + "202 CHECKMULTISIGS, fails due to 201 op limit" + ], + [ + "", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIG", + "", + "OP_COUNT", + "Fails due to 201 script operation limit" + ], + [ + "1", + "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY", + "", + "OP_COUNT", + "" + ], + [ + "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", + "21 CHECKMULTISIG 1", + "", + "PUBKEY_COUNT", + "nPubKeys > 20" + ], + [ + "0 'sig' 1 0", + "CHECKMULTISIG 1", + "", + "SIG_COUNT", + "nSigs > nPubKeys" + ], + [ + "NOP 0x01 1", + "BLAKE2B 0x20 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", + "", + "SIG_PUSHONLY", + "Tests for Script.IsPushOnly()" + ], + [ + "0 0x01 0x50", + "BLAKE2B 0x20 0xece424a6bb6ddf4db592c0faed60685047a361b1 EQUAL", + "", + "BAD_OPCODE", + "OP_RESERVED in P2SH should fail" + ], + [ + "0 0x01", + "VER BLAKE2B 0x20 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", + "", + "BAD_OPCODE", + "OP_VER in P2SH should fail" + ], + [ + "0x00", + "'00' EQUAL", + "", + "EVAL_FALSE", + "Basic OP_0 execution" + ], + [ + "MINIMALDATA enforcement for PUSHDATAs" + ], + [ + "0x4c 0x00", + "DROP 1", + "", + "MINIMALDATA", + "Empty vector minimally represented by OP_0" + ], + [ + "0x01 0x81", + "DROP 1", + "", + "MINIMALDATA", + "-1 minimally represented by OP_1NEGATE" + ], + [ + "0x01 0x01", + "DROP 1", + "", + "MINIMALDATA", + "1 to 16 minimally represented by OP_1 to OP_16" + ], + [ + "0x01 0x02", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x03", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x04", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x05", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x06", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x07", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x08", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x09", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0a", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0b", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0c", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0d", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0e", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x0f", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x01 0x10", + "DROP 1", + "", + "MINIMALDATA" + ], + [ + "0x4c 0x48 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA1 of 72 bytes minimally represented by direct push" + ], + [ + "0x4d 0xFF00 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA2 of 255 bytes minimally represented by PUSHDATA1" + ], + [ + "0x4e 0x00010000 0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + "DROP 1", + "", + "MINIMALDATA", + "PUSHDATA4 of 256 bytes minimally represented by PUSHDATA2" + ], + [ + "MINIMALDATA enforcement for numeric arguments" + ], + [ + "0x01 0x00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x02 0x0000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x01 0x80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "0x80 (negative zero) numequals 0" + ], + [ + "0x02 0x0080", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 0" + ], + [ + "0x02 0x0500", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 5" + ], + [ + "0x03 0x050000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals 5" + ], + [ + "0x02 0x0580", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals -5" + ], + [ + "0x03 0x050080", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "numequals -5" + ], + [ + "0x03 0xff7f80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffff" + ], + [ + "0x03 0xff7f00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xff7f" + ], + [ + "0x04 0xffff7f80", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffffff" + ], + [ + "0x04 0xffff7f00", + "NOT DROP 1", + "", + "UNKNOWN_ERROR", + "Minimal encoding is 0xffff7f" + ], + [ + "Test every numeric-accepting opcode for correct handling of the numeric minimal encoding rule" + ], + [ + "1 0x02 0x0000", + "PICK DROP", + "", + "UNKNOWN_ERROR" + ], + [ + "1 0x02 0x0000", + "ROLL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "1ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "1SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "NEGATE DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "ABS DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "NOT DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000", + "0NOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "ADD DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "SUB DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "BOOLAND DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "BOOLAND DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "BOOLOR DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "BOOLOR DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 1", + "NUMEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMEQUALVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "NUMEQUALVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "NUMNOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "NUMNOTEQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "LESSTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "LESSTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "GREATERTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "GREATERTHAN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "LESSTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "LESSTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "GREATERTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "GREATERTHANOREQUAL DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "MIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "MIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "MAX DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "MAX DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0 0", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000 0", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0 0x02 0x0000", + "WITHIN DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0 1", + "CHECKMULTISIG DROP 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0 0x02 0x0000", + "CHECKMULTISIGVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "0x02 0x0000 0", + "CHECKMULTISIGVERIFY 1", + "", + "UNKNOWN_ERROR" + ], + [ + "Check MINIMALIF" + ], + [ + "2", + "IF TRUE ELSE FALSE", + "", + "MINIMALIF" + ], + [ + "2", + "NOTIF TRUE ELSE FALSE", + "", + "MINIMALIF" + ], + [ + "Order of CHECKMULTISIG evaluation tests, inverted by swapping the order of" + ], + [ + "pubkeys/signatures so they fail due to the STRICTENC rules on validly encoded" + ], + [ + "signatures and pubkeys." + ], + [ + "0x41 0x833682d4f60cc916a22a2c263e658fa662c49badb1e2a8c6208987bf99b1abd740498371480069e7a7a6e7471bf78c27bd9a1fd04fb212a92017346250ac187b01 0x41 0xea4a8d20562a950f4695dc24804565482e9fa111704886179d0c348f2b8a15fe691a305cd599c59c131677146661d5b98cb935330989a85f33afc70d0a21add101", + "2 0x21 0x02865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac0 0 2 CHECKMULTISIG NOT", + "", + "PUBKEYFORMAT", + "2-of-2 CHECKMULTISIG NOT with the first pubkey invalid, and both signatures validly encoded." + ], + [ + "CHECKSEQUENCEVERIFY tests" + ], + [ + "", + "CHECKSEQUENCEVERIFY", + "", + "INVALID_STACK_OPERATION", + "CSV automatically fails on a empty stack" + ], + [ + "0", + "CHECKSEQUENCEVERIFY", + "", + "UNSATISFIED_LOCKTIME", + "CSV fails if stack top bit 1 << 31 is set and the tx version < 2" + ], + [ + "4294967296", + "CHECKSEQUENCEVERIFY", + "", + "UNSATISFIED_LOCKTIME", + "CSV fails if stack top bit 1 << 31 is not set, and tx version < 2" + ], + [ + "NULLFAIL should cover all signatures and signatures only" + ], + [ + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", + "", + "OK", + "BIP66 and NULLFAIL-compliant" + ], + [ + "0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", + "", + "NULLFAIL", + "BIP66-compliant but not NULLFAIL-compliant 4" + ], + [ + "The End" + ] +] diff --git a/mining/src/block_template/selector.rs b/mining/src/block_template/selector.rs index 6acacb22d3..166a68d902 100644 --- a/mining/src/block_template/selector.rs +++ b/mining/src/block_template/selector.rs @@ -145,8 +145,8 @@ impl RebalancingWeightedTransactionSelector { // Enforce maximum gas per subnetwork per block. // Also check for overflow. if !selected_tx.tx.subnetwork_id.is_builtin_or_native() { - let subnetwork_id = selected_tx.tx.subnetwork_id.clone(); - let gas_usage = self.gas_usage_map.entry(subnetwork_id.clone()).or_insert(0); + let subnetwork_id = selected_tx.tx.subnetwork_id; + let gas_usage = self.gas_usage_map.entry(subnetwork_id).or_insert(0); let tx_gas = selected_tx.tx.gas; let next_gas_usage = (*gas_usage).checked_add(tx_gas); if next_gas_usage.is_none() || next_gas_usage.unwrap() > self.selectable_txs[selected_candidate.index].gas_limit { diff --git a/rpc/core/src/convert/tx.rs b/rpc/core/src/convert/tx.rs index 63a749bf42..c7b9e9e2ae 100644 --- a/rpc/core/src/convert/tx.rs +++ b/rpc/core/src/convert/tx.rs @@ -17,7 +17,7 @@ impl From<&Transaction> for RpcTransaction { inputs: item.inputs.iter().map(RpcTransactionInput::from).collect(), outputs: item.outputs.iter().map(RpcTransactionOutput::from).collect(), lock_time: item.lock_time, - subnetwork_id: item.subnetwork_id.clone(), + subnetwork_id: item.subnetwork_id, gas: item.gas, payload: item.payload.clone(), mass: item.mass(), @@ -62,7 +62,7 @@ impl TryFrom for Transaction { .map(kaspa_consensus_core::tx::TransactionOutput::try_from) .collect::>>()?, item.lock_time, - item.subnetwork_id.clone(), + item.subnetwork_id, item.gas, item.payload.clone(), ); @@ -96,7 +96,7 @@ impl From<&Transaction> for RpcOptionalTransaction { inputs: item.inputs.iter().map(RpcOptionalTransactionInput::from).collect(), outputs: item.outputs.iter().map(RpcOptionalTransactionOutput::from).collect(), lock_time: Some(item.lock_time), - subnetwork_id: Some(item.subnetwork_id.clone()), + subnetwork_id: Some(item.subnetwork_id), gas: Some(item.gas), payload: Some(item.payload.clone()), mass: Some(item.mass()), diff --git a/rpc/core/src/wasm/convert.rs b/rpc/core/src/wasm/convert.rs index 49e62aeadd..bfca6588f1 100644 --- a/rpc/core/src/wasm/convert.rs +++ b/rpc/core/src/wasm/convert.rs @@ -68,7 +68,7 @@ cfg_if::cfg_if! { inputs, outputs, lock_time: inner.lock_time, - subnetwork_id: inner.subnetwork_id.clone(), + subnetwork_id: inner.subnetwork_id, gas: inner.gas, payload: inner.payload.clone(), mass: inner.mass, @@ -117,7 +117,7 @@ cfg_if::cfg_if! { inputs, outputs, lock_time: Some(inner.lock_time), - subnetwork_id: Some(inner.subnetwork_id.clone()), + subnetwork_id: Some(inner.subnetwork_id), gas: Some(inner.gas), payload: Some(inner.payload.clone()), mass: Some(inner.mass), diff --git a/rpc/service/src/converter/consensus.rs b/rpc/service/src/converter/consensus.rs index 1e998c7026..610f0e90b7 100644 --- a/rpc/service/src/converter/consensus.rs +++ b/rpc/service/src/converter/consensus.rs @@ -145,7 +145,7 @@ impl ConsensusConverter { inputs: transaction.inputs.iter().map(|x| self.get_transaction_input(x)).collect(), outputs: transaction.outputs.iter().map(|x| self.get_transaction_output(x)).collect(), lock_time: transaction.lock_time, - subnetwork_id: transaction.subnetwork_id.clone(), + subnetwork_id: transaction.subnetwork_id, gas: transaction.gas, payload: transaction.payload.clone(), mass: transaction.mass(), @@ -417,7 +417,7 @@ impl ConsensusConverter { }, lock_time: if verbosity.include_lock_time.unwrap_or(false) { Some(transaction.lock_time) } else { Default::default() }, subnetwork_id: if verbosity.include_subnetwork_id.unwrap_or(false) { - Some(transaction.subnetwork_id.clone()) + Some(transaction.subnetwork_id) } else { Default::default() }, @@ -470,7 +470,7 @@ impl ConsensusConverter { Default::default() }, lock_time: if verbosity.include_lock_time.unwrap_or(false) { Some(transaction.tx.lock_time) } else { Default::default() }, - subnetwork_id: Some(transaction.tx.subnetwork_id.clone()), + subnetwork_id: Some(transaction.tx.subnetwork_id), gas: Some(transaction.tx.gas), payload: Some(transaction.tx.payload.clone()), mass: Some(transaction.tx.mass()), diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 846d437610..338328410e 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -30,8 +30,9 @@ use kaspa_consensus_core::constants::{BLOCK_VERSION, SOMPI_PER_KASPA, TRANSIENT_ use kaspa_consensus_core::errors::block::{BlockProcessResult, RuleError}; use kaspa_consensus_core::hashing; use kaspa_consensus_core::header::Header; +use kaspa_consensus_core::merkle::calc_hash_merkle_root; use kaspa_consensus_core::mining_rules::MiningRules; -use kaspa_consensus_core::subnets::SubnetworkId; +use kaspa_consensus_core::subnets::{SubnetworkId, SUBNETWORK_ID_NATIVE}; use kaspa_consensus_core::tx::{ MutableTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, }; @@ -43,6 +44,7 @@ use kaspa_core::time::unix_now; use kaspa_database::utils::get_kaspa_tempdir; use kaspa_hashes::Hash; use kaspa_rpc_core::RpcHeader; +use kaspa_txscript::pay_to_script_hash_script; use kaspa_utils::arc::ArcExtensions; use crate::common; @@ -63,8 +65,8 @@ use kaspa_math::Uint256; use kaspa_muhash::MuHash; use kaspa_notify::subscription::context::SubscriptionContext; use kaspa_txscript::caches::TxScriptCacheCounters; -use kaspa_txscript::opcodes::codes::OpTrue; -use kaspa_txscript::script_builder::ScriptBuilderResult; +use kaspa_txscript::opcodes::codes::{Op0, OpCat, OpDrop, OpEqual, OpTrue, OpTxOutputSpk}; +use kaspa_txscript::script_builder::{ScriptBuilder, ScriptBuilderResult}; use kaspa_utxoindex::api::{UtxoIndexApi, UtxoIndexProxy}; use kaspa_utxoindex::UtxoIndex; use serde::{Deserialize, Serialize}; @@ -1485,6 +1487,258 @@ async fn kip10_test() { assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); } +#[tokio::test] +async fn covenants_activation_test() { + const ACTIVATION_DAA_SCORE: u64 = 3; + let config = ConfigBuilder::new(DEVNET_PARAMS) + .skip_proof_of_work() + .edit_consensus_params(|p| { + p.coinbase_maturity = 0; + p.covenants_activation = ForkActivation::new(ACTIVATION_DAA_SCORE) + }) + .build(); + + let consensus = TestConsensus::new(&config); + let wait_handles = consensus.init(); + + // Mine a funding block whose reward will later pay to the covenant P2SH + let mut next_id: u64 = 1; + let mut tip = config.genesis.hash; + + // Redeem script that uses OpCat (disabled before covenants activation, enabled after) + let redeem_script = ScriptBuilder::new() + .add_data(&[0xaa]) + .unwrap() + .add_data(&[0xbb]) + .unwrap() + .add_op(OpCat) + .unwrap() + .add_data(&[0xaa, 0xbb]) + .unwrap() + .add_op(OpEqual) + .unwrap() + .drain(); + let miner_spk = pay_to_script_hash_script(&redeem_script); + + // First block sets the miner script to the covenant P2SH + let block1_hash = next_id.into(); + let block1 = + consensus.build_utxo_valid_block_with_parents(block1_hash, vec![tip], MinerData::new(miner_spk.clone(), vec![]), vec![]); + consensus.validate_and_insert_block(block1.to_immutable()).virtual_state_task.await.unwrap(); + tip = block1_hash; + next_id += 1; + + // Second block creates the coinbase that pays to the previous block's miner SPK. + let block2_hash = next_id.into(); + let block2 = consensus.build_utxo_valid_block_with_parents( + block2_hash, + vec![tip], + MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]), + vec![], + ); + consensus.validate_and_insert_block(block2.to_immutable()).virtual_state_task.await.unwrap(); + let (funding_outpoint, funding_amount) = { + let cb = &consensus.get_block(block2_hash).unwrap().transactions[0]; + (TransactionOutpoint::new(cb.id(), 0), cb.outputs[0].value) + }; + tip = block2_hash; + next_id += 1; + + // Advance chain until just before activation (DAA score = ACTIVATION_DAA_SCORE - 1) + while consensus.get_virtual_daa_score() < ACTIVATION_DAA_SCORE - 1 { + consensus.add_utxo_valid_block_with_parents(next_id.into(), vec![tip], vec![]).await.unwrap(); + tip = next_id.into(); + next_id += 1; + } + assert_eq!(consensus.get_virtual_daa_score(), ACTIVATION_DAA_SCORE - 1); + + // Transaction spending the test UTXO using the covenant opcode + let mut tx = Transaction::new( + 0, + vec![TransactionInput::new(funding_outpoint, ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(), 0, 0)], + vec![TransactionOutput::new(funding_amount - 5000, ScriptPublicKey::from_vec(0, vec![OpTrue]))], + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + ); + tx.finalize(); + let tx_id = tx.id(); + + let mut tx = MutableTransaction::from_tx(tx); + // This triggers storage mass population + let _ = consensus.validate_mempool_transaction(&mut tx, &TransactionValidationArgs::default()); + let tx = tx.tx.unwrap_or_clone(); + + // Pre-activation: inserting block with the covenant opcode should be rejected + { + let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); + let mut block = consensus.build_utxo_valid_block_with_parents(next_id.into(), vec![tip], miner_data.clone(), vec![]); + + block.transactions.push(tx.clone()); + block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter()); + + let block_status = consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await; + assert!(matches!(block_status, Ok(BlockStatus::StatusDisqualifiedFromChain))); + assert_eq!(consensus.lkg_virtual_state.load().daa_score, ACTIVATION_DAA_SCORE - 1); + } + + next_id += 1; + + // Advance to activation + consensus.add_utxo_valid_block_with_parents(next_id.into(), vec![tip], vec![]).await.unwrap(); + tip = next_id.into(); + next_id += 1; + + // Post-activation: same transaction should now be accepted + let status = consensus.add_utxo_valid_block_with_parents(next_id.into(), vec![tip], vec![tx.clone()]).await; + assert!(matches!(status, Ok(BlockStatus::StatusUTXOValid))); + assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); + + consensus.shutdown(wait_handles); +} + +#[tokio::test] +async fn push_limit_activation_test() { + const ACTIVATION_DAA_SCORE: u64 = 4; + let config = ConfigBuilder::new(DEVNET_PARAMS) + .skip_proof_of_work() + .edit_consensus_params(|p| { + p.coinbase_maturity = 0; + p.covenants_activation = ForkActivation::new(ACTIVATION_DAA_SCORE) + }) + .build(); + + let consensus = TestConsensus::new(&config); + let wait_handles = consensus.init(); + + // Mine a funding block whose reward will later pay to the covenant P2SH + let mut next_id: u64 = 1; + let mut tip = config.genesis.hash; + + // Redeem script that pushes the script pubkey onto the stack + let redeem_script = ScriptBuilder::new() + .add_op(Op0) + .unwrap() + .add_op(OpTxOutputSpk) + .unwrap() + .add_op(OpDrop) + .unwrap() + .add_op(OpTrue) + .unwrap() + .drain(); + let miner_spk = pay_to_script_hash_script(&redeem_script); + let miner_data = MinerData::new(miner_spk.clone(), vec![]); + + // First block sets the miner script to the covenant P2SH + let block1_hash = next_id.into(); + let block1 = consensus.build_utxo_valid_block_with_parents(block1_hash, vec![tip], miner_data.clone(), vec![]); + consensus.validate_and_insert_block(block1.to_immutable()).virtual_state_task.await.unwrap(); + tip = block1_hash; + next_id += 1; + + // Second block creates the coinbase that pays to the previous block's miner SPK. + let block2_hash = next_id.into(); + let block2 = consensus.build_utxo_valid_block_with_parents(block2_hash, vec![tip], miner_data.clone(), vec![]); + consensus.validate_and_insert_block(block2.to_immutable()).virtual_state_task.await.unwrap(); + let (funding_outpoint1, funding_amount1) = { + let cb = &consensus.get_block(block2_hash).unwrap().transactions[0]; + (TransactionOutpoint::new(cb.id(), 0), cb.outputs[0].value) + }; + tip = block2_hash; + next_id += 1; + + // Third block creates another coinbase that pays to the previous block's miner SPK. + let block3_hash = next_id.into(); + let block3 = consensus.build_utxo_valid_block_with_parents(block3_hash, vec![tip], miner_data.clone(), vec![]); + consensus.validate_and_insert_block(block3.to_immutable()).virtual_state_task.await.unwrap(); + let (funding_outpoint2, funding_amount2) = { + let cb = &consensus.get_block(block3_hash).unwrap().transactions[0]; + (TransactionOutpoint::new(cb.id(), 0), cb.outputs[0].value) + }; + tip = block3_hash; + next_id += 1; + + // Advance chain until just before activation (DAA score = ACTIVATION_DAA_SCORE - 1) + while consensus.get_virtual_daa_score() < ACTIVATION_DAA_SCORE - 1 { + consensus.add_utxo_valid_block_with_parents(next_id.into(), vec![tip], vec![]).await.unwrap(); + tip = next_id.into(); + next_id += 1; + } + assert_eq!(consensus.get_virtual_daa_score(), ACTIVATION_DAA_SCORE - 1); + + // Pre-activation: inserting block with a transaction that pushes more than 520 bytes onto the stack should be accepted + { + // Transaction spending the UTXO that pushes more than 520 bytes onto the stack (since it has an SPK of 1000 bytes) + let mut tx = Transaction::new( + 0, + vec![TransactionInput::new(funding_outpoint2, ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(), 0, 0)], + vec![TransactionOutput::new(funding_amount2 - 5000, ScriptPublicKey::from_vec(0, vec![0u8; 1000]))], + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + ); + tx.finalize(); + let tx_id = tx.id(); + + let mut tx = MutableTransaction::from_tx(tx); + // This triggers storage mass population + let _ = consensus.validate_mempool_transaction(&mut tx, &TransactionValidationArgs::default()); + let tx = tx.tx.unwrap_or_clone(); + + let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); + let mut block = consensus.build_utxo_valid_block_with_parents(next_id.into(), vec![tip], miner_data.clone(), vec![]); + + block.transactions.push(tx.clone()); + block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter()); + + let block_status = consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await; + assert!(matches!(block_status, Ok(BlockStatus::StatusUTXOValid))); + assert!(consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); + } + + next_id += 1; + + // Advance to activation + consensus.add_utxo_valid_block_with_parents(next_id.into(), vec![tip], vec![]).await.unwrap(); + tip = next_id.into(); + next_id += 1; + + // Post-activation: a similar transaction should now be rejected + { + // Transaction spending the UTXO that pushes more than 520 bytes onto the stack (since it has an SPK of 1000 bytes) + let mut tx = Transaction::new( + 0, + vec![TransactionInput::new(funding_outpoint1, ScriptBuilder::new().add_data(&redeem_script).unwrap().drain(), 0, 0)], + vec![TransactionOutput::new(funding_amount1 - 5000, ScriptPublicKey::from_vec(0, vec![0u8; 1000]))], + 0, + SUBNETWORK_ID_NATIVE, + 0, + vec![], + ); + tx.finalize(); + let tx_id = tx.id(); + + let mut tx = MutableTransaction::from_tx(tx); + // This triggers storage mass population + let _ = consensus.validate_mempool_transaction(&mut tx, &TransactionValidationArgs::default()); + let tx = tx.tx.unwrap_or_clone(); + + let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); + let mut block = consensus.build_utxo_valid_block_with_parents(next_id.into(), vec![tip], miner_data.clone(), vec![]); + + block.transactions.push(tx.clone()); + block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter()); + + let block_status = consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await; + assert!(matches!(block_status, Ok(BlockStatus::StatusDisqualifiedFromChain))); + assert!(!consensus.lkg_virtual_state.load().accepted_tx_ids.contains(&tx_id)); + } + + consensus.shutdown(wait_handles); +} + #[tokio::test] async fn payload_test() { let config = ConfigBuilder::new(DEVNET_PARAMS) diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index a3341e0669..45f3a34980 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -470,7 +470,8 @@ impl PSKT { let reused_values = SigHashReusedValuesUnsync::new(); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { - TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache).execute()?; + TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, Default::default()) + .execute()?; >::Ok(()) })?; }