From 78aab3f4cd2ad18f8f3c78433c82301d1c23f85e Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Tue, 7 Jun 2022 16:53:11 +0200 Subject: [PATCH 01/10] feat(stack): implement control flow --- stack/src/lib.rs | 311 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 306 insertions(+), 5 deletions(-) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 64acf797f..5c1479bf7 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -1,6 +1,6 @@ use scriptful::{ - core::Script, - prelude::{Machine, Stack}, + core::{Script, ScriptRef}, + prelude::Stack, }; use serde::{Deserialize, Serialize}; @@ -19,6 +19,12 @@ pub enum MyOperator { Equal, Hash160, CheckMultiSig, + /// Stop script execution if top-most element of stack is not "true" + Verify, + // Control flow + If, + Else, + EndIf, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] @@ -137,11 +143,158 @@ fn check_multi_sig(bytes_pkhs: Vec, bytes_keyed_signatures: Vec, operator: &MyOperator) { +fn my_operator_system( + stack: &mut Stack, + operator: &MyOperator, + if_stack: &mut ConditionStack, +) -> MyControlFlow { + if !if_stack.all_true() { + match operator { + MyOperator::If => { + if_stack.push_back(false); + } + MyOperator::Else => { + if if_stack.toggle_top().is_none() { + stack.push(MyValue::Boolean(false)); + return MyControlFlow::Break; + } + } + MyOperator::EndIf => { + if if_stack.pop_back().is_none() { + stack.push(MyValue::Boolean(false)); + return MyControlFlow::Break; + } + } + _ => {} + } + + return MyControlFlow::Continue; + } + match operator { MyOperator::Equal => equal_operator(stack), MyOperator::Hash160 => hash_160_operator(stack), MyOperator::CheckMultiSig => check_multisig_operator(stack), + MyOperator::Verify => { + let top = stack.pop(); + if top != MyValue::Boolean(true) { + // Push the element back because there is a check in execute_script that needs a + // false value to mark the script execution as failed, otherwise it may be marked as + // success + stack.push(top); + return MyControlFlow::Break; + } + } + MyOperator::If => { + let top = stack.pop(); + if let MyValue::Boolean(b) = top { + if_stack.push_back(b); + } else { + stack.push(MyValue::Boolean(false)); + return MyControlFlow::Break; + } + } + MyOperator::Else => { + if if_stack.toggle_top().is_none() { + stack.push(MyValue::Boolean(false)); + return MyControlFlow::Break; + } + } + MyOperator::EndIf => { + if if_stack.pop_back().is_none() { + stack.push(MyValue::Boolean(false)); + return MyControlFlow::Break; + } + } + } + + MyControlFlow::Continue +} + +// ConditionStack implementation from bitcoin-core +// https://github.com/bitcoin/bitcoin/blob/505ba3966562b10d6dd4162f3216a120c73a4edb/src/script/interpreter.cpp#L272 +// https://bitslog.com/2017/04/17/new-quadratic-delays-in-bitcoin-scripts/ +/** A data type to abstract out the condition stack during script execution. +* +* Conceptually it acts like a vector of booleans, one for each level of nested +* IF/THEN/ELSE, indicating whether we're in the active or inactive branch of +* each. +* +* The elements on the stack cannot be observed individually; we only need to +* expose whether the stack is empty and whether or not any false values are +* present at all. To implement OP_ELSE, a toggle_top modifier is added, which +* flips the last value without returning it. +* +* This uses an optimized implementation that does not materialize the +* actual stack. Instead, it just stores the size of the would-be stack, +* and the position of the first false value in it. + */ +pub struct ConditionStack { + stack_size: u32, + first_false_pos: u32, +} + +impl Default for ConditionStack { + fn default() -> Self { + Self { + stack_size: 0, + first_false_pos: Self::NO_FALSE, + } + } +} + +impl ConditionStack { + const NO_FALSE: u32 = u32::MAX; + + pub fn is_empty(&self) -> bool { + self.stack_size == 0 + } + + pub fn all_true(&self) -> bool { + self.first_false_pos == Self::NO_FALSE + } + + pub fn push_back(&mut self, b: bool) { + if (self.first_false_pos == Self::NO_FALSE) && !b { + // The stack consists of all true values, and a false is added. + // The first false value will appear at the current size. + self.first_false_pos = self.stack_size; + } + + self.stack_size += 1; + } + + pub fn pop_back(&mut self) -> Option<()> { + if self.stack_size == 0 { + return None; + } + + self.stack_size -= 1; + if self.first_false_pos == self.stack_size { + // When popping off the first false value, everything becomes true. + self.first_false_pos = Self::NO_FALSE; + } + + Some(()) + } + + pub fn toggle_top(&mut self) -> Option<()> { + if self.stack_size == 0 { + return None; + } + + if self.first_false_pos == Self::NO_FALSE { + // The current stack is all true values; the first false will be the top. + self.first_false_pos = self.stack_size - 1; + } else if self.first_false_pos == self.stack_size - 1 { + // The top is the first false value; toggling it will make everything true. + self.first_false_pos = Self::NO_FALSE; + } else { + // There is a false value, but not on top. No action is needed as toggling + // anything but the first false value is unobservable. + } + + Some(()) } } @@ -194,10 +347,10 @@ pub fn encode(a: Script) -> Vec { fn execute_script(script: Script) -> bool { // Instantiate the machine with a reference to your operator system. - let mut machine = Machine::new(&my_operator_system); + let mut machine = Machine2::new(&my_operator_system); let result = machine.run_script(&script); - result == Some(&MyValue::Boolean(true)) + result == None || result == Some(&MyValue::Boolean(true)) } fn execute_locking_script(redeem_bytes: &[u8], locking_bytes: &[u8; 20]) -> bool { @@ -240,6 +393,67 @@ pub fn execute_complete_script( execute_redeem_script(witness_bytes, redeem_bytes) } +// TODO: use control flow enum from scriptful library when ready +pub enum MyControlFlow { + Continue, + Break, +} + +pub struct Machine2<'a, Op, Val> +where + Val: core::fmt::Debug + core::cmp::PartialEq, +{ + op_sys: &'a dyn Fn(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, + stack: Stack, + if_stack: ConditionStack, +} + +impl<'a, Op, Val> Machine2<'a, Op, Val> +where + Op: core::fmt::Debug + core::cmp::Eq, + Val: core::fmt::Debug + core::cmp::PartialEq + core::clone::Clone, +{ + pub fn new( + op_sys: &'a dyn Fn(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, + ) -> Self { + Self { + op_sys, + stack: Stack::::default(), + if_stack: ConditionStack::default(), + } + } + + pub fn operate(&mut self, item: &Item) -> MyControlFlow { + match item { + Item::Operator(operator) => { + (self.op_sys)(&mut self.stack, operator, &mut self.if_stack) + } + Item::Value(value) => { + if self.if_stack.all_true() { + self.stack.push((*value).clone()); + } + + MyControlFlow::Continue + } + } + } + + pub fn run_script(&mut self, script: ScriptRef) -> Option<&Val> { + for item in script { + match self.operate(item) { + MyControlFlow::Continue => { + continue; + } + MyControlFlow::Break => { + break; + } + } + } + + self.stack.topmost() + } +} + #[cfg(test)] mod tests { use super::*; @@ -414,4 +628,91 @@ mod tests { &encode(redeem_script) )); } + + #[test] + fn test_execute_script_op_verify() { + let s = vec![ + Item::Value(MyValue::String("patata".to_string())), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(execute_script(s)); + + let s = vec![ + Item::Value(MyValue::String("patata".to_string())), + Item::Value(MyValue::String("potato".to_string())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(!execute_script(s)); + } + + #[test] + fn test_execute_script_op_if() { + let s = vec![ + Item::Value(MyValue::String("patata".to_string())), + Item::Value(MyValue::Boolean(true)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::String("potato".to_string())), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(execute_script(s)); + + let s = vec![ + Item::Value(MyValue::String("patata".to_string())), + Item::Value(MyValue::Boolean(false)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::String("potato".to_string())), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(!execute_script(s)); + } + + #[test] + fn test_execute_script_op_if_nested() { + let s = vec![ + Item::Value(MyValue::String("patata".to_string())), + Item::Value(MyValue::Boolean(true)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::Boolean(false)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::String("potato".to_string())), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(execute_script(s)); + + let s = vec![ + Item::Value(MyValue::String("potato".to_string())), + Item::Value(MyValue::Boolean(false)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::Boolean(false)), + Item::Operator(MyOperator::If), + Item::Value(MyValue::String("patata".to_string())), + Item::Operator(MyOperator::Else), + Item::Value(MyValue::String("potato".to_string())), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::EndIf), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + ]; + assert!(execute_script(s)); + } } From 65e183ed1aa791cedb8610864ea48a5092b4ff52 Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Wed, 8 Jun 2022 11:25:54 +0200 Subject: [PATCH 02/10] feat(stack): allow context in machine operate function --- stack/src/lib.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 5c1479bf7..8328e1172 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -3,6 +3,7 @@ use scriptful::{ prelude::Stack, }; use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; use witnet_crypto::hash::{calculate_sha256, Sha256}; use witnet_data_structures::{ @@ -347,7 +348,7 @@ pub fn encode(a: Script) -> Vec { fn execute_script(script: Script) -> bool { // Instantiate the machine with a reference to your operator system. - let mut machine = Machine2::new(&my_operator_system); + let mut machine = Machine2::new(my_operator_system); let result = machine.run_script(&script); result == None || result == Some(&MyValue::Boolean(true)) @@ -399,27 +400,29 @@ pub enum MyControlFlow { Break, } -pub struct Machine2<'a, Op, Val> +pub struct Machine2 where Val: core::fmt::Debug + core::cmp::PartialEq, + F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, { - op_sys: &'a dyn Fn(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, + op_sys: F, stack: Stack, if_stack: ConditionStack, + phantom_op: PhantomData, } -impl<'a, Op, Val> Machine2<'a, Op, Val> +impl Machine2 where Op: core::fmt::Debug + core::cmp::Eq, Val: core::fmt::Debug + core::cmp::PartialEq + core::clone::Clone, + F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, { - pub fn new( - op_sys: &'a dyn Fn(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, - ) -> Self { + pub fn new(op_sys: F) -> Self { Self { op_sys, stack: Stack::::default(), if_stack: ConditionStack::default(), + phantom_op: PhantomData, } } @@ -715,4 +718,17 @@ mod tests { ]; assert!(execute_script(s)); } + + #[test] + fn machine_with_context() { + let mut v = vec![0u32]; + let mut m = Machine2::new(|_stack: &mut Stack<()>, operator, _if_stack| { + v.push(*operator); + + MyControlFlow::Continue + }); + m.run_script(&[Item::Operator(1), Item::Operator(3), Item::Operator(2)]); + + assert_eq!(v, vec![0, 1, 3, 2]); + } } From c301938eb7661903f769308313b451743815c3cc Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Wed, 8 Jun 2022 12:36:48 +0200 Subject: [PATCH 03/10] feat: implement CheckTimelock scripting operator --- stack/src/lib.rs | 109 ++++++++++++++++++++++++++------- validations/src/validations.rs | 18 +++++- 2 files changed, 101 insertions(+), 26 deletions(-) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 8328e1172..c770267fe 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -20,6 +20,7 @@ pub enum MyOperator { Equal, Hash160, CheckMultiSig, + CheckTimeLock, /// Stop script execution if top-most element of stack is not "true" Verify, // Control flow @@ -143,11 +144,26 @@ fn check_multi_sig(bytes_pkhs: Vec, bytes_keyed_signatures: Vec, block_timestamp: i64) { + let timelock = stack.pop(); + match timelock { + MyValue::Integer(timelock) => { + let timelock_ok = i128::from(block_timestamp) >= timelock; + stack.push(MyValue::Boolean(timelock_ok)); + } + _ => { + // TODO change panic by error + unreachable!("CheckTimelock should pick an integer as a first value"); + } + } +} + // An operator system decides what to do with the stack when each operator is applied on it. fn my_operator_system( stack: &mut Stack, operator: &MyOperator, if_stack: &mut ConditionStack, + context: &ScriptContext, ) -> MyControlFlow { if !if_stack.all_true() { match operator { @@ -176,6 +192,7 @@ fn my_operator_system( MyOperator::Equal => equal_operator(stack), MyOperator::Hash160 => hash_160_operator(stack), MyOperator::CheckMultiSig => check_multisig_operator(stack), + MyOperator::CheckTimeLock => check_timelock_operator(stack, context.block_timestamp), MyOperator::Verify => { let top = stack.pop(); if top != MyValue::Boolean(true) { @@ -346,15 +363,24 @@ pub fn encode(a: Script) -> Vec { serde_json::to_vec(&x).unwrap() } -fn execute_script(script: Script) -> bool { +#[derive(Default)] +pub struct ScriptContext { + pub block_timestamp: i64, +} + +fn execute_script(script: Script, context: &ScriptContext) -> bool { // Instantiate the machine with a reference to your operator system. - let mut machine = Machine2::new(my_operator_system); + let mut machine = Machine2::new(|a, b, c| my_operator_system(a, b, c, context)); let result = machine.run_script(&script); result == None || result == Some(&MyValue::Boolean(true)) } -fn execute_locking_script(redeem_bytes: &[u8], locking_bytes: &[u8; 20]) -> bool { +fn execute_locking_script( + redeem_bytes: &[u8], + locking_bytes: &[u8; 20], + context: &ScriptContext, +) -> bool { // Check locking script let mut locking_script = vec![ Item::Operator(MyOperator::Hash160), @@ -366,32 +392,37 @@ fn execute_locking_script(redeem_bytes: &[u8], locking_bytes: &[u8; 20]) -> bool locking_script.insert(0, Item::Value(MyValue::Bytes(redeem_bytes.to_vec()))); // Execute the script - execute_script(locking_script) + execute_script(locking_script, context) } -fn execute_redeem_script(witness_bytes: &[u8], redeem_bytes: &[u8]) -> bool { +fn execute_redeem_script( + witness_bytes: &[u8], + redeem_bytes: &[u8], + context: &ScriptContext, +) -> bool { // Execute witness script concatenated with redeem script let mut witness_script = decode(witness_bytes); let redeem_script = decode(redeem_bytes); witness_script.extend(redeem_script); // Execute the script - execute_script(witness_script) + execute_script(witness_script, context) } pub fn execute_complete_script( witness_bytes: &[u8], redeem_bytes: &[u8], locking_bytes: &[u8; 20], + context: &ScriptContext, ) -> bool { // Execute locking script - let result = execute_locking_script(redeem_bytes, locking_bytes); + let result = execute_locking_script(redeem_bytes, locking_bytes, context); if !result { return false; } // Execute witness script concatenated with redeem script - execute_redeem_script(witness_bytes, redeem_bytes) + execute_redeem_script(witness_bytes, redeem_bytes, context) } // TODO: use control flow enum from scriptful library when ready @@ -473,14 +504,14 @@ mod tests { Item::Value(MyValue::String("patata".to_string())), Item::Operator(MyOperator::Equal), ]; - assert!(execute_script(s)); + assert!(execute_script(s, &ScriptContext::default())); let s = vec![ Item::Value(MyValue::String("patata".to_string())), Item::Value(MyValue::String("potato".to_string())), Item::Operator(MyOperator::Equal), ]; - assert!(!execute_script(s)); + assert!(!execute_script(s, &ScriptContext::default())); } #[test] @@ -489,14 +520,16 @@ mod tests { let locking_script = EQUAL_OPERATOR_HASH; assert!(execute_locking_script( &encode(redeem_script), - &locking_script + &locking_script, + &ScriptContext::default(), )); let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script = [1; 20]; assert!(!execute_locking_script( &encode(redeem_script), - &locking_script + &locking_script, + &ScriptContext::default(), )); } @@ -509,7 +542,8 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; assert!(execute_redeem_script( &encode(witness), - &encode(redeem_script) + &encode(redeem_script), + &ScriptContext::default(), )); let witness = vec![ @@ -519,7 +553,8 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; assert!(!execute_redeem_script( &encode(witness), - &encode(redeem_script) + &encode(redeem_script), + &ScriptContext::default(), )); } @@ -535,6 +570,7 @@ mod tests { &encode(witness), &encode(redeem_script), &locking_script, + &ScriptContext::default(), )); let witness = vec![ @@ -547,6 +583,7 @@ mod tests { &encode(witness), &encode(redeem_script), &locking_script, + &ScriptContext::default(), )); let witness = vec![ @@ -559,6 +596,7 @@ mod tests { &encode(witness), &encode(redeem_script), &locking_script, + &ScriptContext::default(), )); } @@ -592,7 +630,8 @@ mod tests { ]; assert!(execute_redeem_script( &encode(witness), - &encode(redeem_script) + &encode(redeem_script), + &ScriptContext::default(), )); let other_valid_witness = vec![ @@ -609,7 +648,8 @@ mod tests { ]; assert!(execute_redeem_script( &encode(other_valid_witness), - &encode(redeem_script) + &encode(redeem_script), + &ScriptContext::default(), )); let pk_4 = PublicKey::from_bytes([4; 33]); @@ -628,7 +668,8 @@ mod tests { ]; assert!(!execute_redeem_script( &encode(invalid_witness), - &encode(redeem_script) + &encode(redeem_script), + &ScriptContext::default(), )); } @@ -640,7 +681,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(execute_script(s)); + assert!(execute_script(s, &ScriptContext::default())); let s = vec![ Item::Value(MyValue::String("patata".to_string())), @@ -648,7 +689,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(!execute_script(s)); + assert!(!execute_script(s, &ScriptContext::default())); } #[test] @@ -664,7 +705,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(execute_script(s)); + assert!(execute_script(s, &ScriptContext::default())); let s = vec![ Item::Value(MyValue::String("patata".to_string())), @@ -677,7 +718,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(!execute_script(s)); + assert!(!execute_script(s, &ScriptContext::default())); } #[test] @@ -698,7 +739,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(execute_script(s)); + assert!(execute_script(s, &ScriptContext::default())); let s = vec![ Item::Value(MyValue::String("potato".to_string())), @@ -716,7 +757,7 @@ mod tests { Item::Operator(MyOperator::Equal), Item::Operator(MyOperator::Verify), ]; - assert!(execute_script(s)); + assert!(execute_script(s, &ScriptContext::default())); } #[test] @@ -731,4 +772,26 @@ mod tests { assert_eq!(v, vec![0, 1, 3, 2]); } + + #[test] + fn test_execute_script_op_check_timelock() { + let s = vec![ + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + ]; + assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); + + let s = vec![ + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + ]; + assert!(execute_script( + s, + &ScriptContext { + block_timestamp: 20_000, + } + )); + } } diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 172ae940a..9cba095ad 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -46,7 +46,7 @@ use witnet_rad::{ script::{create_radon_script_from_filters_and_reducer, unpack_radon_script}, types::{serial_iter_decode, RadonTypes}, }; -use witnet_stack::{execute_complete_script, Item, MyValue}; +use witnet_stack::{execute_complete_script, Item, MyValue, ScriptContext}; /// Returns the fee of a value transfer transaction. /// @@ -309,12 +309,15 @@ pub fn validate_vt_transaction<'a>( .into()); } + let block_timestamp = epoch_constants.epoch_timestamp(epoch)?; + validate_transaction_signatures( &vt_tx.witness, &vt_tx.body.inputs, vt_tx.hash(), utxo_diff, signatures_to_verify, + block_timestamp, )?; // A value transfer transaction must have at least one input @@ -412,12 +415,15 @@ pub fn validate_dr_transaction<'a>( .map(vtt_signature_to_witness) .collect(); + let block_timestamp = epoch_constants.epoch_timestamp(epoch)?; + validate_transaction_signatures( &dr_tx_signatures_vec_u8, &dr_tx.body.inputs, dr_tx.hash(), utxo_diff, signatures_to_verify, + block_timestamp, )?; // A data request can only have 0 or 1 outputs @@ -1211,6 +1217,7 @@ pub fn validate_transaction_signatures( tx_hash: Hash, utxo_set: &UtxoDiff<'_>, signatures_to_verify: &mut Vec, + block_timestamp: i64, ) -> Result<(), failure::Error> { if signatures.len() != inputs.len() { return Err(TransactionError::MismatchingSignaturesNumber { @@ -1259,10 +1266,15 @@ pub fn validate_transaction_signatures( output: output_pointer.clone(), })?; let redeem_script_hash = output.pkh; + let script_context = ScriptContext { block_timestamp }; // Script execution assumes that all the signatures are valid, the signatures will be // validated later. - let res = - execute_complete_script(witness, &input.redeem_script, redeem_script_hash.bytes()); + let res = execute_complete_script( + witness, + &input.redeem_script, + redeem_script_hash.bytes(), + &script_context, + ); if !res { return Err(TransactionError::ScriptExecutionFailed { From 7057d7f8b677fcd7023cf75d2a8e12a2ce77e0be Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Wed, 8 Jun 2022 12:39:37 +0200 Subject: [PATCH 04/10] feat(stack): Sha256 operator --- stack/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index c770267fe..c27b4b280 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -19,6 +19,7 @@ pub use scriptful::prelude::Item; pub enum MyOperator { Equal, Hash160, + Sha256, CheckMultiSig, CheckTimeLock, /// Stop script execution if top-most element of stack is not "true" @@ -63,6 +64,16 @@ fn hash_160_operator(stack: &mut Stack) { } } +fn sha_256_operator(stack: &mut Stack) { + let a = stack.pop(); + if let MyValue::Bytes(bytes) = a { + let Sha256(h) = calculate_sha256(&bytes); + stack.push(MyValue::Bytes(h.to_vec())); + } else { + // TODO: hash other types? + } +} + fn check_multisig_operator(stack: &mut Stack) { let m = stack.pop(); match m { @@ -191,6 +202,7 @@ fn my_operator_system( match operator { MyOperator::Equal => equal_operator(stack), MyOperator::Hash160 => hash_160_operator(stack), + MyOperator::Sha256 => sha_256_operator(stack), MyOperator::CheckMultiSig => check_multisig_operator(stack), MyOperator::CheckTimeLock => check_timelock_operator(stack, context.block_timestamp), MyOperator::Verify => { From 81eb69a22a2e559b12b5e25d8b9ce4c9e01955c6 Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Wed, 8 Jun 2022 18:17:35 +0200 Subject: [PATCH 05/10] feat: add error handling to script encode and decode --- node/src/actors/chain_manager/handlers.rs | 2 +- src/cli/node/json_rpc_client.rs | 8 +- stack/src/lib.rs | 102 ++++++++++++++-------- validations/src/validations.rs | 4 +- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/node/src/actors/chain_manager/handlers.rs b/node/src/actors/chain_manager/handlers.rs index 7688f91b2..e8505b2b6 100644 --- a/node/src/actors/chain_manager/handlers.rs +++ b/node/src/actors/chain_manager/handlers.rs @@ -1310,7 +1310,7 @@ impl Handler for ChainManager { .into_actor(self) .then(|s, _act, _ctx| match s { Ok(_signatures) => { - let multi_sig_witness = witnet_stack::encode(vec![]); + let multi_sig_witness = witnet_stack::encode(vec![]).unwrap(); let num_inputs = vtt.inputs.len(); let transaction = Transaction::ValueTransfer(VTTransaction { body: vtt, diff --git a/src/cli/node/json_rpc_client.rs b/src/cli/node/json_rpc_client.rs index 16c82e02c..747b67a89 100644 --- a/src/cli/node/json_rpc_client.rs +++ b/src/cli/node/json_rpc_client.rs @@ -737,7 +737,7 @@ pub fn create_multisig_address( ]); let locking_script_hash = - PublicKeyHash::from_script_bytes(&witnet_stack::encode(redeem_script)); + PublicKeyHash::from_script_bytes(&witnet_stack::encode(redeem_script)?); println!( "Sending to {}-of-{} multisig address {} composed of {:?}", @@ -778,7 +778,7 @@ pub fn create_opened_multisig( Item::Value(MyValue::Integer(i128::from(n))), Item::Operator(MyOperator::CheckMultiSig), ]); - let redeem_script_bytes = witnet_stack::encode(redeem_script); + let redeem_script_bytes = witnet_stack::encode(redeem_script)?; let vt_outputs = vec![ValueTransferOutput { pkh: address, value, @@ -854,13 +854,13 @@ pub fn sign_tx(addr: SocketAddr, hex: String, dry_run: bool) -> Result<(), failu match tx { Transaction::ValueTransfer(ref mut vtt) => { let signature_bytes = signature.to_pb_bytes()?; - let mut script = witnet_stack::decode(&vtt.witness[0]); + let mut script = witnet_stack::decode(&vtt.witness[0])?; println!("Previous script:\n{:?}", script); script.push(Item::Value(MyValue::Signature(signature_bytes))); println!("Post script:\n{:?}", script); - let encoded_script = witnet_stack::encode(script); + let encoded_script = witnet_stack::encode(script)?; vtt.witness[0] = encoded_script; diff --git a/stack/src/lib.rs b/stack/src/lib.rs index c27b4b280..4ae043472 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -3,6 +3,7 @@ use scriptful::{ prelude::Stack, }; use serde::{Deserialize, Serialize}; +use std::fmt::Formatter; use std::marker::PhantomData; use witnet_crypto::hash::{calculate_sha256, Sha256}; @@ -364,15 +365,17 @@ where } } -pub fn decode(a: &[u8]) -> Script { - let x: Vec> = serde_json::from_slice(a).unwrap(); +pub fn decode(a: &[u8]) -> Result, ScriptError> { + let x: Vec> = + serde_json::from_slice(a).map_err(ScriptError::Decode)?; - x.into_iter().map(Into::into).collect() + Ok(x.into_iter().map(Into::into).collect()) } -pub fn encode(a: Script) -> Vec { +pub fn encode(a: Script) -> Result, ScriptError> { let x: Vec> = a.into_iter().map(Into::into).collect(); - serde_json::to_vec(&x).unwrap() + + serde_json::to_vec(&x).map_err(ScriptError::Encode) } #[derive(Default)] @@ -380,6 +383,23 @@ pub struct ScriptContext { pub block_timestamp: i64, } +#[derive(Debug)] +pub enum ScriptError { + Decode(serde_json::Error), + Encode(serde_json::Error), +} + +impl std::fmt::Display for ScriptError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ScriptError::Decode(e) => write!(f, "Decode script failed: {}", e), + ScriptError::Encode(e) => write!(f, "Encode script failed: {}", e), + } + } +} + +impl std::error::Error for ScriptError {} + fn execute_script(script: Script, context: &ScriptContext) -> bool { // Instantiate the machine with a reference to your operator system. let mut machine = Machine2::new(|a, b, c| my_operator_system(a, b, c, context)); @@ -411,14 +431,14 @@ fn execute_redeem_script( witness_bytes: &[u8], redeem_bytes: &[u8], context: &ScriptContext, -) -> bool { +) -> Result { // Execute witness script concatenated with redeem script - let mut witness_script = decode(witness_bytes); - let redeem_script = decode(redeem_bytes); + let mut witness_script = decode(witness_bytes)?; + let redeem_script = decode(redeem_bytes)?; witness_script.extend(redeem_script); // Execute the script - execute_script(witness_script, context) + Ok(execute_script(witness_script, context)) } pub fn execute_complete_script( @@ -426,11 +446,11 @@ pub fn execute_complete_script( redeem_bytes: &[u8], locking_bytes: &[u8; 20], context: &ScriptContext, -) -> bool { +) -> Result { // Execute locking script let result = execute_locking_script(redeem_bytes, locking_bytes, context); if !result { - return false; + return Ok(false); } // Execute witness script concatenated with redeem script @@ -531,7 +551,7 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script = EQUAL_OPERATOR_HASH; assert!(execute_locking_script( - &encode(redeem_script), + &encode(redeem_script).unwrap(), &locking_script, &ScriptContext::default(), )); @@ -539,7 +559,7 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script = [1; 20]; assert!(!execute_locking_script( - &encode(redeem_script), + &encode(redeem_script).unwrap(), &locking_script, &ScriptContext::default(), )); @@ -553,10 +573,11 @@ mod tests { ]; let redeem_script = vec![Item::Operator(MyOperator::Equal)]; assert!(execute_redeem_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &ScriptContext::default(), - )); + ) + .unwrap()); let witness = vec![ Item::Value(MyValue::String("patata".to_string())), @@ -564,10 +585,11 @@ mod tests { ]; let redeem_script = vec![Item::Operator(MyOperator::Equal)]; assert!(!execute_redeem_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &ScriptContext::default(), - )); + ) + .unwrap()); } #[test] @@ -579,11 +601,12 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script = EQUAL_OPERATOR_HASH; assert!(execute_complete_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &locking_script, &ScriptContext::default(), - )); + ) + .unwrap()); let witness = vec![ Item::Value(MyValue::String("patata".to_string())), @@ -592,11 +615,12 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script = EQUAL_OPERATOR_HASH; assert!(!execute_complete_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &locking_script, &ScriptContext::default(), - )); + ) + .unwrap()); let witness = vec![ Item::Value(MyValue::String("patata".to_string())), @@ -605,11 +629,12 @@ mod tests { let redeem_script = vec![Item::Operator(MyOperator::Equal)]; let locking_script: [u8; 20] = [1; 20]; assert!(!execute_complete_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &locking_script, &ScriptContext::default(), - )); + ) + .unwrap()); } fn ks_from_pk(pk: PublicKey) -> KeyedSignature { @@ -641,10 +666,11 @@ mod tests { Item::Operator(MyOperator::CheckMultiSig), ]; assert!(execute_redeem_script( - &encode(witness), - &encode(redeem_script), + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), &ScriptContext::default(), - )); + ) + .unwrap()); let other_valid_witness = vec![ Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap())), @@ -659,10 +685,11 @@ mod tests { Item::Operator(MyOperator::CheckMultiSig), ]; assert!(execute_redeem_script( - &encode(other_valid_witness), - &encode(redeem_script), + &encode(other_valid_witness).unwrap(), + &encode(redeem_script).unwrap(), &ScriptContext::default(), - )); + ) + .unwrap()); let pk_4 = PublicKey::from_bytes([4; 33]); let ks_4 = ks_from_pk(pk_4); @@ -679,10 +706,11 @@ mod tests { Item::Operator(MyOperator::CheckMultiSig), ]; assert!(!execute_redeem_script( - &encode(invalid_witness), - &encode(redeem_script), + &encode(invalid_witness).unwrap(), + &encode(redeem_script).unwrap(), &ScriptContext::default(), - )); + ) + .unwrap()); } #[test] diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 9cba095ad..6b1ea565b 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -1274,7 +1274,7 @@ pub fn validate_transaction_signatures( &input.redeem_script, redeem_script_hash.bytes(), &script_context, - ); + )?; if !res { return Err(TransactionError::ScriptExecutionFailed { @@ -1285,7 +1285,7 @@ pub fn validate_transaction_signatures( .into()); } - let witness_script = witnet_stack::decode(witness); + let witness_script = witnet_stack::decode(witness)?; let mut num_signatures = 0; // The witness field must have at least one signature for item in witness_script { From 85dc1dd9656d37e038dd876f6579b808434dfbc9 Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Mon, 13 Jun 2022 16:34:50 +0200 Subject: [PATCH 06/10] feat(node): avoid unused call to signature_mngr in BuildScriptTx --- node/src/actors/chain_manager/handlers.rs | 28 ++++++++--------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/node/src/actors/chain_manager/handlers.rs b/node/src/actors/chain_manager/handlers.rs index e8505b2b6..693b9584d 100644 --- a/node/src/actors/chain_manager/handlers.rs +++ b/node/src/actors/chain_manager/handlers.rs @@ -1306,25 +1306,17 @@ impl Handler for ChainManager { Box::pin(actix::fut::err(e.into())) } Ok(vtt) => { - let fut = signature_mngr::sign_transaction(&vtt, vtt.inputs.len()) - .into_actor(self) - .then(|s, _act, _ctx| match s { - Ok(_signatures) => { - let multi_sig_witness = witnet_stack::encode(vec![]).unwrap(); - let num_inputs = vtt.inputs.len(); - let transaction = Transaction::ValueTransfer(VTTransaction { - body: vtt, - witness: vec![multi_sig_witness; num_inputs], - }); - actix::fut::result(Ok(transaction)) - } - Err(e) => { - log::error!("Failed to sign value transfer transaction: {}", e); - actix::fut::result(Err(e)) - } - }); + // Script transactions are not signed by this method because the witness may need + // something more aside from a single signature, so script transactions need to be + // manually signed using other methods. + let empty_witness = witnet_stack::encode(vec![]).unwrap(); + let num_inputs = vtt.inputs.len(); + let transaction = Transaction::ValueTransfer(VTTransaction { + body: vtt, + witness: vec![empty_witness; num_inputs], + }); - Box::pin(fut) + Box::pin(actix::fut::result(Ok(transaction))) } } } From 9eb710e77d345935bf08a43b8f62430929eef7ef Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Mon, 13 Jun 2022 17:08:40 +0200 Subject: [PATCH 07/10] feat(stack): remove MyControlFlow enum, use simple Result instead --- stack/src/lib.rs | 80 ++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 4ae043472..27555957a 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -176,7 +176,7 @@ fn my_operator_system( operator: &MyOperator, if_stack: &mut ConditionStack, context: &ScriptContext, -) -> MyControlFlow { +) -> Result<(), ()> { if !if_stack.all_true() { match operator { MyOperator::If => { @@ -184,20 +184,18 @@ fn my_operator_system( } MyOperator::Else => { if if_stack.toggle_top().is_none() { - stack.push(MyValue::Boolean(false)); - return MyControlFlow::Break; + return Err(()); } } MyOperator::EndIf => { if if_stack.pop_back().is_none() { - stack.push(MyValue::Boolean(false)); - return MyControlFlow::Break; + return Err(()); } } _ => {} } - return MyControlFlow::Continue; + return Ok(()); } match operator { @@ -209,11 +207,7 @@ fn my_operator_system( MyOperator::Verify => { let top = stack.pop(); if top != MyValue::Boolean(true) { - // Push the element back because there is a check in execute_script that needs a - // false value to mark the script execution as failed, otherwise it may be marked as - // success - stack.push(top); - return MyControlFlow::Break; + return Err(()); } } MyOperator::If => { @@ -221,25 +215,22 @@ fn my_operator_system( if let MyValue::Boolean(b) = top { if_stack.push_back(b); } else { - stack.push(MyValue::Boolean(false)); - return MyControlFlow::Break; + return Err(()); } } MyOperator::Else => { if if_stack.toggle_top().is_none() { - stack.push(MyValue::Boolean(false)); - return MyControlFlow::Break; + return Err(()); } } MyOperator::EndIf => { if if_stack.pop_back().is_none() { - stack.push(MyValue::Boolean(false)); - return MyControlFlow::Break; + return Err(()); } } } - MyControlFlow::Continue + Ok(()) } // ConditionStack implementation from bitcoin-core @@ -405,7 +396,17 @@ fn execute_script(script: Script, context: &ScriptContext) let mut machine = Machine2::new(|a, b, c| my_operator_system(a, b, c, context)); let result = machine.run_script(&script); - result == None || result == Some(&MyValue::Boolean(true)) + match result { + Ok(res) => { + // Script execution is considered successful if the stack is empty or if the top-most + // element of the stack is "true". + res == None || res == Some(&MyValue::Boolean(true)) + } + Err(_e) => { + // TODO: return cause of script execution failure + false + } + } } fn execute_locking_script( @@ -457,16 +458,10 @@ pub fn execute_complete_script( execute_redeem_script(witness_bytes, redeem_bytes, context) } -// TODO: use control flow enum from scriptful library when ready -pub enum MyControlFlow { - Continue, - Break, -} - -pub struct Machine2 +pub struct Machine2 where Val: core::fmt::Debug + core::cmp::PartialEq, - F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, + F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> Result<(), E>, { op_sys: F, stack: Stack, @@ -474,11 +469,11 @@ where phantom_op: PhantomData, } -impl Machine2 +impl Machine2 where Op: core::fmt::Debug + core::cmp::Eq, Val: core::fmt::Debug + core::cmp::PartialEq + core::clone::Clone, - F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> MyControlFlow, + F: FnMut(&mut Stack, &Op, &mut ConditionStack) -> Result<(), E>, { pub fn new(op_sys: F) -> Self { Self { @@ -489,7 +484,7 @@ where } } - pub fn operate(&mut self, item: &Item) -> MyControlFlow { + pub fn operate(&mut self, item: &Item) -> Result, E> { match item { Item::Operator(operator) => { (self.op_sys)(&mut self.stack, operator, &mut self.if_stack) @@ -499,24 +494,18 @@ where self.stack.push((*value).clone()); } - MyControlFlow::Continue + Ok(()) } } + .map(|()| self.stack.topmost()) } - pub fn run_script(&mut self, script: ScriptRef) -> Option<&Val> { + pub fn run_script(&mut self, script: ScriptRef) -> Result, E> { for item in script { - match self.operate(item) { - MyControlFlow::Continue => { - continue; - } - MyControlFlow::Break => { - break; - } - } + self.operate(item)?; } - self.stack.topmost() + Ok(self.stack.topmost()) } } @@ -806,9 +795,14 @@ mod tests { let mut m = Machine2::new(|_stack: &mut Stack<()>, operator, _if_stack| { v.push(*operator); - MyControlFlow::Continue + if *operator == 0 { + return Err(()); + } + + Ok(()) }); - m.run_script(&[Item::Operator(1), Item::Operator(3), Item::Operator(2)]); + m.run_script(&[Item::Operator(1), Item::Operator(3), Item::Operator(2)]) + .unwrap(); assert_eq!(v, vec![0, 1, 3, 2]); } From 0ff9da4a39d36a1ee6532ece163b6fd910cf89c3 Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Tue, 14 Jun 2022 17:03:10 +0200 Subject: [PATCH 08/10] feat(stack): implement CheckSig operator --- stack/src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 27555957a..61d371596 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -21,6 +21,7 @@ pub enum MyOperator { Equal, Hash160, Sha256, + CheckSig, CheckMultiSig, CheckTimeLock, /// Stop script execution if top-most element of stack is not "true" @@ -75,6 +76,14 @@ fn sha_256_operator(stack: &mut Stack) { } } +fn check_sig_operator(stack: &mut Stack) { + let pkh = stack.pop(); + let keyed_signature = stack.pop(); + // CheckSig operator is validated as a 1-of-1 multisig + let res = check_multi_sig(vec![pkh], vec![keyed_signature]); + stack.push(MyValue::Boolean(res)); +} + fn check_multisig_operator(stack: &mut Stack) { let m = stack.pop(); match m { @@ -202,6 +211,7 @@ fn my_operator_system( MyOperator::Equal => equal_operator(stack), MyOperator::Hash160 => hash_160_operator(stack), MyOperator::Sha256 => sha_256_operator(stack), + MyOperator::CheckSig => check_sig_operator(stack), MyOperator::CheckMultiSig => check_multisig_operator(stack), MyOperator::CheckTimeLock => check_timelock_operator(stack, context.block_timestamp), MyOperator::Verify => { @@ -632,6 +642,40 @@ mod tests { public_key: pk, } } + + #[test] + fn test_check_sig() { + let pk_1 = PublicKey::from_bytes([1; 33]); + let pk_2 = PublicKey::from_bytes([2; 33]); + + let ks_1 = ks_from_pk(pk_1.clone()); + let ks_2 = ks_from_pk(pk_2); + + let witness = vec![Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap()))]; + let redeem_script = vec![ + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + ]; + assert!(execute_redeem_script( + &encode(witness).unwrap(), + &encode(redeem_script).unwrap(), + &ScriptContext::default(), + ) + .unwrap()); + + let invalid_witness = vec![Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap()))]; + let redeem_script = vec![ + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + ]; + assert!(!execute_redeem_script( + &encode(invalid_witness).unwrap(), + &encode(redeem_script).unwrap(), + &ScriptContext::default(), + ) + .unwrap()); + } + #[test] fn test_check_multisig() { let pk_1 = PublicKey::from_bytes([1; 33]); From c96a82e9b806ff5f2c64a9166a3aa48cc0333cdc Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Wed, 15 Jun 2022 12:45:39 +0200 Subject: [PATCH 09/10] test(stack): add atomic swap use case test --- stack/src/lib.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 61d371596..77f26775f 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -872,4 +872,122 @@ mod tests { } )); } + + #[test] + fn test_execute_script_atomic_swap() { + let secret = vec![1, 2, 3, 4]; + let hash_secret = calculate_sha256(&secret); + let pk_1 = PublicKey::from_bytes([1; 33]); + let pk_2 = PublicKey::from_bytes([2; 33]); + + let ks_1 = ks_from_pk(pk_1.clone()); + let ks_2 = ks_from_pk(pk_2.clone()); + + // 1 can spend after timelock + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap())), + Item::Value(MyValue::Boolean(true)), + // Redeem script + Item::Operator(MyOperator::If), + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(execute_script( + s, + &ScriptContext { + block_timestamp: 20_000 + } + )); + + // 1 cannot spend before timelock + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap())), + Item::Value(MyValue::Boolean(true)), + // Redeem script + Item::Operator(MyOperator::If), + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); + + // 2 can spend with secret + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap())), + Item::Value(MyValue::Bytes(secret)), + Item::Value(MyValue::Boolean(false)), + // Redeem script + Item::Operator(MyOperator::If), + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(execute_script(s, &ScriptContext { block_timestamp: 0 })); + + // 2 cannot spend with a wrong secret + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap())), + Item::Value(MyValue::Bytes(vec![0, 0, 0, 0])), + Item::Value(MyValue::Boolean(false)), + // Redeem script + Item::Operator(MyOperator::If), + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); + } } From b55709075598abc5176fa99c10c89a4a92f22718 Mon Sep 17 00:00:00 2001 From: Tomasz Polaczyk Date: Mon, 20 Jun 2022 15:26:51 +0200 Subject: [PATCH 10/10] test(stack): add another atomic swap test case --- stack/src/lib.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/stack/src/lib.rs b/stack/src/lib.rs index 77f26775f..400d4b5ec 100644 --- a/stack/src/lib.rs +++ b/stack/src/lib.rs @@ -129,7 +129,8 @@ fn check_multi_sig(bytes_pkhs: Vec, bytes_keyed_signatures: Vec { // TODO change panic by error - unreachable!("check_multi_sig should pick only bytes"); + //unreachable!("check_multi_sig should pick only bytes"); + return false; } } } @@ -990,4 +991,143 @@ mod tests { ]; assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); } + + #[test] + fn test_execute_script_atomic_swap_2() { + let secret = vec![1, 2, 3, 4]; + let hash_secret = calculate_sha256(&secret); + let pk_1 = PublicKey::from_bytes([1; 33]); + let pk_2 = PublicKey::from_bytes([2; 33]); + + let ks_1 = ks_from_pk(pk_1.clone()); + let ks_2 = ks_from_pk(pk_2.clone()); + + // 1 can spend after timelock + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap())), + // Redeem script + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::If), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(execute_script( + s, + &ScriptContext { + block_timestamp: 20_000 + } + )); + + // 1 cannot spend before timelock + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_1.to_pb_bytes().unwrap())), + // Redeem script + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::If), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); + + // 2 can spend with secret + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap())), + Item::Value(MyValue::Bytes(secret.clone())), + // Redeem script + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::If), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(execute_script(s, &ScriptContext { block_timestamp: 0 })); + + // 2 cannot spend with a wrong secret + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap())), + Item::Value(MyValue::Bytes(vec![0, 0, 0, 0])), + // Redeem script + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::If), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(!execute_script(s, &ScriptContext { block_timestamp: 0 })); + + // 2 cannot spend after timelock + let s = vec![ + // Witness script + Item::Value(MyValue::Signature(ks_2.to_pb_bytes().unwrap())), + Item::Value(MyValue::Bytes(secret)), + // Redeem script + Item::Value(MyValue::Integer(10_000)), + Item::Operator(MyOperator::CheckTimeLock), + Item::Operator(MyOperator::If), + Item::Value(MyValue::Bytes(pk_1.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::Else), + Item::Operator(MyOperator::Sha256), + Item::Value(MyValue::Bytes(hash_secret.as_ref().to_vec())), + Item::Operator(MyOperator::Equal), + Item::Operator(MyOperator::Verify), + Item::Value(MyValue::Bytes(pk_2.pkh().bytes().to_vec())), + Item::Operator(MyOperator::CheckSig), + Item::Operator(MyOperator::Verify), + Item::Operator(MyOperator::EndIf), + ]; + assert!(!execute_script( + s, + &ScriptContext { + block_timestamp: 20_000 + } + )); + } }