elements_miniscript/extensions/
introspect_ops.rs

1//! Miniscript Arithmetic expressions:
2//! Note that this fragment is only supported for Tapscript context
3use std::convert::TryFrom;
4use std::fmt;
5use std::str::FromStr;
6
7use bitcoin::hashes::{sha256, Hash};
8use elements::address::Payload;
9use elements::confidential::Asset;
10use elements::hex::{FromHex, ToHex};
11use elements::opcodes::all::*;
12use elements::{confidential, encode, script, Address, AddressParams};
13
14use super::index_ops::IdxExpr;
15use super::param::{ExtParamTranslator, TranslateExtParam};
16use super::{ArgFromStr, CovExtArgs, EvalError, ExtParam, FromTokenIterError, ParseableExt, TxEnv};
17use crate::expression::{FromTree, Tree};
18use crate::miniscript::context::ScriptContextError;
19use crate::miniscript::lex::{Token as Tk, TokenIter};
20use crate::miniscript::satisfy::{Satisfaction, Witness};
21use crate::miniscript::types::extra_props::{OpLimits, TimelockInfo};
22use crate::miniscript::types::{Base, Correctness, Dissat, ExtData, Input, Malleability};
23use crate::{
24    expression, interpreter, script_num_size, Error, ExtTranslator, Extension, Satisfier,
25    ToPublicKey, TranslateExt,
26};
27
28/// Enum representing operations with transaction assets.
29/// Every variant of this enum pushes a 32 byte asset + 1 byte prefix on stack top..
30/// These operations also support confidential assets.
31/// This will abort when
32///     - Supplied index is out of bounds.
33#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
34pub enum AssetExpr<T: ExtParam> {
35    /* leaf fragments/terminals */
36    /// A constant asset id
37    /// Minimal push of this `<asset_id>`
38    Const(T),
39    /// Asset under the current executing input
40    /// `INSPECTCURRENTINPUTINDEX INPSECTINPUTASSET`
41    CurrInputAsset,
42    /// Explicit asset at the given input index
43    /// `i INPSECTINPUTASSET`
44    Input(IdxExpr),
45    /// Explicit asset at the given output index
46    /// `i INPSECTOUTPUTASSET`
47    Output(IdxExpr),
48}
49
50/// Enum representing operations with transaction values.
51/// Every variant of this enum pushes a 32 byte value + 1 byte prefix on stack top..
52/// These operations also support confidential values.
53/// This will abort when
54///     - Supplied index is out of bounds.
55#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
56pub enum ValueExpr<T: ExtParam> {
57    /* leaf fragments/terminals */
58    /// A constant Value
59    Const(T),
60    /// Value under the current executing input
61    /// `INSPECTCURRENTINPUTINDEX INPSECTINPUTVALUE`
62    CurrInputValue,
63    ///  Value(possibly confidential) at the given input index
64    /// `i INPSECTINPUTVALUE`
65    Input(IdxExpr),
66    /// Value(possibly confidential) at the given output index
67    /// `i INPSECTOUTPUTVALUE`
68    Output(IdxExpr),
69}
70
71/// Enum representing operations with transaction script pubkeys.
72/// Every variant of this enum pushes a witness program + 1 byte witness version on stack top.
73/// If the script pubkey is not a witness program. Push a sha256 hash of the
74/// script pubkey followed by -1 witness version
75/// This will abort when
76///     - Supplied index is out of bounds.
77#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
78pub enum SpkExpr<T: ExtParam> {
79    /* leaf fragments/terminals */
80    /// A constant fixed script pubkey
81    /// Pushes the witness program followed by witness version
82    /// Pushes -1 if the legacy script pubkeys followed by sha256 hash of script pubkey
83    Const(T),
84    /// Script pubkey under the current executing input
85    /// `INSPECTCURRENTINPUTINDEX INPSECTINPUTSCRIPTPUBKEY`
86    CurrInputSpk,
87    /// Explicit asset at the given input index
88    /// `i INPSECTINPUTSCRIPTPUBKEY`
89    Input(IdxExpr),
90    /// Explicit asset at the given output index
91    /// `i INPSECTOUTPUTSCRIPTPUBKEY`
92    Output(IdxExpr),
93}
94
95/// Miniscript Fragment containing arith expressions
96/// Expr cannot be directly used a miniscript fragment because it pushes a 64 bit
97/// value on stack. Two expressions can be combined with Arith to something is
98/// of Base type B to be used in miniscript expressions
99#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
100pub enum CovOps<T: ExtParam> {
101    /// Checks that asset is explicit
102    /// `[X] <1> EQUAL NIP`
103    IsExpAsset(AssetExpr<T>),
104    /// Checks if the value is explicit
105    /// `[X] <1> EQUAL NIP`
106    /// The script translation is same as that of IsExpAsset, but the data structure
107    /// distinguishes them for clarity.
108    IsExpValue(ValueExpr<T>),
109    /// Checks that both assets are equal (maybe confidential)
110    /// `[X] TOALTSTACK [Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND`
111    AssetEq(AssetExpr<T>, AssetExpr<T>),
112    /// Checks that both values are equal (maybe confidential)
113    /// `[X] TOALTSTACK [Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND`
114    ValueEq(ValueExpr<T>, ValueExpr<T>),
115    /// Script pubkey equal. Checks the witness version and program. Also works for
116    /// legacy programs.
117    /// `[X] TOALTSTACK [Y] FROMALTSTACK EQUAL TOALTSTACK EQUAL FROMALTSTACK BOOLAND`
118    SpkEq(SpkExpr<T>, SpkExpr<T>),
119    /// Current input index equality
120    /// `<i> PUSHCURRENTINPUTINDEX EQUAL`
121    CurrIndEq(usize),
122    /// Index equality
123    /// `[X] [Y] EQUAL`
124    IdxEq(IdxExpr, IdxExpr),
125}
126
127impl<T: ExtParam> AssetExpr<T> {
128    /// Returns the script size of this [`AssetExpr<T>`].
129    fn script_size(&self) -> usize {
130        match self {
131            AssetExpr::Const(_) => 33 + 1,
132            AssetExpr::CurrInputAsset => 2,
133            AssetExpr::Input(i) => i.script_size() + 1,
134            AssetExpr::Output(i) => i.script_size() + 1,
135        }
136    }
137
138    /// Returns the extention translation from AssetExpr<T> to AssetExpr<Q>
139    fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<AssetExpr<Q>, E>
140    where
141        Ext: ExtParamTranslator<T, Q, E>,
142        Q: ExtParam,
143    {
144        let res = match self {
145            AssetExpr::Const(c) => AssetExpr::Const(t.ext(c)?),
146            AssetExpr::CurrInputAsset => AssetExpr::CurrInputAsset,
147            AssetExpr::Input(i) => AssetExpr::Input(i.clone()),
148            AssetExpr::Output(i) => AssetExpr::Output(i.clone()),
149        };
150        Ok(res)
151    }
152}
153
154impl<T: ExtParam> fmt::Display for AssetExpr<T> {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        match self {
157            AssetExpr::Const(asset) => write!(f, "{}", asset),
158            AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
159            AssetExpr::Input(i) => write!(f, "inp_asset({})", i),
160            AssetExpr::Output(i) => write!(f, "out_asset({})", i),
161        }
162    }
163}
164
165impl<T: ExtParam> fmt::Debug for AssetExpr<T> {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            AssetExpr::Const(asset) => write!(f, "{:?}", asset),
169            AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
170            AssetExpr::Input(i) => write!(f, "inp_asset({:?})", i),
171            AssetExpr::Output(i) => write!(f, "out_asset({:?})", i),
172        }
173    }
174}
175
176impl<T: ExtParam> ArgFromStr for AssetExpr<T> {
177    fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
178        let top = expression::Tree::from_str(s)?;
179        Self::from_tree_parent(&top, parent, pos)
180    }
181}
182
183impl<T: ExtParam> AssetExpr<T> {
184    fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
185        match (top.name, top.args.len()) {
186            ("curr_inp_asset", 0) => Ok(AssetExpr::CurrInputAsset),
187            ("inp_asset", 1) => expression::unary(top, AssetExpr::Input),
188            ("out_asset", 1) => expression::unary(top, AssetExpr::Output),
189            (asset, 0) => Ok(AssetExpr::Const(T::arg_from_str(asset, parent, pos)?)),
190            _ => Err(Error::Unexpected(format!(
191                "{}({} args) while parsing Extension",
192                top.name,
193                top.args.len(),
194            ))),
195        }
196    }
197}
198
199impl<T: ExtParam> ValueExpr<T> {
200    /// Returns the script size of this [`ValueExpr<T>`].
201    fn script_size(&self) -> usize {
202        match self {
203            ValueExpr::Const(_c) => 33 + 1, // Worst case size for fee estimation
204            ValueExpr::CurrInputValue => 2,
205            ValueExpr::Input(i) => i.script_size() + 1,
206            ValueExpr::Output(i) => i.script_size() + 1,
207        }
208    }
209
210    /// Returns the extention translation from ValueExpr<T> to ValueExpr<Q>
211    fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<ValueExpr<Q>, E>
212    where
213        Ext: ExtParamTranslator<T, Q, E>,
214        Q: ExtParam,
215    {
216        let res = match self {
217            ValueExpr::Const(c) => ValueExpr::Const(t.ext(c)?),
218            ValueExpr::CurrInputValue => ValueExpr::CurrInputValue,
219            ValueExpr::Input(i) => ValueExpr::Input(i.clone()),
220            ValueExpr::Output(i) => ValueExpr::Output(i.clone()),
221        };
222        Ok(res)
223    }
224}
225
226impl<T: ExtParam> fmt::Display for ValueExpr<T> {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self {
229            ValueExpr::Const(asset) => write!(f, "{}", asset),
230            ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
231            ValueExpr::Input(i) => write!(f, "inp_value({})", i),
232            ValueExpr::Output(i) => write!(f, "out_value({})", i),
233        }
234    }
235}
236
237impl<T: ExtParam> fmt::Debug for ValueExpr<T> {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        match self {
240            ValueExpr::Const(asset) => write!(f, "{:?}", asset),
241            ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
242            ValueExpr::Input(i) => write!(f, "inp_value({:?})", i),
243            ValueExpr::Output(i) => write!(f, "out_value({:?})", i),
244        }
245    }
246}
247
248impl<T: ExtParam> ArgFromStr for ValueExpr<T> {
249    fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
250        let top = expression::Tree::from_str(s)?;
251        Self::from_tree_parent(&top, parent, pos)
252    }
253}
254
255impl<T: ExtParam> ValueExpr<T> {
256    fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
257        match (top.name, top.args.len()) {
258            ("curr_inp_value", 0) => Ok(ValueExpr::CurrInputValue),
259            ("inp_value", 1) => expression::unary(top, ValueExpr::Input),
260            ("out_value", 1) => expression::unary(top, ValueExpr::Output),
261            (value, 0) => Ok(ValueExpr::Const(T::arg_from_str(value, parent, pos)?)),
262            _ => Err(Error::Unexpected(format!(
263                "{}({} args) while parsing Extension",
264                top.name,
265                top.args.len(),
266            ))),
267        }
268    }
269}
270
271impl<T: ExtParam> SpkExpr<T> {
272    /// Returns the script size of this [`SpkExpr<T>`].
273    fn script_size(&self) -> usize {
274        match self {
275            SpkExpr::Const(_c) => 32 + 1 + 1,
276            SpkExpr::CurrInputSpk => 2,
277            SpkExpr::Input(i) => i.script_size() + 1,
278            SpkExpr::Output(i) => i.script_size() + 1,
279        }
280    }
281
282    /// Returns the extention translation from SpkExpr<T> to SpkExpr<Q>
283    fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<SpkExpr<Q>, E>
284    where
285        Ext: ExtParamTranslator<T, Q, E>,
286        Q: ExtParam,
287    {
288        let res = match self {
289            SpkExpr::Const(c) => SpkExpr::Const(t.ext(c)?),
290            SpkExpr::CurrInputSpk => SpkExpr::CurrInputSpk,
291            SpkExpr::Input(i) => SpkExpr::Input(i.clone()),
292            SpkExpr::Output(i) => SpkExpr::Output(i.clone()),
293        };
294        Ok(res)
295    }
296}
297
298impl<T: ExtParam> fmt::Display for SpkExpr<T> {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        match self {
301            SpkExpr::Const(asset) => write!(f, "{}", asset),
302            SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
303            SpkExpr::Input(i) => write!(f, "inp_spk({})", i),
304            SpkExpr::Output(i) => write!(f, "out_spk({})", i),
305        }
306    }
307}
308
309impl<T: ExtParam> fmt::Debug for SpkExpr<T> {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        match self {
312            SpkExpr::Const(asset) => write!(f, "{:?}", asset),
313            SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
314            SpkExpr::Input(i) => write!(f, "inp_spk({:?})", i),
315            SpkExpr::Output(i) => write!(f, "out_spk({:?})", i),
316        }
317    }
318}
319
320impl<T: ExtParam> ArgFromStr for SpkExpr<T> {
321    fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
322        let top = expression::Tree::from_str(s)?;
323        Self::from_tree_parent(&top, parent, pos)
324    }
325}
326
327impl<T: ExtParam> SpkExpr<T> {
328    fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
329        match (top.name, top.args.len()) {
330            ("curr_inp_spk", 0) => Ok(SpkExpr::CurrInputSpk),
331            ("inp_spk", 1) => expression::unary(top, SpkExpr::Input),
332            ("out_spk", 1) => expression::unary(top, SpkExpr::Output),
333            (asset, 0) => Ok(SpkExpr::Const(T::arg_from_str(asset, parent, pos)?)),
334            _ => Err(Error::Unexpected(format!(
335                "{}({} args) while parsing Extension",
336                top.name,
337                top.args.len(),
338            ))),
339        }
340    }
341}
342
343impl<T: ExtParam> fmt::Display for CovOps<T> {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        match self {
346            CovOps::IsExpAsset(a) => write!(f, "is_exp_asset({})", a),
347            CovOps::IsExpValue(v) => write!(f, "is_exp_value({})", v),
348            CovOps::AssetEq(a, b) => write!(f, "asset_eq({},{})", a, b),
349            CovOps::ValueEq(a, b) => write!(f, "value_eq({},{})", a, b),
350            CovOps::SpkEq(a, b) => write!(f, "spk_eq({},{})", a, b),
351            CovOps::CurrIndEq(i) => write!(f, "curr_idx_eq({})", i),
352            CovOps::IdxEq(a, b) => write!(f, "idx_eq({},{})", a, b),
353        }
354    }
355}
356
357impl<T: ExtParam> fmt::Debug for CovOps<T> {
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        match self {
360            CovOps::IsExpAsset(a) => write!(f, "is_exp_asset({:?})", a),
361            CovOps::IsExpValue(v) => write!(f, "is_exp_value({:?})", v),
362            CovOps::AssetEq(a, b) => write!(f, "asset_eq({:?},{:?})", a, b),
363            CovOps::ValueEq(a, b) => write!(f, "value_eq({:?},{:?})", a, b),
364            CovOps::SpkEq(a, b) => write!(f, "spk_eq({:?},{:?})", a, b),
365            CovOps::CurrIndEq(i) => write!(f, "curr_idx_eq({:?})", i),
366            CovOps::IdxEq(a, b) => write!(f, "idx_eq({},{})", a, b),
367        }
368    }
369}
370
371impl<T: ExtParam> FromStr for CovOps<T> {
372    type Err = Error;
373
374    fn from_str(s: &str) -> Result<Self, Self::Err> {
375        let top = expression::Tree::from_str(s)?;
376        Self::from_tree(&top)
377    }
378}
379
380impl<T: ExtParam> FromTree for CovOps<T> {
381    fn from_tree(top: &Tree<'_>) -> Result<Self, Error> {
382        match (top.name, top.args.len()) {
383            ("is_exp_asset", 1) => {
384                AssetExpr::from_tree_parent(&top.args[0], top.name, 0).map(CovOps::IsExpAsset)
385            }
386            ("is_exp_value", 1) => {
387                ValueExpr::from_tree_parent(&top.args[0], top.name, 0).map(CovOps::IsExpValue)
388            }
389            ("asset_eq", 2) => {
390                let l = AssetExpr::from_tree_parent(&top.args[0], top.name, 0)?;
391                let r = AssetExpr::from_tree_parent(&top.args[1], top.name, 1)?;
392                Ok(CovOps::AssetEq(l, r))
393            }
394            ("value_eq", 2) => {
395                let l = ValueExpr::from_tree_parent(&top.args[0], top.name, 0)?;
396                let r = ValueExpr::from_tree_parent(&top.args[1], top.name, 1)?;
397                Ok(CovOps::ValueEq(l, r))
398            }
399            ("spk_eq", 2) => {
400                let l = SpkExpr::from_tree_parent(&top.args[0], top.name, 0)?;
401                let r = SpkExpr::from_tree_parent(&top.args[1], top.name, 1)?;
402                Ok(CovOps::SpkEq(l, r))
403            }
404            ("curr_idx_eq", 1) => {
405                expression::terminal(&top.args[0], expression::parse_num::<usize>)
406                    .map(CovOps::CurrIndEq)
407            }
408            ("idx_eq", 2) => {
409                let l = IdxExpr::from_tree(&top.args[0])?;
410                let r = IdxExpr::from_tree(&top.args[1])?;
411                Ok(CovOps::IdxEq(l, r))
412            }
413            _ => Err(Error::Unexpected(format!(
414                "{}({} args) while parsing Extension",
415                top.name,
416                top.args.len(),
417            ))),
418        }
419    }
420}
421
422impl<T: ExtParam> Extension for CovOps<T> {
423    fn corr_prop(&self) -> Correctness {
424        Correctness {
425            base: Base::B,
426            input: Input::Zero,    // No input from stack
427            dissatisfiable: false, // No dissatisfactions possible from stack inputs
428            unit: true,
429        }
430    }
431
432    fn mall_prop(&self) -> Malleability {
433        Malleability {
434            dissat: Dissat::None, // No dissatisfactions from stack inputs
435            safe: false,          // Unsafe as a top fragment
436            non_malleable: true, // Script satisfaction is non-malleable, whole fragment tx could be malleable
437        }
438    }
439
440    fn extra_prop(&self) -> ExtData {
441        ExtData {
442            pk_cost: self.script_size(), // 1 opcodes, 1 key push, msg, 1 msg push
443            has_free_verify: matches!(self, CovOps::CurrIndEq(..)),
444            stack_elem_count_sat: Some(0),
445            stack_elem_count_dissat: Some(0),
446            max_sat_size: Some((0, 0)),
447            max_dissat_size: Some((0, 0)),
448            timelock_info: TimelockInfo::default(),
449            exec_stack_elem_count_sat: Some(4), // There is composition in asset/value/spk expressions. Only max 4 depth with asset expressions
450            exec_stack_elem_count_dissat: Some(4),
451            ops: OpLimits {
452                // Opcodes are really not relevant in tapscript as BIP342 removes all rules on them
453                // So, don't make any effort in trying to compute and cache them.
454                count: 0,
455                sat: Some(0),
456                nsat: Some(0),
457            },
458        }
459    }
460
461    fn script_size(&self) -> usize {
462        match self {
463            CovOps::IsExpAsset(a) => a.script_size() + 3,
464            CovOps::IsExpValue(v) => v.script_size() + 3,
465            CovOps::AssetEq(a, b) => a.script_size() + b.script_size() + 7,
466            CovOps::ValueEq(a, b) => a.script_size() + b.script_size() + 7,
467            CovOps::SpkEq(a, b) => a.script_size() + b.script_size() + 7,
468            CovOps::CurrIndEq(i) => script_num_size(*i) + 2,
469            CovOps::IdxEq(a, b) => a.script_size() + b.script_size() + 1,
470        }
471    }
472
473    fn from_name_tree(name: &str, children: &[Tree<'_>]) -> Result<Self, FromTokenIterError> {
474        let tree = Tree {
475            name,
476            args: children.to_vec(), // Cloning references here, it is possible to avoid the to_vec() here,
477                                     // but it requires lot of refactor.
478        };
479        Self::from_tree(&tree).map_err(|_| FromTokenIterError)
480    }
481
482    fn segwit_ctx_checks(&self) -> Result<(), ScriptContextError> {
483        // New opcodes only supported in taproot context
484        Err(ScriptContextError::ExtensionError(
485            "Introspection opcodes only available in Taproot".to_string(),
486        ))
487    }
488}
489
490impl<PArg, QArg> TranslateExt<CovOps<PArg>, CovOps<QArg>> for CovOps<PArg>
491where
492    CovOps<PArg>: Extension,
493    CovOps<QArg>: Extension,
494    PArg: ExtParam,
495    QArg: ExtParam,
496{
497    type Output = CovOps<QArg>;
498
499    fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
500    where
501        T: ExtTranslator<CovOps<PArg>, CovOps<QArg>, E>,
502    {
503        t.ext(self)
504    }
505}
506
507// Use ExtParamTranslator as a ExtTranslator
508impl<T, PArg, QArg, E> ExtTranslator<CovOps<PArg>, CovOps<QArg>, E> for T
509where
510    T: ExtParamTranslator<PArg, QArg, E>,
511    PArg: ExtParam,
512    QArg: ExtParam,
513{
514    /// Translates one extension to another
515    fn ext(&mut self, cov_ops: &CovOps<PArg>) -> Result<CovOps<QArg>, E> {
516        TranslateExtParam::translate_ext(cov_ops, self)
517    }
518}
519
520/// Wrapper around [`elements::Script`] for representing script pubkeys
521// Required because the fmt::Display of elements::Script does not print hex
522#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
523pub struct Spk(SpkInner);
524
525impl Spk {
526    /// Creates a new [`Spk`].
527    pub fn new(s: elements::Script) -> Self {
528        Spk(SpkInner::Script(s))
529    }
530}
531
532/// Script pubkey representing either a known script or a hash of legacy script
533#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
534pub enum SpkInner {
535    /// A complete known script
536    Script(elements::Script),
537    /// An hashed legacy script pubkey
538    Hashed(sha256::Hash),
539}
540
541impl fmt::Display for Spk {
542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543        match &self.0 {
544            SpkInner::Script(s) => write!(f, "{}", s.to_hex()),
545            SpkInner::Hashed(_h) => write!(f, "hashed_spk"), // This should never be used
546        }
547    }
548}
549
550impl ArgFromStr for Spk {
551    fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
552        if parent != "spk_eq" {
553            return Err(Error::Unexpected(
554                "spk expressions can only used in spk_eq".to_string(),
555            ));
556        }
557        let inner = elements::Script::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
558        Ok(Spk::new(inner))
559    }
560}
561
562impl ArgFromStr for confidential::Asset {
563    fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
564        if parent != "asset_eq" && parent != "is_exp_asset" {
565            return Err(Error::Unexpected(
566                "asset expressions only allowed inside asset_eq and is_exp_asset".to_string(),
567            ));
568        }
569        let asset_hex = Vec::<u8>::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
570        elements::encode::deserialize(&asset_hex).map_err(|e| Error::Unexpected(e.to_string()))
571    }
572}
573
574impl ArgFromStr for confidential::Value {
575    fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
576        if parent != "value_eq" && parent != "is_exp_value" {
577            return Err(Error::Unexpected(
578                "value expressions only allowed inside value_eq and is_exp_value".to_string(),
579            ));
580        }
581        let asset_hex = Vec::<u8>::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
582        elements::encode::deserialize(&asset_hex).map_err(|e| Error::Unexpected(e.to_string()))
583    }
584}
585
586// Internal helper function to construct asset from prefix and commitments
587fn asset(pref: u8, comm: &[u8]) -> Option<confidential::Asset> {
588    let mut bytes = [0u8; 33];
589    bytes[0] = pref;
590    if comm.len() != 32 {
591        return None;
592    }
593    bytes[1..].copy_from_slice(comm);
594    encode::deserialize(&bytes).ok()
595}
596
597// Internal helper function to construct asset from prefix and components
598fn value(pref: u8, comm: &[u8]) -> Option<confidential::Value> {
599    if comm.len() == 32 {
600        let mut bytes = [0u8; 33];
601        bytes[0] = pref;
602        bytes[1..].copy_from_slice(comm);
603        encode::deserialize(&bytes).ok()
604    } else if comm.len() == 8 {
605        let mut bytes = [0u8; 8];
606        bytes.copy_from_slice(comm);
607        if pref == 1 {
608            Some(confidential::Value::Explicit(u64::from_le_bytes(bytes)))
609        } else {
610            None
611        }
612    } else {
613        None
614    }
615}
616
617// Internal helper function to construct script pubkey from prefix and components
618fn spk(pref: i8, prog: &[u8]) -> Option<elements::Script> {
619    if pref == -1 {
620        // Cannot infer script pubkey from sha256::Hash
621        // In future, we can add fragments to check against certain hard-coded spks like fee spk.
622        None
623    } else if pref <= 16 && pref >= 0 {
624        Some(
625            script::Builder::new()
626                .push_int(pref as i64)
627                .push_slice(prog)
628                .into_script(),
629        )
630    } else {
631        None
632    }
633}
634
635// Internal function to convert a script pubkey into (witness version, program)
636// This converts legacy programs to (-1, sha256::Hash(spk))
637fn spk_to_components(s: &elements::Script) -> (i8, Vec<u8>) {
638    if !s.is_witness_program() {
639        (
640            -1,
641            sha256::Hash::hash(s.as_bytes()).to_byte_array().to_vec(),
642        )
643    } else {
644        // indirect way to get payload.
645        // The address parameters don't really matter here
646        let addr = Address::from_script(s, None, &AddressParams::ELEMENTS).unwrap();
647        if let Payload::WitnessProgram { version, program } = addr.payload {
648            (version.to_u8() as i8, program)
649        } else {
650            unreachable!("All witness programs have well defined payload")
651        }
652    }
653}
654
655impl AssetExpr<CovExtArgs> {
656    /// Push this script to builder
657    /// Panics when trying to push a Null asset. This never occur in honest use-cases
658    /// as there is no such thing as Null asset
659    pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
660        match self {
661            AssetExpr::Const(CovExtArgs::Asset(a)) => {
662                match a {
663                    Asset::Null => unreachable!("Attempt to push Null asset"),
664                    Asset::Explicit(a) => builder.push_slice(a.into_inner().as_ref()).push_int(1), // explicit prefix
665                    Asset::Confidential(c) => {
666                        let ser = c.serialize();
667                        builder.push_slice(&ser[1..]).push_int(ser[0] as i64)
668                    }
669                }
670            }
671            AssetExpr::Const(_) => unreachable!(
672                "Both constructors from_str and from_token_iter
673            check that the correct variant is used in asset"
674            ),
675            AssetExpr::CurrInputAsset => builder
676                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
677                .push_opcode(OP_INSPECTINPUTASSET),
678            AssetExpr::Input(i) => i.push_to_builder(builder).push_opcode(OP_INSPECTINPUTASSET),
679            AssetExpr::Output(i) => i
680                .push_to_builder(builder)
681                .push_opcode(OP_INSPECTOUTPUTASSET),
682        }
683    }
684
685    /// Evaluate this expression
686    pub fn eval(&self, env: &TxEnv) -> Result<confidential::Asset, EvalError> {
687        match self {
688            AssetExpr::Const(CovExtArgs::Asset(a)) => Ok(*a),
689            AssetExpr::Const(_) => unreachable!(
690                "Both constructors from_str and from_token_iter
691            check that the correct variant is used in asset"
692            ),
693            AssetExpr::CurrInputAsset => {
694                if env.idx() >= env.spent_utxos().len() {
695                    return Err(EvalError::UtxoIndexOutOfBounds(
696                        env.idx(),
697                        env.spent_utxos().len(),
698                    ));
699                }
700                Ok(env.spent_utxos()[env.idx()].asset)
701            }
702            AssetExpr::Input(i) => {
703                let i = i.eval(env)?;
704                if i >= env.spent_utxos().len() {
705                    return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
706                }
707                Ok(env.spent_utxos()[i].asset)
708            }
709            AssetExpr::Output(i) => {
710                let i = i.eval(env)?;
711                if i >= env.tx().output.len() {
712                    return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
713                }
714                Ok(env.tx().output[i].asset)
715            }
716        }
717    }
718
719    /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos
720    /// Expression is parsed from tokens`[start:end_pos]`
721    pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
722        let tks = tokens;
723        let e = end_pos; // short abbreviations for succinct readable code
724        if let Some(&[Tk::Bytes32(asset_comm), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
725            let asset = asset(u8::try_from(i).ok()?, asset_comm)?;
726            Some((AssetExpr::Const(CovExtArgs::Asset(asset)), e - 2))
727        } else if let Some(&[Tk::CurrInp, Tk::InpAsset]) = tks.get(e.checked_sub(2)?..e) {
728            Some((AssetExpr::CurrInputAsset, e - 2))
729        } else if let Some(&[Tk::InpAsset]) = tks.get(e.checked_sub(1)?..e) {
730            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
731            Some((AssetExpr::Input(idx_expr), e))
732        } else if let Some(&[Tk::OutAsset]) = tks.get(e.checked_sub(1)?..e) {
733            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
734            Some((AssetExpr::Output(idx_expr), e))
735        } else {
736            None
737        }
738    }
739}
740
741impl ValueExpr<CovExtArgs> {
742    /// Push this script to builder
743    pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
744        match self {
745            ValueExpr::Const(CovExtArgs::Value(a)) => {
746                match a {
747                    confidential::Value::Null => {
748                        builder.push_slice(&0i64.to_le_bytes()).push_int(1)
749                    } // null amounts are 0 values
750                    confidential::Value::Explicit(a) => {
751                        builder.push_slice(&a.to_le_bytes()).push_int(1)
752                    } // explicit prefix
753                    confidential::Value::Confidential(c) => {
754                        let ser = c.serialize();
755                        builder.push_slice(&ser[1..]).push_int(ser[0] as i64)
756                    }
757                }
758            }
759            ValueExpr::Const(_) => unreachable!(
760                "Both constructors from_str and from_token_iter
761            check that the correct variant is used in Value"
762            ),
763            ValueExpr::CurrInputValue => builder
764                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
765                .push_opcode(OP_INSPECTINPUTVALUE),
766            ValueExpr::Input(i) => i.push_to_builder(builder).push_opcode(OP_INSPECTINPUTVALUE),
767            ValueExpr::Output(i) => i
768                .push_to_builder(builder)
769                .push_opcode(OP_INSPECTOUTPUTVALUE),
770        }
771    }
772
773    /// Evaluate this expression
774    pub fn eval(&self, env: &TxEnv) -> Result<confidential::Value, EvalError> {
775        match self {
776            ValueExpr::Const(CovExtArgs::Value(a)) => Ok(*a),
777            ValueExpr::Const(_) => unreachable!(
778                "Both constructors from_str and from_token_iter
779            check that the correct variant is used in Value"
780            ),
781            ValueExpr::CurrInputValue => {
782                if env.idx() >= env.spent_utxos().len() {
783                    return Err(EvalError::UtxoIndexOutOfBounds(
784                        env.idx(),
785                        env.spent_utxos().len(),
786                    ));
787                }
788                Ok(env.spent_utxos()[env.idx()].value)
789            }
790            ValueExpr::Input(i) => {
791                let i = i.eval(env)?;
792                if i >= env.spent_utxos().len() {
793                    return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
794                }
795                Ok(env.spent_utxos()[i].value)
796            }
797            ValueExpr::Output(i) => {
798                let i = i.eval(env)?;
799                if i >= env.tx().output.len() {
800                    return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
801                }
802                Ok(env.tx().output[i].value)
803            }
804        }
805    }
806
807    /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos
808    /// Expression is parsed from tokens`[start:end_pos]`
809    pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
810        let tks = tokens;
811        let e = end_pos; // short abbreviations for succinct readable code
812        if let Some(&[Tk::Bytes32(value_comm), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
813            let value = value(u8::try_from(i).ok()?, value_comm)?;
814            Some((ValueExpr::Const(CovExtArgs::Value(value)), e - 2))
815        } else if let Some(&[Tk::Bytes8(exp_val), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
816            let value = value(u8::try_from(i).ok()?, exp_val)?;
817            Some((ValueExpr::Const(CovExtArgs::Value(value)), e - 2))
818        } else if let Some(&[Tk::CurrInp, Tk::InpValue]) = tks.get(e.checked_sub(2)?..e) {
819            Some((ValueExpr::CurrInputValue, e - 2))
820        } else if let Some(&[Tk::InpValue]) = tks.get(e.checked_sub(1)?..e) {
821            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
822            Some((ValueExpr::Input(idx_expr), e))
823        } else if let Some(&[Tk::OutValue]) = tks.get(e.checked_sub(1)?..e) {
824            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
825            Some((ValueExpr::Output(idx_expr), e))
826        } else {
827            None
828        }
829    }
830}
831
832impl SpkExpr<CovExtArgs> {
833    /// Push this script to builder
834    pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
835        match self {
836            SpkExpr::Const(CovExtArgs::Script(s)) => {
837                let (ver, prog) = match &s.0 {
838                    SpkInner::Script(s) => spk_to_components(s),
839                    SpkInner::Hashed(h) => (-1, h.to_byte_array().to_vec()),
840                };
841                builder.push_slice(&prog).push_int(ver as i64)
842            }
843            SpkExpr::Const(_) => unreachable!(
844                "Both constructors from_str and from_token_iter
845            check that the correct variant is used in Script"
846            ),
847            SpkExpr::CurrInputSpk => builder
848                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
849                .push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
850            SpkExpr::Input(i) => i
851                .push_to_builder(builder)
852                .push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
853            SpkExpr::Output(i) => i
854                .push_to_builder(builder)
855                .push_opcode(OP_INSPECTOUTPUTSCRIPTPUBKEY),
856        }
857    }
858
859    /// Evaluate this expression
860    pub fn eval(&self, env: &TxEnv) -> Result<(i8, Vec<u8>), EvalError> {
861        let res = match self {
862            SpkExpr::Const(CovExtArgs::Script(s)) => match &s.0 {
863                SpkInner::Script(s) => spk_to_components(s),
864                SpkInner::Hashed(h) => (-1, h.to_byte_array().to_vec()),
865            },
866            SpkExpr::Const(_) => unreachable!(
867                "Both constructors from_str and from_token_iter
868            check that the correct variant is used in Script pubkey"
869            ),
870            SpkExpr::CurrInputSpk => {
871                if env.idx() >= env.spent_utxos().len() {
872                    return Err(EvalError::UtxoIndexOutOfBounds(
873                        env.idx(),
874                        env.spent_utxos().len(),
875                    ));
876                }
877                spk_to_components(&env.spent_utxos()[env.idx()].script_pubkey)
878            }
879            SpkExpr::Input(i) => {
880                let i = i.eval(env)?;
881                if i >= env.spent_utxos().len() {
882                    return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
883                }
884                spk_to_components(&(env.spent_utxos()[i].script_pubkey))
885            }
886            SpkExpr::Output(i) => {
887                let i = i.eval(env)?;
888                if i >= env.tx().output.len() {
889                    return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
890                }
891                spk_to_components(&(env.tx().output[i].script_pubkey))
892            }
893        };
894        Ok(res)
895    }
896
897    /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos
898    /// Expression is parsed from tokens`[start:end_pos]`
899    pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
900        let tks = tokens;
901        let e = end_pos; // short abbreviations for succinct readable code
902        if let Some(&[Tk::Bytes32(spk_vec), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
903            let script = spk(i8::try_from(i).ok()?, spk_vec)?;
904            Some((SpkExpr::Const(CovExtArgs::Script(Spk::new(script))), e - 2))
905        } else if let Some(&[Tk::Bytes32(spk_vec), Tk::NumNeg1]) = tks.get(e.checked_sub(2)?..e) {
906            let mut inner = [0u8; 32];
907            inner.copy_from_slice(spk_vec);
908            let hashed_spk = Spk(SpkInner::Hashed(sha256::Hash::from_byte_array(inner)));
909            Some((SpkExpr::Const(CovExtArgs::Script(hashed_spk)), e - 2))
910        } else if let Some(&[Tk::Push(ref spk_vec), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
911            let script = spk(i8::try_from(i).ok()?, spk_vec)?;
912            Some((SpkExpr::Const(CovExtArgs::Script(Spk::new(script))), e - 2))
913        } else if let Some(&[Tk::CurrInp, Tk::InpSpk]) = tks.get(e.checked_sub(2)?..e) {
914            Some((SpkExpr::CurrInputSpk, e - 2))
915        } else if let Some(&[Tk::InpSpk]) = tks.get(e.checked_sub(1)?..e) {
916            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
917            Some((SpkExpr::Input(idx_expr), e))
918        } else if let Some(&[Tk::OutSpk]) = tks.get(e.checked_sub(1)?..e) {
919            let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
920            Some((SpkExpr::Output(idx_expr), e))
921        } else {
922            None
923        }
924    }
925}
926
927impl CovOps<CovExtArgs> {
928    /// Push this script to builder
929    pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
930        match self {
931            CovOps::IsExpAsset(x) => x
932                .push_to_builder(builder)
933                .push_int(1)
934                .push_opcode(OP_EQUAL)
935                .push_opcode(OP_NIP),
936            CovOps::IsExpValue(x) => x
937                .push_to_builder(builder)
938                .push_int(1)
939                .push_opcode(OP_EQUAL)
940                .push_opcode(OP_NIP),
941            CovOps::AssetEq(x, y) => {
942                // pushes [asset_x] [pref_x] [asset_y] [pref_y] on top. Check that both prefixes and values match.
943                let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
944                let builder = y
945                    .push_to_builder(builder)
946                    .push_opcode(OP_FROMALTSTACK)
947                    .push_opcode(OP_EQUAL);
948                builder
949                    .push_opcode(OP_TOALTSTACK)
950                    .push_opcode(OP_EQUAL)
951                    .push_opcode(OP_FROMALTSTACK)
952                    .push_opcode(OP_BOOLAND)
953            }
954            CovOps::ValueEq(x, y) => {
955                // pushes [value_x] [pref_x] [value_y] [pref_y] on top. Check that both prefixes and values match.
956                let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
957                let builder = y
958                    .push_to_builder(builder)
959                    .push_opcode(OP_FROMALTSTACK)
960                    .push_opcode(OP_EQUAL);
961                builder
962                    .push_opcode(OP_TOALTSTACK)
963                    .push_opcode(OP_EQUAL)
964                    .push_opcode(OP_FROMALTSTACK)
965                    .push_opcode(OP_BOOLAND)
966            }
967            CovOps::SpkEq(x, y) => {
968                // pushes [spk_x] [wit_ver_x] [spk_y] [wit_ver_y] on top. Check that both prefixes and values match.
969                let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
970                let builder = y
971                    .push_to_builder(builder)
972                    .push_opcode(OP_FROMALTSTACK)
973                    .push_opcode(OP_EQUAL);
974                builder
975                    .push_opcode(OP_TOALTSTACK)
976                    .push_opcode(OP_EQUAL)
977                    .push_opcode(OP_FROMALTSTACK)
978                    .push_opcode(OP_BOOLAND)
979            }
980            CovOps::CurrIndEq(i) => builder
981                .push_int(*i as i64)
982                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
983                .push_opcode(OP_EQUAL),
984            CovOps::IdxEq(x, y) => {
985                // pushes [idx_x] [idx_y] on top. Check that both prefixes and values match.
986                let builder = x.push_to_builder(builder);
987                let builder = y.push_to_builder(builder);
988                builder.push_opcode(OP_EQUAL)
989            }
990        }
991    }
992
993    /// Evaluate this expression
994    pub fn eval(&self, env: &TxEnv) -> Result<bool, EvalError> {
995        match self {
996            CovOps::IsExpAsset(x) => x.eval(env).map(|x| x.is_explicit()),
997            CovOps::IsExpValue(y) => y.eval(env).map(|y| y.is_explicit()),
998            CovOps::AssetEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
999            CovOps::ValueEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
1000            CovOps::SpkEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
1001            CovOps::CurrIndEq(i) => Ok(*i == env.idx()),
1002            CovOps::IdxEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
1003        }
1004    }
1005
1006    /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos
1007    /// Expression is parsed from tokens`[start:end_pos]`
1008    pub fn from_tokens(tks: &[Tk]) -> Option<(Self, usize)> {
1009        let e = tks.len();
1010        if let Some(&[Tk::Num(i), Tk::CurrInp, Tk::Equal]) = tks.get(e.checked_sub(3)?..e) {
1011            Some((CovOps::CurrIndEq(i as usize), e - 3))
1012        } else if let Some(&[Tk::Equal]) = tks.get(e.checked_sub(1)?..e) {
1013            let (y, e) = IdxExpr::from_tokens(tks, e - 1)?;
1014            let (x, e) = IdxExpr::from_tokens(tks, e)?;
1015            Some((CovOps::IdxEq(x, y), e))
1016        } else if let Some(&[Tk::Num(1), Tk::Equal, Tk::Nip]) = tks.get(e.checked_sub(3)?..e) {
1017            if let Some((asset, e)) = AssetExpr::from_tokens(tks, e - 3) {
1018                Some((CovOps::IsExpAsset(asset), e))
1019            } else if let Some((value, e)) = ValueExpr::from_tokens(tks, e - 3) {
1020                Some((CovOps::IsExpValue(value), e))
1021            } else {
1022                None
1023            }
1024        } else if let Some(
1025            &[Tk::FromAltStack, Tk::Equal, Tk::ToAltStack, Tk::Equal, Tk::FromAltStack, Tk::BoolAnd],
1026        ) = tks.get(e.checked_sub(6)?..e)
1027        {
1028            let res = if let Some((y, e)) = AssetExpr::from_tokens(tks, e - 6) {
1029                if tks.get(e - 1) != Some(&Tk::ToAltStack) {
1030                    return None;
1031                }
1032                if let Some((x, e)) = AssetExpr::from_tokens(tks, e - 1) {
1033                    Some((CovOps::AssetEq(x, y), e))
1034                } else {
1035                    None
1036                }
1037            } else {
1038                None
1039            };
1040            if res.is_some() {
1041                return res;
1042            }
1043            let res = if let Some((y, e)) = ValueExpr::from_tokens(tks, e - 6) {
1044                if tks.get(e - 1) != Some(&Tk::ToAltStack) {
1045                    return None;
1046                }
1047                if let Some((x, e)) = ValueExpr::from_tokens(tks, e - 1) {
1048                    Some((CovOps::ValueEq(x, y), e))
1049                } else {
1050                    None
1051                }
1052            } else {
1053                None
1054            };
1055            if res.is_some() {
1056                return res;
1057            }
1058            if let Some((y, e)) = SpkExpr::from_tokens(tks, e - 6) {
1059                if tks.get(e - 1) != Some(&Tk::ToAltStack) {
1060                    return None;
1061                }
1062                if let Some((x, e)) = SpkExpr::from_tokens(tks, e - 1) {
1063                    Some((CovOps::SpkEq(x, y), e))
1064                } else {
1065                    None
1066                }
1067            } else {
1068                None
1069            }
1070        } else {
1071            None
1072        }
1073    }
1074}
1075
1076impl ParseableExt for CovOps<CovExtArgs> {
1077    fn satisfy<Pk, S>(&self, sat: &S) -> Satisfaction
1078    where
1079        Pk: ToPublicKey,
1080        S: Satisfier<Pk>,
1081    {
1082        let env = match (
1083            sat.lookup_tx(),
1084            sat.lookup_spent_utxos(),
1085            sat.lookup_curr_inp(),
1086        ) {
1087            (Some(tx), Some(utxos), Some(idx)) => match TxEnv::new(tx, utxos, idx) {
1088                Some(x) => x,
1089                None => {
1090                    return Satisfaction {
1091                        stack: Witness::Impossible,
1092                        has_sig: false,
1093                    }
1094                }
1095            },
1096            _ => {
1097                return Satisfaction {
1098                    stack: Witness::Impossible,
1099                    has_sig: false,
1100                }
1101            }
1102        };
1103        let wit = match self.eval(&env) {
1104            Ok(false) => Witness::Unavailable,
1105            Ok(true) => Witness::empty(),
1106            Err(_e) => Witness::Impossible,
1107        };
1108        Satisfaction {
1109            stack: wit,
1110            has_sig: false,
1111        }
1112    }
1113
1114    fn dissatisfy<Pk, S>(&self, sat: &S) -> Satisfaction
1115    where
1116        Pk: ToPublicKey,
1117        S: Satisfier<Pk>,
1118    {
1119        let env = match (
1120            sat.lookup_tx(),
1121            sat.lookup_spent_utxos(),
1122            sat.lookup_curr_inp(),
1123        ) {
1124            (Some(tx), Some(utxos), Some(idx)) => match TxEnv::new(tx, utxos, idx) {
1125                Some(x) => x,
1126                None => {
1127                    return Satisfaction {
1128                        stack: Witness::Impossible,
1129                        has_sig: false,
1130                    }
1131                }
1132            },
1133            _ => {
1134                return Satisfaction {
1135                    stack: Witness::Impossible,
1136                    has_sig: false,
1137                }
1138            }
1139        };
1140        let wit = match self.eval(&env) {
1141            Ok(false) => Witness::empty(),
1142            Ok(true) => Witness::Unavailable,
1143            Err(_e) => Witness::Impossible,
1144        };
1145        Satisfaction {
1146            stack: wit,
1147            has_sig: false,
1148        }
1149    }
1150
1151    fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
1152        self.push_to_builder(builder)
1153    }
1154
1155    fn from_token_iter(tokens: &mut TokenIter<'_>) -> Result<Self, FromTokenIterError> {
1156        let len = tokens.len();
1157        match Self::from_tokens(tokens.as_inner_mut()) {
1158            Some((res, last_pos)) => {
1159                tokens.advance(len - last_pos).ok_or(FromTokenIterError)?;
1160                Ok(res)
1161            }
1162            None => Err(FromTokenIterError),
1163        }
1164    }
1165
1166    fn evaluate(
1167        &self,
1168        stack: &mut interpreter::Stack,
1169        txenv: Option<&TxEnv>,
1170    ) -> Result<bool, interpreter::Error> {
1171        let txenv = txenv
1172            .as_ref()
1173            .ok_or(interpreter::Error::ArithError(EvalError::TxEnvNotPresent))?;
1174
1175        match self.eval(txenv) {
1176            Ok(true) => {
1177                stack.push(interpreter::Element::Satisfied);
1178                Ok(true)
1179            }
1180            Ok(false) => {
1181                stack.push(interpreter::Element::Dissatisfied);
1182                Ok(false)
1183            }
1184            Err(e) => Err(interpreter::Error::ArithError(e)),
1185        }
1186    }
1187}
1188
1189impl<PArg, QArg> TranslateExtParam<PArg, QArg> for CovOps<PArg>
1190where
1191    PArg: ExtParam,
1192    QArg: ExtParam,
1193{
1194    type Output = CovOps<QArg>;
1195
1196    fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
1197    where
1198        T: ExtParamTranslator<PArg, QArg, E>,
1199    {
1200        match self {
1201            CovOps::IsExpAsset(a) => Ok(CovOps::IsExpAsset(a._translate_ext(t)?)),
1202            CovOps::IsExpValue(v) => Ok(CovOps::IsExpValue(v._translate_ext(t)?)),
1203            CovOps::AssetEq(x, y) => {
1204                Ok(CovOps::AssetEq(x._translate_ext(t)?, y._translate_ext(t)?))
1205            }
1206            CovOps::ValueEq(x, y) => {
1207                Ok(CovOps::ValueEq(x._translate_ext(t)?, y._translate_ext(t)?))
1208            }
1209            CovOps::SpkEq(x, y) => Ok(CovOps::SpkEq(x._translate_ext(t)?, y._translate_ext(t)?)),
1210            CovOps::CurrIndEq(i) => Ok(CovOps::CurrIndEq(*i)),
1211            CovOps::IdxEq(x, y) => Ok(CovOps::IdxEq(x.clone(), y.clone())),
1212        }
1213    }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218    use bitcoin::key::XOnlyPublicKey;
1219
1220    use super::*;
1221    use crate::test_utils::{StrExtTranslator, StrXOnlyKeyTranslator};
1222    use crate::{Miniscript, Segwitv0, Tap, TranslatePk};
1223
1224    #[test]
1225    fn test_index_ops() {
1226        // index ops tests with different index types
1227        _test_parse("is_exp_asset(inp_asset(curr_idx))");
1228        _test_parse("is_exp_asset(inp_asset(idx_add(9,curr_idx)))");
1229        _test_parse("is_exp_asset(inp_asset(idx_sub(9,curr_idx)))");
1230        _test_parse("is_exp_asset(inp_asset(idx_mul(9,curr_idx)))");
1231        _test_parse("is_exp_asset(inp_asset(idx_div(9,curr_idx)))");
1232        _test_parse("is_exp_asset(inp_asset(idx_mul(1,idx_add(9,curr_idx))))");
1233        _test_parse("is_exp_asset(inp_asset(idx_sub(idx_mul(1,idx_add(9,curr_idx)),1)))");
1234
1235        // test type parent fragments
1236        _test_parse("is_exp_asset(out_asset(idx_add(9,curr_idx)))");
1237        _test_parse("is_exp_value(inp_value(idx_add(9,curr_idx)))");
1238        _test_parse("is_exp_value(out_value(idx_add(9,curr_idx)))");
1239        _test_parse("spk_eq(inp_spk(idx_add(9,curr_idx)),out_spk(idx_sub(9,curr_idx)))");
1240
1241        _test_parse("idx_eq(10,idx_add(9,curr_idx))");
1242    }
1243
1244    #[test]
1245    fn cov_parse() {
1246        // This does not test the evaluation
1247        _test_parse("is_exp_asset(ConfAst)");
1248        _test_parse("is_exp_asset(ExpAst)");
1249        _test_parse("is_exp_asset(curr_inp_asset)");
1250        _test_parse("is_exp_asset(inp_asset(9))");
1251        _test_parse("is_exp_asset(out_asset(9))");
1252        _test_parse("asset_eq(ConfAst,ExpAst)");
1253        _test_parse("asset_eq(curr_inp_asset,out_asset(1))");
1254        _test_parse("asset_eq(inp_asset(3),out_asset(1))");
1255
1256        // same tests for values
1257        _test_parse("is_exp_value(ConfVal)");
1258        _test_parse("is_exp_value(ExpVal)");
1259        _test_parse("is_exp_value(curr_inp_value)");
1260        _test_parse("is_exp_value(inp_value(9))");
1261        _test_parse("is_exp_value(out_value(9))");
1262        _test_parse("value_eq(ConfVal,ExpVal)");
1263        _test_parse("value_eq(curr_inp_value,out_value(1))");
1264        _test_parse("value_eq(inp_value(3),out_value(1))");
1265
1266        // same tests for spks
1267        _test_parse("spk_eq(V0Spk,out_spk(1))");
1268        _test_parse("spk_eq(V1Spk,inp_spk(1))");
1269        _test_parse("spk_eq(curr_inp_spk,out_spk(1))");
1270        _test_parse("spk_eq(inp_spk(3),out_spk(1))");
1271        _test_parse("spk_eq(out_spk(2),V1Spk)");
1272
1273        // Testing the current input index
1274        _test_parse("curr_idx_eq(1)");
1275        _test_parse("curr_idx_eq(0)");
1276
1277        // test some misc combinations with other miniscript fragments
1278        _test_parse(
1279            "and_v(v:pk(K),and_v(v:is_exp_value(out_value(1)),is_exp_asset(out_asset(1))))",
1280        );
1281        _test_parse("and_v(v:pk(K),and_v(v:value_eq(ConfVal,ConfVal),spk_eq(V1Spk,V1Spk)))");
1282        _test_parse("and_v(v:pk(K),and_v(v:value_eq(ConfVal,ConfVal),and_v(v:spk_eq(V1Spk,V1Spk),curr_idx_eq(1))))");
1283    }
1284
1285    #[test]
1286    fn options_fail_test() {
1287        type MsExt = Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>>;
1288
1289        // 33 bytes explicit asset succeeds
1290        MsExt::from_str_insane("asset_eq(out_asset(0),0179d51a47e4ac8e32306486dd0926a88678c392f2ed5f213e3ff2ad461c7c25e1)").unwrap();
1291        // 32 bytes explicit asset without prefix fails
1292        MsExt::from_str_insane("asset_eq(out_asset(0),79d51a47e4ac8e32306486dd0926a88678c392f2ed5f213e3ff2ad461c7c25e1)").unwrap_err();
1293    }
1294
1295    #[rustfmt::skip]
1296    fn _test_parse(s: &str) {
1297        type MsExtStr = Miniscript<String, Tap, CovOps<String>>;
1298        type MsExt = Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>>;
1299        type MsExtSegwitv0 = Miniscript<String, Segwitv0, CovOps<CovExtArgs>>;
1300
1301        // Make sure that parsing this errors in segwit context
1302        assert!(MsExtSegwitv0::from_str_insane(s).is_err());
1303
1304        let ms = MsExtStr::from_str_insane(s).unwrap();
1305        // test string rtt
1306        assert_eq!(ms.to_string(), s);
1307        let mut t = StrXOnlyKeyTranslator::default();
1308        let mut ext_t = StrExtTranslator::default();
1309        {
1310            ext_t.ext_map.insert("V1Spk".to_string(),CovExtArgs::spk(elements::Script::from_str("5120c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()));
1311            ext_t.ext_map.insert("V0Spk".to_string(),CovExtArgs::spk(elements::Script::from_str("0020c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()));
1312            ext_t.ext_map.insert("ConfAst".to_string(),CovExtArgs::asset(encode::deserialize(&Vec::<u8>::from_hex("0adef814ab021498562ab4717287305d3f7abb5686832fe6183e1db495abef7cc7").unwrap()).unwrap()));
1313            ext_t.ext_map.insert("ExpAst".to_string(),CovExtArgs::asset(encode::deserialize(&Vec::<u8>::from_hex("01c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()).unwrap()));
1314            ext_t.ext_map.insert("ConfVal".to_string(),CovExtArgs::value(encode::deserialize(&Vec::<u8>::from_hex("09def814ab021498562ab4717287305d3f7abb5686832fe6183e1db495abef7cc7").unwrap()).unwrap()));
1315            ext_t.ext_map.insert("ExpVal".to_string(),CovExtArgs::value(encode::deserialize(&Vec::<u8>::from_hex("010000000011110000").unwrap()).unwrap()));
1316        }
1317        let ms: Miniscript<XOnlyPublicKey, Tap, CovOps<String>> = ms.translate_pk(&mut t).unwrap();
1318        let ms: Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>> = ms.translate_ext(&mut ext_t).unwrap();
1319        // script rtt
1320        assert_eq!(ms.encode(), MsExt::parse_insane(&ms.encode()).unwrap().encode());
1321        // String rtt of the translated script
1322        assert_eq!(ms, MsExt::from_str_insane(&ms.to_string()).unwrap())
1323    }
1324}