use crate::OpCodes::OP_0;
use std::str::FromStr;
use crate::{BSVErrors, OpCodes, PublicKey, Script, ScriptBit, Signature, VarInt};
use hex::FromHexError;
use num_traits::FromPrimitive;
use serde::{Deserialize, Serialize};
use strum_macros::Display;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ScriptTemplateErrors {
#[error("Script did not match template at index {0}. {2} is not equal to {1:?}. Error: {3:?}")]
MatchFailure(usize, MatchToken, ScriptBit, BSVErrors),
#[error("Failed to parse OP_DATA code {0}: {1}")]
OpDataParse(String, String),
#[error("Script Template and Script lengths do not match.")]
LengthsDiffer,
#[error("{0}")]
MalformedHex(
#[from]
#[source]
FromHexError,
),
}
#[derive(Debug, Clone, Display)]
pub enum DataLengthConstraints {
Equals,
GreaterThan,
LessThan,
GreaterThanOrEquals,
LessThanOrEquals,
}
#[derive(Debug, Clone, Display)]
pub enum MatchToken {
OpCode(OpCodes),
Push(Vec<u8>),
PushData(OpCodes, Vec<u8>),
AnyData,
Data(usize, DataLengthConstraints),
Signature,
PublicKey,
PublicKeyHash,
}
#[derive(Debug, Clone, Display, Serialize, Deserialize)]
pub enum MatchDataTypes {
Data,
Signature,
PublicKey,
PublicKeyHash,
}
#[derive(Debug, Clone)]
pub struct ScriptTemplate(Vec<MatchToken>);
impl ScriptTemplate {
fn map_string_to_match_token(code: &str) -> Result<MatchToken, ScriptTemplateErrors> {
if code.len() < 3 {
if let Ok(num_code) = u8::from_str(code) {
match num_code {
0 => return Ok(MatchToken::OpCode(OP_0)),
v @ 1..=16 => return Ok(MatchToken::OpCode(OpCodes::from_u8(v + 80).unwrap())),
_ => (),
}
}
}
match OpCodes::from_str(code) {
Ok(OpCodes::OP_SIG) => return Ok(MatchToken::Signature),
Ok(OpCodes::OP_PUBKEY) => return Ok(MatchToken::PublicKey),
Ok(OpCodes::OP_PUBKEYHASH) => return Ok(MatchToken::PublicKeyHash),
Ok(OpCodes::OP_DATA) => return Ok(MatchToken::AnyData),
Ok(v) => return Ok(MatchToken::OpCode(v)),
Err(_) => (),
}
if code.starts_with(&OpCodes::OP_DATA.to_string()) {
if let Some((_, length_str)) = code.split_once(">=") {
let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
return Ok(MatchToken::Data(len, DataLengthConstraints::GreaterThanOrEquals));
}
if let Some((_, length_str)) = code.split_once("<=") {
let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
return Ok(MatchToken::Data(len, DataLengthConstraints::LessThanOrEquals));
}
if let Some((_, length_str)) = code.split_once('=') {
let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
return Ok(MatchToken::Data(len, DataLengthConstraints::Equals));
}
if let Some((_, length_str)) = code.split_once('>') {
let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
return Ok(MatchToken::Data(len, DataLengthConstraints::GreaterThan));
}
if let Some((_, length_str)) = code.split_once('<') {
let len = usize::from_str(length_str).map_err(|e| ScriptTemplateErrors::OpDataParse(code.to_string(), e.to_string()))?;
return Ok(MatchToken::Data(len, DataLengthConstraints::LessThan));
}
}
let data_bytes = hex::decode(code)?;
let token = match VarInt::get_pushdata_opcode(data_bytes.len() as u64) {
Some(v) => MatchToken::PushData(v, data_bytes),
None => MatchToken::Push(data_bytes),
};
Ok(token)
}
pub fn from_script_impl(script: &Script) -> Result<ScriptTemplate, ScriptTemplateErrors> {
ScriptTemplate::from_asm_string_impl(&script.to_asm_string_impl(false))
}
pub fn from_asm_string_impl(asm: &str) -> Result<ScriptTemplate, ScriptTemplateErrors> {
let tokens: Result<Vec<_>, _> = asm.split(' ').map(ScriptTemplate::map_string_to_match_token).collect();
Ok(ScriptTemplate(tokens?))
}
}
impl ScriptTemplate {
pub fn from_script(script: &Script) -> Result<ScriptTemplate, ScriptTemplateErrors> {
ScriptTemplate::from_script_impl(script)
}
pub fn from_asm_string(asm: &str) -> Result<ScriptTemplate, ScriptTemplateErrors> {
ScriptTemplate::from_asm_string_impl(asm)
}
}
impl Script {
pub fn match_impl(&self, script_template: &ScriptTemplate) -> Result<Vec<(MatchDataTypes, Vec<u8>)>, 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 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),
(MatchToken::PushData(tmpl_op, tmpl_data), ScriptBit::PushData(op, data)) => Ok(tmpl_op == op && tmpl_data == data),
(MatchToken::Data(len, constraint), ScriptBit::PushData(_, data) | ScriptBit::Push(data)) => match constraint {
DataLengthConstraints::Equals => Ok(&data.len() == len),
DataLengthConstraints::GreaterThan => Ok(&data.len() > len),
DataLengthConstraints::LessThan => Ok(&data.len() < len),
DataLengthConstraints::GreaterThanOrEquals => Ok(&data.len() >= len),
DataLengthConstraints::LessThanOrEquals => Ok(&data.len() <= len),
},
(MatchToken::AnyData, ScriptBit::Push(_)) => Ok(true),
(MatchToken::AnyData, ScriptBit::PushData(_, _)) => Ok(true),
(MatchToken::Signature, ScriptBit::Push(sig_buf)) => Signature::from_der_impl(sig_buf).map(|_| true),
(MatchToken::PublicKey, ScriptBit::Push(pubkey_buf)) => PublicKey::from_bytes_impl(pubkey_buf).map(|_| true),
(MatchToken::PublicKeyHash, ScriptBit::Push(pubkeyhash_buf)) => Ok(pubkeyhash_buf.len() == 20),
_ => Ok(false),
};
match is_match {
Ok(true) => (),
Ok(false) => {
return Err(ScriptTemplateErrors::MatchFailure(
i,
template.clone(),
script.clone(),
BSVErrors::GenericError(format!("{} != {}", template, script)),
));
}
Err(e) => {
return Err(ScriptTemplateErrors::MatchFailure(i, template.clone(), script.clone(), e));
}
}
match (template, script) {
(MatchToken::Data(_, _), ScriptBit::PushData(_, data) | ScriptBit::Push(data)) => matches.push((MatchDataTypes::Data, data.clone())),
(MatchToken::AnyData, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Data, data.clone())),
(MatchToken::AnyData, ScriptBit::PushData(_, data)) => matches.push((MatchDataTypes::Data, data.clone())),
(MatchToken::Signature, ScriptBit::Push(data)) => matches.push((MatchDataTypes::Signature, data.clone())),
(MatchToken::PublicKey, ScriptBit::Push(data)) => matches.push((MatchDataTypes::PublicKey, data.clone())),
(MatchToken::PublicKeyHash, ScriptBit::Push(data)) => matches.push((MatchDataTypes::PublicKeyHash, data.clone())), _ => (),
}
}
Ok(matches)
}
pub fn test_impl(&self, script_template: &ScriptTemplate) -> bool {
self.match_impl(script_template).is_ok()
}
}
impl Script {
pub fn matches(&self, script_template: &ScriptTemplate) -> Result<Vec<(MatchDataTypes, Vec<u8>)>, ScriptTemplateErrors> {
self.match_impl(script_template)
}
pub fn is_match(&self, script_template: &ScriptTemplate) -> bool {
self.test_impl(script_template)
}
}