Skip to content

Commit b7c76cf

Browse files
Add P2Tr compiler
Introduce a `compiler_tr` API for compiling a policy to a Tr Descriptor.
1 parent c68d3e0 commit b7c76cf

File tree

3 files changed

+94
-16
lines changed

3 files changed

+94
-16
lines changed

src/policy/compiler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type PolicyCache<Pk, Ctx> =
3737

3838
///Ordered f64 for comparison
3939
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
40-
struct OrdF64(f64);
40+
pub(crate) struct OrdF64(pub f64);
4141

4242
impl Eq for OrdF64 {}
4343
impl Ord for OrdF64 {

src/policy/concrete.rs

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ use {
3131
crate::descriptor::TapTree,
3232
crate::miniscript::ScriptContext,
3333
crate::policy::compiler::CompilerError,
34+
crate::policy::compiler::OrdF64,
3435
crate::policy::{compiler, Concrete, Liftable, Semantic},
3536
crate::Descriptor,
3637
crate::Miniscript,
3738
crate::Tap,
38-
std::collections::HashMap,
39+
std::cmp::Reverse,
40+
std::collections::{BinaryHeap, HashMap},
3941
std::sync::Arc,
4042
};
4143

@@ -157,15 +159,23 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
157159
}
158160
}
159161

162+
/// Compile [`Policy::Or`] and [`Policy::Threshold`] according to odds
160163
#[cfg(feature = "compiler")]
161-
fn compile_leaf_taptree(&self) -> Result<TapTree<Pk>, Error> {
162-
let compilation = self.compile::<Tap>().unwrap();
163-
Ok(TapTree::Leaf(Arc::new(compilation)))
164+
fn compile_tr_policy(&self) -> Result<TapTree<Pk>, Error> {
165+
let leaf_compilations: Vec<_> = self
166+
.to_tapleaf_prob_vec(1.0)
167+
.into_iter()
168+
.filter(|x| x.1 != Policy::Unsatisfiable)
169+
.map(|(prob, ref policy)| (OrdF64(prob), compiler::best_compilation(policy).unwrap()))
170+
.collect();
171+
let taptree = with_huffman_tree::<Pk>(leaf_compilations).unwrap();
172+
Ok(taptree)
164173
}
165174

166-
/// Extract the Taproot internal_key from policy tree.
175+
/// Extract the internal_key from policy tree.
167176
#[cfg(feature = "compiler")]
168177
fn extract_key(self, unspendable_key: Option<Pk>) -> Result<(Pk, Policy<Pk>), Error> {
178+
// Making sure the borrow ends before you move the value.
169179
let mut internal_key: Option<Pk> = None;
170180
{
171181
let mut prob = 0.;
@@ -206,11 +216,28 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
206216
}
207217
}
208218

209-
/// Compile the [`Tr`] descriptor into optimized [`TapTree`] implementation
219+
/// Compile the [`Policy`] into a [`Tr`][`Descriptor::Tr`] Descriptor.
220+
///
221+
/// ### TapTree compilation
222+
///
223+
/// The policy tree constructed by root-level disjunctions over [`Or`][`Policy::Or`] and
224+
/// [`Thresh`][`Policy::Threshold`](1, ..) which is flattened into a vector (with respective
225+
/// probabilities derived from odds) of policies.
226+
/// For example, the policy `thresh(1,or(pk(A),pk(B)),and(or(pk(C),pk(D)),pk(E)))` gives the vector
227+
/// `[pk(A),pk(B),and(or(pk(C),pk(D)),pk(E)))]`. Each policy in the vector is compiled into
228+
/// the respective miniscripts. A Huffman Tree is created from this vector which optimizes over
229+
/// the probabilitity of satisfaction for the respective branch in the TapTree.
230+
// TODO: We might require other compile errors for Taproot.
210231
#[cfg(feature = "compiler")]
211232
pub fn compile_tr(&self, unspendable_key: Option<Pk>) -> Result<Descriptor<Pk>, Error> {
212233
let (internal_key, policy) = self.clone().extract_key(unspendable_key)?;
213-
let tree = Descriptor::new_tr(internal_key, Some(policy.compile_leaf_taptree()?))?;
234+
let tree = Descriptor::new_tr(
235+
internal_key,
236+
match policy {
237+
Policy::Trivial => None,
238+
policy => Some(policy.compile_tr_policy()?),
239+
},
240+
)?;
214241
Ok(tree)
215242
}
216243

@@ -764,3 +791,34 @@ where
764791
Policy::from_tree_prob(top, false).map(|(_, result)| result)
765792
}
766793
}
794+
795+
/// Create a Huffman Tree from compiled [Miniscript] nodes
796+
#[cfg(feature = "compiler")]
797+
fn with_huffman_tree<Pk: MiniscriptKey>(
798+
ms: Vec<(OrdF64, Miniscript<Pk, Tap>)>,
799+
) -> Result<TapTree<Pk>, Error> {
800+
let mut node_weights = BinaryHeap::<(Reverse<OrdF64>, TapTree<Pk>)>::new();
801+
for (prob, script) in ms {
802+
node_weights.push((Reverse(prob), TapTree::Leaf(Arc::new(script))));
803+
}
804+
if node_weights.is_empty() {
805+
return Err(errstr("Empty Miniscript compilation"));
806+
}
807+
while node_weights.len() > 1 {
808+
let (p1, s1) = node_weights.pop().expect("len must atleast be two");
809+
let (p2, s2) = node_weights.pop().expect("len must atleast be two");
810+
811+
let p = (p1.0).0 + (p2.0).0;
812+
node_weights.push((
813+
Reverse(OrdF64(p)),
814+
TapTree::Tree(Arc::from(s1), Arc::from(s2)),
815+
));
816+
}
817+
818+
debug_assert!(node_weights.len() == 1);
819+
let node = node_weights
820+
.pop()
821+
.expect("huffman tree algorithm is broken")
822+
.1;
823+
Ok(node)
824+
}

src/policy/mod.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -371,15 +371,35 @@ mod tests {
371371

372372
#[test]
373373
#[cfg(feature = "compiler")]
374-
fn single_leaf_tr_compile() {
375-
let unspendable_key: String = "z".to_string();
376-
let policy: Concrete<String> = policy_str!("thresh(2,pk(A),pk(B),pk(C),pk(D))");
377-
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
374+
fn taproot_compile() {
375+
// Trivial single-node compilation
376+
let unspendable_key: String = "UNSPENDABLE".to_string();
377+
{
378+
let policy: Concrete<String> = policy_str!("thresh(2,pk(A),pk(B),pk(C),pk(D))");
379+
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
378380

379-
let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a(2,A,B,C,D)");
380-
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
381-
let expected_descriptor = Descriptor::new_tr(unspendable_key, Some(tree)).unwrap();
381+
let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a(2,A,B,C,D)");
382+
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
383+
let expected_descriptor =
384+
Descriptor::new_tr(unspendable_key.clone(), Some(tree)).unwrap();
385+
assert_eq!(descriptor, expected_descriptor);
386+
}
387+
388+
// Trivial multi-node compilation
389+
{
390+
let policy: Concrete<String> = policy_str!("or(and(pk(A),pk(B)),and(pk(C),pk(D)))");
391+
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
382392

383-
assert_eq!(descriptor, expected_descriptor);
393+
let left_ms_compilation: Arc<Miniscript<String, Tap>> =
394+
Arc::new(ms_str!("and_v(v:pk(C),pk(D))"));
395+
let right_ms_compilation: Arc<Miniscript<String, Tap>> =
396+
Arc::new(ms_str!("and_v(v:pk(A),pk(B))"));
397+
let left_node: Arc<TapTree<String>> = Arc::from(TapTree::Leaf(left_ms_compilation));
398+
let right_node: Arc<TapTree<String>> = Arc::from(TapTree::Leaf(right_ms_compilation));
399+
let tree: TapTree<String> = TapTree::Tree(left_node, right_node);
400+
let expected_descriptor =
401+
Descriptor::new_tr(unspendable_key.clone(), Some(tree)).unwrap();
402+
assert_eq!(descriptor, expected_descriptor);
403+
}
384404
}
385405
}

0 commit comments

Comments
 (0)