use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
use bitcoin::hashes::{sha256, Hash};
use elements::address::Payload;
use elements::confidential::Asset;
use elements::hex::{FromHex, ToHex};
use elements::opcodes::all::*;
use elements::{confidential, encode, script, Address, AddressParams};
use super::index_ops::IdxExpr;
use super::param::{ExtParamTranslator, TranslateExtParam};
use super::{ArgFromStr, CovExtArgs, EvalError, ExtParam, FromTokenIterError, ParseableExt, TxEnv};
use crate::expression::{FromTree, Tree};
use crate::miniscript::context::ScriptContextError;
use crate::miniscript::lex::{Token as Tk, TokenIter};
use crate::miniscript::satisfy::{Satisfaction, Witness};
use crate::miniscript::types::extra_props::{OpLimits, TimelockInfo};
use crate::miniscript::types::{Base, Correctness, Dissat, ExtData, Input, Malleability};
use crate::{
expression, interpreter, script_num_size, Error, ExtTranslator, Extension, Satisfier,
ToPublicKey, TranslateExt,
};
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub enum AssetExpr<T: ExtParam> {
Const(T),
CurrInputAsset,
Input(IdxExpr),
Output(IdxExpr),
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub enum ValueExpr<T: ExtParam> {
Const(T),
CurrInputValue,
Input(IdxExpr),
Output(IdxExpr),
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub enum SpkExpr<T: ExtParam> {
Const(T),
CurrInputSpk,
Input(IdxExpr),
Output(IdxExpr),
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub enum CovOps<T: ExtParam> {
IsExpAsset(AssetExpr<T>),
IsExpValue(ValueExpr<T>),
AssetEq(AssetExpr<T>, AssetExpr<T>),
ValueEq(ValueExpr<T>, ValueExpr<T>),
SpkEq(SpkExpr<T>, SpkExpr<T>),
CurrIndEq(usize),
IdxEq(IdxExpr, IdxExpr),
}
impl<T: ExtParam> AssetExpr<T> {
fn script_size(&self) -> usize {
match self {
AssetExpr::Const(_) => 33 + 1,
AssetExpr::CurrInputAsset => 2,
AssetExpr::Input(i) => i.script_size() + 1,
AssetExpr::Output(i) => i.script_size() + 1,
}
}
fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<AssetExpr<Q>, E>
where
Ext: ExtParamTranslator<T, Q, E>,
Q: ExtParam,
{
let res = match self {
AssetExpr::Const(c) => AssetExpr::Const(t.ext(c)?),
AssetExpr::CurrInputAsset => AssetExpr::CurrInputAsset,
AssetExpr::Input(i) => AssetExpr::Input(i.clone()),
AssetExpr::Output(i) => AssetExpr::Output(i.clone()),
};
Ok(res)
}
}
impl<T: ExtParam> fmt::Display for AssetExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AssetExpr::Const(asset) => write!(f, "{}", asset),
AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
AssetExpr::Input(i) => write!(f, "inp_asset({})", i),
AssetExpr::Output(i) => write!(f, "out_asset({})", i),
}
}
}
impl<T: ExtParam> fmt::Debug for AssetExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AssetExpr::Const(asset) => write!(f, "{:?}", asset),
AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
AssetExpr::Input(i) => write!(f, "inp_asset({:?})", i),
AssetExpr::Output(i) => write!(f, "out_asset({:?})", i),
}
}
}
impl<T: ExtParam> ArgFromStr for AssetExpr<T> {
fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
let top = expression::Tree::from_str(s)?;
Self::from_tree_parent(&top, parent, pos)
}
}
impl<T: ExtParam> AssetExpr<T> {
fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
match (top.name, top.args.len()) {
("curr_inp_asset", 0) => Ok(AssetExpr::CurrInputAsset),
("inp_asset", 1) => expression::unary(top, AssetExpr::Input),
("out_asset", 1) => expression::unary(top, AssetExpr::Output),
(asset, 0) => Ok(AssetExpr::Const(T::arg_from_str(asset, parent, pos)?)),
_ => Err(Error::Unexpected(format!(
"{}({} args) while parsing Extension",
top.name,
top.args.len(),
))),
}
}
}
impl<T: ExtParam> ValueExpr<T> {
fn script_size(&self) -> usize {
match self {
ValueExpr::Const(_c) => 33 + 1, ValueExpr::CurrInputValue => 2,
ValueExpr::Input(i) => i.script_size() + 1,
ValueExpr::Output(i) => i.script_size() + 1,
}
}
fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<ValueExpr<Q>, E>
where
Ext: ExtParamTranslator<T, Q, E>,
Q: ExtParam,
{
let res = match self {
ValueExpr::Const(c) => ValueExpr::Const(t.ext(c)?),
ValueExpr::CurrInputValue => ValueExpr::CurrInputValue,
ValueExpr::Input(i) => ValueExpr::Input(i.clone()),
ValueExpr::Output(i) => ValueExpr::Output(i.clone()),
};
Ok(res)
}
}
impl<T: ExtParam> fmt::Display for ValueExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueExpr::Const(asset) => write!(f, "{}", asset),
ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
ValueExpr::Input(i) => write!(f, "inp_value({})", i),
ValueExpr::Output(i) => write!(f, "out_value({})", i),
}
}
}
impl<T: ExtParam> fmt::Debug for ValueExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueExpr::Const(asset) => write!(f, "{:?}", asset),
ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
ValueExpr::Input(i) => write!(f, "inp_value({:?})", i),
ValueExpr::Output(i) => write!(f, "out_value({:?})", i),
}
}
}
impl<T: ExtParam> ArgFromStr for ValueExpr<T> {
fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
let top = expression::Tree::from_str(s)?;
Self::from_tree_parent(&top, parent, pos)
}
}
impl<T: ExtParam> ValueExpr<T> {
fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
match (top.name, top.args.len()) {
("curr_inp_value", 0) => Ok(ValueExpr::CurrInputValue),
("inp_value", 1) => expression::unary(top, ValueExpr::Input),
("out_value", 1) => expression::unary(top, ValueExpr::Output),
(value, 0) => Ok(ValueExpr::Const(T::arg_from_str(value, parent, pos)?)),
_ => Err(Error::Unexpected(format!(
"{}({} args) while parsing Extension",
top.name,
top.args.len(),
))),
}
}
}
impl<T: ExtParam> SpkExpr<T> {
fn script_size(&self) -> usize {
match self {
SpkExpr::Const(_c) => 32 + 1 + 1,
SpkExpr::CurrInputSpk => 2,
SpkExpr::Input(i) => i.script_size() + 1,
SpkExpr::Output(i) => i.script_size() + 1,
}
}
fn _translate_ext<Q, E, Ext>(&self, t: &mut Ext) -> Result<SpkExpr<Q>, E>
where
Ext: ExtParamTranslator<T, Q, E>,
Q: ExtParam,
{
let res = match self {
SpkExpr::Const(c) => SpkExpr::Const(t.ext(c)?),
SpkExpr::CurrInputSpk => SpkExpr::CurrInputSpk,
SpkExpr::Input(i) => SpkExpr::Input(i.clone()),
SpkExpr::Output(i) => SpkExpr::Output(i.clone()),
};
Ok(res)
}
}
impl<T: ExtParam> fmt::Display for SpkExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpkExpr::Const(asset) => write!(f, "{}", asset),
SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
SpkExpr::Input(i) => write!(f, "inp_spk({})", i),
SpkExpr::Output(i) => write!(f, "out_spk({})", i),
}
}
}
impl<T: ExtParam> fmt::Debug for SpkExpr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpkExpr::Const(asset) => write!(f, "{:?}", asset),
SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
SpkExpr::Input(i) => write!(f, "inp_spk({:?})", i),
SpkExpr::Output(i) => write!(f, "out_spk({:?})", i),
}
}
}
impl<T: ExtParam> ArgFromStr for SpkExpr<T> {
fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
let top = expression::Tree::from_str(s)?;
Self::from_tree_parent(&top, parent, pos)
}
}
impl<T: ExtParam> SpkExpr<T> {
fn from_tree_parent(top: &Tree<'_>, parent: &str, pos: usize) -> Result<Self, Error> {
match (top.name, top.args.len()) {
("curr_inp_spk", 0) => Ok(SpkExpr::CurrInputSpk),
("inp_spk", 1) => expression::unary(top, SpkExpr::Input),
("out_spk", 1) => expression::unary(top, SpkExpr::Output),
(asset, 0) => Ok(SpkExpr::Const(T::arg_from_str(asset, parent, pos)?)),
_ => Err(Error::Unexpected(format!(
"{}({} args) while parsing Extension",
top.name,
top.args.len(),
))),
}
}
}
impl<T: ExtParam> fmt::Display for CovOps<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CovOps::IsExpAsset(a) => write!(f, "is_exp_asset({})", a),
CovOps::IsExpValue(v) => write!(f, "is_exp_value({})", v),
CovOps::AssetEq(a, b) => write!(f, "asset_eq({},{})", a, b),
CovOps::ValueEq(a, b) => write!(f, "value_eq({},{})", a, b),
CovOps::SpkEq(a, b) => write!(f, "spk_eq({},{})", a, b),
CovOps::CurrIndEq(i) => write!(f, "curr_idx_eq({})", i),
CovOps::IdxEq(a, b) => write!(f, "idx_eq({},{})", a, b),
}
}
}
impl<T: ExtParam> fmt::Debug for CovOps<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CovOps::IsExpAsset(a) => write!(f, "is_exp_asset({:?})", a),
CovOps::IsExpValue(v) => write!(f, "is_exp_value({:?})", v),
CovOps::AssetEq(a, b) => write!(f, "asset_eq({:?},{:?})", a, b),
CovOps::ValueEq(a, b) => write!(f, "value_eq({:?},{:?})", a, b),
CovOps::SpkEq(a, b) => write!(f, "spk_eq({:?},{:?})", a, b),
CovOps::CurrIndEq(i) => write!(f, "curr_idx_eq({:?})", i),
CovOps::IdxEq(a, b) => write!(f, "idx_eq({},{})", a, b),
}
}
}
impl<T: ExtParam> FromStr for CovOps<T> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let top = expression::Tree::from_str(s)?;
Self::from_tree(&top)
}
}
impl<T: ExtParam> FromTree for CovOps<T> {
fn from_tree(top: &Tree<'_>) -> Result<Self, Error> {
match (top.name, top.args.len()) {
("is_exp_asset", 1) => {
AssetExpr::from_tree_parent(&top.args[0], top.name, 0).map(CovOps::IsExpAsset)
}
("is_exp_value", 1) => {
ValueExpr::from_tree_parent(&top.args[0], top.name, 0).map(CovOps::IsExpValue)
}
("asset_eq", 2) => {
let l = AssetExpr::from_tree_parent(&top.args[0], top.name, 0)?;
let r = AssetExpr::from_tree_parent(&top.args[1], top.name, 1)?;
Ok(CovOps::AssetEq(l, r))
}
("value_eq", 2) => {
let l = ValueExpr::from_tree_parent(&top.args[0], top.name, 0)?;
let r = ValueExpr::from_tree_parent(&top.args[1], top.name, 1)?;
Ok(CovOps::ValueEq(l, r))
}
("spk_eq", 2) => {
let l = SpkExpr::from_tree_parent(&top.args[0], top.name, 0)?;
let r = SpkExpr::from_tree_parent(&top.args[1], top.name, 1)?;
Ok(CovOps::SpkEq(l, r))
}
("curr_idx_eq", 1) => {
expression::terminal(&top.args[0], expression::parse_num::<usize>)
.map(CovOps::CurrIndEq)
}
("idx_eq", 2) => {
let l = IdxExpr::from_tree(&top.args[0])?;
let r = IdxExpr::from_tree(&top.args[1])?;
Ok(CovOps::IdxEq(l, r))
}
_ => Err(Error::Unexpected(format!(
"{}({} args) while parsing Extension",
top.name,
top.args.len(),
))),
}
}
}
impl<T: ExtParam> Extension for CovOps<T> {
fn corr_prop(&self) -> Correctness {
Correctness {
base: Base::B,
input: Input::Zero, dissatisfiable: false, unit: true,
}
}
fn mall_prop(&self) -> Malleability {
Malleability {
dissat: Dissat::None, safe: false, non_malleable: true, }
}
fn extra_prop(&self) -> ExtData {
ExtData {
pk_cost: self.script_size(), has_free_verify: matches!(self, CovOps::CurrIndEq(..)),
stack_elem_count_sat: Some(0),
stack_elem_count_dissat: Some(0),
max_sat_size: Some((0, 0)),
max_dissat_size: Some((0, 0)),
timelock_info: TimelockInfo::default(),
exec_stack_elem_count_sat: Some(4), exec_stack_elem_count_dissat: Some(4),
ops: OpLimits {
count: 0,
sat: Some(0),
nsat: Some(0),
},
}
}
fn script_size(&self) -> usize {
match self {
CovOps::IsExpAsset(a) => a.script_size() + 3,
CovOps::IsExpValue(v) => v.script_size() + 3,
CovOps::AssetEq(a, b) => a.script_size() + b.script_size() + 7,
CovOps::ValueEq(a, b) => a.script_size() + b.script_size() + 7,
CovOps::SpkEq(a, b) => a.script_size() + b.script_size() + 7,
CovOps::CurrIndEq(i) => script_num_size(*i) + 2,
CovOps::IdxEq(a, b) => a.script_size() + b.script_size() + 1,
}
}
fn from_name_tree(name: &str, children: &[Tree<'_>]) -> Result<Self, FromTokenIterError> {
let tree = Tree {
name,
args: children.to_vec(), };
Self::from_tree(&tree).map_err(|_| FromTokenIterError)
}
fn segwit_ctx_checks(&self) -> Result<(), ScriptContextError> {
Err(ScriptContextError::ExtensionError(
"Introspection opcodes only available in Taproot".to_string(),
))
}
}
impl<PArg, QArg> TranslateExt<CovOps<PArg>, CovOps<QArg>> for CovOps<PArg>
where
CovOps<PArg>: Extension,
CovOps<QArg>: Extension,
PArg: ExtParam,
QArg: ExtParam,
{
type Output = CovOps<QArg>;
fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
where
T: ExtTranslator<CovOps<PArg>, CovOps<QArg>, E>,
{
t.ext(self)
}
}
impl<T, PArg, QArg, E> ExtTranslator<CovOps<PArg>, CovOps<QArg>, E> for T
where
T: ExtParamTranslator<PArg, QArg, E>,
PArg: ExtParam,
QArg: ExtParam,
{
fn ext(&mut self, cov_ops: &CovOps<PArg>) -> Result<CovOps<QArg>, E> {
TranslateExtParam::translate_ext(cov_ops, self)
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Spk(SpkInner);
impl Spk {
pub fn new(s: elements::Script) -> Self {
Spk(SpkInner::Script(s))
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum SpkInner {
Script(elements::Script),
Hashed(sha256::Hash),
}
impl fmt::Display for Spk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
SpkInner::Script(s) => write!(f, "{}", s.to_hex()),
SpkInner::Hashed(_h) => write!(f, "hashed_spk"), }
}
}
impl ArgFromStr for Spk {
fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
if parent != "spk_eq" {
return Err(Error::Unexpected(
"spk expressions can only used in spk_eq".to_string(),
));
}
let inner = elements::Script::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
Ok(Spk::new(inner))
}
}
impl ArgFromStr for confidential::Asset {
fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
if parent != "asset_eq" && parent != "is_exp_asset" {
return Err(Error::Unexpected(
"asset expressions only allowed inside asset_eq and is_exp_asset".to_string(),
));
}
let asset_hex = Vec::<u8>::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
elements::encode::deserialize(&asset_hex).map_err(|e| Error::Unexpected(e.to_string()))
}
}
impl ArgFromStr for confidential::Value {
fn arg_from_str(s: &str, parent: &str, _pos: usize) -> Result<Self, Error> {
if parent != "value_eq" && parent != "is_exp_value" {
return Err(Error::Unexpected(
"value expressions only allowed inside value_eq and is_exp_value".to_string(),
));
}
let asset_hex = Vec::<u8>::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
elements::encode::deserialize(&asset_hex).map_err(|e| Error::Unexpected(e.to_string()))
}
}
fn asset(pref: u8, comm: &[u8]) -> Option<confidential::Asset> {
let mut bytes = [0u8; 33];
bytes[0] = pref;
if comm.len() != 32 {
return None;
}
bytes[1..].copy_from_slice(comm);
encode::deserialize(&bytes).ok()
}
fn value(pref: u8, comm: &[u8]) -> Option<confidential::Value> {
if comm.len() == 32 {
let mut bytes = [0u8; 33];
bytes[0] = pref;
bytes[1..].copy_from_slice(comm);
encode::deserialize(&bytes).ok()
} else if comm.len() == 8 {
let mut bytes = [0u8; 8];
bytes.copy_from_slice(comm);
if pref == 1 {
Some(confidential::Value::Explicit(u64::from_le_bytes(bytes)))
} else {
None
}
} else {
None
}
}
fn spk(pref: i8, prog: &[u8]) -> Option<elements::Script> {
if pref == -1 {
None
} else if pref <= 16 && pref >= 0 {
Some(
script::Builder::new()
.push_int(pref as i64)
.push_slice(prog)
.into_script(),
)
} else {
None
}
}
fn spk_to_components(s: &elements::Script) -> (i8, Vec<u8>) {
if !s.is_witness_program() {
(
-1,
sha256::Hash::hash(s.as_bytes()).to_byte_array().to_vec(),
)
} else {
let addr = Address::from_script(s, None, &AddressParams::ELEMENTS).unwrap();
if let Payload::WitnessProgram { version, program } = addr.payload {
(version.to_u8() as i8, program)
} else {
unreachable!("All witness programs have well defined payload")
}
}
}
impl AssetExpr<CovExtArgs> {
pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
match self {
AssetExpr::Const(CovExtArgs::Asset(a)) => {
match a {
Asset::Null => unreachable!("Attempt to push Null asset"),
Asset::Explicit(a) => builder.push_slice(a.into_inner().as_ref()).push_int(1), Asset::Confidential(c) => {
let ser = c.serialize();
builder.push_slice(&ser[1..]).push_int(ser[0] as i64)
}
}
}
AssetExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in asset"
),
AssetExpr::CurrInputAsset => builder
.push_opcode(OP_PUSHCURRENTINPUTINDEX)
.push_opcode(OP_INSPECTINPUTASSET),
AssetExpr::Input(i) => i.push_to_builder(builder).push_opcode(OP_INSPECTINPUTASSET),
AssetExpr::Output(i) => i
.push_to_builder(builder)
.push_opcode(OP_INSPECTOUTPUTASSET),
}
}
pub fn eval(&self, env: &TxEnv) -> Result<confidential::Asset, EvalError> {
match self {
AssetExpr::Const(CovExtArgs::Asset(a)) => Ok(*a),
AssetExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in asset"
),
AssetExpr::CurrInputAsset => {
if env.idx() >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(
env.idx(),
env.spent_utxos().len(),
));
}
Ok(env.spent_utxos()[env.idx()].asset)
}
AssetExpr::Input(i) => {
let i = i.eval(env)?;
if i >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
}
Ok(env.spent_utxos()[i].asset)
}
AssetExpr::Output(i) => {
let i = i.eval(env)?;
if i >= env.tx().output.len() {
return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
}
Ok(env.tx().output[i].asset)
}
}
}
pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
let tks = tokens;
let e = end_pos; if let Some(&[Tk::Bytes32(asset_comm), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
let asset = asset(u8::try_from(i).ok()?, asset_comm)?;
Some((AssetExpr::Const(CovExtArgs::Asset(asset)), e - 2))
} else if let Some(&[Tk::CurrInp, Tk::InpAsset]) = tks.get(e.checked_sub(2)?..e) {
Some((AssetExpr::CurrInputAsset, e - 2))
} else if let Some(&[Tk::InpAsset]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((AssetExpr::Input(idx_expr), e))
} else if let Some(&[Tk::OutAsset]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((AssetExpr::Output(idx_expr), e))
} else {
None
}
}
}
impl ValueExpr<CovExtArgs> {
pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
match self {
ValueExpr::Const(CovExtArgs::Value(a)) => {
match a {
confidential::Value::Null => {
builder.push_slice(&0i64.to_le_bytes()).push_int(1)
} confidential::Value::Explicit(a) => {
builder.push_slice(&a.to_le_bytes()).push_int(1)
} confidential::Value::Confidential(c) => {
let ser = c.serialize();
builder.push_slice(&ser[1..]).push_int(ser[0] as i64)
}
}
}
ValueExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in Value"
),
ValueExpr::CurrInputValue => builder
.push_opcode(OP_PUSHCURRENTINPUTINDEX)
.push_opcode(OP_INSPECTINPUTVALUE),
ValueExpr::Input(i) => i.push_to_builder(builder).push_opcode(OP_INSPECTINPUTVALUE),
ValueExpr::Output(i) => i
.push_to_builder(builder)
.push_opcode(OP_INSPECTOUTPUTVALUE),
}
}
pub fn eval(&self, env: &TxEnv) -> Result<confidential::Value, EvalError> {
match self {
ValueExpr::Const(CovExtArgs::Value(a)) => Ok(*a),
ValueExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in Value"
),
ValueExpr::CurrInputValue => {
if env.idx() >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(
env.idx(),
env.spent_utxos().len(),
));
}
Ok(env.spent_utxos()[env.idx()].value)
}
ValueExpr::Input(i) => {
let i = i.eval(env)?;
if i >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
}
Ok(env.spent_utxos()[i].value)
}
ValueExpr::Output(i) => {
let i = i.eval(env)?;
if i >= env.tx().output.len() {
return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
}
Ok(env.tx().output[i].value)
}
}
}
pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
let tks = tokens;
let e = end_pos; if let Some(&[Tk::Bytes32(value_comm), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
let value = value(u8::try_from(i).ok()?, value_comm)?;
Some((ValueExpr::Const(CovExtArgs::Value(value)), e - 2))
} else if let Some(&[Tk::Bytes8(exp_val), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
let value = value(u8::try_from(i).ok()?, exp_val)?;
Some((ValueExpr::Const(CovExtArgs::Value(value)), e - 2))
} else if let Some(&[Tk::CurrInp, Tk::InpValue]) = tks.get(e.checked_sub(2)?..e) {
Some((ValueExpr::CurrInputValue, e - 2))
} else if let Some(&[Tk::InpValue]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((ValueExpr::Input(idx_expr), e))
} else if let Some(&[Tk::OutValue]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((ValueExpr::Output(idx_expr), e))
} else {
None
}
}
}
impl SpkExpr<CovExtArgs> {
pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
match self {
SpkExpr::Const(CovExtArgs::Script(s)) => {
let (ver, prog) = match &s.0 {
SpkInner::Script(s) => spk_to_components(s),
SpkInner::Hashed(h) => (-1, h.to_byte_array().to_vec()),
};
builder.push_slice(&prog).push_int(ver as i64)
}
SpkExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in Script"
),
SpkExpr::CurrInputSpk => builder
.push_opcode(OP_PUSHCURRENTINPUTINDEX)
.push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
SpkExpr::Input(i) => i
.push_to_builder(builder)
.push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
SpkExpr::Output(i) => i
.push_to_builder(builder)
.push_opcode(OP_INSPECTOUTPUTSCRIPTPUBKEY),
}
}
pub fn eval(&self, env: &TxEnv) -> Result<(i8, Vec<u8>), EvalError> {
let res = match self {
SpkExpr::Const(CovExtArgs::Script(s)) => match &s.0 {
SpkInner::Script(s) => spk_to_components(s),
SpkInner::Hashed(h) => (-1, h.to_byte_array().to_vec()),
},
SpkExpr::Const(_) => unreachable!(
"Both constructors from_str and from_token_iter
check that the correct variant is used in Script pubkey"
),
SpkExpr::CurrInputSpk => {
if env.idx() >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(
env.idx(),
env.spent_utxos().len(),
));
}
spk_to_components(&env.spent_utxos()[env.idx()].script_pubkey)
}
SpkExpr::Input(i) => {
let i = i.eval(env)?;
if i >= env.spent_utxos().len() {
return Err(EvalError::UtxoIndexOutOfBounds(i, env.spent_utxos().len()));
}
spk_to_components(&(env.spent_utxos()[i].script_pubkey))
}
SpkExpr::Output(i) => {
let i = i.eval(env)?;
if i >= env.tx().output.len() {
return Err(EvalError::OutputIndexOutOfBounds(i, env.tx().output.len()));
}
spk_to_components(&(env.tx().output[i].script_pubkey))
}
};
Ok(res)
}
pub fn from_tokens(tokens: &[Tk], end_pos: usize) -> Option<(Self, usize)> {
let tks = tokens;
let e = end_pos; if let Some(&[Tk::Bytes32(spk_vec), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
let script = spk(i8::try_from(i).ok()?, spk_vec)?;
Some((SpkExpr::Const(CovExtArgs::Script(Spk::new(script))), e - 2))
} else if let Some(&[Tk::Bytes32(spk_vec), Tk::NumNeg1]) = tks.get(e.checked_sub(2)?..e) {
let mut inner = [0u8; 32];
inner.copy_from_slice(spk_vec);
let hashed_spk = Spk(SpkInner::Hashed(sha256::Hash::from_byte_array(inner)));
Some((SpkExpr::Const(CovExtArgs::Script(hashed_spk)), e - 2))
} else if let Some(&[Tk::Push(ref spk_vec), Tk::Num(i)]) = tks.get(e.checked_sub(2)?..e) {
let script = spk(i8::try_from(i).ok()?, spk_vec)?;
Some((SpkExpr::Const(CovExtArgs::Script(Spk::new(script))), e - 2))
} else if let Some(&[Tk::CurrInp, Tk::InpSpk]) = tks.get(e.checked_sub(2)?..e) {
Some((SpkExpr::CurrInputSpk, e - 2))
} else if let Some(&[Tk::InpSpk]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((SpkExpr::Input(idx_expr), e))
} else if let Some(&[Tk::OutSpk]) = tks.get(e.checked_sub(1)?..e) {
let (idx_expr, e) = IdxExpr::from_tokens(tks, e - 1)?;
Some((SpkExpr::Output(idx_expr), e))
} else {
None
}
}
}
impl CovOps<CovExtArgs> {
pub fn push_to_builder(&self, builder: script::Builder) -> script::Builder {
match self {
CovOps::IsExpAsset(x) => x
.push_to_builder(builder)
.push_int(1)
.push_opcode(OP_EQUAL)
.push_opcode(OP_NIP),
CovOps::IsExpValue(x) => x
.push_to_builder(builder)
.push_int(1)
.push_opcode(OP_EQUAL)
.push_opcode(OP_NIP),
CovOps::AssetEq(x, y) => {
let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
let builder = y
.push_to_builder(builder)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_EQUAL);
builder
.push_opcode(OP_TOALTSTACK)
.push_opcode(OP_EQUAL)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_BOOLAND)
}
CovOps::ValueEq(x, y) => {
let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
let builder = y
.push_to_builder(builder)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_EQUAL);
builder
.push_opcode(OP_TOALTSTACK)
.push_opcode(OP_EQUAL)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_BOOLAND)
}
CovOps::SpkEq(x, y) => {
let builder = x.push_to_builder(builder).push_opcode(OP_TOALTSTACK);
let builder = y
.push_to_builder(builder)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_EQUAL);
builder
.push_opcode(OP_TOALTSTACK)
.push_opcode(OP_EQUAL)
.push_opcode(OP_FROMALTSTACK)
.push_opcode(OP_BOOLAND)
}
CovOps::CurrIndEq(i) => builder
.push_int(*i as i64)
.push_opcode(OP_PUSHCURRENTINPUTINDEX)
.push_opcode(OP_EQUAL),
CovOps::IdxEq(x, y) => {
let builder = x.push_to_builder(builder);
let builder = y.push_to_builder(builder);
builder.push_opcode(OP_EQUAL)
}
}
}
pub fn eval(&self, env: &TxEnv) -> Result<bool, EvalError> {
match self {
CovOps::IsExpAsset(x) => x.eval(env).map(|x| x.is_explicit()),
CovOps::IsExpValue(y) => y.eval(env).map(|y| y.is_explicit()),
CovOps::AssetEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
CovOps::ValueEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
CovOps::SpkEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
CovOps::CurrIndEq(i) => Ok(*i == env.idx()),
CovOps::IdxEq(x, y) => Ok(x.eval(env)? == y.eval(env)?),
}
}
pub fn from_tokens(tks: &[Tk]) -> Option<(Self, usize)> {
let e = tks.len();
if let Some(&[Tk::Num(i), Tk::CurrInp, Tk::Equal]) = tks.get(e.checked_sub(3)?..e) {
Some((CovOps::CurrIndEq(i as usize), e - 3))
} else if let Some(&[Tk::Equal]) = tks.get(e.checked_sub(1)?..e) {
let (y, e) = IdxExpr::from_tokens(tks, e - 1)?;
let (x, e) = IdxExpr::from_tokens(tks, e)?;
Some((CovOps::IdxEq(x, y), e))
} else if let Some(&[Tk::Num(1), Tk::Equal, Tk::Nip]) = tks.get(e.checked_sub(3)?..e) {
if let Some((asset, e)) = AssetExpr::from_tokens(tks, e - 3) {
Some((CovOps::IsExpAsset(asset), e))
} else if let Some((value, e)) = ValueExpr::from_tokens(tks, e - 3) {
Some((CovOps::IsExpValue(value), e))
} else {
None
}
} else if let Some(
&[Tk::FromAltStack, Tk::Equal, Tk::ToAltStack, Tk::Equal, Tk::FromAltStack, Tk::BoolAnd],
) = tks.get(e.checked_sub(6)?..e)
{
let res = if let Some((y, e)) = AssetExpr::from_tokens(tks, e - 6) {
if tks.get(e - 1) != Some(&Tk::ToAltStack) {
return None;
}
if let Some((x, e)) = AssetExpr::from_tokens(tks, e - 1) {
Some((CovOps::AssetEq(x, y), e))
} else {
None
}
} else {
None
};
if res.is_some() {
return res;
}
let res = if let Some((y, e)) = ValueExpr::from_tokens(tks, e - 6) {
if tks.get(e - 1) != Some(&Tk::ToAltStack) {
return None;
}
if let Some((x, e)) = ValueExpr::from_tokens(tks, e - 1) {
Some((CovOps::ValueEq(x, y), e))
} else {
None
}
} else {
None
};
if res.is_some() {
return res;
}
if let Some((y, e)) = SpkExpr::from_tokens(tks, e - 6) {
if tks.get(e - 1) != Some(&Tk::ToAltStack) {
return None;
}
if let Some((x, e)) = SpkExpr::from_tokens(tks, e - 1) {
Some((CovOps::SpkEq(x, y), e))
} else {
None
}
} else {
None
}
} else {
None
}
}
}
impl ParseableExt for CovOps<CovExtArgs> {
fn satisfy<Pk, S>(&self, sat: &S) -> Satisfaction
where
Pk: ToPublicKey,
S: Satisfier<Pk>,
{
let env = match (
sat.lookup_tx(),
sat.lookup_spent_utxos(),
sat.lookup_curr_inp(),
) {
(Some(tx), Some(utxos), Some(idx)) => match TxEnv::new(tx, utxos, idx) {
Some(x) => x,
None => {
return Satisfaction {
stack: Witness::Impossible,
has_sig: false,
}
}
},
_ => {
return Satisfaction {
stack: Witness::Impossible,
has_sig: false,
}
}
};
let wit = match self.eval(&env) {
Ok(false) => Witness::Unavailable,
Ok(true) => Witness::empty(),
Err(_e) => Witness::Impossible,
};
Satisfaction {
stack: wit,
has_sig: false,
}
}
fn dissatisfy<Pk, S>(&self, sat: &S) -> Satisfaction
where
Pk: ToPublicKey,
S: Satisfier<Pk>,
{
let env = match (
sat.lookup_tx(),
sat.lookup_spent_utxos(),
sat.lookup_curr_inp(),
) {
(Some(tx), Some(utxos), Some(idx)) => match TxEnv::new(tx, utxos, idx) {
Some(x) => x,
None => {
return Satisfaction {
stack: Witness::Impossible,
has_sig: false,
}
}
},
_ => {
return Satisfaction {
stack: Witness::Impossible,
has_sig: false,
}
}
};
let wit = match self.eval(&env) {
Ok(false) => Witness::empty(),
Ok(true) => Witness::Unavailable,
Err(_e) => Witness::Impossible,
};
Satisfaction {
stack: wit,
has_sig: false,
}
}
fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
self.push_to_builder(builder)
}
fn from_token_iter(tokens: &mut TokenIter<'_>) -> Result<Self, FromTokenIterError> {
let len = tokens.len();
match Self::from_tokens(tokens.as_inner_mut()) {
Some((res, last_pos)) => {
tokens.advance(len - last_pos).ok_or(FromTokenIterError)?;
Ok(res)
}
None => Err(FromTokenIterError),
}
}
fn evaluate(
&self,
stack: &mut interpreter::Stack,
txenv: Option<&TxEnv>,
) -> Result<bool, interpreter::Error> {
let txenv = txenv
.as_ref()
.ok_or(interpreter::Error::ArithError(EvalError::TxEnvNotPresent))?;
match self.eval(txenv) {
Ok(true) => {
stack.push(interpreter::Element::Satisfied);
Ok(true)
}
Ok(false) => {
stack.push(interpreter::Element::Dissatisfied);
Ok(false)
}
Err(e) => Err(interpreter::Error::ArithError(e)),
}
}
}
impl<PArg, QArg> TranslateExtParam<PArg, QArg> for CovOps<PArg>
where
PArg: ExtParam,
QArg: ExtParam,
{
type Output = CovOps<QArg>;
fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
where
T: ExtParamTranslator<PArg, QArg, E>,
{
match self {
CovOps::IsExpAsset(a) => Ok(CovOps::IsExpAsset(a._translate_ext(t)?)),
CovOps::IsExpValue(v) => Ok(CovOps::IsExpValue(v._translate_ext(t)?)),
CovOps::AssetEq(x, y) => {
Ok(CovOps::AssetEq(x._translate_ext(t)?, y._translate_ext(t)?))
}
CovOps::ValueEq(x, y) => {
Ok(CovOps::ValueEq(x._translate_ext(t)?, y._translate_ext(t)?))
}
CovOps::SpkEq(x, y) => Ok(CovOps::SpkEq(x._translate_ext(t)?, y._translate_ext(t)?)),
CovOps::CurrIndEq(i) => Ok(CovOps::CurrIndEq(*i)),
CovOps::IdxEq(x, y) => Ok(CovOps::IdxEq(x.clone(), y.clone())),
}
}
}
#[cfg(test)]
mod tests {
use bitcoin::key::XOnlyPublicKey;
use super::*;
use crate::test_utils::{StrExtTranslator, StrXOnlyKeyTranslator};
use crate::{Miniscript, Segwitv0, Tap, TranslatePk};
#[test]
fn test_index_ops() {
_test_parse("is_exp_asset(inp_asset(curr_idx))");
_test_parse("is_exp_asset(inp_asset(idx_add(9,curr_idx)))");
_test_parse("is_exp_asset(inp_asset(idx_sub(9,curr_idx)))");
_test_parse("is_exp_asset(inp_asset(idx_mul(9,curr_idx)))");
_test_parse("is_exp_asset(inp_asset(idx_div(9,curr_idx)))");
_test_parse("is_exp_asset(inp_asset(idx_mul(1,idx_add(9,curr_idx))))");
_test_parse("is_exp_asset(inp_asset(idx_sub(idx_mul(1,idx_add(9,curr_idx)),1)))");
_test_parse("is_exp_asset(out_asset(idx_add(9,curr_idx)))");
_test_parse("is_exp_value(inp_value(idx_add(9,curr_idx)))");
_test_parse("is_exp_value(out_value(idx_add(9,curr_idx)))");
_test_parse("spk_eq(inp_spk(idx_add(9,curr_idx)),out_spk(idx_sub(9,curr_idx)))");
_test_parse("idx_eq(10,idx_add(9,curr_idx))");
}
#[test]
fn cov_parse() {
_test_parse("is_exp_asset(ConfAst)");
_test_parse("is_exp_asset(ExpAst)");
_test_parse("is_exp_asset(curr_inp_asset)");
_test_parse("is_exp_asset(inp_asset(9))");
_test_parse("is_exp_asset(out_asset(9))");
_test_parse("asset_eq(ConfAst,ExpAst)");
_test_parse("asset_eq(curr_inp_asset,out_asset(1))");
_test_parse("asset_eq(inp_asset(3),out_asset(1))");
_test_parse("is_exp_value(ConfVal)");
_test_parse("is_exp_value(ExpVal)");
_test_parse("is_exp_value(curr_inp_value)");
_test_parse("is_exp_value(inp_value(9))");
_test_parse("is_exp_value(out_value(9))");
_test_parse("value_eq(ConfVal,ExpVal)");
_test_parse("value_eq(curr_inp_value,out_value(1))");
_test_parse("value_eq(inp_value(3),out_value(1))");
_test_parse("spk_eq(V0Spk,out_spk(1))");
_test_parse("spk_eq(V1Spk,inp_spk(1))");
_test_parse("spk_eq(curr_inp_spk,out_spk(1))");
_test_parse("spk_eq(inp_spk(3),out_spk(1))");
_test_parse("spk_eq(out_spk(2),V1Spk)");
_test_parse("curr_idx_eq(1)");
_test_parse("curr_idx_eq(0)");
_test_parse(
"and_v(v:pk(K),and_v(v:is_exp_value(out_value(1)),is_exp_asset(out_asset(1))))",
);
_test_parse("and_v(v:pk(K),and_v(v:value_eq(ConfVal,ConfVal),spk_eq(V1Spk,V1Spk)))");
_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))))");
}
#[test]
fn options_fail_test() {
type MsExt = Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>>;
MsExt::from_str_insane("asset_eq(out_asset(0),0179d51a47e4ac8e32306486dd0926a88678c392f2ed5f213e3ff2ad461c7c25e1)").unwrap();
MsExt::from_str_insane("asset_eq(out_asset(0),79d51a47e4ac8e32306486dd0926a88678c392f2ed5f213e3ff2ad461c7c25e1)").unwrap_err();
}
#[rustfmt::skip]
fn _test_parse(s: &str) {
type MsExtStr = Miniscript<String, Tap, CovOps<String>>;
type MsExt = Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>>;
type MsExtSegwitv0 = Miniscript<String, Segwitv0, CovOps<CovExtArgs>>;
assert!(MsExtSegwitv0::from_str_insane(s).is_err());
let ms = MsExtStr::from_str_insane(s).unwrap();
assert_eq!(ms.to_string(), s);
let mut t = StrXOnlyKeyTranslator::default();
let mut ext_t = StrExtTranslator::default();
{
ext_t.ext_map.insert("V1Spk".to_string(),CovExtArgs::spk(elements::Script::from_str("5120c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()));
ext_t.ext_map.insert("V0Spk".to_string(),CovExtArgs::spk(elements::Script::from_str("0020c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()));
ext_t.ext_map.insert("ConfAst".to_string(),CovExtArgs::asset(encode::deserialize(&Vec::<u8>::from_hex("0adef814ab021498562ab4717287305d3f7abb5686832fe6183e1db495abef7cc7").unwrap()).unwrap()));
ext_t.ext_map.insert("ExpAst".to_string(),CovExtArgs::asset(encode::deserialize(&Vec::<u8>::from_hex("01c73ac1b7a518499b9642aed8cfa15d5401e5bd85ad760b937b69521c297722f0").unwrap()).unwrap()));
ext_t.ext_map.insert("ConfVal".to_string(),CovExtArgs::value(encode::deserialize(&Vec::<u8>::from_hex("09def814ab021498562ab4717287305d3f7abb5686832fe6183e1db495abef7cc7").unwrap()).unwrap()));
ext_t.ext_map.insert("ExpVal".to_string(),CovExtArgs::value(encode::deserialize(&Vec::<u8>::from_hex("010000000011110000").unwrap()).unwrap()));
}
let ms: Miniscript<XOnlyPublicKey, Tap, CovOps<String>> = ms.translate_pk(&mut t).unwrap();
let ms: Miniscript<XOnlyPublicKey, Tap, CovOps<CovExtArgs>> = ms.translate_ext(&mut ext_t).unwrap();
assert_eq!(ms.encode(), MsExt::parse_insane(&ms.encode()).unwrap().encode());
assert_eq!(ms, MsExt::from_str_insane(&ms.to_string()).unwrap())
}
}