@@ -24,6 +24,7 @@ use bitcoin::hashes::{hash160, ripemd160};
2424#[ cfg( feature = "compiler" ) ]
2525use {
2626 crate :: descriptor:: TapTree ,
27+ crate :: miniscript:: limits:: MAX_TAPLEAFS_PER_TAPTREE ,
2728 crate :: miniscript:: ScriptContext ,
2829 crate :: policy:: compiler:: CompilerError ,
2930 crate :: policy:: compiler:: OrdF64 ,
@@ -186,9 +187,14 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
186187 /// B C
187188 ///
188189 /// gives the vector [(2/3, A), (1/3 * 3/4, B), (1/3 * 1/4, C)].
190+ ///
191+ /// ## Constraints
192+ ///
193+ /// Since this splitting might lead to exponential blow-up, we constraint the number of
194+ /// leaf-nodes to [`MAX_TAPLEAFS_PER_TAPTREE`].
189195 #[ cfg( feature = "compiler" ) ]
190196 fn to_tapleaf_prob_vec ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
191- match * self {
197+ match self {
192198 Policy :: Or ( ref subs) => {
193199 let total_odds: usize = subs. iter ( ) . map ( |( ref k, _) | k) . sum ( ) ;
194200 subs. iter ( )
@@ -198,17 +204,141 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
198204 . flatten ( )
199205 . collect :: < Vec < _ > > ( )
200206 }
201- Policy :: Threshold ( k, ref subs) if k == 1 => {
207+ Policy :: Threshold ( k, ref subs) if * k == 1 => {
202208 let total_odds = subs. len ( ) ;
203209 subs. iter ( )
204210 . map ( |policy| policy. to_tapleaf_prob_vec ( prob / total_odds as f64 ) )
205211 . flatten ( )
206212 . collect :: < Vec < _ > > ( )
207213 }
208- ref x => vec ! [ ( prob, x. clone( ) ) ] ,
214+ // Instead of the fixed-point algorithm, consider direct k-choose-n split if limits
215+ // don't exceed
216+ x => vec ! [ ( prob, x. clone( ) ) ] ,
217+ }
218+ }
219+
220+ /// Given a [`Policy`], return a vector of policies whose disjunction is isomorphic to the initial one.
221+ /// This function is supposed to incrementally expand i.e. represent the policy as disjunction over
222+ /// sub-policies output by it. The probability calculations are similar as
223+ /// [to_tapleaf_prob_vec][`Policy::to_tapleaf_prob_vec`]
224+ #[ cfg( feature = "compiler" ) ]
225+ fn enumerate_pol ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
226+ match self {
227+ Policy :: Or ( subs) => {
228+ let total_odds = subs. iter ( ) . fold ( 0 , |acc, x| acc + x. 0 ) ;
229+ subs. iter ( )
230+ . map ( |( odds, pol) | ( prob * * odds as f64 / total_odds as f64 , pol. clone ( ) ) )
231+ . collect :: < Vec < _ > > ( )
232+ }
233+ Policy :: Threshold ( k, subs) if * k == 1 => {
234+ let total_odds = subs. len ( ) ;
235+ subs. iter ( )
236+ . map ( |pol| ( prob / total_odds as f64 , pol. clone ( ) ) )
237+ . collect :: < Vec < _ > > ( )
238+ }
239+ Policy :: Threshold ( k, subs) if * k != subs. len ( ) => generate_combination ( subs, prob, * k) ,
240+ pol => vec ! [ ( prob, pol. clone( ) ) ] ,
209241 }
210242 }
211243
244+ /// Generates a root-level disjunctive tree over the given policy tree, by using fixed-point
245+ /// algorithm to enumerate the disjunctions until exhaustive root-level enumeration or limits
246+ /// exceed.
247+ /// For a given [policy][`Policy`], we maintain an [ordered set][`BTreeSet`] of `(prob, policy)`
248+ /// (ordered by probability) to maintain the list of enumerated sub-policies whose disjunction
249+ /// is isomorphic to initial policy (*invariant*).
250+ #[ cfg( feature = "compiler" ) ]
251+ fn enumerate_policy_tree ( & self , prob : f64 ) -> Vec < ( f64 , Self ) > {
252+ let mut tapleaf_prob_vec = BTreeSet :: < ( Reverse < OrdF64 > , Self ) > :: new ( ) ;
253+ // Store probability corresponding to policy in the enumerated tree. This is required since
254+ // owing to the current [policy element enumeration algorithm][`Policy::enumerate_pol`],
255+ // two passes of the algorithm might result in same sub-policy showing up. Currently, we
256+ // merge the nodes by adding up the corresponding probabilities for the same policy.
257+ let mut pol_prob_map = HashMap :: < Self , OrdF64 > :: new ( ) ;
258+
259+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( prob) ) , self . clone ( ) ) ) ;
260+ pol_prob_map. insert ( self . clone ( ) , OrdF64 ( prob) ) ;
261+
262+ // Since we know that policy enumeration *must* result in increase in total number of nodes,
263+ // we can maintain the length of the ordered set to check if the
264+ // [enumeration pass][`Policy::enumerate_pol`] results in further policy split or not.
265+ let mut prev_len = 0usize ;
266+ // This is required since we merge some corresponding policy nodes, so we can explicitly
267+ // store the variables
268+ let mut enum_len = tapleaf_prob_vec. len ( ) ;
269+
270+ let mut ret: Vec < ( f64 , Self ) > = vec ! [ ] ;
271+
272+ // Stopping condition: When NONE of the inputs can be further enumerated.
273+ ' outer: loop {
274+ //--- FIND a plausible node ---
275+
276+ let mut prob: Reverse < OrdF64 > = Reverse ( OrdF64 ( 0.0 ) ) ;
277+ let mut curr_policy: & Self = & Policy :: Unsatisfiable ;
278+ let mut curr_pol_replace_vec: Vec < ( f64 , Self ) > = vec ! [ ( prob. 0 . 0 , curr_policy. clone( ) ) ] ;
279+
280+ // The nodes which can't be enumerated further are directly appended to ret and removed
281+ // from the ordered set.
282+ let mut to_del: Vec < ( f64 , Self ) > = vec ! [ ] ;
283+ for ( i, ( p, pol) ) in tapleaf_prob_vec. iter ( ) . enumerate ( ) {
284+ curr_pol_replace_vec = pol. enumerate_pol ( p. 0 . 0 ) ;
285+ enum_len += curr_pol_replace_vec. len ( ) - 1 ; // A disjunctive node should have seperated this into more nodes
286+ if prev_len < enum_len {
287+ // Plausible node found
288+ prob = * p;
289+ curr_policy = pol;
290+ break ;
291+ } else if i == tapleaf_prob_vec. len ( ) - 1 {
292+ // No enumerable node found i.e. STOP
293+ // Move all the elements to final return set
294+ ret. append ( & mut to_del) ;
295+ ret. push ( ( p. 0 . 0 , pol. clone ( ) ) ) ;
296+ break ' outer;
297+ } else {
298+ // Either node is enumerable, or we have
299+ // Mark all non-enumerable nodes to remove
300+ to_del. push ( ( p. 0 . 0 , pol. clone ( ) ) ) ;
301+ }
302+ }
303+
304+ // --- Sanity Checks ---
305+ if enum_len > MAX_TAPLEAFS_PER_TAPTREE || curr_policy == & Policy :: Unsatisfiable {
306+ break ;
307+ }
308+
309+ // If total number of nodes are in limits, we remove the current node and replace it
310+ // with children nodes
311+
312+ // Remove current node
313+ tapleaf_prob_vec. remove ( & ( prob, curr_policy. clone ( ) ) ) ;
314+ // Remove marked nodes (minor optimization)
315+ for ( p, pol) in to_del {
316+ tapleaf_prob_vec. remove ( & ( Reverse ( OrdF64 ( p) ) , pol. clone ( ) ) ) ;
317+ ret. push ( ( p, pol. clone ( ) ) ) ;
318+ }
319+
320+ // Append node if not previously exists, else update the respective probability
321+ for ( p, policy) in curr_pol_replace_vec {
322+
323+ match pol_prob_map. get ( & policy) {
324+ Some ( prev_prob) => {
325+ tapleaf_prob_vec. remove ( & ( Reverse ( * prev_prob) , policy. clone ( ) ) ) ;
326+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( prev_prob. 0 + p) ) , policy. clone ( ) ) ) ;
327+ pol_prob_map. insert ( policy. clone ( ) , OrdF64 ( prev_prob. 0 + p) ) ;
328+ }
329+ None => {
330+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( p) ) , policy. clone ( ) ) ) ;
331+ pol_prob_map. insert ( policy. clone ( ) , OrdF64 ( p) ) ;
332+ }
333+ }
334+ }
335+ // --- Update --- total sub-policies count (considering no merging of nodes)
336+ prev_len = enum_len;
337+ }
338+
339+ ret
340+ }
341+
212342 /// Extract the internal_key from policy tree.
213343 #[ cfg( feature = "compiler" ) ]
214344 fn extract_key ( self , unspendable_key : Option < Pk > ) -> Result < ( Pk , Policy < Pk > ) , Error > {
@@ -305,6 +435,56 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
305435 }
306436 }
307437
438+ /// Compile the [`Policy`] into a [`Tr`][`Descriptor::Tr`] Descriptor, with policy-enumeration
439+ /// by [`Policy::enumerate_policy_tree`].
440+ ///
441+ /// ### TapTree compilation
442+ ///
443+ /// The policy tree constructed by root-level disjunctions over [`Or`][`Policy::Or`] and
444+ /// [`Thresh`][`Policy::Threshold`](k, ..n..) which is flattened into a vector (with respective
445+ /// probabilities derived from odds) of policies.
446+ /// For example, the policy `thresh(1,or(pk(A),pk(B)),and(or(pk(C),pk(D)),pk(E)))` gives the vector
447+ /// `[pk(A),pk(B),and(or(pk(C),pk(D)),pk(E)))]`.
448+ ///
449+ /// ### Policy enumeration
450+ ///
451+ /// Refer to [`Policy::enumerate_policy_tree`] for the current strategy implemented.
452+ #[ cfg( feature = "compiler" ) ]
453+ pub fn compile_tr_private_experimental (
454+ & self ,
455+ unspendable_key : Option < Pk > ,
456+ ) -> Result < Descriptor < Pk > , Error > {
457+ self . is_valid ( ) ?; // Check for validity
458+ match self . is_safe_nonmalleable ( ) {
459+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
460+ ( _, false ) => Err ( Error :: from (
461+ CompilerError :: ImpossibleNonMalleableCompilation ,
462+ ) ) ,
463+ _ => {
464+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
465+ let tree = Descriptor :: new_tr (
466+ internal_key,
467+ match policy {
468+ Policy :: Trivial => None ,
469+ policy => {
470+ let leaf_compilations: Vec < _ > = policy
471+ . enumerate_policy_tree ( 1.0 )
472+ . into_iter ( )
473+ . filter ( |x| x. 1 != Policy :: Unsatisfiable )
474+ . map ( |( prob, ref pol) | {
475+ ( OrdF64 ( prob) , compiler:: best_compilation ( pol) . unwrap ( ) )
476+ } )
477+ . collect ( ) ;
478+ let taptree = with_huffman_tree :: < Pk > ( leaf_compilations) . unwrap ( ) ;
479+ Some ( taptree)
480+ }
481+ } ,
482+ ) ?;
483+ Ok ( tree)
484+ }
485+ }
486+ }
487+
308488 /// Compile the [`Policy`] into desc_ctx [`Descriptor`]
309489 ///
310490 /// In case of [Tr][`DescriptorCtx::Tr`], `internal_key` is used for the Taproot comilation when
@@ -508,15 +688,17 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
508688 fn num_tap_leaves ( & self ) -> usize {
509689 match self {
510690 Policy :: Or ( subs) => subs. iter ( ) . map ( |( _prob, pol) | pol. num_tap_leaves ( ) ) . sum ( ) ,
511- Policy :: Threshold ( k, subs) if * k == 1 => combination ( subs. len ( ) , * k) ,
691+ Policy :: Threshold ( k, subs) if * k == 1 => {
692+ subs. iter ( ) . map ( |pol| pol. num_tap_leaves ( ) ) . sum ( )
693+ }
512694 _ => 1 ,
513695 }
514696 }
515697
516698 /// Check on the number of TapLeaves
517699 #[ cfg( feature = "compiler" ) ]
518- fn check_tapleaf ( & self ) -> Result < ( ) , Error > {
519- if self . num_tap_leaves ( ) > 100_000 {
700+ fn check_tapleaf ( & self ) -> Result < ( ) , Error > {
701+ if self . num_tap_leaves ( ) > MAX_TAPLEAFS_PER_TAPTREE {
520702 return Err ( errstr ( "Too many Tapleaves" ) ) ;
521703 }
522704 Ok ( ( ) )
@@ -939,12 +1121,32 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
9391121 . pop ( )
9401122 . expect ( "huffman tree algorithm is broken" )
9411123 . 1 ;
1124+ Ok ( node)
1125+ }
9421126
1127+ /// Enumerate a [Thresh][`Policy::Threshold`](k, ..n..) into `n` different thresh.
1128+ ///
1129+ /// ## Strategy
1130+ ///
1131+ /// `thresh(k, x_1...x_n) := thresh(1, thresh(k, x_2...x_n), thresh(k, x_1x_3...x_n), ...., thresh(k, x_1...x_{n-1}))`
1132+ /// by the simple argument that choosing `k` conditions from `n` available conditions might not contain
1133+ /// any one of the conditions exclusively.
9431134#[ cfg( feature = "compiler" ) ]
944- fn combination ( n : usize , k : usize ) -> usize {
945- if k == 0 {
946- 1
947- } else {
948- n * combination ( n - 1 , k - 1 ) / k
1135+ fn generate_combination < Pk : MiniscriptKey > (
1136+ policy_vec : & Vec < Policy < Pk > > ,
1137+ prob : f64 ,
1138+ k : usize ,
1139+ ) -> Vec < ( f64 , Policy < Pk > ) > {
1140+ debug_assert ! ( k <= policy_vec. len( ) ) ;
1141+
1142+ let mut ret: Vec < ( f64 , Policy < Pk > ) > = vec ! [ ] ;
1143+ for i in 0 ..policy_vec. len ( ) {
1144+ let mut policies: Vec < Policy < Pk > > = policy_vec. clone ( ) ;
1145+ policies. remove ( i) ;
1146+ ret. push ( (
1147+ prob / policy_vec. len ( ) as f64 ,
1148+ Policy :: < Pk > :: Threshold ( k, policies) ,
1149+ ) ) ;
9491150 }
1151+ ret
9501152}
0 commit comments