use alloc::{
string::{String, ToString},
vec::Vec,
};
use thiserror::Error;
use crate::{
interpreter,
op::{self},
opcode::{self, push_value::LargeValue::*, Operation::*, PossiblyBad, PushValue},
signature, Opcode,
};
pub(crate) mod iter;
#[allow(missing_docs)]
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum Error {
#[error(
"Script size{} exceeded maxmimum ({} bytes)",
.0.map_or("", |size| " ({size} bytes)"),
Code::MAX_SIZE
)]
ScriptSize(Option<usize>),
#[error("during parsing: {0}")]
Opcode(opcode::Error),
#[error("non-push opcode encountered in script sig when push-only required")]
SigPushOnly,
#[error("during interpretation: {1}")]
Interpreter(Option<opcode::PossiblyBad>, interpreter::Error),
#[error("external error: {0}")]
ExternalError(&'static str),
#[error("{} closed before the end of the script", match .0 { 1 => "1 conditional opcode wasn’t", n => "{n} conditional opcodes weren’t"})]
UnclosedConditional(usize),
#[error("the script is P2SH, but there was no redeem script left on the stack")]
MissingRedeemScript,
#[error("clean stack requirement not met")]
CleanStack,
}
impl Error {
pub const AMBIGUOUS_COUNT_DISABLED: Self =
Self::ExternalError("ambiguous OpCount or DisabledOpcode error");
pub const AMBIGUOUS_UNKNOWN_NUM_HIGHS: Self =
Self::ExternalError("ambiguous Unknown, or ScriptNum, or HighS error");
pub fn normalize(&self) -> Self {
match self {
Self::ScriptSize(Some(_)) => Self::ScriptSize(None),
Self::Opcode(oerr) => match oerr {
opcode::Error::Read { .. } => {
Self::Interpreter(None, interpreter::Error::BadOpcode)
}
opcode::Error::Disabled(_) => Self::AMBIGUOUS_COUNT_DISABLED,
opcode::Error::PushSize(Some(_)) => Self::from(opcode::Error::PushSize(None)),
_ => self.clone(),
},
Self::Interpreter(
Some(opcode::PossiblyBad::Good(op::IF | op::NOTIF)),
interpreter::Error::InvalidStackOperation(_),
) => Self::Interpreter(None, interpreter::Error::UnbalancedConditional),
Self::Interpreter(
Some(opcode::PossiblyBad::Good(op::FROMALTSTACK)),
interpreter::Error::InvalidStackOperation(_),
) => Self::Interpreter(
Some(opcode::PossiblyBad::Good(op::FROMALTSTACK)),
interpreter::Error::InvalidStackOperation(None),
),
Self::Interpreter(_, ierr) => match ierr {
interpreter::Error::OpCount => Self::AMBIGUOUS_COUNT_DISABLED,
interpreter::Error::SignatureEncoding(signature::Error::SigHighS) => {
Self::AMBIGUOUS_UNKNOWN_NUM_HIGHS
}
interpreter::Error::Num(_) => Self::AMBIGUOUS_UNKNOWN_NUM_HIGHS,
interpreter::Error::Verify => self.clone(),
_ => Self::Interpreter(None, ierr.normalize()),
},
Self::UnclosedConditional(_) => {
Self::Interpreter(None, interpreter::Error::UnbalancedConditional)
}
Self::MissingRedeemScript => {
Self::Interpreter(None, interpreter::Error::InvalidStackOperation(None))
}
_ => self.clone(),
}
}
}
impl From<opcode::Error> for Error {
fn from(value: opcode::Error) -> Self {
Error::Opcode(value)
}
}
type AnnOpcode = Result<Opcode, Vec<Error>>;
pub trait Evaluable {
fn byte_len(&self) -> usize;
fn to_bytes(&self) -> Vec<u8>;
fn eval(
&self,
flags: interpreter::Flags,
checker: &dyn interpreter::SignatureChecker,
stack: interpreter::Stack<Vec<u8>>,
) -> Result<interpreter::Stack<Vec<u8>>, Error>;
fn is_pay_to_script_hash(&self) -> bool;
fn is_push_only(&self) -> bool;
}
pub trait Asm {
fn to_asm(&self, attempt_sighash_decode: bool) -> String;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Component<T>(pub Vec<T>);
pub type Sig = Component<PushValue>;
pub type PubKey = Component<Opcode>;
pub type Redeem = Component<Opcode>;
pub type FromChain = Component<opcode::PossiblyBad>;
impl<T: Clone> Component<T> {
pub fn weaken<U: From<T>>(&self) -> Component<U> {
Component(self.0.iter().cloned().map(|op| U::from(op)).collect())
}
}
impl<T: opcode::Evaluable> Component<T> {
pub fn parse(raw_script: &Code) -> Result<Self, Error> {
raw_script
.parse()
.map(|mpb| mpb.map_err(Error::Opcode).and_then(T::restrict))
.collect::<Result<_, _>>()
.map(Component)
}
}
impl<T: Into<opcode::PossiblyBad> + Clone> Component<T> {
pub fn refine<U: opcode::Evaluable>(&self) -> Result<Component<U>, Error> {
self.0
.iter()
.cloned()
.map(|op| U::restrict(op.into()))
.collect::<Result<_, _>>()
.map(Component)
}
}
impl<T: Into<opcode::PossiblyBad> + opcode::Evaluable + Clone> Evaluable for Component<T> {
fn byte_len(&self) -> usize {
self.0.iter().map(T::byte_len).sum()
}
fn to_bytes(&self) -> Vec<u8> {
self.0.iter().flat_map(|elem| elem.to_bytes()).collect()
}
fn eval(
&self,
flags: interpreter::Flags,
checker: &dyn interpreter::SignatureChecker,
stack: interpreter::Stack<Vec<u8>>,
) -> Result<interpreter::Stack<Vec<u8>>, Error> {
match self.byte_len() {
..=Code::MAX_SIZE => iter::eval(
self.0.iter().cloned().map(Ok),
flags,
&Code(self.to_bytes()),
stack,
checker,
),
n => Err(Error::ScriptSize(Some(n))),
}
}
fn is_pay_to_script_hash(&self) -> bool {
match &self
.0
.iter()
.map(|op| op.clone().into())
.collect::<Vec<_>>()[..]
{
[opcode::PossiblyBad::Good(Opcode::Operation(OP_HASH160)), opcode::PossiblyBad::Good(Opcode::PushValue(PushValue::LargeValue(
PushdataBytelength(v),
))), opcode::PossiblyBad::Good(Opcode::Operation(OP_EQUAL))] => v.len() == 0x14,
_ => false,
}
}
fn is_push_only(&self) -> bool {
self.0.iter().all(|op| {
matches!(
op.extract_push_value(),
Ok(_)
| Err(
Error::Opcode(opcode::Error::PushSize(_))
| Error::Interpreter(
Some(opcode::PossiblyBad::Bad(opcode::Bad::OP_RESERVED)),
interpreter::Error::BadOpcode
)
)
)
})
}
}
impl<T: Asm> Asm for Component<T> {
fn to_asm(&self, attempt_sighash_decode: bool) -> String {
self.0
.iter()
.map(|op| op.to_asm(attempt_sighash_decode))
.collect::<Vec<_>>()
.join(" ")
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Parser<'a>(&'a [u8]);
impl<'a> Iterator for Parser<'a> {
type Item = Result<opcode::PossiblyBad, opcode::Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
None
} else {
let (res, rem) = opcode::PossiblyBad::parse(self.0);
self.0 = rem;
Some(res)
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComponentType {
Sig,
PubKey,
Redeem,
}
#[derive(Clone, Debug)]
pub struct Code(pub Vec<u8>);
impl Code {
pub(crate) const MAX_SIZE: usize = 10_000;
pub fn parse(&self) -> Parser<'_> {
Parser(&self.0)
}
pub fn annotate(&self, flags: &interpreter::Flags) -> Vec<AnnOpcode> {
self.parse()
.map(|mpb| {
mpb.map_err(|e| vec![Error::Opcode(e)]).and_then(|pb| {
pb.analyze(flags)
.map_err(|ierrs| {
ierrs
.into_iter()
.map(|ie| Error::Interpreter(Some(pb.clone()), ie))
.collect()
})
.cloned()
})
})
.collect::<Vec<_>>()
}
pub fn to_component(&self) -> Result<Component<opcode::PossiblyBad>, opcode::Error> {
self.parse().collect::<Result<_, _>>().map(Component)
}
pub fn serialize(script: &[Opcode]) -> Vec<u8> {
script.iter().flat_map(Vec::from).collect()
}
pub fn sig_op_count(&self, accurate: bool) -> u32 {
iter::sig_op_count(self.parse(), accurate)
}
pub fn is_unspendable(&self) -> bool {
self.parse().next() == Some(Ok(opcode::PossiblyBad::Good(op::RETURN)))
|| self.0.len() > Self::MAX_SIZE
}
}
impl Evaluable for Code {
fn byte_len(&self) -> usize {
self.0.len()
}
fn to_bytes(&self) -> Vec<u8> {
self.0.clone()
}
fn eval(
&self,
flags: interpreter::Flags,
checker: &dyn interpreter::SignatureChecker,
stack: interpreter::Stack<Vec<u8>>,
) -> Result<interpreter::Stack<Vec<u8>>, Error> {
match self.byte_len() {
..=Code::MAX_SIZE => iter::eval(
self.parse().map(|res| res.map_err(Error::Opcode)),
flags,
self,
stack,
checker,
),
n => Err(Error::ScriptSize(Some(n))),
}
}
fn is_pay_to_script_hash(&self) -> bool {
self.parse()
.collect::<Result<Vec<_>, _>>()
.map_or(false, |ops| Component(ops).is_pay_to_script_hash())
}
fn is_push_only(&self) -> bool {
self.parse().all(|op| {
matches!(
op,
Err(opcode::Error::PushSize(_))
| Ok(opcode::PossiblyBad::Good(Opcode::PushValue(_))
| opcode::PossiblyBad::Bad(opcode::Bad::OP_RESERVED))
)
})
}
}
impl Asm for Code {
fn to_asm(&self, attempt_sighash_decode: bool) -> String {
let mut v = Vec::new();
let is_unspendable = self.is_unspendable();
for r in self.parse() {
match r {
Ok(PossiblyBad::Good(op)) => {
v.push(op.to_asm(attempt_sighash_decode && !is_unspendable))
}
Ok(PossiblyBad::Bad(_)) | Err(_) => {
v.push("[error]".to_string());
break;
}
}
}
v.join(" ")
}
}
impl core::fmt::Display for Code {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_asm(false))
}
}
pub struct Raw {
pub sig: Code,
pub pub_key: Code,
}
impl Raw {
pub fn from_raw_parts(sig: Vec<u8>, pub_key: Vec<u8>) -> Self {
Raw {
sig: Code(sig),
pub_key: Code(pub_key),
}
}
pub(crate) fn map<T>(&self, f: impl Fn(&Code) -> T) -> (T, T) {
(f(&self.sig), f(&self.pub_key))
}
pub fn annotate(&self, flags: &interpreter::Flags) -> (Vec<AnnOpcode>, Vec<AnnOpcode>) {
self.map(|c| c.annotate(flags))
}
pub fn eval(
&self,
flags: interpreter::Flags,
checker: &dyn interpreter::SignatureChecker,
) -> Result<bool, (ComponentType, Error)> {
iter::eval_script(&self.sig, &self.pub_key, flags, checker)
}
}
impl Asm for Raw {
fn to_asm(&self, attempt_sighash_decode: bool) -> String {
self.sig.to_asm(attempt_sighash_decode) + " " + &self.pub_key.to_asm(false)
}
}
impl core::fmt::Display for Raw {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_asm(true))
}
}