use std::fmt;
use elements::encode::serialize;
use elements::hashes::{sha256d, Hash};
use elements::hex::{FromHex, ToHex};
use super::{FromTokenIterError, ParseableExt, TxEnv};
use crate::descriptor::CovError;
use crate::miniscript::astelem::StackCtxOperations;
use crate::miniscript::context::ScriptContextError;
use crate::miniscript::lex::{Token as Tk, TokenIter};
use crate::miniscript::limits::{MAX_SCRIPT_ELEMENT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEM_SIZE};
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::policy::{self, Liftable};
use crate::{expression, interpreter, Error, Extension, MiniscriptKey, Satisfier, ToPublicKey};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub struct LegacyOutputsPref {
pub pref: Vec<u8>,
}
impl fmt::Display for LegacyOutputsPref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "outputs_pref({})", self.pref.to_hex())
}
}
impl<Pk: MiniscriptKey> Liftable<Pk> for LegacyOutputsPref {
fn lift(&self) -> Result<policy::Semantic<Pk>, Error> {
Err(Error::CovError(CovError::CovenantLift))
}
}
impl Extension for LegacyOutputsPref {
fn segwit_ctx_checks(&self) -> Result<(), ScriptContextError> {
if self.pref.len() > MAX_SCRIPT_ELEMENT_SIZE {
Err(ScriptContextError::CovElementSizeExceeded)
} else {
Ok(())
}
}
fn corr_prop(&self) -> Correctness {
Correctness {
base: Base::B,
input: Input::Any, dissatisfiable: true, unit: true,
}
}
fn mall_prop(&self) -> Malleability {
Malleability {
dissat: Dissat::Unknown,
safe: false,
non_malleable: true,
}
}
fn extra_prop(&self) -> ExtData {
let max_wit_sz = MAX_SCRIPT_ELEMENT_SIZE - self.pref.len();
ExtData {
pk_cost: 8 + self.pref.len() + 1 + 6, has_free_verify: true,
stack_elem_count_sat: Some(7),
stack_elem_count_dissat: Some(7),
max_sat_size: Some((max_wit_sz, max_wit_sz)),
max_dissat_size: Some((0, 0)), timelock_info: TimelockInfo::default(),
exec_stack_elem_count_sat: Some(3), exec_stack_elem_count_dissat: Some(3),
ops: OpLimits {
count: 13,
sat: Some(0),
nsat: Some(0),
},
}
}
fn script_size(&self) -> usize {
8 + self.pref.len() + 1 + 6 }
fn from_name_tree(
name: &str,
children: &[expression::Tree<'_>],
) -> Result<Self, FromTokenIterError> {
if children.len() == 1 && name == "outputs_pref" {
let pref = expression::terminal(&children[0], Vec::<u8>::from_hex)
.map_err(|_| FromTokenIterError)?;
Ok(Self { pref })
} else {
Err(FromTokenIterError)
}
}
}
impl ParseableExt for LegacyOutputsPref {
fn satisfy<Pk, S>(&self, sat: &S) -> Satisfaction
where
Pk: ToPublicKey,
S: Satisfier<Pk>,
{
let wit = match sat.lookup_outputs() {
Some(outs) => {
let mut ser_out = Vec::new();
let num_wit_elems =
MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE + 1;
let mut witness = Vec::with_capacity(num_wit_elems);
for out in outs {
ser_out.extend(serialize(out));
}
if ser_out.len() > MAX_SCRIPT_ELEMENT_SIZE {
Witness::Impossible
} else if ser_out.starts_with(&self.pref) {
let mut iter = ser_out.into_iter().skip(self.pref.len()).peekable();
while iter.peek().is_some() {
let chk_size = MAX_STANDARD_P2WSH_STACK_ITEM_SIZE;
let chunk: Vec<u8> = iter.by_ref().take(chk_size).collect();
witness.push(chunk);
}
while witness.len() < num_wit_elems {
witness.push(vec![]);
}
Witness::Stack(witness)
} else {
Witness::Impossible
}
}
None => Witness::Unavailable,
};
Satisfaction {
stack: wit,
has_sig: false,
}
}
fn dissatisfy<Pk, S>(&self, sat: &S) -> Satisfaction
where
Pk: ToPublicKey,
S: Satisfier<Pk>,
{
let wit = match sat.lookup_outputs() {
Some(outs) => {
let mut ser_out = Vec::new();
for out in outs {
ser_out.extend(serialize(out));
}
let num_wit_elems = MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE;
let mut witness = Vec::with_capacity(num_wit_elems);
if self.pref != ser_out.as_slice() {
while witness.len() < num_wit_elems {
witness.push(vec![]);
}
Witness::Stack(witness)
} else if self.pref.len() != MAX_SCRIPT_ELEMENT_SIZE {
witness.push(vec![1]);
while witness.len() < num_wit_elems {
witness.push(vec![]);
}
Witness::Stack(witness)
} else {
Witness::Impossible
}
}
None => Witness::Unavailable,
};
Satisfaction {
stack: wit,
has_sig: false,
}
}
fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
builder.check_item_pref(4, &self.pref)
}
fn from_token_iter(tokens: &mut TokenIter<'_>) -> Result<Self, FromTokenIterError> {
let outputs_pref = {
let sl = tokens.peek_slice(15).ok_or(FromTokenIterError)?;
if let Tk::Push(pref) = &sl[6] {
if sl[0] == Tk::Cat
&& sl[1] == Tk::Cat
&& sl[2] == Tk::Cat
&& sl[3] == Tk::Cat
&& sl[4] == Tk::Cat
&& sl[5] == Tk::Cat
&& sl[7] == Tk::Swap
&& sl[8] == Tk::Cat
&& sl[9] == Tk::Hash256
&& sl[11] == Tk::Num(4)
&& sl[12] == Tk::Sub
&& sl[13] == Tk::Pick
&& sl[14] == Tk::Equal
{
Self { pref: pref.clone() }
} else {
return Err(FromTokenIterError);
}
} else {
return Err(FromTokenIterError);
}
};
tokens.advance(15).expect("Size checked previously");
Ok(outputs_pref)
}
fn evaluate(
&self,
stack: &mut interpreter::Stack,
_txenv: Option<&TxEnv>,
) -> Result<bool, interpreter::Error> {
let hash_outputs = stack[3];
let hash_outputs = hash_outputs.try_push()?;
let max_elems = MAX_SCRIPT_ELEMENT_SIZE / MAX_STANDARD_P2WSH_STACK_ITEM_SIZE + 1;
if hash_outputs.len() == 32 {
if stack.len() < max_elems {
return Err(interpreter::Error::UnexpectedStackEnd);
}
let mut outputs_builder = Vec::new();
outputs_builder.extend(&self.pref);
let len = stack.len();
for i in 0..max_elems {
outputs_builder.extend(stack[len - max_elems + i].into_slice());
}
for _ in 0..max_elems {
stack.pop().unwrap();
}
if sha256d::Hash::hash(&outputs_builder).as_byte_array() == hash_outputs {
stack.push(interpreter::Element::Satisfied);
Ok(true)
} else {
Ok(false)
}
} else {
Err(interpreter::Error::CovWitnessSizeErr {
pos: 9,
expected: 32,
actual: hash_outputs.len(),
})
}
}
}
#[cfg(test)]
mod tests {
use bitcoin::PublicKey;
use super::*;
use crate::{Miniscript, Segwitv0};
#[test]
fn test_outputs_pref() {
type MsExtVer = Miniscript<PublicKey, Segwitv0, LegacyOutputsPref>;
let ms = MsExtVer::from_str_insane("outputs_pref(aa)").unwrap();
assert_eq!(ms.to_string(), "outputs_pref(aa)");
assert_eq!(ms, MsExtVer::parse_insane(&ms.encode()).unwrap())
}
}