Skip to content

Commit 9a360e6

Browse files
Add P2Tr compiler
Introduce a `compiler_tr` API for compiling a policy to a Tr Descriptor.
1 parent 872e06b commit 9a360e6

File tree

3 files changed

+117
-37
lines changed

3 files changed

+117
-37
lines changed

src/policy/compiler.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type PolicyCache<Pk, Ctx> =
3838

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

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

src/policy/concrete.rs

+65-8
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ use miniscript::types::extra_props::TimeLockInfo;
2929
use {
3030
descriptor::TapTree,
3131
miniscript::ScriptContext,
32-
policy::compiler::CompilerError,
32+
policy::compiler::{CompilerError, OrdF64},
3333
policy::Concrete,
3434
policy::{compiler, Liftable, Semantic},
35-
std::collections::HashMap,
35+
std::cmp::Reverse,
36+
std::collections::{BinaryHeap, HashMap},
3637
std::sync::Arc,
3738
Descriptor, Miniscript, Tap,
3839
};
@@ -156,15 +157,23 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
156157
}
157158
}
158159

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

165-
/// Extract the Taproot internal_key from policy tree.
173+
/// Extract the internal_key from policy tree.
166174
#[cfg(feature = "compiler")]
167175
fn extract_key(self, unspendable_key: Option<Pk>) -> Result<(Pk, Policy<Pk>), Error> {
176+
// Making sure the borrow ends before you move the value.
168177
let mut internal_key: Option<Pk> = None;
169178
{
170179
let mut prob = 0.;
@@ -205,11 +214,28 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
205214
}
206215
}
207216

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

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

src/policy/mod.rs

+51-28
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,26 @@
2121
//! The format represents EC public keys abstractly to allow wallets to replace
2222
//! these with BIP32 paths, pay-to-contract instructions, etc.
2323
//!
24-
use {error, fmt};
25-
26-
#[cfg(feature = "compiler")]
27-
pub mod compiler;
28-
pub mod concrete;
29-
pub mod semantic;
30-
3124
use descriptor::Descriptor;
3225
use miniscript::{Miniscript, ScriptContext};
26+
use Error;
27+
use MiniscriptKey;
3328
use Terminal;
29+
use {error, fmt};
3430

3531
pub use self::concrete::Policy as Concrete;
3632
/// Semantic policies are "abstract" policies elsewhere; but we
3733
/// avoid this word because it is a reserved keyword in Rust
3834
pub use self::semantic::Policy as Semantic;
39-
use Error;
40-
use MiniscriptKey;
35+
36+
#[cfg(feature = "compiler")]
37+
pub mod compiler;
38+
pub mod concrete;
39+
pub mod semantic;
4140

4241
/// Policy entailment algorithm maximum number of terminals allowed
4342
const ENTAILMENT_MAX_TERMINALS: usize = 20;
43+
4444
/// Trait describing script representations which can be lifted into
4545
/// an abstract policy, by discarding information.
4646
/// After Lifting all policies are converted into `KeyHash(Pk::HasH)` to
@@ -228,20 +228,23 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Concrete<Pk> {
228228

229229
#[cfg(test)]
230230
mod tests {
231-
use super::{
232-
super::miniscript::{context::Segwitv0, Miniscript},
233-
Concrete, Liftable, Semantic,
234-
};
235-
use bitcoin;
236-
#[cfg(feature = "compiler")]
237-
use descriptor::TapTree;
238231
use std::str::FromStr;
239232
#[cfg(feature = "compiler")]
240233
use std::sync::Arc;
234+
235+
use bitcoin;
236+
237+
#[cfg(feature = "compiler")]
238+
use descriptor::TapTree;
241239
use DummyKey;
242240
#[cfg(feature = "compiler")]
243241
use {Descriptor, Tap};
244242

243+
use super::{
244+
super::miniscript::{context::Segwitv0, Miniscript},
245+
Concrete, Liftable, Semantic,
246+
};
247+
245248
type ConcretePol = Concrete<DummyKey>;
246249
type SemanticPol = Semantic<DummyKey>;
247250

@@ -361,27 +364,47 @@ mod tests {
361364
2,
362365
vec![
363366
Semantic::KeyHash(key_a.pubkey_hash().as_hash()),
364-
Semantic::Older(42)
365-
]
367+
Semantic::Older(42),
368+
],
366369
),
367-
Semantic::KeyHash(key_b.pubkey_hash().as_hash())
368-
]
370+
Semantic::KeyHash(key_b.pubkey_hash().as_hash()),
371+
],
369372
),
370373
ms_str.lift().unwrap()
371374
);
372375
}
373376

374377
#[test]
375378
#[cfg(feature = "compiler")]
376-
fn single_leaf_tr_compile() {
377-
let unspendable_key: String = "z".to_string();
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();
379+
fn taproot_compile() {
380+
// Trivial single-node compilation
381+
let unspendable_key: String = "UNSPENDABLE".to_string();
382+
{
383+
let policy: Concrete<String> = policy_str!("thresh(2,pk(A),pk(B),pk(C),pk(D))");
384+
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
385+
386+
let ms_compilation: Miniscript<String, Tap> = ms_str!("multi_a(2,A,B,C,D)");
387+
let tree: TapTree<String> = TapTree::Leaf(Arc::new(ms_compilation));
388+
let expected_descriptor =
389+
Descriptor::new_tr(unspendable_key.clone(), Some(tree)).unwrap();
390+
assert_eq!(descriptor, expected_descriptor);
391+
}
380392

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 = Descriptor::new_tr(unspendable_key, Some(tree)).unwrap();
393+
// Trivial multi-node compilation
394+
{
395+
let policy: Concrete<String> = policy_str!("or(and(pk(A),pk(B)),and(pk(C),pk(D)))");
396+
let descriptor = policy.compile_tr(Some(unspendable_key.clone())).unwrap();
384397

385-
assert_eq!(descriptor, expected_descriptor);
398+
let left_ms_compilation: Arc<Miniscript<String, Tap>> =
399+
Arc::new(ms_str!("and_v(v:pk(C),pk(D))"));
400+
let right_ms_compilation: Arc<Miniscript<String, Tap>> =
401+
Arc::new(ms_str!("and_v(v:pk(A),pk(B))"));
402+
let left_node: Arc<TapTree<String>> = Arc::from(TapTree::Leaf(left_ms_compilation));
403+
let right_node: Arc<TapTree<String>> = Arc::from(TapTree::Leaf(right_ms_compilation));
404+
let tree: TapTree<String> = TapTree::Tree(left_node, right_node);
405+
let expected_descriptor =
406+
Descriptor::new_tr(unspendable_key.clone(), Some(tree)).unwrap();
407+
assert_eq!(descriptor, expected_descriptor);
408+
}
386409
}
387410
}

0 commit comments

Comments
 (0)