diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 530be3d83..08f678e5d 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -12,7 +12,7 @@ use std::error; use sync::Arc; -use crate::miniscript::context::SigType; +use crate::miniscript::context::{ScriptContextError, SigType}; use crate::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG}; use crate::miniscript::types::{self, ErrorKind, Type}; use crate::miniscript::ScriptContext; @@ -25,7 +25,7 @@ type PolicyCache = BTreeMap< BTreeMap>, >; /// Detailed error type for compiler. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum CompilerError { /// `And` fragments only support two args. NonBinaryArgAnd, @@ -43,6 +43,9 @@ pub enum CompilerError { /// In a Taproot compilation, no "unspendable key" was provided and no in-policy /// key could be used as an internal key. NoInternalKey, + /// A policy key is not valid under the selected script context (e.g. an + /// x-only key in Segwit v0 or an uncompressed key in Taproot). + ContextError(ScriptContextError), /// The selected Taproot internal key is uncompressed. UncompressedTaprootInternalKey, /// A Huffman merge during Taproot compilation would place a leaf beyond @@ -79,6 +82,7 @@ impl fmt::Display for CompilerError { "At least one spending path has exceeded the standardness or consensus limits", ), Self::NoInternalKey => f.write_str("Taproot compilation had no internal key available"), + Self::ContextError(ref e) => fmt::Display::fmt(e, f), Self::UncompressedTaprootInternalKey => { f.write_str("Taproot compilation selected an uncompressed internal key") } @@ -254,6 +258,7 @@ impl error::Error for CompilerError { | TooManyTapleaves { .. } | IfFragmentInNativeLeaf { .. } => None, PolicyError(e) => Some(e), + ContextError(e) => Some(e), } } } @@ -944,6 +949,10 @@ where insert_wrap!(AstElemExt::trivial()); } Concrete::Key(ref pk) => { + // The compiler must never produce a Miniscript that is invalid + // under the target script context (e.g. an x-only key in Segwit + // v0, or an uncompressed key in Taproot). + Ctx::check_pk(pk).map_err(CompilerError::ContextError)?; insert_wrap!(AstElemExt::pk_h(pk.clone())); insert_wrap!(AstElemExt::pk_k(pk.clone())); } @@ -1184,7 +1193,7 @@ mod tests { use bitcoin::hashes; use super::*; - use crate::miniscript::{Legacy, Segwitv0, Tap}; + use crate::miniscript::{BareCtx, Legacy, Segwitv0, Tap}; use crate::policy::Liftable; use crate::{script_num_size, AbsLockTime, RelLockTime, Threshold, ToPublicKey}; @@ -1628,4 +1637,41 @@ mod tests { assert_eq!(miniscript, expected); } + + #[test] + fn context_invalid_keys() { + // X-only keys are not allowed in Segwitv0, Legacy or Bare contexts; + // the compiler must return an error rather than produce an invalid + // Miniscript. + let x_only_key = "08c0fcf8895f4361b4fc77afe2ad53b0bd27dcebfd863421b2b246dc283d4103"; + let policy: Concrete = policy_str!("pk({})", x_only_key); + assert_eq!( + policy.compile::(), + Err(CompilerError::ContextError(ScriptContextError::XOnlyKeysNotAllowed( + x_only_key.to_string(), + "Segwitv0" + ))), + ); + policy.compile::().unwrap_err(); + policy.compile::().unwrap_err(); + // ..but they are allowed in a Taproot context. + policy.compile::().unwrap(); + + // The same restriction applies to keys compiled into multi fragments. + let k2 = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"; + let multi_pol: Concrete = + policy_str!("thresh(1,pk({}),pk({}))", x_only_key, k2); + multi_pol.compile::().unwrap_err(); + + // Uncompressed keys are not allowed in Segwitv0 or Taproot contexts. + let uncompressed = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"; + let policy: Concrete = policy_str!("pk({})", uncompressed); + assert_eq!( + policy.compile::(), + Err(CompilerError::ContextError(ScriptContextError::UncompressedKeysNotAllowed)), + ); + policy.compile::().unwrap_err(); + // ..but they are allowed in a Legacy context. + policy.compile::().unwrap(); + } }