use miniscript::limits::{
MAX_OPS_PER_SCRIPT, MAX_SCRIPTSIG_SIZE, MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_SIZE,
MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS,
};
use miniscript::types;
use std::fmt;
use util::witness_to_scriptsig;
use Error;
use {Miniscript, MiniscriptKey, Terminal};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ScriptContextError {
MalleablePkH,
MalleableOrI,
MalleableDupIf,
CompressedOnly,
MaxWitnessItemssExceeded,
MaxOpCountExceeded,
MaxWitnessScriptSizeExceeded,
MaxRedeemScriptSizeExceeded,
MaxScriptSigSizeExceeded,
ImpossibleSatisfaction,
}
impl fmt::Display for ScriptContextError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ScriptContextError::MalleablePkH => write!(f, "PkH is malleable under Legacy rules"),
ScriptContextError::MalleableOrI => write!(f, "OrI is malleable under Legacy rules"),
ScriptContextError::MalleableDupIf => {
write!(f, "DupIf is malleable under Legacy rules")
}
ScriptContextError::CompressedOnly => {
write!(f, "Uncompressed pubkeys not allowed in segwit context")
}
ScriptContextError::MaxWitnessItemssExceeded => write!(
f,
"At least one spending path in the Miniscript fragment has more \
witness items than MAX_STANDARD_P2WSH_STACK_ITEMS.",
),
ScriptContextError::MaxOpCountExceeded => write!(
f,
"At least one satisfaction path in the Miniscript fragment contains \
more than MAX_OPS_PER_SCRIPT opcodes."
),
ScriptContextError::MaxWitnessScriptSizeExceeded => write!(
f,
"The Miniscript corresponding Script would be larger than \
MAX_STANDARD_P2WSH_SCRIPT_SIZE bytes."
),
ScriptContextError::MaxRedeemScriptSizeExceeded => write!(
f,
"The Miniscript corresponding Script would be larger than \
MAX_SCRIPT_ELEMENT_SIZE bytes."
),
ScriptContextError::MaxScriptSigSizeExceeded => write!(
f,
"At least one satisfaction in Miniscript would be larger than \
MAX_SCRIPTSIG_SIZE scriptsig"
),
ScriptContextError::ImpossibleSatisfaction => {
write!(
f,
"Impossible to satisfy Miniscript under the current context"
)
}
}
}
}
pub trait ScriptContext:
fmt::Debug + Clone + Ord + PartialOrd + Eq + PartialEq + private::Sealed
{
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
_frag: &Terminal<Pk, Ctx>,
) -> Result<(), ScriptContextError>;
fn check_witness<Pk: MiniscriptKey, Ctx: ScriptContext>(
_witness: &[Vec<u8>],
) -> Result<(), ScriptContextError> {
Ok(())
}
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Option<usize>;
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_global_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_global_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Self::check_global_consensus_validity(ms)?;
Self::check_global_policy_validity(ms)?;
Ok(())
}
fn check_local_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Self::check_global_consensus_validity(ms)?;
Self::check_global_policy_validity(ms)?;
Self::check_local_consensus_validity(ms)?;
Self::check_local_policy_validity(ms)?;
Ok(())
}
fn top_level_type_check<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), Error> {
if ms.ty.corr.base != types::Base::B {
return Err(Error::NonTopLevel(format!("{:?}", ms)));
}
Ok(())
}
fn other_top_level_checks<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), Error> {
Ok(())
}
fn top_level_checks<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), Error> {
Self::top_level_type_check(ms)?;
Self::other_top_level_checks(ms)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Legacy {}
impl ScriptContext for Legacy {
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
frag: &Terminal<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match *frag {
Terminal::PkH(ref _pkh) => Err(ScriptContextError::MalleablePkH),
Terminal::OrI(ref _a, ref _b) => Err(ScriptContextError::MalleableOrI),
Terminal::DupIf(ref _ms) => Err(ScriptContextError::MalleableDupIf),
_ => Ok(()),
}
}
fn check_witness<Pk: MiniscriptKey, Ctx: ScriptContext>(
witness: &[Vec<u8>],
) -> Result<(), ScriptContextError> {
if witness_to_scriptsig(witness).len() > MAX_SCRIPTSIG_SIZE {
return Err(ScriptContextError::MaxScriptSigSizeExceeded);
}
Ok(())
}
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
if ms.ext.pk_cost > MAX_SCRIPT_ELEMENT_SIZE {
return Err(ScriptContextError::MaxRedeemScriptSizeExceeded);
}
Ok(())
}
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match ms.ext.ops_count_sat {
None => Err(ScriptContextError::MaxOpCountExceeded),
Some(op_count) if op_count > MAX_OPS_PER_SCRIPT => {
Err(ScriptContextError::MaxOpCountExceeded)
}
_ => Ok(()),
}
}
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match ms.max_satisfaction_size() {
Err(_e) => Err(ScriptContextError::ImpossibleSatisfaction),
Ok(size) if size > MAX_SCRIPTSIG_SIZE => {
Err(ScriptContextError::MaxScriptSigSizeExceeded)
}
_ => Ok(()),
}
}
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Option<usize> {
ms.ext.max_sat_size.map(|x| x.1)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Segwitv0 {}
impl ScriptContext for Segwitv0 {
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
_frag: &Terminal<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_witness<Pk: MiniscriptKey, Ctx: ScriptContext>(
witness: &[Vec<u8>],
) -> Result<(), ScriptContextError> {
if witness.len() > MAX_STANDARD_P2WSH_STACK_ITEMS {
return Err(ScriptContextError::MaxWitnessItemssExceeded);
}
Ok(())
}
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
if ms.ext.pk_cost > MAX_SCRIPT_SIZE {
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
}
match ms.node {
Terminal::PkK(ref pk) => {
if pk.is_uncompressed() {
return Err(ScriptContextError::CompressedOnly);
}
Ok(())
}
_ => Ok(()),
}
}
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match ms.ext.ops_count_sat {
None => Err(ScriptContextError::MaxOpCountExceeded),
Some(op_count) if op_count > MAX_OPS_PER_SCRIPT => {
Err(ScriptContextError::MaxOpCountExceeded)
}
_ => Ok(()),
}
}
fn check_global_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
if ms.ext.pk_cost > MAX_STANDARD_P2WSH_SCRIPT_SIZE {
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
}
Ok(())
}
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match ms.max_satisfaction_witness_elements() {
Err(_e) => Err(ScriptContextError::ImpossibleSatisfaction),
Ok(max_witness_items) if max_witness_items > MAX_STANDARD_P2WSH_STACK_ITEMS => {
Err(ScriptContextError::MaxWitnessItemssExceeded)
}
_ => Ok(()),
}
}
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Option<usize> {
ms.ext.max_sat_size.map(|x| x.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum BareCtx {}
impl ScriptContext for BareCtx {
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
_frag: &Terminal<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
if ms.ext.pk_cost > MAX_SCRIPT_SIZE {
return Err(ScriptContextError::MaxWitnessScriptSizeExceeded);
}
Ok(())
}
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
match ms.ext.ops_count_sat {
None => Err(ScriptContextError::MaxOpCountExceeded),
Some(op_count) if op_count > MAX_OPS_PER_SCRIPT => {
Err(ScriptContextError::MaxOpCountExceeded)
}
_ => Ok(()),
}
}
fn other_top_level_checks<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Result<(), Error> {
match &ms.node {
Terminal::Check(ref ms) => match &ms.node {
Terminal::PkH(_pkh) => Ok(()),
Terminal::PkK(_pk) => Ok(()),
_ => Err(Error::NonStandardBareScript),
},
Terminal::Multi(_k, subs) if subs.len() <= 3 => Ok(()),
_ => Err(Error::NonStandardBareScript),
}
}
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
ms: &Miniscript<Pk, Ctx>,
) -> Option<usize> {
ms.ext.max_sat_size.map(|x| x.1)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum NoChecks {}
impl ScriptContext for NoChecks {
fn check_terminal_non_malleable<Pk: MiniscriptKey, Ctx: ScriptContext>(
_frag: &Terminal<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_global_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_global_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_local_policy_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn check_local_consensus_validity<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Result<(), ScriptContextError> {
Ok(())
}
fn max_satisfaction_size<Pk: MiniscriptKey, Ctx: ScriptContext>(
_ms: &Miniscript<Pk, Ctx>,
) -> Option<usize> {
panic!("Tried to compute a satisfaction size bound on a no-checks miniscript")
}
}
mod private {
use super::{BareCtx, Legacy, NoChecks, Segwitv0};
pub trait Sealed {}
impl Sealed for BareCtx {}
impl Sealed for Legacy {}
impl Sealed for Segwitv0 {}
impl Sealed for NoChecks {}
}