Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions src/policy/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,7 +25,7 @@ type PolicyCache<Pk, Ctx> = BTreeMap<
BTreeMap<CompilationKey, AstElemExt<Pk, Ctx>>,
>;
/// 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,
Expand All @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -254,6 +258,7 @@ impl error::Error for CompilerError {
| TooManyTapleaves { .. }
| IfFragmentInNativeLeaf { .. } => None,
PolicyError(e) => Some(e),
ContextError(e) => Some(e),
}
}
}
Expand Down Expand Up @@ -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()));
}
Expand Down Expand Up @@ -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};

Expand Down Expand Up @@ -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<bitcoin::XOnlyPublicKey> = policy_str!("pk({})", x_only_key);
assert_eq!(
policy.compile::<Segwitv0>(),
Err(CompilerError::ContextError(ScriptContextError::XOnlyKeysNotAllowed(
x_only_key.to_string(),
"Segwitv0"
))),
);
policy.compile::<Legacy>().unwrap_err();
policy.compile::<BareCtx>().unwrap_err();
// ..but they are allowed in a Taproot context.
policy.compile::<Tap>().unwrap();

// The same restriction applies to keys compiled into multi fragments.
let k2 = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
let multi_pol: Concrete<bitcoin::XOnlyPublicKey> =
policy_str!("thresh(1,pk({}),pk({}))", x_only_key, k2);
multi_pol.compile::<Segwitv0>().unwrap_err();

// Uncompressed keys are not allowed in Segwitv0 or Taproot contexts.
let uncompressed = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
let policy: Concrete<bitcoin::PublicKey> = policy_str!("pk({})", uncompressed);
assert_eq!(
policy.compile::<Segwitv0>(),
Err(CompilerError::ContextError(ScriptContextError::UncompressedKeysNotAllowed)),
);
policy.compile::<Tap>().unwrap_err();
// ..but they are allowed in a Legacy context.
policy.compile::<Legacy>().unwrap();
}
}
Loading