1use crate::OpCodes::OP_0;
2use std::str::FromStr;
3
4use crate::{BSVErrors, OpCodes, PublicKey, Script, ScriptBit, Signature, VarInt};
5use hex::FromHexError;
6use num_traits::FromPrimitive;
7use serde::{Deserialize, Serialize};
8use strum_macros::Display;
9use thiserror::Error;
10
11#[cfg(target_arch = "wasm32")]
12use wasm_bindgen::{prelude::*, throw_str, JsValue};
13
14#[derive(Debug, Error)]
15pub enum ScriptTemplateErrors {
16 #[error("Script did not match template at index {0}. {2} is not equal to {1:?}. Error: {3:?}")]
17 MatchFailure(usize, MatchToken, ScriptBit, BSVErrors),
18
19 #[error("Failed to parse OP_DATA code {0}: {1}")]
20 OpDataParse(String, String),
21
22 #[error("Script Template and Script lengths do not match.")]
23 LengthsDiffer,
24
25 #[error("{0}")]
26 MalformedHex(
27 #[from]
28 #[source]
29 FromHexError,
30 ),
31}
32
33#[cfg_attr(all(target_arch = "wasm32"), wasm_bindgen)]
34#[derive(Debug, Clone, Display)]
35pub enum DataLengthConstraints {
36 Equals,
37 GreaterThan,
38 LessThan,
39 GreaterThanOrEquals,
40 LessThanOrEquals,
41}
42
43#[derive(Debug, Clone, Display)]
44pub enum MatchToken {
45 OpCode(OpCodes),
47 Push(Vec<u8>),
48 PushData(OpCodes, Vec<u8>),
49
50 AnyData,
52 Data(usize, DataLengthConstraints),
53 Signature,
54 PublicKey,
55 PublicKeyHash,
56}
57
58#[cfg_attr(all(target_arch = "wasm32"), wasm_bindgen)]
59#[derive(Debug, Clone, Display, Serialize, Deserialize)]
60pub enum MatchDataTypes {
61 Data,
62 Signature,
63 PublicKey,
64 PublicKeyHash,
65}
66
67#[cfg_attr(all(target_arch = "wasm32"), wasm_bindgen)]
68#[derive(Debug, Clone)]
69pub struct ScriptTemplate(Vec<MatchToken>);
70
71impl ScriptTemplate {
72 fn map_string_to_match_token(code: &str) -> Result<MatchToken, ScriptTemplateErrors> {
73 if let Ok(num_code) = u8::from_str(code) {
75 match num_code {
76 0 => return Ok(MatchToken::OpCode(OP_0)),
77 v @ 1..=16 => return Ok(MatchToken::OpCode(OpCodes::from_u8(v + 80).unwrap())),
78 _ => (),
79 }
80 }
81
82 match OpCodes::from_str(code) {
84 Ok(OpCodes::OP_SIG) => return Ok(MatchToken::Signature),
85 Ok(OpCodes::OP_PUBKEY) => return Ok(MatchToken::PublicKey),
86 Ok(OpCodes::OP_PUBKEYHASH) => return Ok(MatchToken::PublicKeyHash),
87 Ok(OpCodes::OP_DATA) => return Ok(MatchToken::AnyData),
88
89 Ok(v) => return Ok(MatchToken::OpCode(v)),
90 Err(_) => (),
91 }
92
93 if code.starts_with(&OpCodes::OP_DATA.to_string()) {
94 if let Some((_, length_str)) = code.split_once(">=") {
96 let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
97 return Ok(MatchToken::Data(len, DataLengthConstraints::GreaterThanOrEquals));
98 }
99
100 if let Some((_, length_str)) = code.split_once("<=") {
102 let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
103 return Ok(MatchToken::Data(len, DataLengthConstraints::LessThanOrEquals));
104 }
105
106 if let Some((_, length_str)) = code.split_once('=') {
108 let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
109 return Ok(MatchToken::Data(len, DataLengthConstraints::Equals));
110 }
111
112 if let Some((_, length_str)) = code.split_once('>') {
114 let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
115 return Ok(MatchToken::Data(len, DataLengthConstraints::GreaterThan));
116 }
117
118 if let Some((_, length_str)) = code.split_once('<') {
120 let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
121 return Ok(MatchToken::Data(len, DataLengthConstraints::LessThan));
122 }
123 }
124
125 let data_bytes = hex::decode(code)?;
127 let token = match VarInt::get_pushdata_opcode(data_bytes.len() as u64) {
128 Some(v) => MatchToken::PushData(v, data_bytes),
129 None => MatchToken::Push(data_bytes),
130 };
131
132 Ok(token)
133 }
134
135 pub fn from_script_impl(script: &Script) -> Result<ScriptTemplate, ScriptTemplateErrors> {
136 ScriptTemplate::from_asm_string_impl(&script.to_asm_string_impl(false))
137 }
138
139 pub fn from_asm_string_impl(asm: &str) -> Result<ScriptTemplate, ScriptTemplateErrors> {
140 let tokens: Result<Vec<_>, _> = asm.split(' ').map(ScriptTemplate::map_string_to_match_token).collect();
141
142 Ok(ScriptTemplate(tokens?))
143 }
144}
145
146#[cfg(not(feature = "wasm-bindgen-script-template"))]
147impl ScriptTemplate {
148 pub fn from_script(script: &Script) -> Result<ScriptTemplate, ScriptTemplateErrors> {
149 ScriptTemplate::from_script_impl(script)
150 }
151
152 pub fn from_asm_string(asm: &str) -> Result<ScriptTemplate, ScriptTemplateErrors> {
153 ScriptTemplate::from_asm_string_impl(asm)
154 }
155}
156
157#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen-script-template"))]
158#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-script-template"), wasm_bindgen)]
159impl ScriptTemplate {
160 pub fn from_script(script: &Script) -> Result<ScriptTemplate, JsValue> {
161 match ScriptTemplate::from_script_impl(script) {
162 Ok(v) => Ok(v),
163 Err(e) => Err(JsValue::from_str(&e.to_string())),
164 }
165 }
166
167 pub fn from_asm_string(asm: &str) -> Result<ScriptTemplate, JsValue> {
168 match ScriptTemplate::from_asm_string_impl(asm) {
169 Ok(v) => Ok(v),
170 Err(e) => Err(JsValue::from_str(&e.to_string())),
171 }
172 }
173}
174
175impl Script {
179 pub fn match_impl(&self, script_template: &ScriptTemplate) -> Result<Vec<(MatchDataTypes, Vec<u8>)>, ScriptTemplateErrors> {
180 if self.0.len() != script_template.0.len() {
181 return Err(ScriptTemplateErrors::LengthsDiffer);
182 }
183
184 let mut matches = vec![];
185
186 for (i, (template, script)) in script_template.0.iter().zip(self.0.iter()).enumerate() {
187 let is_match = match (template, script) {
188 (MatchToken::OpCode(tmpl_code), ScriptBit::OpCode(op_code)) => Ok(tmpl_code == op_code),
189 (MatchToken::Push(tmpl_data), ScriptBit::Push(data)) => Ok(tmpl_data == data),
190 (MatchToken::PushData(tmpl_op, tmpl_data), ScriptBit::PushData(op, data)) => Ok(tmpl_op == op && tmpl_data == data),
191
192 (MatchToken::Data(len, constraint), ScriptBit::PushData(_, data) | ScriptBit::Push(data)) => match constraint {
193 DataLengthConstraints::Equals => Ok(&data.len() == len),
194 DataLengthConstraints::GreaterThan => Ok(&data.len() > len),
195 DataLengthConstraints::LessThan => Ok(&data.len() < len),
196 DataLengthConstraints::GreaterThanOrEquals => Ok(&data.len() >= len),
197 DataLengthConstraints::LessThanOrEquals => Ok(&data.len() <= len),
198 },
199
200 (MatchToken::AnyData, ScriptBit::Push(_)) => Ok(true),
201 (MatchToken::AnyData, ScriptBit::PushData(_, _)) => Ok(true),
202
203 (MatchToken::Signature, ScriptBit::Push(sig_buf)) => Signature::from_der_impl(sig_buf).map(|_| true),
204
205 (MatchToken::PublicKey, ScriptBit::Push(pubkey_buf)) => PublicKey::from_bytes_impl(pubkey_buf).map(|_| true),
206
207 (MatchToken::PublicKeyHash, ScriptBit::Push(pubkeyhash_buf)) => Ok(pubkeyhash_buf.len() == 20), _ => Ok(false),
210 };
211
212 match is_match {
213 Ok(_) => (),
214 Err(e) => {
215 return Err(ScriptTemplateErrors::MatchFailure(i, template.clone(), script.clone(), e));
216 }
217 }
218
219 match (template, script) {
221 (MatchToken::Data(_, _), ScriptBit::PushData(_, data) | ScriptBit::Push(data)) => matches.push((MatchDataTypes::Data, data.clone())),
222
223 (MatchToken::AnyData, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Data, data.clone())),
224 (MatchToken::AnyData, ScriptBit::PushData(_, data)) => matches.push((MatchDataTypes::Data, data.clone())),
225
226 (MatchToken::Signature, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Signature, data.clone())),
227
228 (MatchToken::PublicKey, ScriptBit::Push(data)) => matches.push((MatchDataTypes::PublicKey, data.clone())),
229
230 (MatchToken::PublicKeyHash, ScriptBit::Push(data)) => matches.push((MatchDataTypes::PublicKeyHash, data.clone())), _ => (),
232 }
233 }
234
235 Ok(matches)
236 }
237
238 pub fn test_impl(&self, script_template: &ScriptTemplate) -> bool {
239 self.match_impl(script_template).is_ok()
240 }
241}
242
243#[cfg(not(feature = "wasm-bindgen-script-template"))]
244impl Script {
245 pub fn matches(&self, script_template: &ScriptTemplate) -> Result<Vec<(MatchDataTypes, Vec<u8>)>, ScriptTemplateErrors> {
268 self.match_impl(script_template)
269 }
270
271 pub fn is_match(&self, script_template: &ScriptTemplate) -> bool {
275 self.test_impl(script_template)
276 }
277}
278
279#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen-script-template"))]
280#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-script-template"), wasm_bindgen)]
281impl Script {
282 pub fn matches(&self, script_template: &ScriptTemplate) -> Result<JsValue, JsValue> {
287 let matches = match self.match_impl(script_template) {
288 Ok(v) => v,
289 Err(e) => return Err(JsValue::from_str(&e.to_string())),
290 };
291
292 match JsValue::from_serde(&matches) {
293 Ok(v) => Ok(v),
294 Err(e) => Err(JsValue::from_str(&e.to_string())),
295 }
296 }
297
298 pub fn is_match(&self, script_template: &ScriptTemplate) -> bool {
303 self.test_impl(script_template)
304 }
305}