Skip to content

Commit 66aeb41

Browse files
committed
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.
1 parent 95c3ff8 commit 66aeb41

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

examples/apo.rs

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
//! Sighash any-prevout emulation using the new opcodes.
2+
3+
use bitcoin;
4+
use elements::confidential::{Asset, Value};
5+
use elements::encode::{self, deserialize};
6+
use elements::hashes::hex::{FromHex, ToHex};
7+
use elements::{confidential, opcodes, AddressParams, AssetId, TxOut};
8+
use miniscript::descriptor::Tr;
9+
use miniscript::extensions::{
10+
AssetExpr, CovExtArgs, CovOps, ParseableExt, Spk, SpkExpr, ValueExpr,
11+
};
12+
use miniscript::miniscript::satisfy::{Satisfaction, Witness};
13+
use miniscript::miniscript::types::extra_props::{OpLimits, TimelockInfo};
14+
use miniscript::miniscript::types::{Correctness, ExtData, Malleability};
15+
use miniscript::{expression, Extension, TxEnv};
16+
extern crate elements_miniscript as miniscript;
17+
18+
use std::fmt;
19+
20+
/// The data that needs to be signed in apo + all.
21+
/// We can decompose this into into individual parts for fixing version, amt, script pubkey
22+
///
23+
/// This structure is onyl an example of how one might implement extension. We do pay any
24+
/// special attention the serialization format, order of serialization etc.
25+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26+
struct SighashAllAPO {
27+
/// The outputs of transaction
28+
outputs: Vec<elements::TxOut>,
29+
/// The input script pubkey
30+
in_asset: elements::confidential::Asset,
31+
/// Input value
32+
in_value: elements::confidential::Value,
33+
/// Input script pubkey
34+
in_spk: elements::Script,
35+
/// The tx version
36+
version: u32,
37+
/// The tx locktime
38+
locktime: u32,
39+
/// The tx sequence
40+
sequence: u32,
41+
}
42+
43+
impl SighashAllAPO {
44+
/// Evaluate the sighash_all_apo
45+
pub fn eval(&self, env: &TxEnv) -> Result<bool, miniscript::interpreter::Error> {
46+
let tx_inp = env
47+
.tx()
48+
.input
49+
.get(env.idx())
50+
.ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
51+
let spent_utxo = env
52+
.spent_utxos()
53+
.get(env.idx())
54+
.ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
55+
if tx_inp.sequence != self.sequence
56+
|| env.tx().version != self.version
57+
|| env.tx().lock_time != self.locktime
58+
|| spent_utxo.asset != self.in_asset
59+
|| spent_utxo.value != self.in_value
60+
|| spent_utxo.script_pubkey != self.in_spk
61+
|| env.tx().output != self.outputs
62+
{
63+
return Ok(false);
64+
}
65+
Ok(true)
66+
}
67+
}
68+
69+
impl PartialOrd for SighashAllAPO {
70+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
71+
Some(self.cmp(other))
72+
}
73+
}
74+
75+
impl Ord for SighashAllAPO {
76+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
77+
// HACKY implementation that allocates a string
78+
self.to_string().cmp(&other.to_string())
79+
}
80+
}
81+
82+
impl fmt::Display for SighashAllAPO {
83+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84+
write!(
85+
f,
86+
"all_apo({},{},{},{},{},{},{})",
87+
encode::serialize_hex(&self.outputs),
88+
encode::serialize_hex(&self.in_asset),
89+
encode::serialize_hex(&self.in_value),
90+
encode::serialize_hex(&self.in_spk),
91+
self.version,
92+
self.locktime,
93+
self.sequence,
94+
)
95+
}
96+
}
97+
98+
impl Extension for SighashAllAPO {
99+
fn corr_prop(&self) -> miniscript::miniscript::types::Correctness {
100+
Correctness {
101+
base: miniscript::miniscript::types::Base::B,
102+
input: miniscript::miniscript::types::Input::Zero,
103+
dissatisfiable: true,
104+
unit: true,
105+
}
106+
}
107+
108+
fn mall_prop(&self) -> miniscript::miniscript::types::Malleability {
109+
Malleability {
110+
dissat: miniscript::miniscript::types::Dissat::Unknown,
111+
safe: false,
112+
non_malleable: true,
113+
}
114+
}
115+
116+
fn extra_prop(&self) -> miniscript::miniscript::types::ExtData {
117+
ExtData {
118+
pk_cost: 500, // dummy size, check real size later
119+
has_free_verify: true,
120+
stack_elem_count_sat: Some(0),
121+
stack_elem_count_dissat: Some(0),
122+
max_sat_size: Some((0, 0)),
123+
max_dissat_size: Some((0, 0)),
124+
timelock_info: TimelockInfo::default(),
125+
exec_stack_elem_count_sat: Some(2), // max stack size during execution = 2 elements
126+
exec_stack_elem_count_dissat: Some(2),
127+
ops: OpLimits {
128+
// Opcodes are really not relevant in tapscript as BIP342 removes all rules on them
129+
count: 1,
130+
sat: Some(0),
131+
nsat: Some(0),
132+
},
133+
}
134+
}
135+
136+
fn script_size(&self) -> usize {
137+
todo!()
138+
}
139+
140+
fn from_name_tree(
141+
name: &str,
142+
children: &[miniscript::expression::Tree<'_>],
143+
) -> Result<Self, ()> {
144+
if children.len() == 7 && name == "all_apo" {
145+
if children.iter().any(|x| !x.args.is_empty()) {
146+
return Err(());
147+
}
148+
let outputs = deser_hex::<Vec<TxOut>>(children[0].name)?;
149+
let in_asset = deser_hex::<elements::confidential::Asset>(children[1].name)?;
150+
let in_value = deser_hex::<elements::confidential::Value>(children[2].name)?;
151+
let in_spk = deser_hex::<elements::Script>(children[3].name)?;
152+
let version = expression::parse_num(children[4].name).map_err(|_e| ())?;
153+
let locktime = expression::parse_num(children[5].name).map_err(|_e| ())?;
154+
let sequence = expression::parse_num(children[6].name).map_err(|_e| ())?;
155+
Ok(SighashAllAPO {
156+
outputs,
157+
in_asset,
158+
in_value,
159+
in_spk,
160+
version,
161+
locktime,
162+
sequence,
163+
})
164+
} else {
165+
// Correct error handling while parsing fromtree
166+
Err(())
167+
}
168+
}
169+
}
170+
171+
impl ParseableExt for SighashAllAPO {
172+
fn from_token_iter(
173+
_tokens: &mut miniscript::miniscript::lex::TokenIter<'_>,
174+
) -> Result<Self, ()> {
175+
// Parsing back from script is currently not implemented
176+
Err(())
177+
}
178+
179+
fn evaluate<'intp, 'txin>(
180+
&'intp self,
181+
_stack: &mut miniscript::interpreter::Stack<'txin>,
182+
txenv: Option<&miniscript::TxEnv>,
183+
) -> Result<bool, miniscript::interpreter::Error> {
184+
let env = txenv.ok_or(miniscript::interpreter::Error::IncorrectCovenantWitness)?;
185+
self.eval(env)
186+
}
187+
188+
#[rustfmt::skip]
189+
fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
190+
let mut builder = builder;
191+
for (i, out) in self.outputs.iter().enumerate() {
192+
let asset_eq = CovOps::<CovExtArgs>::AssetEq(
193+
AssetExpr::Const(out.asset.into()),
194+
AssetExpr::Output(i),
195+
);
196+
let value_eq = CovOps::<CovExtArgs>::ValueEq(
197+
ValueExpr::Const(out.value.into()),
198+
ValueExpr::Output(i),
199+
);
200+
let spk_eq = CovOps::<CovExtArgs>::SpkEq(
201+
SpkExpr::Const(Spk(out.script_pubkey.clone()).into()),
202+
SpkExpr::Output(i),
203+
);
204+
builder = asset_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
205+
builder = value_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
206+
builder = spk_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
207+
}
208+
209+
use opcodes::all::*;
210+
builder = builder
211+
.push_opcode(OP_INSPECTVERSION).push_slice(&self.version.to_le_bytes()).push_opcode(OP_EQUALVERIFY)
212+
.push_opcode(OP_INSPECTLOCKTIME).push_slice(&self.locktime.to_le_bytes()).push_opcode(OP_EQUALVERIFY);
213+
builder = builder
214+
.push_opcode(OP_PUSHCURRENTINPUTINDEX)
215+
.push_opcode(OP_INSPECTINPUTSEQUENCE)
216+
.push_slice(&self.sequence.to_le_bytes())
217+
.push_opcode(OP_EQUALVERIFY);
218+
let in_asset_eq = CovOps::<CovExtArgs>::AssetEq(
219+
AssetExpr::Const(self.in_asset.into()),
220+
AssetExpr::CurrInputAsset,
221+
);
222+
let in_value_eq = CovOps::<CovExtArgs>::ValueEq(
223+
ValueExpr::Const(self.in_value.into()),
224+
ValueExpr::CurrInputValue,
225+
);
226+
let in_spk_eq = CovOps::<CovExtArgs>::SpkEq(
227+
SpkExpr::Const(Spk(self.in_spk.clone()).into()),
228+
SpkExpr::CurrInputSpk,
229+
);
230+
builder = in_asset_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
231+
builder = in_value_eq.push_to_builder(builder).push_opcode(opcodes::all::OP_VERIFY);
232+
in_spk_eq.push_to_builder(builder)
233+
}
234+
235+
fn satisfy<Pk, S>(&self, sat: &S) -> miniscript::miniscript::satisfy::Satisfaction
236+
where
237+
Pk: miniscript::ToPublicKey,
238+
S: miniscript::Satisfier<Pk>,
239+
{
240+
let env = match (
241+
sat.lookup_tx(),
242+
sat.lookup_spent_utxos(),
243+
sat.lookup_curr_inp(),
244+
) {
245+
(Some(tx), Some(spent_utxos), Some(idx)) => TxEnv::new(tx, spent_utxos, idx),
246+
_ => {
247+
return Satisfaction {
248+
stack: Witness::Impossible,
249+
has_sig: false,
250+
}
251+
}
252+
};
253+
let env = match env {
254+
Some(env) => env,
255+
None => {
256+
return Satisfaction {
257+
stack: Witness::Impossible,
258+
has_sig: false,
259+
}
260+
}
261+
};
262+
let wit = match self.eval(&env) {
263+
Ok(false) => Witness::Unavailable,
264+
Ok(true) => Witness::empty(),
265+
Err(_e) => Witness::Impossible,
266+
};
267+
Satisfaction {
268+
stack: wit,
269+
has_sig: false,
270+
}
271+
}
272+
273+
fn dissatisfy<Pk, S>(&self, _sat: &S) -> miniscript::miniscript::satisfy::Satisfaction
274+
where
275+
Pk: miniscript::ToPublicKey,
276+
S: miniscript::Satisfier<Pk>,
277+
{
278+
// This is incorrect!!!!, but done for testing purposes
279+
Satisfaction {
280+
stack: Witness::Impossible,
281+
has_sig: false,
282+
}
283+
}
284+
}
285+
286+
fn deser_hex<T>(hex: &str) -> Result<T, ()>
287+
where
288+
T: encode::Decodable,
289+
{
290+
let bytes = Vec::<u8>::from_hex(hex).map_err(|_| ())?;
291+
deserialize(&bytes).map_err(|_| ())
292+
}
293+
294+
fn main() {
295+
let tap_script = elements::script::Builder::default()
296+
.push_opcode(opcodes::all::OP_PUSHNUM_1)
297+
.push_slice(
298+
&Vec::<u8>::from_hex(
299+
"052ef9779ac3955ef438bbcde77411f31bf0e05fbe1b2b563fb5f0475c8a8e71",
300+
)
301+
.unwrap(),
302+
)
303+
.into_script();
304+
let conf_asset = Asset::from_commitment(
305+
&Vec::<u8>::from_hex("0bdabc318c05dfc1f911bd7e4608ad75c75b3cc25b2fe98fa3966597ab9a0a473f")
306+
.unwrap(),
307+
)
308+
.unwrap();
309+
let conf_value = Value::from_commitment(
310+
&Vec::<u8>::from_hex("08fb70255ab047990780c71fed7b874ca4ece6583ade26b37bf7d7f1c9284f4c84")
311+
.unwrap(),
312+
)
313+
.unwrap();
314+
let mut apo = SighashAllAPO {
315+
outputs: vec![elements::TxOut::default(); 2],
316+
in_asset: confidential::Asset::Explicit(
317+
AssetId::from_hex("5a62ef74ac90662be6a115855853c1a9d4407d955d28446c67c1568e532e61e9")
318+
.unwrap(),
319+
),
320+
in_value: confidential::Value::Explicit(1000),
321+
in_spk: tap_script.clone(),
322+
version: 3,
323+
locktime: 1_000_000,
324+
sequence: 0xfffffffe,
325+
};
326+
apo.outputs[0].asset = conf_asset;
327+
apo.outputs[0].value = conf_value;
328+
apo.outputs[0].script_pubkey = tap_script.clone();
329+
apo.outputs[1].asset = conf_asset;
330+
apo.outputs[1].value = conf_value;
331+
apo.outputs[1].script_pubkey = tap_script.clone();
332+
333+
let internal_pk = "02052ef9779ac3955ef438bbcde77411f31bf0e05fbe1b2b563fb5f0475c8a8e71";
334+
335+
let desc = Tr::<bitcoin::PublicKey, SighashAllAPO>::from_str_insane(&format!(
336+
"eltr({},{})",
337+
internal_pk, &apo,
338+
))
339+
.unwrap();
340+
println!(
341+
"desc addr: {}",
342+
desc.address(None, &AddressParams::ELEMENTS)
343+
);
344+
345+
let tap_script = desc.iter_scripts().next().unwrap().1;
346+
println!("{}", tap_script.encode().to_hex());
347+
println!("{:?}", tap_script.encode());
348+
}

0 commit comments

Comments
 (0)