Skip to main content

zcash_script/script/
mod.rs

1//! Managing sequences of opcodes.
2
3use alloc::{
4    string::{String, ToString},
5    vec::Vec,
6};
7
8use thiserror::Error;
9
10use crate::{
11    interpreter,
12    op::{self},
13    opcode::{self, push_value::LargeValue::*, Operation::*, PossiblyBad, PushValue},
14    signature, Opcode,
15};
16
17pub(crate) mod iter;
18
19/// Errors that can occur during script verification.
20#[allow(missing_docs)]
21#[derive(Clone, Debug, PartialEq, Eq, Error)]
22pub enum Error {
23    // Max sizes
24    #[error(
25        "Script size{} exceeded maxmimum ({} bytes)",
26        .0.map_or("", |size| " ({size} bytes)"),
27        Code::MAX_SIZE
28    )]
29    ScriptSize(Option<usize>),
30
31    #[error("during parsing: {0}")]
32    Opcode(opcode::Error),
33
34    #[error("non-push opcode encountered in script sig when push-only required")]
35    SigPushOnly,
36
37    /// __TODO__: Remove the [`Option`] around [`opcode::PossiblyBad`] when C++ support is removed.
38    #[error("during interpretation: {1}")]
39    Interpreter(Option<opcode::PossiblyBad>, interpreter::Error),
40
41    /// A error external to the script validation code. This can come from the stepper.
42    ///
43    /// __TODO__: Replace the `str` with a type parameter, which will be `Void` in validation code,
44    /// but can be different in the steppers.
45    #[error("external error: {0}")]
46    ExternalError(&'static str),
47
48    #[error("{} closed before the end of the script", match .0 { 1 => "1 conditional opcode wasn’t", n => "{n} conditional opcodes weren’t"})]
49    UnclosedConditional(usize),
50
51    #[error("the script is P2SH, but there was no redeem script left on the stack")]
52    MissingRedeemScript,
53
54    #[error("clean stack requirement not met")]
55    CleanStack,
56}
57
58impl Error {
59    /// This case is only generated in comparisons. It merges the `interpreter::Error::OpCount` case
60    /// with the `opcode::Error::DisabledOpcode` case. This is because there is an edge case when
61    /// there is a disabled opcode as the `MAX_OP_COUNT + 1` operation (not opcode) in a script. In
62    /// this case, the C++ implementation checks the op_count first, while the Rust implementation
63    /// fails on disabled opcodes as soon as they’re read (since the script is guaranteed to fail if
64    /// they occur, even in an inactive branch). To allow comparison tests to pass (especially
65    /// property & fuzz tests), we need these two failure cases to be seen as identical.
66    pub const AMBIGUOUS_COUNT_DISABLED: Self =
67        Self::ExternalError("ambiguous OpCount or DisabledOpcode error");
68
69    /// This case is only generated in comparisons. It merges `Self::ScriptNumError` and
70    /// `Self::SigHighS`, which can only come from the Rust implementation, with
71    /// `ScriptError_t_SCRIPT_ERR_UNKNOWN_ERROR`, which can only come from the C++ implementation,
72    /// but in at least all of the cases that either of the Rust error cases would happen.
73    pub const AMBIGUOUS_UNKNOWN_NUM_HIGHS: Self =
74        Self::ExternalError("ambiguous Unknown, or ScriptNum, or HighS error");
75
76    /// Convert errors that don’t exist in the C++ code into the cases that do.
77    pub fn normalize(&self) -> Self {
78        match self {
79            Self::ScriptSize(Some(_)) => Self::ScriptSize(None),
80            Self::Opcode(oerr) => match oerr {
81                opcode::Error::Read { .. } => {
82                    Self::Interpreter(None, interpreter::Error::BadOpcode)
83                }
84                opcode::Error::Disabled(_) => Self::AMBIGUOUS_COUNT_DISABLED,
85                opcode::Error::PushSize(Some(_)) => Self::from(opcode::Error::PushSize(None)),
86                _ => self.clone(),
87            },
88            Self::Interpreter(
89                Some(opcode::PossiblyBad::Good(op::IF | op::NOTIF)),
90                interpreter::Error::InvalidStackOperation(_),
91            ) => Self::Interpreter(None, interpreter::Error::UnbalancedConditional),
92            Self::Interpreter(
93                Some(opcode::PossiblyBad::Good(op::FROMALTSTACK)),
94                interpreter::Error::InvalidStackOperation(_),
95            ) => Self::Interpreter(
96                Some(opcode::PossiblyBad::Good(op::FROMALTSTACK)),
97                interpreter::Error::InvalidStackOperation(None),
98            ),
99            Self::Interpreter(_, ierr) => match ierr {
100                interpreter::Error::OpCount => Self::AMBIGUOUS_COUNT_DISABLED,
101                interpreter::Error::SignatureEncoding(signature::Error::SigHighS) => {
102                    Self::AMBIGUOUS_UNKNOWN_NUM_HIGHS
103                }
104                interpreter::Error::Num(_) => Self::AMBIGUOUS_UNKNOWN_NUM_HIGHS,
105                interpreter::Error::Verify => self.clone(),
106                _ => Self::Interpreter(None, ierr.normalize()),
107            },
108            Self::UnclosedConditional(_) => {
109                Self::Interpreter(None, interpreter::Error::UnbalancedConditional)
110            }
111            Self::MissingRedeemScript => {
112                Self::Interpreter(None, interpreter::Error::InvalidStackOperation(None))
113            }
114            _ => self.clone(),
115        }
116    }
117}
118
119impl From<opcode::Error> for Error {
120    fn from(value: opcode::Error) -> Self {
121        Error::Opcode(value)
122    }
123}
124
125type AnnOpcode = Result<Opcode, Vec<Error>>;
126
127/// Evaluation functions for script components.
128pub trait Evaluable {
129    /// Get the byte length of this script sig.
130    fn byte_len(&self) -> usize;
131
132    /// Convert a sequence of `Opcode`s to the bytes that would be included in a transaction.
133    fn to_bytes(&self) -> Vec<u8>;
134
135    /// Evaluate this script component.
136    fn eval(
137        &self,
138        flags: interpreter::Flags,
139        checker: &dyn interpreter::SignatureChecker,
140        stack: interpreter::Stack<Vec<u8>>,
141    ) -> Result<interpreter::Stack<Vec<u8>>, Error>;
142
143    /// Returns true iff this script is P2SH.
144    fn is_pay_to_script_hash(&self) -> bool;
145
146    /// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical).
147    fn is_push_only(&self) -> bool;
148}
149
150/// Type that has a "asm" representation.
151pub trait Asm {
152    /// Return the "asm" representation of this type.
153    ///
154    /// The `attempt_sighash_decode` flag indicates whether to attempt to parse
155    /// [`signature::Decoded`] objects from data within the script that matches the format
156    /// of a signature. Only set this to `true` for scripts you believe could contain
157    /// signatures (e.g. [`Sig`]). This flag does nothing if the `signature-validation`
158    /// feature flag is disabled.
159    fn to_asm(&self, attempt_sighash_decode: bool) -> String;
160}
161
162/// A script component is used as either the script sig or script pubkey in a script. Depending on
163/// `T` (which generally needs an `opcode::Evaluable` instance), it has different properties:
164///
165/// - `PossiblyBad` – used for scripts that need to go through consensus-compatible verification
166///   (that is, read from the chain)
167/// - `Opcode` – used for script pubkey and non-push-only script sigs that are authored to be placed
168///   on the chain
169/// - `PushValue` – used for push-only script sigs that are authored to be placed on the chain
170#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct Component<T>(pub Vec<T>);
172
173/// A script sig has only `PushValue` elements.
174pub type Sig = Component<PushValue>;
175
176/// A script pubkey has any `Opcode`.
177pub type PubKey = Component<Opcode>;
178
179/// A redeem script has any `Opcode`.
180pub type Redeem = Component<Opcode>;
181
182/// A script component (sig or pubkey) that came from the chain.
183///
184/// This is used to preserve particular bits that authored scripts don’t allow.
185///
186/// **NB**: `Code(script_bytes).to_component().map_err(Error::Opcode).and_then(Component::eval))`
187///         has the same _semantics_ as `Code(script_bytes).eval(_)`, but it won’t have the same
188///         error message because `to_component` will return a parse error anywhere in the script
189///         before `Component::eval` is called, while `Code::eval` interleaves the parsing and
190///         evaluation, like the C++ implementation.
191///
192/// Unlike `Raw`, this allows the chain data to be combined with authored data in, for example,
193/// `Script<opcode::PushValue, opcode::PossiblyBad>`, which can then be evaluated holistically.
194pub type FromChain = Component<opcode::PossiblyBad>;
195
196impl<T: Clone> Component<T> {
197    /// Convert a `Component` to a less restricted opcode type, infallibly.
198    pub fn weaken<U: From<T>>(&self) -> Component<U> {
199        Component(self.0.iter().cloned().map(|op| U::from(op)).collect())
200    }
201}
202
203impl<T: opcode::Evaluable> Component<T> {
204    /// This parses an entire script.
205    ///
206    /// **NB**: If `T` is not `opcode::PossiblyBad`, this is stricter than the incremental parsing
207    ///         that is done during `verify_script`, because it fails on unknown opcodes no matter
208    ///         where they occur (when normally they only fail if they would be evaluated).
209    pub fn parse(raw_script: &Code) -> Result<Self, Error> {
210        raw_script
211            .parse()
212            .map(|mpb| mpb.map_err(Error::Opcode).and_then(T::restrict))
213            .collect::<Result<_, _>>()
214            .map(Component)
215    }
216}
217
218impl<T: Into<opcode::PossiblyBad> + Clone> Component<T> {
219    /// Convert a `Component` to a more restricted opcode type, erroring if it’s not
220    /// possible.
221    pub fn refine<U: opcode::Evaluable>(&self) -> Result<Component<U>, Error> {
222        self.0
223            .iter()
224            .cloned()
225            .map(|op| U::restrict(op.into()))
226            .collect::<Result<_, _>>()
227            .map(Component)
228    }
229}
230
231impl<T: Into<opcode::PossiblyBad> + opcode::Evaluable + Clone> Evaluable for Component<T> {
232    fn byte_len(&self) -> usize {
233        self.0.iter().map(T::byte_len).sum()
234    }
235
236    /// Convert a sequence of `Opcode`s to the bytes that would be included in a transaction.
237    fn to_bytes(&self) -> Vec<u8> {
238        self.0.iter().flat_map(|elem| elem.to_bytes()).collect()
239    }
240
241    fn eval(
242        &self,
243        flags: interpreter::Flags,
244        checker: &dyn interpreter::SignatureChecker,
245        stack: interpreter::Stack<Vec<u8>>,
246    ) -> Result<interpreter::Stack<Vec<u8>>, Error> {
247        // There's a limit on how large scripts can be.
248        match self.byte_len() {
249            ..=Code::MAX_SIZE => iter::eval(
250                self.0.iter().cloned().map(Ok),
251                flags,
252                &Code(self.to_bytes()),
253                stack,
254                checker,
255            ),
256            n => Err(Error::ScriptSize(Some(n))),
257        }
258    }
259
260    /// Returns true iff this script is P2SH.
261    fn is_pay_to_script_hash(&self) -> bool {
262        match &self
263            .0
264            .iter()
265            .map(|op| op.clone().into())
266            .collect::<Vec<_>>()[..]
267        {
268            [opcode::PossiblyBad::Good(Opcode::Operation(OP_HASH160)), opcode::PossiblyBad::Good(Opcode::PushValue(PushValue::LargeValue(
269                PushdataBytelength(v),
270            ))), opcode::PossiblyBad::Good(Opcode::Operation(OP_EQUAL))] => v.len() == 0x14,
271            _ => false,
272        }
273    }
274
275    /// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical).
276    fn is_push_only(&self) -> bool {
277        self.0.iter().all(|op| {
278            matches!(
279                op.extract_push_value(),
280                Ok(_)
281                    | Err(
282                        // NB: The C++ impl only checks the push size during interpretation, so
283                        // we need to pass this check for too-big `PushValue`s.
284                        Error::Opcode(opcode::Error::PushSize(_))
285                            | Error::Interpreter(
286                                Some(opcode::PossiblyBad::Bad(opcode::Bad::OP_RESERVED)),
287                                interpreter::Error::BadOpcode
288                            )
289                    )
290            )
291        })
292    }
293}
294
295impl<T: Asm> Asm for Component<T> {
296    fn to_asm(&self, attempt_sighash_decode: bool) -> String {
297        self.0
298            .iter()
299            .map(|op| op.to_asm(attempt_sighash_decode))
300            .collect::<Vec<_>>()
301            .join(" ")
302    }
303}
304
305/// An iterator that provides `Opcode`s from a byte stream.
306#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
307pub struct Parser<'a>(&'a [u8]);
308
309impl Iterator for Parser<'_> {
310    type Item = Result<opcode::PossiblyBad, opcode::Error>;
311    fn next(&mut self) -> Option<Self::Item> {
312        if self.0.is_empty() {
313            None
314        } else {
315            let (res, rem) = opcode::PossiblyBad::parse(self.0);
316            self.0 = rem;
317            Some(res)
318        }
319    }
320}
321
322/// When an entire [`crate::Script`] is validated, this is used to tag errors with which component
323/// they came from.
324#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
325pub enum ComponentType {
326    /// The script sig.
327    Sig,
328    /// The script pubkey.
329    PubKey,
330    /// The redeem script from a P2SH script.
331    Redeem,
332}
333
334/// Serialized script, used inside transaction inputs and outputs
335#[derive(Clone, Debug)]
336pub struct Code(pub Vec<u8>);
337
338impl Code {
339    /// Maximum script length in bytes
340    pub(crate) const MAX_SIZE: usize = 10_000;
341
342    /// Produce an [`Opcode`] iterator from [`Code`].
343    pub fn parse(&self) -> Parser<'_> {
344        Parser(&self.0)
345    }
346
347    /// Returns a script annotated with errors that could occur during evaluation.
348    pub fn annotate(&self, flags: &interpreter::Flags) -> Vec<AnnOpcode> {
349        self.parse()
350            .map(|mpb| {
351                mpb.map_err(|e| vec![Error::Opcode(e)]).and_then(|pb| {
352                    pb.analyze(flags)
353                        .map_err(|ierrs| {
354                            ierrs
355                                .into_iter()
356                                .map(|ie| Error::Interpreter(Some(pb.clone()), ie))
357                                .collect()
358                        })
359                        .cloned()
360                })
361            })
362            .collect::<Vec<_>>()
363    }
364
365    /// Convert this into a `Component`, which can then be combined in authored scripts in `Script`.
366    pub fn to_component(&self) -> Result<Component<opcode::PossiblyBad>, opcode::Error> {
367        self.parse().collect::<Result<_, _>>().map(Component)
368    }
369
370    /// Convert a sequence of `Opcode`s to the bytes that would be included in a transaction.
371    pub fn serialize(script: &[Opcode]) -> Vec<u8> {
372        script.iter().flat_map(Vec::from).collect()
373    }
374
375    /// Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
376    /// as 20 sigops. With pay-to-script-hash, that changed:
377    /// CHECKMULTISIGs serialized in script_sigs are
378    /// counted more accurately, assuming they are of the form
379    ///  ... OP_N CHECKMULTISIG ...
380    pub fn sig_op_count(&self, accurate: bool) -> u32 {
381        iter::sig_op_count(self.parse(), accurate)
382    }
383
384    /// Returns whether the script is guaranteed to fail at execution,
385    /// regardless of the initial stack.
386    ///
387    /// The only purpose of this function is to reproduce the behavior of the
388    /// zcashd function `IsUnspendable()`, which is used for "asm" encoding
389    /// and dust transaction filtering.
390    /// <https://github.com/zcash/zcash/blob/2352fbc1ed650ac4369006bea11f7f20ee046b84/src/script/script.h#L617-L620>
391    pub fn is_unspendable(&self) -> bool {
392        self.parse().next() == Some(Ok(opcode::PossiblyBad::Good(op::RETURN)))
393            || self.0.len() > Self::MAX_SIZE
394    }
395}
396
397impl Evaluable for Code {
398    /// Get the byte length of this script sig.
399    fn byte_len(&self) -> usize {
400        self.0.len()
401    }
402
403    /// Convert a sequence of `Opcode`s to the bytes that would be included in a transaction.
404    fn to_bytes(&self) -> Vec<u8> {
405        self.0.clone()
406    }
407
408    fn eval(
409        &self,
410        flags: interpreter::Flags,
411        checker: &dyn interpreter::SignatureChecker,
412        stack: interpreter::Stack<Vec<u8>>,
413    ) -> Result<interpreter::Stack<Vec<u8>>, Error> {
414        match self.byte_len() {
415            ..=Code::MAX_SIZE => iter::eval(
416                self.parse().map(|res| res.map_err(Error::Opcode)),
417                flags,
418                self,
419                stack,
420                checker,
421            ),
422            n => Err(Error::ScriptSize(Some(n))),
423        }
424    }
425
426    /// Returns true iff this script is P2SH.
427    fn is_pay_to_script_hash(&self) -> bool {
428        self.parse()
429            .collect::<Result<Vec<_>, _>>()
430            .is_ok_and(|ops| Component(ops).is_pay_to_script_hash())
431    }
432
433    /// Called by `IsStandardTx` and P2SH/BIP62 VerifyScript (which makes it consensus-critical).
434    fn is_push_only(&self) -> bool {
435        self.parse().all(|op| {
436            matches!(
437                op,
438                // NB: The C++ impl only checks the push size during interpretation, so we need to
439                //     pass this check for too-big `PushValue`s.
440                Err(opcode::Error::PushSize(_))
441                    | Ok(opcode::PossiblyBad::Good(Opcode::PushValue(_))
442                        | opcode::PossiblyBad::Bad(opcode::Bad::OP_RESERVED))
443            )
444        })
445    }
446}
447
448impl Asm for Code {
449    fn to_asm(&self, attempt_sighash_decode: bool) -> String {
450        let mut v = Vec::new();
451        let is_unspendable = self.is_unspendable();
452        for r in self.parse() {
453            match r {
454                Ok(PossiblyBad::Good(op)) => {
455                    v.push(op.to_asm(attempt_sighash_decode && !is_unspendable))
456                }
457                Ok(PossiblyBad::Bad(_)) | Err(_) => {
458                    v.push("[error]".to_string());
459                    break;
460                }
461            }
462        }
463        v.join(" ")
464    }
465}
466
467impl core::fmt::Display for Code {
468    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
469        write!(f, "{}", self.to_asm(false))
470    }
471}
472
473/// A script represented by two byte sequences – one is the sig, the other is the pubkey.
474pub struct Raw {
475    /// The script signature from the spending transaction.
476    pub sig: Code,
477    /// The script pubkey from the funding transaction.
478    pub pub_key: Code,
479}
480
481impl Raw {
482    /// Create a [`Raw`] script from the slices extracted from transactions.
483    pub fn from_raw_parts(sig: Vec<u8>, pub_key: Vec<u8>) -> Self {
484        Raw {
485            sig: Code(sig),
486            pub_key: Code(pub_key),
487        }
488    }
489
490    /// Apply a function to both components of a script, returning the tuple of results.
491    pub(crate) fn map<T>(&self, f: impl Fn(&Code) -> T) -> (T, T) {
492        (f(&self.sig), f(&self.pub_key))
493    }
494
495    /// Returns a script annotated with errors that could occur during evaluation.
496    pub fn annotate(&self, flags: &interpreter::Flags) -> (Vec<AnnOpcode>, Vec<AnnOpcode>) {
497        self.map(|c| c.annotate(flags))
498    }
499
500    /// Validate a [`Raw`] script.
501    pub fn eval(
502        &self,
503        flags: interpreter::Flags,
504        checker: &dyn interpreter::SignatureChecker,
505    ) -> Result<bool, (ComponentType, Error)> {
506        iter::eval_script(&self.sig, &self.pub_key, flags, checker)
507    }
508}
509
510impl Asm for Raw {
511    fn to_asm(&self, attempt_sighash_decode: bool) -> String {
512        self.sig.to_asm(attempt_sighash_decode) + " " + &self.pub_key.to_asm(false)
513    }
514}
515
516impl core::fmt::Display for Raw {
517    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
518        write!(f, "{}", self.to_asm(true))
519    }
520}