Skip to content

Commit 7efe5e0

Browse files
committed
Merge #291: Taproot Compiler
2b13694 Add non-trivial multi-node example (Aman Rojjha) 1c2a80e Add validity and malleability checks. (Aman Rojjha) 0866807 Add P2Tr compiler (Aman Rojjha) 285207e Internal-key extraction done (Aman Rojjha) 26fc574 Policy to single-leaf TapTree compilation done (Aman Rojjha) Pull request description: This PR builds on top of #278. This is one in a series of PRs aimed to implement a feature-complete *Pay-to-Taproot* **P2Tr** compiler. Specifically, this introduces a basic compilation for a given policy by collecting the leaf nodes of the *tree* generated by considering root-level disjunctive `OR`and using this to generate a `TapTree`. > Assuming that _duplicate keys_ are **NOT** possible even in different branches of the TapTree. # Follow Up PRs - #342 - Uses heuristic for tree-generation/ _merging_ algorithm while buillding `TapTree` to optimize over the *expected average total cost*. - #352 - A future PR implementing enumerative strategies for optimizing `thresh` `k`-of-`n` and similar structures. ACKs for top commit: apoelstra: ACK 2b13694 sanket1729: ACK 2b13694 . Let's push forward on this. In the interest of ACKs and multiple rebases already done here, I am merging this. We recently shifted to rust 2018. And this code has a bunch of warnings because of it. Tree-SHA512: 4ceca51a383f5d52b572a16937bbcc3a9c53f0247e4b6df57a6547fd0b1c7cc33ff04dd9a476914bcf6d0a09e255918b8f7ebfe176c839d6ae31c84613dce67f
2 parents 36c8569 + 2b13694 commit 7efe5e0

File tree

4 files changed

+349
-20
lines changed

4 files changed

+349
-20
lines changed

src/policy/compiler.rs

+27-11
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type PolicyCache<Pk, Ctx> =
3636

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

4141
impl Eq for OrdF64 {}
4242
impl Ord for OrdF64 {
@@ -1007,18 +1007,23 @@ where
10071007
})
10081008
.collect();
10091009

1010-
if key_vec.len() == subs.len() && subs.len() <= MAX_PUBKEYS_PER_MULTISIG {
1011-
insert_wrap!(AstElemExt::terminal(Terminal::Multi(k, key_vec)));
1012-
}
1013-
// Not a threshold, it's always more optimal to translate it to and()s as we save the
1014-
// resulting threshold check (N EQUAL) in any case.
1015-
else if k == subs.len() {
1016-
let mut policy = subs.first().expect("No sub policy in thresh() ?").clone();
1017-
for sub in &subs[1..] {
1018-
policy = Concrete::And(vec![sub.clone(), policy]);
1010+
match Ctx::sig_type() {
1011+
SigType::Schnorr if key_vec.len() == subs.len() => {
1012+
insert_wrap!(AstElemExt::terminal(Terminal::MultiA(k, key_vec)))
1013+
}
1014+
SigType::Ecdsa
1015+
if key_vec.len() == subs.len() && subs.len() <= MAX_PUBKEYS_PER_MULTISIG =>
1016+
{
1017+
insert_wrap!(AstElemExt::terminal(Terminal::Multi(k, key_vec)))
10191018
}
1019+
_ if k == subs.len() => {
1020+
let mut it = subs.iter();
1021+
let mut policy = it.next().expect("No sub policy in thresh() ?").clone();
1022+
policy = it.fold(policy, |acc, pol| Concrete::And(vec![acc, pol.clone()]));
10201023

1021-
ret = best_compilations(policy_cache, &policy, sat_prob, dissat_prob)?;
1024+
ret = best_compilations(policy_cache, &policy, sat_prob, dissat_prob)?;
1025+
}
1026+
_ => {}
10221027
}
10231028

10241029
// FIXME: Should we also optimize thresh(1, subs) ?
@@ -1569,6 +1574,17 @@ mod tests {
15691574
))
15701575
);
15711576
}
1577+
1578+
#[test]
1579+
fn compile_tr_thresh() {
1580+
for k in 1..4 {
1581+
let small_thresh: Concrete<String> =
1582+
policy_str!("{}", &format!("thresh({},pk(B),pk(C),pk(D))", k));
1583+
let small_thresh_ms: Miniscript<String, Tap> = small_thresh.compile().unwrap();
1584+
let small_thresh_ms_expected: Miniscript<String, Tap> = ms_str!("multi_a({},B,C,D)", k);
1585+
assert_eq!(small_thresh_ms, small_thresh_ms_expected);
1586+
}
1587+
}
15721588
}
15731589

15741590
#[cfg(all(test, feature = "unstable"))]

src/policy/concrete.rs

+200-8
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,27 @@ use std::{error, fmt, str};
2020

2121
use bitcoin::hashes::hex::FromHex;
2222
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
23+
#[cfg(feature = "compiler")]
24+
use {
25+
crate::descriptor::TapTree,
26+
crate::miniscript::ScriptContext,
27+
crate::policy::compiler::CompilerError,
28+
crate::policy::compiler::OrdF64,
29+
crate::policy::{compiler, Concrete, Liftable, Semantic},
30+
crate::Descriptor,
31+
crate::Miniscript,
32+
crate::Tap,
33+
std::cmp::Reverse,
34+
std::collections::{BinaryHeap, HashMap},
35+
std::sync::Arc,
36+
};
2337

2438
use super::ENTAILMENT_MAX_TERMINALS;
2539
use crate::expression::{self, FromTree};
2640
use crate::miniscript::limits::{HEIGHT_TIME_THRESHOLD, SEQUENCE_LOCKTIME_TYPE_FLAG};
2741
use crate::miniscript::types::extra_props::TimeLockInfo;
28-
#[cfg(feature = "compiler")]
29-
use crate::miniscript::ScriptContext;
30-
#[cfg(feature = "compiler")]
31-
use crate::policy::compiler;
32-
#[cfg(feature = "compiler")]
33-
use crate::policy::compiler::CompilerError;
34-
#[cfg(feature = "compiler")]
35-
use crate::Miniscript;
3642
use crate::{errstr, Error, ForEach, ForEachKey, MiniscriptKey};
43+
3744
/// Concrete policy which corresponds directly to a Miniscript structure,
3845
/// and whose disjunctions are annotated with satisfaction probabilities
3946
/// to assist the compiler
@@ -128,6 +135,136 @@ impl fmt::Display for PolicyError {
128135
}
129136

130137
impl<Pk: MiniscriptKey> Policy<Pk> {
138+
/// Flatten the [`Policy`] tree structure into a Vector of tuple `(leaf script, leaf probability)`
139+
/// with leaf probabilities corresponding to odds for sub-branch in the policy.
140+
/// We calculate the probability of selecting the sub-branch at every level and calculate the
141+
/// leaf probabilities as the probability of traversing through required branches to reach the
142+
/// leaf node, i.e. multiplication of the respective probabilities.
143+
///
144+
/// For example, the policy tree: OR
145+
/// / \
146+
/// 2 1 odds
147+
/// / \
148+
/// A OR
149+
/// / \
150+
/// 3 1 odds
151+
/// / \
152+
/// B C
153+
///
154+
/// gives the vector [(2/3, A), (1/3 * 3/4, B), (1/3 * 1/4, C)].
155+
#[cfg(feature = "compiler")]
156+
fn to_tapleaf_prob_vec(&self, prob: f64) -> Vec<(f64, Policy<Pk>)> {
157+
match *self {
158+
Policy::Or(ref subs) => {
159+
let total_odds: usize = subs.iter().map(|(ref k, _)| k).sum();
160+
subs.iter()
161+
.map(|(k, ref policy)| {
162+
policy.to_tapleaf_prob_vec(prob * *k as f64 / total_odds as f64)
163+
})
164+
.flatten()
165+
.collect::<Vec<_>>()
166+
}
167+
Policy::Threshold(k, ref subs) if k == 1 => {
168+
let total_odds = subs.len();
169+
subs.iter()
170+
.map(|policy| policy.to_tapleaf_prob_vec(prob / total_odds as f64))
171+
.flatten()
172+
.collect::<Vec<_>>()
173+
}
174+
ref x => vec![(prob, x.clone())],
175+
}
176+
}
177+
178+
/// Compile [`Policy::Or`] and [`Policy::Threshold`] according to odds
179+
#[cfg(feature = "compiler")]
180+
fn compile_tr_policy(&self) -> Result<TapTree<Pk>, Error> {
181+
let leaf_compilations: Vec<_> = self
182+
.to_tapleaf_prob_vec(1.0)
183+
.into_iter()
184+
.filter(|x| x.1 != Policy::Unsatisfiable)
185+
.map(|(prob, ref policy)| (OrdF64(prob), compiler::best_compilation(policy).unwrap()))
186+
.collect();
187+
let taptree = with_huffman_tree::<Pk>(leaf_compilations).unwrap();
188+
Ok(taptree)
189+
}
190+
191+
/// Extract the internal_key from policy tree.
192+
#[cfg(feature = "compiler")]
193+
fn extract_key(self, unspendable_key: Option<Pk>) -> Result<(Pk, Policy<Pk>), Error> {
194+
let mut internal_key: Option<Pk> = None;
195+
{
196+
let mut prob = 0.;
197+
let semantic_policy = self.lift()?;
198+
let concrete_keys = self.keys();
199+
let key_prob_map: HashMap<_, _> = self
200+
.to_tapleaf_prob_vec(1.0)
201+
.into_iter()
202+
.filter(|(_, ref pol)| match *pol {
203+
Concrete::Key(..) => true,
204+
_ => false,
205+
})
206+
.map(|(prob, key)| (key, prob))
207+
.collect();
208+
209+
for key in concrete_keys.into_iter() {
210+
if semantic_policy
211+
.clone()
212+
.satisfy_constraint(&Semantic::KeyHash(key.to_pubkeyhash()), true)
213+
== Semantic::Trivial
214+
{
215+
match key_prob_map.get(&Concrete::Key(key.clone())) {
216+
Some(val) => {
217+
if *val > prob {
218+
prob = *val;
219+
internal_key = Some(key.clone());
220+
}
221+
}
222+
None => return Err(errstr("Key should have existed in the HashMap!")),
223+
}
224+
}
225+
}
226+
}
227+
match (internal_key, unspendable_key) {
228+
(Some(ref key), _) => Ok((key.clone(), self.translate_unsatisfiable_pk(&key))),
229+
(_, Some(key)) => Ok((key, self)),
230+
_ => Err(errstr("No viable internal key found.")),
231+
}
232+
}
233+
234+
/// Compile the [`Policy`] into a [`Tr`][`Descriptor::Tr`] Descriptor.
235+
///
236+
/// ### TapTree compilation
237+
///
238+
/// The policy tree constructed by root-level disjunctions over [`Or`][`Policy::Or`] and
239+
/// [`Thresh`][`Policy::Threshold`](1, ..) which is flattened into a vector (with respective
240+
/// probabilities derived from odds) of policies.
241+
/// For example, the policy `thresh(1,or(pk(A),pk(B)),and(or(pk(C),pk(D)),pk(E)))` gives the vector
242+
/// `[pk(A),pk(B),and(or(pk(C),pk(D)),pk(E)))]`. Each policy in the vector is compiled into
243+
/// the respective miniscripts. A Huffman Tree is created from this vector which optimizes over
244+
/// the probabilitity of satisfaction for the respective branch in the TapTree.
245+
// TODO: We might require other compile errors for Taproot.
246+
#[cfg(feature = "compiler")]
247+
pub fn compile_tr(&self, unspendable_key: Option<Pk>) -> Result<Descriptor<Pk>, Error> {
248+
self.is_valid()?; // Check for validity
249+
match self.is_safe_nonmalleable() {
250+
(false, _) => Err(Error::from(CompilerError::TopLevelNonSafe)),
251+
(_, false) => Err(Error::from(
252+
CompilerError::ImpossibleNonMalleableCompilation,
253+
)),
254+
_ => {
255+
let (internal_key, policy) = self.clone().extract_key(unspendable_key)?;
256+
let tree = Descriptor::new_tr(
257+
internal_key,
258+
match policy {
259+
Policy::Trivial => None,
260+
policy => Some(policy.compile_tr_policy()?),
261+
},
262+
)?;
263+
Ok(tree)
264+
}
265+
}
266+
}
267+
131268
/// Compile the descriptor into an optimized `Miniscript` representation
132269
#[cfg(feature = "compiler")]
133270
pub fn compile<Ctx: ScriptContext>(&self) -> Result<Miniscript<Pk, Ctx>, CompilerError> {
@@ -226,6 +363,30 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
226363
}
227364
}
228365

366+
/// Translate `Concrete::Key(key)` to `Concrete::Unsatisfiable` when extracting TapKey
367+
pub fn translate_unsatisfiable_pk(self, key: &Pk) -> Policy<Pk> {
368+
match self {
369+
Policy::Key(ref k) if k.clone() == *key => Policy::Unsatisfiable,
370+
Policy::And(subs) => Policy::And(
371+
subs.into_iter()
372+
.map(|sub| sub.translate_unsatisfiable_pk(key))
373+
.collect::<Vec<_>>(),
374+
),
375+
Policy::Or(subs) => Policy::Or(
376+
subs.into_iter()
377+
.map(|(k, sub)| (k, sub.translate_unsatisfiable_pk(key)))
378+
.collect::<Vec<_>>(),
379+
),
380+
Policy::Threshold(k, subs) => Policy::Threshold(
381+
k,
382+
subs.into_iter()
383+
.map(|sub| sub.translate_unsatisfiable_pk(key))
384+
.collect::<Vec<_>>(),
385+
),
386+
x => x,
387+
}
388+
}
389+
229390
/// Get all keys in the policy
230391
pub fn keys(&self) -> Vec<&Pk> {
231392
match *self {
@@ -645,3 +806,34 @@ where
645806
Policy::from_tree_prob(top, false).map(|(_, result)| result)
646807
}
647808
}
809+
810+
/// Create a Huffman Tree from compiled [Miniscript] nodes
811+
#[cfg(feature = "compiler")]
812+
fn with_huffman_tree<Pk: MiniscriptKey>(
813+
ms: Vec<(OrdF64, Miniscript<Pk, Tap>)>,
814+
) -> Result<TapTree<Pk>, Error> {
815+
let mut node_weights = BinaryHeap::<(Reverse<OrdF64>, TapTree<Pk>)>::new();
816+
for (prob, script) in ms {
817+
node_weights.push((Reverse(prob), TapTree::Leaf(Arc::new(script))));
818+
}
819+
if node_weights.is_empty() {
820+
return Err(errstr("Empty Miniscript compilation"));
821+
}
822+
while node_weights.len() > 1 {
823+
let (p1, s1) = node_weights.pop().expect("len must atleast be two");
824+
let (p2, s2) = node_weights.pop().expect("len must atleast be two");
825+
826+
let p = (p1.0).0 + (p2.0).0;
827+
node_weights.push((
828+
Reverse(OrdF64(p)),
829+
TapTree::Tree(Arc::from(s1), Arc::from(s2)),
830+
));
831+
}
832+
833+
debug_assert!(node_weights.len() == 1);
834+
let node = node_weights
835+
.pop()
836+
.expect("huffman tree algorithm is broken")
837+
.1;
838+
Ok(node)
839+
}

0 commit comments

Comments
 (0)