Skip to content

Commit

Permalink
trivial P2Tr compiler done
Browse files Browse the repository at this point in the history
Taproot internal-key extraction from policy done
  • Loading branch information
SarcasticNastik committed Jan 17, 2022
1 parent b36c116 commit e507066
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/policy/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type PolicyCache<Pk, Ctx> =

///Ordered f64 for comparison
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
struct OrdF64(f64);
pub(crate) struct OrdF64(pub f64);

impl Eq for OrdF64 {}
impl Ord for OrdF64 {
Expand Down
144 changes: 131 additions & 13 deletions src/policy/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ use miniscript::limits::{HEIGHT_TIME_THRESHOLD, SEQUENCE_LOCKTIME_TYPE_FLAG};
use miniscript::types::extra_props::TimeLockInfo;
#[cfg(feature = "compiler")]
use {
descriptor::TapTree, miniscript::ScriptContext, policy::compiler,
policy::compiler::CompilerError, std::sync::Arc, Descriptor, Miniscript, Tap,
descriptor::TapTree,
miniscript::ScriptContext,
policy::compiler::{CompilerError, OrdF64},
policy::{compiler, Liftable, Semantic},
std::cmp::Reverse,
std::collections::BinaryHeap,
std::sync::Arc,
Descriptor, Miniscript, Tap,
};
use {Error, ForEach, ForEachKey, MiniscriptKey};

Expand Down Expand Up @@ -126,27 +132,115 @@ impl fmt::Display for PolicyError {
}

impl<Pk: MiniscriptKey> Policy<Pk> {
/// Single-Node compilation
/// Create a Huffman Tree from compiled [Miniscript] nodes
#[cfg(feature = "compiler")]
fn compile_leaf_taptree(&self) -> Result<TapTree<Pk>, Error> {
let compilation = self.compile::<Tap>().unwrap();
Ok(TapTree::Leaf(Arc::new(compilation)))
fn with_huffman_tree<T>(
ms: Vec<(OrdF64, Miniscript<Pk, Tap>)>,
f: T,
) -> Result<TapTree<Pk>, Error>
where
T: Fn(OrdF64) -> OrdF64,
{
// Pattern match terminal Or/ Terminal (with equal odds)
let mut node_weights = BinaryHeap::<(Reverse<OrdF64>, Arc<TapTree<Pk>>)>::new();
for (prob, script) in ms {
node_weights.push((Reverse(f(prob)), Arc::from(TapTree::Leaf(Arc::new(script)))));
}
if node_weights.is_empty() {
return Err(errstr("Empty Miniscript compilation"));
}
while node_weights.len() > 1 {
let (p1, s1) = node_weights.pop().expect("len must atleast be two");
let (p2, s2) = node_weights.pop().expect("len must atleast be two");

let p = (p1.0).0 + (p2.0).0;
node_weights.push((Reverse(OrdF64(p)), Arc::from(TapTree::Tree(s1, s2))));
}

debug_assert!(node_weights.len() == 1);
let node = node_weights
.pop()
.expect("huffman tree algorithm is broken")
.1;
Ok((*node).clone())
}

/// Extract the Taproot internal_key from policy tree.
/// Flatten the [`Policy`] tree structure into a Vector with corresponding leaf probability
// TODO: 1. Can try to push the maximum of scaling factors and accordingly update later for
// TODO: 1. integer metric. (Accordingly change metrics everywhere)
#[cfg(feature = "compiler")]
fn extract_key(&self, unspendable_key: Option<Pk>) -> Result<(Pk, &Policy<Pk>), Error> {
match unspendable_key {
Some(key) => Ok((key, self)),
None => Err(errstr("No internal key found")),
fn to_tapleaf_prob_vec(&self, prob: f64) -> Vec<(f64, Policy<Pk>)> {
match *self {
Policy::Or(ref subs) => {
let total_odds: usize = subs.iter().map(|(ref k, _)| k).sum();
subs.iter()
.map(|(k, ref policy)| {
policy.to_tapleaf_prob_vec(prob * *k as f64 / total_odds as f64)
})
.flatten()
.collect::<Vec<_>>()
}
Policy::Threshold(k, ref subs) if k == 1 => {
let total_odds = subs.len();
subs.iter()
.map(|policy| policy.to_tapleaf_prob_vec(prob / total_odds as f64))
.flatten()
.collect::<Vec<_>>()
}
ref x => vec![(prob, x.clone())],
}
}

/// Compile [`Policy::Or`] and [`Policy::Threshold`] according to odds
#[cfg(feature = "compiler")]
fn compile_tr_policy(&self) -> Result<TapTree<Pk>, Error> {
let leaf_compilations: Vec<_> = self
.to_tapleaf_prob_vec(1.0)
.into_iter()
.map(|(prob, ref policy)| (OrdF64(prob), policy.compile::<Tap>().unwrap()))
.collect();
let taptree = Self::with_huffman_tree(leaf_compilations, |x| x).unwrap();
Ok(taptree)
}

/// Extract the internal_key from policy tree.
#[cfg(feature = "compiler")]
fn extract_key(self, unspendable_key: Option<Pk>) -> Result<(Pk, Policy<Pk>), Error> {
// Making sure the borrow ends before you move the value.
let mut internal_key: Option<Pk> = None;
{
let semantic_policy = (&self).lift()?;
let concrete_keys = (&self).keys().into_iter().collect::<HashSet<_>>();
for key in concrete_keys.iter() {
if semantic_policy
.clone()
.satisfy_constraint(&Semantic::KeyHash(key.to_pubkeyhash()), true)
== Semantic::Trivial
{
internal_key = Some((*key).clone());
break;
}
}
}
match (internal_key, unspendable_key) {
(Some(ref key), _) => Ok((key.clone(), self.translate_unsatisfiable_pk(&key))),
(_, Some(key)) => Ok((key, self)),
_ => Err(errstr("No viable internal key found.")),
}
}

/// Compile the [`Tr`] descriptor into optimized [`TapTree`] implementation
// TODO: We might require other compile errors for Taproot. Will discuss and update.
#[cfg(feature = "compiler")]
pub fn compile_tr(&self, unspendable_key: Option<Pk>) -> Result<Descriptor<Pk>, Error> {
let (internal_key, policy) = self.extract_key(unspendable_key)?;
let tree = Descriptor::new_tr(internal_key, Some(policy.compile_leaf_taptree()?))?;
let (internal_key, policy) = self.clone().extract_key(unspendable_key)?;
let tree = Descriptor::new_tr(
internal_key,
match policy {
Policy::Trivial => None,
policy => Some(policy.compile_tr_policy()?),
},
)?;
Ok(tree)
}

Expand Down Expand Up @@ -250,6 +344,30 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
}
}

/// Translate `Semantic::Key(key)` to `Semantic::Unsatisfiable` when extracting TapKey
pub fn translate_unsatisfiable_pk(self, key: &Pk) -> Policy<Pk> {
match self {
Policy::Key(ref k) if k.clone() == *key => Policy::Unsatisfiable,
Policy::And(subs) => Policy::And(
subs.into_iter()
.map(|sub| sub.translate_unsatisfiable_pk(key))
.collect::<Vec<_>>(),
),
Policy::Or(subs) => Policy::Or(
subs.into_iter()
.map(|(k, sub)| (k, sub.translate_unsatisfiable_pk(key)))
.collect::<Vec<_>>(),
),
Policy::Threshold(k, subs) => Policy::Threshold(
k,
subs.into_iter()
.map(|sub| sub.translate_unsatisfiable_pk(key))
.collect::<Vec<_>>(),
),
x => x,
}
}

/// Get all keys in the policy
pub fn keys(&self) -> Vec<&Pk> {
match *self {
Expand Down
17 changes: 7 additions & 10 deletions src/policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,16 +374,13 @@ mod tests {
#[test]
#[cfg(feature = "compiler")]
fn single_leaf_tr_compile() {
for k in 1..5 {
let unspendable_key: String = "z".to_string();
let policy: Concrete<String> = policy_str!("thresh({},pk(A),pk(B),pk(C),pk(D))", k);
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
let unspendable_key: String = "UNSPENDABLE".to_string();
let policy: Concrete<String> = policy_str!("thresh(2,pk(A),pk(A),pk(C),pk(D))");
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();

let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a({},A,B,C,D)", k);
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
let expected_descriptor = Descriptor::new_tr(unspendable_key, Some(tree)).unwrap();

assert_eq!(descriptor, expected_descriptor);
}
let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a(2,C,D)");
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
let expected_descriptor = Descriptor::new_tr("A".to_string(), Some(tree)).unwrap();
assert_eq!(descriptor, expected_descriptor);
}
}
15 changes: 6 additions & 9 deletions src/policy/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
// policy.
// Witness is currently encoded as policy. Only accepts leaf fragment and
// a normalized policy
fn satisfy_constraint(self, witness: &Policy<Pk>, available: bool) -> Policy<Pk> {
pub(crate) fn satisfy_constraint(self, witness: &Policy<Pk>, available: bool) -> Policy<Pk> {
debug_assert!(self.clone().normalized() == self.clone());
match *witness {
// only for internal purposes, safe to use unreachable!
Expand Down Expand Up @@ -679,10 +679,7 @@ mod tests {
let policy = StringPolicy::from_str("or(pkh(),older(1000))").unwrap();
assert_eq!(
policy,
Policy::Threshold(
1,
vec![Policy::KeyHash("".to_owned()), Policy::Older(1000),]
)
Policy::Threshold(1, vec![Policy::KeyHash("".to_owned()), Policy::Older(1000)])
);
assert_eq!(policy.relative_timelocks(), vec![1000]);
assert_eq!(policy.absolute_timelocks(), vec![]);
Expand All @@ -698,7 +695,7 @@ mod tests {
policy,
Policy::Threshold(
1,
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable,]
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable],
)
);
assert_eq!(policy.relative_timelocks(), vec![]);
Expand All @@ -711,7 +708,7 @@ mod tests {
policy,
Policy::Threshold(
2,
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable,]
vec![Policy::KeyHash("".to_owned()), Policy::Unsatisfiable],
)
);
assert_eq!(policy.relative_timelocks(), vec![]);
Expand All @@ -735,7 +732,7 @@ mod tests {
Policy::Older(1000),
Policy::Older(2000),
Policy::Older(2000),
]
],
)
);
assert_eq!(
Expand All @@ -759,7 +756,7 @@ mod tests {
Policy::Older(1000),
Policy::Unsatisfiable,
Policy::Unsatisfiable,
]
],
)
);
assert_eq!(
Expand Down

0 comments on commit e507066

Please sign in to comment.