elements_miniscript/extensions/
index_ops.rs

1//! Miniscript Index expressions:
2//! Note that these fragment is only supported for Tapscript context
3//! Refer to the spec for additional details.
4use std::fmt;
5
6use elements::opcodes::{self};
7use elements::script;
8
9use super::{EvalError, TxEnv};
10use crate::expression::{FromTree, Tree};
11use crate::miniscript::lex::Token as Tk;
12use crate::{expression, script_num_size, Error};
13
14/// Enum representing operations with input/output indexes.
15/// Pushes a single CScriptNum on stack top. This is used to represent the index of the input or output.
16#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
17pub enum IdxExpr {
18    /* leaf fragments/terminals */
19    /// A constant
20    /// `<i>` as CScriptNum
21    Const(usize),
22    /// Current Input index
23    CurrIdx,
24    /// Add two IdxExpr
25    /// `[X] [Y] ADD`
26    Add(Box<IdxExpr>, Box<IdxExpr>),
27    /// Subtract two IdxExpr
28    /// `[X] [Y] SUB`
29    Sub(Box<IdxExpr>, Box<IdxExpr>),
30    /// Multiply two IdxExpr
31    /// `[X] SCIPTNUMTOLE64 [Y] SCIPTNUMTOLE64 MUL64 <1> EQUALVERIFY LE64TOSCIPTNUM`
32    Mul(Box<IdxExpr>, Box<IdxExpr>),
33    /// Divide two IdxExpr (integer division)
34    /// `[X] SCIPTNUMTOLE64 [Y] SCIPTNUMTOLE64 DIV64 <1> EQUALVERIFY NIP LE64TOSCIPTNUM`
35    Div(Box<IdxExpr>, Box<IdxExpr>),
36}
37
38impl IdxExpr {
39    /// Returns the script size of this [`IdxExpr`].
40    pub fn script_size(&self) -> usize {
41        match self {
42            IdxExpr::Const(i) => script_num_size(*i),
43            IdxExpr::CurrIdx => 1,
44            IdxExpr::Add(x, y) => x.script_size() + y.script_size() + 1,
45            IdxExpr::Sub(x, y) => x.script_size() + y.script_size() + 1,
46            IdxExpr::Mul(x, y) => x.script_size() + y.script_size() + 6,
47            IdxExpr::Div(x, y) => x.script_size() + y.script_size() + 7,
48        }
49    }
50}
51
52impl fmt::Display for IdxExpr {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            IdxExpr::Const(i) => write!(f, "{}", i),
56            IdxExpr::CurrIdx => write!(f, "curr_idx"),
57            IdxExpr::Add(x, y) => write!(f, "idx_add({},{})", x, y),
58            IdxExpr::Sub(x, y) => write!(f, "idx_sub({},{})", x, y),
59            IdxExpr::Mul(x, y) => write!(f, "idx_mul({},{})", x, y),
60            IdxExpr::Div(x, y) => write!(f, "idx_div({},{})", x, y),
61        }
62    }
63}
64
65impl fmt::Debug for IdxExpr {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            IdxExpr::Const(i) => write!(f, "{:?}", i),
69            IdxExpr::CurrIdx => write!(f, "curr_idx"),
70            IdxExpr::Add(x, y) => write!(f, "idx_add({:?},{:?})", x, y),
71            IdxExpr::Sub(x, y) => write!(f, "idx_sub({:?},{:?})", x, y),
72            IdxExpr::Mul(x, y) => write!(f, "idx_mul({:?},{:?})", x, y),
73            IdxExpr::Div(x, y) => write!(f, "idx_div({:?},{:?})", x, y),
74        }
75    }
76}
77
78impl FromTree for IdxExpr {
79    fn from_tree(top: &Tree<'_>) -> Result<Self, Error> {
80        match (top.name, top.args.len()) {
81            ("curr_idx", 0) => Ok(IdxExpr::CurrIdx),
82            ("idx_add", 2) => Ok(IdxExpr::Add(
83                Box::new(Self::from_tree(&top.args[0])?),
84                Box::new(Self::from_tree(&top.args[1])?),
85            )),
86            ("idx_sub", 2) => Ok(IdxExpr::Sub(
87                Box::new(Self::from_tree(&top.args[0])?),
88                Box::new(Self::from_tree(&top.args[1])?),
89            )),
90            ("idx_mul", 2) => Ok(IdxExpr::Mul(
91                Box::new(Self::from_tree(&top.args[0])?),
92                Box::new(Self::from_tree(&top.args[1])?),
93            )),
94            ("idx_div", 2) => Ok(IdxExpr::Div(
95                Box::new(Self::from_tree(&top.args[0])?),
96                Box::new(Self::from_tree(&top.args[1])?),
97            )),
98            (_num, 0) => {
99                expression::terminal(top, expression::parse_num::<usize>).map(IdxExpr::Const)
100            }
101            _ => Err(Error::Unexpected(format!("Unexpected token: {:?}", top))),
102        }
103    }
104}
105
106impl IdxExpr {
107    /// Push this script to builder
108    /// Panics when trying to push a Null asset. This never occur in honest use-cases
109    /// as there is no such thing as Null asset
110    pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
111        use opcodes::all::*;
112        match self {
113            IdxExpr::Const(i) => builder.push_int(*i as i64),
114            IdxExpr::CurrIdx => builder.push_opcode(OP_PUSHCURRENTINPUTINDEX),
115            IdxExpr::Add(x, y) => {
116                let builder = x.push_to_builder(builder);
117                let builder = y.push_to_builder(builder);
118                builder.push_opcode(OP_ADD)
119            }
120            IdxExpr::Sub(x, y) => {
121                let builder = x.push_to_builder(builder);
122                let builder = y.push_to_builder(builder);
123                builder.push_opcode(OP_SUB)
124            }
125            IdxExpr::Mul(x, y) => {
126                let builder = x.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64);
127                let builder = y.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64);
128                builder
129                    .push_opcode(OP_MUL64)
130                    .push_int(1)
131                    .push_opcode(OP_EQUALVERIFY)
132                    .push_opcode(OP_LE64TOSCRIPTNUM)
133            }
134            IdxExpr::Div(x, y) => {
135                let builder = x.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64);
136                let builder = y.push_to_builder(builder).push_opcode(OP_SCRIPTNUMTOLE64);
137                builder
138                    .push_opcode(OP_DIV64)
139                    .push_int(1)
140                    .push_opcode(OP_EQUALVERIFY)
141                    .push_opcode(OP_NIP)
142                    .push_opcode(OP_LE64TOSCRIPTNUM)
143            }
144        }
145    }
146
147    /// Evaluate this expression
148    pub fn eval(&self, env: &TxEnv) -> Result<usize, EvalError> {
149        match self {
150            IdxExpr::Const(i) => Ok(*i),
151            IdxExpr::CurrIdx => Ok(env.idx),
152            IdxExpr::Add(x, y) => Ok(x.eval(env)? + y.eval(env)?),
153            IdxExpr::Sub(x, y) => Ok(x.eval(env)? - y.eval(env)?),
154            IdxExpr::Mul(x, y) => Ok(x.eval(env)? * y.eval(env)?),
155            IdxExpr::Div(x, y) => Ok(x.eval(env)? / y.eval(env)?),
156        }
157    }
158
159    /// Returns (self, start_pos) parsed reversed form tokens starting with index end_pos
160    /// Expression is parsed from tokens `[start:end_pos]`
161    #[rustfmt::skip]
162    pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
163        let tks = tokens;
164        let e = end_pos; // short abbreviations for succinct readable code
165        if let Some(&[Tk::Num(i)]) = tks.get(e.checked_sub(1)?..e) {
166            Some((IdxExpr::Const(i as usize), e - 1))
167        } else if let Some(&[Tk::CurrInp]) = tks.get(e.checked_sub(1)?..e) {
168            Some((IdxExpr::CurrIdx, e - 1))
169        } else if let Some(&[Tk::Add]) = tks.get(e.checked_sub(1)?..e) {
170            let (y, e) = IdxExpr::from_tokens(tks, e - 1)?;
171            let (x, e) = IdxExpr::from_tokens(tks, e)?;
172            Some((IdxExpr::Add(Box::new(x), Box::new(y)), e))
173        } else if let Some(&[Tk::Sub]) = tks.get(e.checked_sub(1)?..e) {
174            let (y, e) = IdxExpr::from_tokens(tks, e - 1)?;
175            let (x, e) = IdxExpr::from_tokens(tks, e)?;
176            Some((IdxExpr::Sub(Box::new(x), Box::new(y)), e))
177        } else if let Some(&[Tk::ScriptNumToLe64, Tk::Mul64, Tk::Num(1), Tk::Equal, Tk::Verify, Tk::Le64ToScriptNum]) = tks.get(e.checked_sub(6)?..e) {
178            let (y, e) = IdxExpr::from_tokens(tks, e - 6)?;
179            if let Some(&[Tk::ScriptNumToLe64]) = tks.get(e.checked_sub(1)?..e) {
180                let (x, e) = IdxExpr::from_tokens(tks, e - 1)?;
181                Some((IdxExpr::Mul(Box::new(x), Box::new(y)), e))
182            } else {
183                None
184            }
185        } else if let Some(&[Tk::ScriptNumToLe64, Tk::Div64, Tk::Num(1), Tk::Equal, Tk::Verify, Tk::Nip, Tk::Le64ToScriptNum]) = tks.get(e.checked_sub(7)?..e) {
186            let (y, e) = IdxExpr::from_tokens(tks, e - 7)?;
187            if let Some(&[Tk::ScriptNumToLe64]) = tks.get(e.checked_sub(1)?..e) {
188                let (x, e) = IdxExpr::from_tokens(tks, e - 1)?;
189                Some((IdxExpr::Div(Box::new(x), Box::new(y)), e))
190            } else {
191                None
192            }
193        } else {
194            None
195        }
196    }
197}