From e09a173f364968996eee260d8b0de769eb99c604 Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 13:26:24 -0700
Subject: [PATCH 1/6] Add CurrOutputSpk

---
 src/extensions/introspect_ops.rs | 24 ++++++++++++++++++++++++
 tests/test_introspect.rs         |  1 +
 2 files changed, 25 insertions(+)

diff --git a/src/extensions/introspect_ops.rs b/src/extensions/introspect_ops.rs
index 2e11afa3..9807410c 100644
--- a/src/extensions/introspect_ops.rs
+++ b/src/extensions/introspect_ops.rs
@@ -89,6 +89,9 @@ pub enum SpkExpr<T: ExtParam> {
     /// Explicit asset at the given output index
     /// i INPSECTOUTPUTSCRIPTPUBKEY
     Output(usize),
+    /// Output spk matching the current executing index. Required for sighash single emulation
+    /// INSPECTCURRENTINPUTINDEX INPSECTOUTPUTSCRIPTPUBKEY
+    CurrOutputSpk,
 }
 
 /// Miniscript Fragment containing arith expressions
@@ -276,6 +279,7 @@ impl<T: ExtParam> SpkExpr<T> {
             SpkExpr::CurrInputSpk => 2,
             SpkExpr::Input(i) => script_num_size(*i) + 1,
             SpkExpr::Output(i) => script_num_size(*i) + 1,
+            SpkExpr::CurrOutputSpk => 2,
         }
     }
 
@@ -290,6 +294,7 @@ impl<T: ExtParam> SpkExpr<T> {
             SpkExpr::CurrInputSpk => SpkExpr::CurrInputSpk,
             SpkExpr::Input(i) => SpkExpr::Input(*i),
             SpkExpr::Output(i) => SpkExpr::Output(*i),
+            SpkExpr::CurrOutputSpk => SpkExpr::CurrOutputSpk,
         };
         Ok(res)
     }
@@ -302,6 +307,7 @@ impl<T: ExtParam> fmt::Display for SpkExpr<T> {
             SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
             SpkExpr::Input(i) => write!(f, "inp_spk({})", i),
             SpkExpr::Output(i) => write!(f, "out_spk({})", i),
+            SpkExpr::CurrOutputSpk => write!(f, "curr_out_spk"),
         }
     }
 }
@@ -313,6 +319,7 @@ impl<T: ExtParam> fmt::Debug for SpkExpr<T> {
             SpkExpr::CurrInputSpk => write!(f, "curr_inp_spk"),
             SpkExpr::Input(i) => write!(f, "inp_spk({:?})", i),
             SpkExpr::Output(i) => write!(f, "out_spk({:?})", i),
+            SpkExpr::CurrOutputSpk => write!(f, "curr_out_spk"),
         }
     }
 }
@@ -332,6 +339,7 @@ impl<T: ExtParam> SpkExpr<T> {
                 .map(SpkExpr::Input),
             ("out_spk", 1) => expression::terminal(&top.args[0], expression::parse_num::<usize>)
                 .map(SpkExpr::Output),
+            ("curr_out_spk", 0) => Ok(SpkExpr::CurrOutputSpk),
             (asset, 0) => Ok(SpkExpr::Const(T::arg_from_str(asset, parent, pos)?)),
             _ => Err(Error::Unexpected(format!(
                 "{}({} args) while parsing Extension",
@@ -818,6 +826,9 @@ impl SpkExpr<CovExtArgs> {
             SpkExpr::CurrInputSpk => builder
                 .push_opcode(OP_PUSHCURRENTINPUTINDEX)
                 .push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
+            SpkExpr::CurrOutputSpk => builder
+                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
+                .push_opcode(OP_INSPECTOUTPUTSCRIPTPUBKEY),
             SpkExpr::Input(i) => builder
                 .push_int(*i as i64)
                 .push_opcode(OP_INSPECTINPUTSCRIPTPUBKEY),
@@ -844,6 +855,15 @@ impl SpkExpr<CovExtArgs> {
                 }
                 spk_to_components(&env.spent_utxos()[env.idx()].script_pubkey)
             }
+            SpkExpr::CurrOutputSpk => {
+                if env.idx() >= env.tx().output.len() {
+                    return Err(EvalError::OutputIndexOutOfBounds(
+                        env.idx(),
+                        env.tx().output.len(),
+                    ));
+                }
+                spk_to_components(&env.tx().output[env.idx()].script_pubkey)
+            }
             SpkExpr::Input(i) => {
                 if *i >= env.spent_utxos().len() {
                     return Err(EvalError::UtxoIndexOutOfBounds(*i, env.spent_utxos().len()));
@@ -873,6 +893,8 @@ impl SpkExpr<CovExtArgs> {
             Some((SpkExpr::Const(CovExtArgs::Script(Spk(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::CurrInp, Tk::OutSpk]) = tks.get(e.checked_sub(2)?..e) {
+            Some((SpkExpr::CurrOutputSpk, e - 2))
         } else if let Some(&[Tk::Num(i), Tk::InpSpk]) = tks.get(e.checked_sub(2)?..e) {
             Some((SpkExpr::Input(i as usize), e - 2))
         } else if let Some(&[Tk::Num(i), Tk::OutSpk]) = tks.get(e.checked_sub(2)?..e) {
@@ -1173,6 +1195,7 @@ mod tests {
         _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(curr_out_spk,out_spk(1))");
         _test_parse("spk_eq(inp_spk(3),out_spk(1))");
 
         // Testing the current input index
@@ -1184,6 +1207,7 @@ mod tests {
             "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),spk_eq(V1Spk,curr_out_spk)))");
         _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))))");
     }
 
diff --git a/tests/test_introspect.rs b/tests/test_introspect.rs
index 31290fdb..ac3af2d3 100644
--- a/tests/test_introspect.rs
+++ b/tests/test_introspect.rs
@@ -218,6 +218,7 @@ fn test_descs(cl: &ElementsD, testdata: &mut TestData) {
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(out_spk(1),out_spk(1))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(spk_v1,spk_v1)))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(curr_inp_spk,inp_spk(0))))");
+    test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(curr_out_spk,out_spk(0))))");
 
     // Testing the current input index
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),curr_idx_eq(0)))");

From acced2756617b913046515f1fa07b53e98ea524a Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 13:31:03 -0700
Subject: [PATCH 2/6] Add parse insane API for Tr descriptors

All other descriptor APIs only deal with sane miniscripts
---
 src/descriptor/mod.rs |  3 ++-
 src/descriptor/tr.rs  | 18 ++++++++++++++++--
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs
index 7d2866da..b501b96c 100644
--- a/src/descriptor/mod.rs
+++ b/src/descriptor/mod.rs
@@ -1022,7 +1022,8 @@ impl_from_str!(
                 Ok(tr) => Ok(Descriptor::Tr(tr)),
                 Err(_) => {
                     // Try parsing with extensions
-                    let tr = Tr::<Pk, T>::from_str(s)?;
+                    // descriptors are always parsed insane. This will improve once we use upstream ExtParams Api.
+                    let tr = Tr::<Pk, T>::from_str_insane(s)?;
                     Ok(Descriptor::TrExt(tr))
                 }
             }
diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs
index ac5c67af..ecbade37 100644
--- a/src/descriptor/tr.rs
+++ b/src/descriptor/tr.rs
@@ -1,7 +1,6 @@
 // Tapscript
 
 use std::cmp::{self, max};
-use std::str::FromStr;
 use std::sync::{Arc, Mutex};
 use std::{fmt, hash};
 
@@ -417,7 +416,7 @@ impl_block_str!(
     fn parse_tr_script_spend(tree: &expression::Tree,) -> Result<TapTree<Pk, Ext>, Error> {
         match tree {
             expression::Tree { name, args } if !name.is_empty() && args.is_empty() => {
-                let script = Miniscript::<Pk, Tap, Ext>::from_str(name)?;
+                let script = Miniscript::<Pk, Tap, Ext>::from_str_insane(name)?;
                 Ok(TapTree::Leaf(Arc::new(script)))
             }
             expression::Tree { name, args } if name.is_empty() && args.len() == 2 => {
@@ -484,6 +483,20 @@ impl_from_str!(
     => Ext; Extension,
     type Err = Error;,
     fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let res = Self::from_str_insane(s)?;
+        if res.iter_scripts().any(|(_, ms)| ms.sanity_check().is_err()) {
+            return Err(Error::BadDescriptor("Sanity check failed".to_string()));
+        }
+        Ok(res)
+    }
+);
+
+#[rustfmt::skip]
+impl_block_str!(
+    Tr<Pk, Ext>,
+    => Ext; Extension,
+    /// Parse taproot descriptors without any sanity checks
+    pub fn from_str_insane(s: &str,) -> Result<Tr<Pk, Ext>, Error> {
         let desc_str = verify_checksum(s)?;
         let top = parse_tr_tree(desc_str)?;
         Self::from_tree(&top)
@@ -742,6 +755,7 @@ where
 mod tests {
     use super::*;
     use crate::{ForEachKey, NoExt};
+    use core::str::FromStr;
 
     #[test]
     fn test_for_each() {

From 6b17abd7b2c71fc7a6be28efe9119cd28e943d72 Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 14:06:20 -0700
Subject: [PATCH 3/6] Add Inspecting CurrOutputIndex.

---
 src/extensions/introspect_ops.rs | 27 +++++++++++++++++++++++++++
 tests/test_introspect.rs         |  1 +
 2 files changed, 28 insertions(+)

diff --git a/src/extensions/introspect_ops.rs b/src/extensions/introspect_ops.rs
index 9807410c..25d7fff2 100644
--- a/src/extensions/introspect_ops.rs
+++ b/src/extensions/introspect_ops.rs
@@ -65,6 +65,10 @@ pub enum ValueExpr<T: ExtParam> {
     /// Value(possibly confidential) at the given output index
     /// i INPSECTOUTPUTVALUE
     Output(usize),
+    /// Value in the corresponding output of the current input index
+    /// Required for sighash single emulation
+    /// INSPECTCURRENTINPUTINDEX INPSECTINPUTVALUE
+    CurrOutputValue,
 }
 
 /// Enum representing operations with transaction script pubkeys.
@@ -205,6 +209,7 @@ impl<T: ExtParam> ValueExpr<T> {
             ValueExpr::CurrInputValue => 2,
             ValueExpr::Input(i) => script_num_size(*i) + 1,
             ValueExpr::Output(i) => script_num_size(*i) + 1,
+            ValueExpr::CurrOutputValue => 2,
         }
     }
 
@@ -219,6 +224,7 @@ impl<T: ExtParam> ValueExpr<T> {
             ValueExpr::CurrInputValue => ValueExpr::CurrInputValue,
             ValueExpr::Input(i) => ValueExpr::Input(*i),
             ValueExpr::Output(i) => ValueExpr::Output(*i),
+            ValueExpr::CurrOutputValue => ValueExpr::CurrOutputValue,
         };
         Ok(res)
     }
@@ -231,6 +237,7 @@ impl<T: ExtParam> fmt::Display for ValueExpr<T> {
             ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
             ValueExpr::Input(i) => write!(f, "inp_value({})", i),
             ValueExpr::Output(i) => write!(f, "out_value({})", i),
+            ValueExpr::CurrOutputValue => write!(f, "curr_out_value"),
         }
     }
 }
@@ -242,6 +249,7 @@ impl<T: ExtParam> fmt::Debug for ValueExpr<T> {
             ValueExpr::CurrInputValue => write!(f, "curr_inp_value"),
             ValueExpr::Input(i) => write!(f, "inp_value({:?})", i),
             ValueExpr::Output(i) => write!(f, "out_value({:?})", i),
+            ValueExpr::CurrOutputValue => write!(f, "curr_out_value"),
         }
     }
 }
@@ -261,6 +269,7 @@ impl<T: ExtParam> ValueExpr<T> {
                 .map(ValueExpr::Input),
             ("out_value", 1) => expression::terminal(&top.args[0], expression::parse_num::<usize>)
                 .map(ValueExpr::Output),
+            ("curr_out_value", 0) => Ok(ValueExpr::CurrOutputValue),
             (value, 0) => Ok(ValueExpr::Const(T::arg_from_str(value, parent, pos)?)),
             _ => Err(Error::Unexpected(format!(
                 "{}({} args) while parsing Extension",
@@ -747,6 +756,9 @@ impl ValueExpr<CovExtArgs> {
             ValueExpr::CurrInputValue => builder
                 .push_opcode(OP_PUSHCURRENTINPUTINDEX)
                 .push_opcode(OP_INSPECTINPUTVALUE),
+            ValueExpr::CurrOutputValue => builder
+                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
+                .push_opcode(OP_INSPECTOUTPUTVALUE),
             ValueExpr::Input(i) => builder
                 .push_int(*i as i64)
                 .push_opcode(OP_INSPECTINPUTVALUE),
@@ -773,6 +785,15 @@ impl ValueExpr<CovExtArgs> {
                 }
                 Ok(env.spent_utxos()[env.idx()].value)
             }
+            ValueExpr::CurrOutputValue => {
+                if env.idx() >= env.tx().output.len() {
+                    return Err(EvalError::OutputIndexOutOfBounds(
+                        env.idx(),
+                        env.tx().output.len(),
+                    ));
+                }
+                Ok(env.tx().output[env.idx()].value)
+            }
             ValueExpr::Input(i) => {
                 if *i >= env.spent_utxos().len() {
                     return Err(EvalError::UtxoIndexOutOfBounds(*i, env.spent_utxos().len()));
@@ -801,6 +822,8 @@ impl ValueExpr<CovExtArgs> {
             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::CurrInp, Tk::OutValue]) = tks.get(e.checked_sub(2)?..e) {
+            Some((ValueExpr::CurrOutputValue, e - 2))
         } else if let Some(&[Tk::Num(i), Tk::InpValue]) = tks.get(e.checked_sub(2)?..e) {
             Some((ValueExpr::Input(i as usize), e - 2))
         } else if let Some(&[Tk::Num(i), Tk::OutValue]) = tks.get(e.checked_sub(2)?..e) {
@@ -1190,6 +1213,7 @@ mod tests {
         _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("value_eq(curr_out_value,out_value(1))");
 
         // same tests for spks
         _test_parse("spk_eq(V0Spk,out_spk(1))");
@@ -1208,6 +1232,9 @@ mod tests {
         );
         _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),spk_eq(V1Spk,curr_out_spk)))");
+        _test_parse(
+            "and_v(v:pk(K),and_v(v:value_eq(curr_out_value,ConfVal),spk_eq(V1Spk,curr_out_spk)))",
+        );
         _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))))");
     }
 
diff --git a/tests/test_introspect.rs b/tests/test_introspect.rs
index ac3af2d3..e3acabae 100644
--- a/tests/test_introspect.rs
+++ b/tests/test_introspect.rs
@@ -211,6 +211,7 @@ fn test_descs(cl: &ElementsD, testdata: &mut TestData) {
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(inp_value(0))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(out_value(1))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(curr_inp_value,inp_value(0))))");
+    test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(curr_out_value,out_value(0))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(out_value(0),out_value(0))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(out_value(1),out_value(1))))");
 

From 8392af8485b68a756d5713208b68b5a26bebb033 Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 15:25:43 -0700
Subject: [PATCH 4/6] Add CurrOutputAsset

---
 src/extensions/introspect_ops.rs | 23 +++++++++++++++++++++++
 tests/test_introspect.rs         |  1 +
 2 files changed, 24 insertions(+)

diff --git a/src/extensions/introspect_ops.rs b/src/extensions/introspect_ops.rs
index 25d7fff2..626b15d7 100644
--- a/src/extensions/introspect_ops.rs
+++ b/src/extensions/introspect_ops.rs
@@ -44,6 +44,9 @@ pub enum AssetExpr<T: ExtParam> {
     /// Explicit asset at the given output index
     /// i INPSECTOUTPUTASSET
     Output(usize),
+    /// Explicit asset at the output index corresponding to the input index
+    /// INSPECTCURRENTINPUTINDEX INPSECTOUTPUTASSET
+    CurrOutputAsset,
 }
 
 /// Enum representing operations with transaction values.
@@ -135,6 +138,7 @@ impl<T: ExtParam> AssetExpr<T> {
             AssetExpr::CurrInputAsset => 2,
             AssetExpr::Input(i) => script_num_size(*i) + 1,
             AssetExpr::Output(i) => script_num_size(*i) + 1,
+            AssetExpr::CurrOutputAsset => 2,
         }
     }
 
@@ -149,6 +153,7 @@ impl<T: ExtParam> AssetExpr<T> {
             AssetExpr::CurrInputAsset => AssetExpr::CurrInputAsset,
             AssetExpr::Input(i) => AssetExpr::Input(*i),
             AssetExpr::Output(i) => AssetExpr::Output(*i),
+            AssetExpr::CurrOutputAsset => AssetExpr::CurrOutputAsset,
         };
         Ok(res)
     }
@@ -161,6 +166,7 @@ impl<T: ExtParam> fmt::Display for AssetExpr<T> {
             AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
             AssetExpr::Input(i) => write!(f, "inp_asset({})", i),
             AssetExpr::Output(i) => write!(f, "out_asset({})", i),
+            AssetExpr::CurrOutputAsset => write!(f, "curr_out_asset"),
         }
     }
 }
@@ -172,6 +178,7 @@ impl<T: ExtParam> fmt::Debug for AssetExpr<T> {
             AssetExpr::CurrInputAsset => write!(f, "curr_inp_asset"),
             AssetExpr::Input(i) => write!(f, "inp_asset({:?})", i),
             AssetExpr::Output(i) => write!(f, "out_asset({:?})", i),
+            AssetExpr::CurrOutputAsset => write!(f, "curr_out_asset"),
         }
     }
 }
@@ -191,6 +198,7 @@ impl<T: ExtParam> AssetExpr<T> {
                 .map(AssetExpr::Input),
             ("out_asset", 1) => expression::terminal(&top.args[0], expression::parse_num::<usize>)
                 .map(AssetExpr::Output),
+            ("curr_out_asset", 0) => Ok(AssetExpr::CurrOutputAsset),
             (asset, 0) => Ok(AssetExpr::Const(T::arg_from_str(asset, parent, pos)?)),
             _ => Err(Error::Unexpected(format!(
                 "{}({} args) while parsing Extension",
@@ -670,6 +678,9 @@ impl AssetExpr<CovExtArgs> {
             AssetExpr::CurrInputAsset => builder
                 .push_opcode(OP_PUSHCURRENTINPUTINDEX)
                 .push_opcode(OP_INSPECTINPUTASSET),
+            AssetExpr::CurrOutputAsset => builder
+                .push_opcode(OP_PUSHCURRENTINPUTINDEX)
+                .push_opcode(OP_INSPECTOUTPUTASSET),
             AssetExpr::Input(i) => builder
                 .push_int(*i as i64)
                 .push_opcode(OP_INSPECTINPUTASSET),
@@ -696,6 +707,15 @@ impl AssetExpr<CovExtArgs> {
                 }
                 Ok(env.spent_utxos()[env.idx()].asset)
             }
+            AssetExpr::CurrOutputAsset => {
+                if env.idx() >= env.tx().output.len() {
+                    return Err(EvalError::OutputIndexOutOfBounds(
+                        env.idx(),
+                        env.tx().output.len(),
+                    ));
+                }
+                Ok(env.tx().output[env.idx()].asset)
+            }
             AssetExpr::Input(i) => {
                 if *i >= env.spent_utxos().len() {
                     return Err(EvalError::UtxoIndexOutOfBounds(*i, env.spent_utxos().len()));
@@ -721,6 +741,8 @@ impl AssetExpr<CovExtArgs> {
             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::CurrInp, Tk::OutAsset]) = tks.get(e.checked_sub(2)?..e) {
+            Some((AssetExpr::CurrOutputAsset, e - 2))
         } else if let Some(&[Tk::Num(i), Tk::InpAsset]) = tks.get(e.checked_sub(2)?..e) {
             Some((AssetExpr::Input(i as usize), e - 2))
         } else if let Some(&[Tk::Num(i), Tk::OutAsset]) = tks.get(e.checked_sub(2)?..e) {
@@ -1202,6 +1224,7 @@ mod tests {
         _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(curr_inp_asset,curr_out_asset)");
         _test_parse("asset_eq(inp_asset(3),out_asset(1))");
 
         // same tests for values
diff --git a/tests/test_introspect.rs b/tests/test_introspect.rs
index e3acabae..e7c7e3ed 100644
--- a/tests/test_introspect.rs
+++ b/tests/test_introspect.rs
@@ -204,6 +204,7 @@ fn test_descs(cl: &ElementsD, testdata: &mut TestData) {
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_asset(out_asset(1))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),asset_eq(curr_inp_asset,inp_asset(0))))");
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),asset_eq(curr_inp_asset,out_asset(0))))");
+    test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),asset_eq(curr_out_asset,out_asset(0))))");
 
     // same tests for values
     test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(exp_value)))");

From 95c3ff8a910b3f7222b4091fade712eb8b5638c3 Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 15:27:02 -0700
Subject: [PATCH 5/6] Add extensions to spec

---
 doc/extension_spec.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/extension_spec.md b/doc/extension_spec.md
index efa42870..be0c86ff 100644
--- a/doc/extension_spec.md
+++ b/doc/extension_spec.md
@@ -67,6 +67,7 @@ Name                    | Script
 curr_inp_asset          | `PUSHCURRENTINPUTINDEX INPSECTINPUTASSET`
 inp_asset(i)            | `i INPSECTINPUTASSET`
 out_asset(i)            | `i INPSECTOUTPUTASSET`
+curr_out_asset          | `PUSHCURRENTINPUTINDEX INPSECTOUTPUTASSET`
 
 ### ValueExpr
 
@@ -80,6 +81,7 @@ Name                    | Script
 curr_inp_value          | `PUSHCURRENTINPUTINDEX INPSECTINPUTVALUE`
 inp_value(i)            | `i INPSECTINPUTVALUE`
 out_value(i)            | `i INPSECTOUTPUTVALUE`
+curr_out_value          | `PUSHCURRENTINPUTINDEX INPSECTOUTPUTVALUE`
 
 ### SpkExpr: Script PubKey Expression
 
@@ -94,6 +96,7 @@ Name                    | Script
 curr_inp_spk            | `PUSHCURRENTINPUTINDEX INPSECTINPUTSCRIPTPUBKEY`
 inp_spk(i)              | `i INPSECTINPUTSCRIPTPUBKEY`
 out_spk(i)              | `i INPSECTOUTPUTASSETSCRIPTPUBKEY`
+curr_out_spk            | `PUSHCURRENTINPUTINDEX INPSECTOUTPUTASSETSCRIPTPUBKEY`
 
 ## Introspection Operations
 

From 66aeb41968c171817a93fc30ae00471f6a8dbe90 Mon Sep 17 00:00:00 2001
From: sanket1729 <sanket1729@gmail.com>
Date: Thu, 6 Oct 2022 17:52:25 -0700
Subject: [PATCH 6/6] Implement APO extensions using `Extension`

Ideally, we want to do using more granular components defined in the
library. As a simple example, this implements apo as a single fragment.
---
 examples/apo.rs | 348 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 348 insertions(+)
 create mode 100644 examples/apo.rs

diff --git a/examples/apo.rs b/examples/apo.rs
new file mode 100644
index 00000000..b442e2b0
--- /dev/null
+++ b/examples/apo.rs
@@ -0,0 +1,348 @@
+//! Sighash any-prevout emulation using the new opcodes.
+
+use bitcoin;
+use elements::confidential::{Asset, Value};
+use elements::encode::{self, deserialize};
+use elements::hashes::hex::{FromHex, ToHex};
+use elements::{confidential, opcodes, AddressParams, AssetId, TxOut};
+use miniscript::descriptor::Tr;
+use miniscript::extensions::{
+    AssetExpr, CovExtArgs, CovOps, ParseableExt, Spk, SpkExpr, ValueExpr,
+};
+use miniscript::miniscript::satisfy::{Satisfaction, Witness};
+use miniscript::miniscript::types::extra_props::{OpLimits, TimelockInfo};
+use miniscript::miniscript::types::{Correctness, ExtData, Malleability};
+use miniscript::{expression, Extension, TxEnv};
+extern crate elements_miniscript as miniscript;
+
+use std::fmt;
+
+/// The data that needs to be signed in apo + all.
+/// We can decompose this into into individual parts for fixing version, amt, script pubkey
+///
+/// This structure is onyl an example of how one might implement extension. We do pay any
+/// special attention the serialization format, order of serialization etc.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct SighashAllAPO {
+    /// The outputs of transaction
+    outputs: Vec<elements::TxOut>,
+    /// The input script pubkey
+    in_asset: elements::confidential::Asset,
+    /// Input value
+    in_value: elements::confidential::Value,
+    /// Input script pubkey
+    in_spk: elements::Script,
+    /// The tx version
+    version: u32,
+    /// The tx locktime
+    locktime: u32,
+    /// The tx sequence
+    sequence: u32,
+}
+
+impl SighashAllAPO {
+    /// Evaluate the sighash_all_apo
+    pub fn eval(&self, env: &TxEnv) -> Result<bool, miniscript::interpreter::Error> {
+        let tx_inp = env
+            .tx()
+            .input
+            .get(env.idx())
+            .ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
+        let spent_utxo = env
+            .spent_utxos()
+            .get(env.idx())
+            .ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
+        if tx_inp.sequence != self.sequence
+            || env.tx().version != self.version
+            || env.tx().lock_time != self.locktime
+            || spent_utxo.asset != self.in_asset
+            || spent_utxo.value != self.in_value
+            || spent_utxo.script_pubkey != self.in_spk
+            || env.tx().output != self.outputs
+        {
+            return Ok(false);
+        }
+        Ok(true)
+    }
+}
+
+impl PartialOrd for SighashAllAPO {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SighashAllAPO {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        // HACKY implementation that allocates a string
+        self.to_string().cmp(&other.to_string())
+    }
+}
+
+impl fmt::Display for SighashAllAPO {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "all_apo({},{},{},{},{},{},{})",
+            encode::serialize_hex(&self.outputs),
+            encode::serialize_hex(&self.in_asset),
+            encode::serialize_hex(&self.in_value),
+            encode::serialize_hex(&self.in_spk),
+            self.version,
+            self.locktime,
+            self.sequence,
+        )
+    }
+}
+
+impl Extension for SighashAllAPO {
+    fn corr_prop(&self) -> miniscript::miniscript::types::Correctness {
+        Correctness {
+            base: miniscript::miniscript::types::Base::B,
+            input: miniscript::miniscript::types::Input::Zero,
+            dissatisfiable: true,
+            unit: true,
+        }
+    }
+
+    fn mall_prop(&self) -> miniscript::miniscript::types::Malleability {
+        Malleability {
+            dissat: miniscript::miniscript::types::Dissat::Unknown,
+            safe: false,
+            non_malleable: true,
+        }
+    }
+
+    fn extra_prop(&self) -> miniscript::miniscript::types::ExtData {
+        ExtData {
+            pk_cost: 500, // dummy size, check real size later
+            has_free_verify: true,
+            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(2), // max stack size during execution = 2 elements
+            exec_stack_elem_count_dissat: Some(2),
+            ops: OpLimits {
+                // Opcodes are really not relevant in tapscript as BIP342 removes all rules on them
+                count: 1,
+                sat: Some(0),
+                nsat: Some(0),
+            },
+        }
+    }
+
+    fn script_size(&self) -> usize {
+        todo!()
+    }
+
+    fn from_name_tree(
+        name: &str,
+        children: &[miniscript::expression::Tree<'_>],
+    ) -> Result<Self, ()> {
+        if children.len() == 7 && name == "all_apo" {
+            if children.iter().any(|x| !x.args.is_empty()) {
+                return Err(());
+            }
+            let outputs = deser_hex::<Vec<TxOut>>(children[0].name)?;
+            let in_asset = deser_hex::<elements::confidential::Asset>(children[1].name)?;
+            let in_value = deser_hex::<elements::confidential::Value>(children[2].name)?;
+            let in_spk = deser_hex::<elements::Script>(children[3].name)?;
+            let version = expression::parse_num(children[4].name).map_err(|_e| ())?;
+            let locktime = expression::parse_num(children[5].name).map_err(|_e| ())?;
+            let sequence = expression::parse_num(children[6].name).map_err(|_e| ())?;
+            Ok(SighashAllAPO {
+                outputs,
+                in_asset,
+                in_value,
+                in_spk,
+                version,
+                locktime,
+                sequence,
+            })
+        } else {
+            // Correct error handling while parsing fromtree
+            Err(())
+        }
+    }
+}
+
+impl ParseableExt for SighashAllAPO {
+    fn from_token_iter(
+        _tokens: &mut miniscript::miniscript::lex::TokenIter<'_>,
+    ) -> Result<Self, ()> {
+        // Parsing back from script is currently not implemented
+        Err(())
+    }
+
+    fn evaluate<'intp, 'txin>(
+        &'intp self,
+        _stack: &mut miniscript::interpreter::Stack<'txin>,
+        txenv: Option<&miniscript::TxEnv>,
+    ) -> Result<bool, miniscript::interpreter::Error> {
+        let env = txenv.ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
+        self.eval(env)
+    }
+
+    #[rustfmt::skip]
+    fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
+        let mut builder = builder;
+        for (i, out) in self.outputs.iter().enumerate() {
+            let asset_eq = CovOps::<CovExtArgs>::AssetEq(
+                AssetExpr::Const(out.asset.into()),
+                AssetExpr::Output(i),
+            );
+            let value_eq = CovOps::<CovExtArgs>::ValueEq(
+                ValueExpr::Const(out.value.into()),
+                ValueExpr::Output(i),
+            );
+            let spk_eq = CovOps::<CovExtArgs>::SpkEq(
+                SpkExpr::Const(Spk(out.script_pubkey.clone()).into()),
+                SpkExpr::Output(i),
+            );
+            builder = asset_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
+            builder = value_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
+            builder = spk_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
+        }
+
+        use opcodes::all::*;
+        builder = builder
+            .push_opcode(OP_INSPECTVERSION).push_slice(&self.version.to_le_bytes()).push_opcode(OP_EQUALVERIFY)
+            .push_opcode(OP_INSPECTLOCKTIME).push_slice(&self.locktime.to_le_bytes()).push_opcode(OP_EQUALVERIFY);
+        builder = builder
+            .push_opcode(OP_PUSHCURRENTINPUTINDEX)
+            .push_opcode(OP_INSPECTINPUTSEQUENCE)
+            .push_slice(&self.sequence.to_le_bytes())
+            .push_opcode(OP_EQUALVERIFY);
+        let in_asset_eq = CovOps::<CovExtArgs>::AssetEq(
+            AssetExpr::Const(self.in_asset.into()),
+            AssetExpr::CurrInputAsset,
+        );
+        let in_value_eq = CovOps::<CovExtArgs>::ValueEq(
+            ValueExpr::Const(self.in_value.into()),
+            ValueExpr::CurrInputValue,
+        );
+        let in_spk_eq = CovOps::<CovExtArgs>::SpkEq(
+            SpkExpr::Const(Spk(self.in_spk.clone()).into()),
+            SpkExpr::CurrInputSpk,
+        );
+        builder = in_asset_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
+        builder = in_value_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
+        in_spk_eq.push_to_builder(builder)
+    }
+
+    fn satisfy<Pk, S>(&self, sat: &S) -> miniscript::miniscript::satisfy::Satisfaction
+    where
+        Pk: miniscript::ToPublicKey,
+        S: miniscript::Satisfier<Pk>,
+    {
+        let env = match (
+            sat.lookup_tx(),
+            sat.lookup_spent_utxos(),
+            sat.lookup_curr_inp(),
+        ) {
+            (Some(tx), Some(spent_utxos), Some(idx)) => TxEnv::new(tx, spent_utxos, idx),
+            _ => {
+                return Satisfaction {
+                    stack: Witness::Impossible,
+                    has_sig: false,
+                }
+            }
+        };
+        let env = match env {
+            Some(env) => env,
+            None => {
+                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) -> miniscript::miniscript::satisfy::Satisfaction
+    where
+        Pk: miniscript::ToPublicKey,
+        S: miniscript::Satisfier<Pk>,
+    {
+        // This is incorrect!!!!, but done for testing purposes
+        Satisfaction {
+            stack: Witness::Impossible,
+            has_sig: false,
+        }
+    }
+}
+
+fn deser_hex<T>(hex: &str) -> Result<T, ()>
+where
+    T: encode::Decodable,
+{
+    let bytes = Vec::<u8>::from_hex(hex).map_err(|_| ())?;
+    deserialize(&bytes).map_err(|_| ())
+}
+
+fn main() {
+    let tap_script = elements::script::Builder::default()
+        .push_opcode(opcodes::all::OP_PUSHNUM_1)
+        .push_slice(
+            &Vec::<u8>::from_hex(
+                "052ef9779ac3955ef438bbcde77411f31bf0e05fbe1b2b563fb5f0475c8a8e71",
+            )
+            .unwrap(),
+        )
+        .into_script();
+    let conf_asset = Asset::from_commitment(
+        &Vec::<u8>::from_hex("0bdabc318c05dfc1f911bd7e4608ad75c75b3cc25b2fe98fa3966597ab9a0a473f")
+            .unwrap(),
+    )
+    .unwrap();
+    let conf_value = Value::from_commitment(
+        &Vec::<u8>::from_hex("08fb70255ab047990780c71fed7b874ca4ece6583ade26b37bf7d7f1c9284f4c84")
+            .unwrap(),
+    )
+    .unwrap();
+    let mut apo = SighashAllAPO {
+        outputs: vec![elements::TxOut::default(); 2],
+        in_asset: confidential::Asset::Explicit(
+            AssetId::from_hex("5a62ef74ac90662be6a115855853c1a9d4407d955d28446c67c1568e532e61e9")
+                .unwrap(),
+        ),
+        in_value: confidential::Value::Explicit(1000),
+        in_spk: tap_script.clone(),
+        version: 3,
+        locktime: 1_000_000,
+        sequence: 0xfffffffe,
+    };
+    apo.outputs[0].asset = conf_asset;
+    apo.outputs[0].value = conf_value;
+    apo.outputs[0].script_pubkey = tap_script.clone();
+    apo.outputs[1].asset = conf_asset;
+    apo.outputs[1].value = conf_value;
+    apo.outputs[1].script_pubkey = tap_script.clone();
+
+    let internal_pk = "02052ef9779ac3955ef438bbcde77411f31bf0e05fbe1b2b563fb5f0475c8a8e71";
+
+    let desc = Tr::<bitcoin::PublicKey, SighashAllAPO>::from_str_insane(&format!(
+        "eltr({},{})",
+        internal_pk, &apo,
+    ))
+    .unwrap();
+    println!(
+        "desc addr: {}",
+        desc.address(None, &AddressParams::ELEMENTS)
+    );
+
+    let tap_script = desc.iter_scripts().next().unwrap().1;
+    println!("{}", tap_script.encode().to_hex());
+    println!("{:?}", tap_script.encode());
+}