From c647c06a8d4ed60e8c48dad303f7f915e22270f6 Mon Sep 17 00:00:00 2001 From: Fabio Ferrari Date: Mon, 19 Feb 2024 09:58:08 -0300 Subject: [PATCH] solve script matches with templates using op_if --- src/script/mod.rs | 96 +++++++++++++++++++++-------------- src/script/script_template.rs | 15 +++--- tests/script.rs | 10 ++++ tests/script_template.rs | 9 ++++ 4 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/script/mod.rs b/src/script/mod.rs index 06fd9e7..d1203f9 100644 --- a/src/script/mod.rs +++ b/src/script/mod.rs @@ -25,13 +25,65 @@ pub use script_template::*; #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Script(pub(crate) Vec); +pub struct ScriptIter<'a> { + scriptbit_iter: Vec>, +} + +impl<'a> Iterator for ScriptIter<'a> { + type Item = &'a ScriptBit; + + fn next(&mut self) -> Option<&'a ScriptBit> { + let next = loop { + match self.scriptbit_iter.last_mut() { + None => return None, + Some(last) => { + match last.next() { + Some(next) => break next, + _ => () + } + } + } + self.scriptbit_iter.pop(); + }; + match next { + ScriptBit::If { code, pass, fail } => { + static SCRIPTBITS_ENDIF: &'static [ScriptBit; 1] = &[ScriptBit::OpCode(OpCodes::OP_ENDIF)]; + self.scriptbit_iter.push(SCRIPTBITS_ENDIF.iter()); + if let Some(fail) = fail { + self.scriptbit_iter.push(fail.iter()); + static SCRIPTBITS_ELSE: &'static [ScriptBit; 1] = &[ScriptBit::OpCode(OpCodes::OP_ELSE)]; + self.scriptbit_iter.push(SCRIPTBITS_ELSE.iter()); + } + self.scriptbit_iter.push(pass.iter()); + match *code { + OpCodes::OP_IF => Some(&ScriptBit::OpCode(OpCodes::OP_IF)), + OpCodes::OP_NOTIF => Some(&ScriptBit::OpCode(OpCodes::OP_NOTIF)), + OpCodes::OP_VERIF => Some(&ScriptBit::OpCode(OpCodes::OP_VERIF)), + OpCodes::OP_VERNOTIF => Some(&ScriptBit::OpCode(OpCodes::OP_VERNOTIF)), + _ => Some(&ScriptBit::OpCode(OpCodes::OP_NOP)) // error? + } + }, + x => Some(x) + } + } +} + /** * Serialise Methods */ impl Script { + fn script_bits_iter(codes: &[ScriptBit]) -> ScriptIter { + ScriptIter { + scriptbit_iter: vec!(codes.iter()), + } + } + + pub fn iter(&self) -> ScriptIter { + Script::script_bits_iter(&self.0) + } + fn script_bits_to_asm_string(codes: &[ScriptBit], extended: bool) -> String { - codes - .iter() + Script::script_bits_iter(codes) .map(|x| match x { ScriptBit::OpCode(OP_0) => match extended { true => OP_0.to_string(), @@ -46,28 +98,7 @@ impl Script { false => hex::encode(bytes), }, ScriptBit::OpCode(code) => code.to_string(), - ScriptBit::If { code, pass, fail } => { - let mut string_parts = vec![]; - - string_parts.push(code.to_string()); - - let pass_string = Script::script_bits_to_asm_string(pass, extended); - if !pass_string.is_empty() { - string_parts.push(pass_string); - } - - if let Some(fail) = fail { - string_parts.push(OpCodes::OP_ELSE.to_string()); - let fail_string = Script::script_bits_to_asm_string(fail, extended); - if !fail_string.is_empty() { - string_parts.push(fail_string); - } - } - - string_parts.push(OpCodes::OP_ENDIF.to_string()); - - string_parts.join(" ") - } + ScriptBit::If { code:_, pass:_, fail:_ } => "".to_string(), // "iter" removes ScriptBit::If ScriptBit::Coinbase(bytes) => hex::encode(bytes), }) .collect::>() @@ -75,8 +106,7 @@ impl Script { } pub fn script_bits_to_bytes(codes: &[ScriptBit]) -> Vec { - let bytes = codes - .iter() + let bytes = Script::script_bits_iter(codes) .flat_map(|x| match x { ScriptBit::OpCode(code) => vec![*code as u8], ScriptBit::Push(bytes) => { @@ -96,19 +126,7 @@ impl Script { pushbytes.extend(bytes); pushbytes } - ScriptBit::If { code, pass, fail } => { - let mut bytes = vec![*code as u8]; - - bytes.extend_from_slice(&Script::script_bits_to_bytes(pass)); - - if let Some(fail) = fail { - bytes.push(OpCodes::OP_ELSE as u8); - bytes.extend_from_slice(&Script::script_bits_to_bytes(fail)); - } - bytes.push(OpCodes::OP_ENDIF as u8); - - bytes - } + ScriptBit::If { code:_, pass:_, fail:_ } => vec!(), // "iter" removes ScriptBit::If ScriptBit::Coinbase(bytes) => bytes.to_vec(), }) .collect(); diff --git a/src/script/script_template.rs b/src/script/script_template.rs index 95ad202..5a74427 100644 --- a/src/script/script_template.rs +++ b/src/script/script_template.rs @@ -166,13 +166,16 @@ impl ScriptTemplate { */ impl Script { pub fn match_impl(&self, script_template: &ScriptTemplate) -> Result)>, ScriptTemplateErrors> { - if self.0.len() != script_template.0.len() { - return Err(ScriptTemplateErrors::LengthsDiffer); - } - let mut matches = vec![]; - - for (i, (template, script)) in script_template.0.iter().zip(self.0.iter()).enumerate() { + let mut script_iter = self.iter(); + let mut template_iter = script_template.0.iter().enumerate(); + + loop { + let (i, template, script) = match (template_iter.next(), script_iter.next()) { + (Some((i,t)),Some(s)) => (i,t,s), + (None,None) => break, + _ => return Err(ScriptTemplateErrors::LengthsDiffer) + }; let is_match = match (template, script) { (MatchToken::OpCode(tmpl_code), ScriptBit::OpCode(op_code)) => Ok(tmpl_code == op_code), (MatchToken::Push(tmpl_data), ScriptBit::Push(data)) => Ok(*tmpl_data == *data), diff --git a/tests/script.rs b/tests/script.rs index 670ac60..23e8d1c 100644 --- a/tests/script.rs +++ b/tests/script.rs @@ -519,4 +519,14 @@ mod script_tests { assert_eq!(&script.to_asm_string(), "OP_RETURN 0 01 0 00 00 00 00 00 00 00 00 00000000") } + + #[test] + fn byte_serialization_with_if() { + let asm = "OP_1 OP_IF OP_1 OP_ELSE OP_0 OP_ENDIF OP_DROP"; + let script = Script::from_asm_string(&asm).unwrap(); + let script_bytes = script.to_bytes(); + let script2 = Script::from_bytes(&script_bytes).unwrap(); + let script2_asm = script2.to_extended_asm_string(); + assert_eq!(asm, script2_asm); + } } diff --git a/tests/script_template.rs b/tests/script_template.rs index 3521bdb..fd208a3 100644 --- a/tests/script_template.rs +++ b/tests/script_template.rs @@ -14,6 +14,15 @@ mod script_template_tests { assert_eq!(script.is_match(&script_template), false); } + #[test] + fn op_if_does_match_template() { + let asm = "OP_1 OP_IF OP_1 OP_ELSE OP_0 OP_ENDIF OP_DROP"; + let script = Script::from_asm_string(&asm).unwrap(); + let script_template = ScriptTemplate::from_asm_string(&asm).unwrap(); + println!("ref {:?} {:?}", script, script_template); + assert_eq!(script.is_match(&script_template), true); + } + #[test] fn exact_script_template_matches_script_without_extracting_data() { let script =