@@ -187,9 +187,14 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
187187 /// B C
188188 ///
189189 /// 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_COMPILATION_LEAVES`].
190195 #[ cfg( feature = "compiler" ) ]
191196 fn to_tapleaf_prob_vec ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
192- match * self {
197+ match self {
193198 Policy :: Or ( ref subs) => {
194199 let total_odds: usize = subs. iter ( ) . map ( |( ref k, _) | k) . sum ( ) ;
195200 subs. iter ( )
@@ -199,17 +204,138 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
199204 . flatten ( )
200205 . collect :: < Vec < _ > > ( )
201206 }
202- Policy :: Threshold ( k, ref subs) if k == 1 => {
207+ Policy :: Threshold ( k, ref subs) if * k == 1 => {
203208 let total_odds = subs. len ( ) ;
204209 subs. iter ( )
205210 . map ( |policy| policy. to_tapleaf_prob_vec ( prob / total_odds as f64 ) )
206211 . flatten ( )
207212 . collect :: < Vec < _ > > ( )
208213 }
209- ref x => vec ! [ ( prob, x. clone( ) ) ] ,
214+ x => vec ! [ ( prob, x. clone( ) ) ] ,
215+ }
216+ }
217+
218+ /// Given a [`Policy`], return a vector of policies whose disjunction is isomorphic to the initial one.
219+ /// This function is supposed to incrementally expand i.e. represent the policy as disjunction over
220+ /// sub-policies output by it. The probability calculations are similar as
221+ /// [to_tapleaf_prob_vec][`Policy::to_tapleaf_prob_vec`]
222+ #[ cfg( feature = "compiler" ) ]
223+ fn enumerate_pol ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
224+ match self {
225+ Policy :: Or ( subs) => {
226+ let total_odds = subs. iter ( ) . fold ( 0 , |acc, x| acc + x. 0 ) ;
227+ subs. iter ( )
228+ . map ( |( odds, pol) | ( prob * * odds as f64 / total_odds as f64 , pol. clone ( ) ) )
229+ . collect :: < Vec < _ > > ( )
230+ }
231+ Policy :: Threshold ( k, subs) if * k == 1 => {
232+ let total_odds = subs. len ( ) ;
233+ subs. iter ( )
234+ . map ( |pol| ( prob / total_odds as f64 , pol. clone ( ) ) )
235+ . collect :: < Vec < _ > > ( )
236+ }
237+ Policy :: Threshold ( k, subs) if * k != subs. len ( ) => generate_combination ( subs, prob, * k) ,
238+ pol => vec ! [ ( prob, pol. clone( ) ) ] ,
210239 }
211240 }
212241
242+ /// Generates a root-level disjunctive tree over the given policy tree, by using fixed-point
243+ /// algorithm to enumerate the disjunctions until exhaustive root-level enumeration or limits
244+ /// exceed.
245+ /// For a given [policy][`Policy`], we maintain an [ordered set][`BTreeSet`] of `(prob, policy)`
246+ /// (ordered by probability) to maintain the list of enumerated sub-policies whose disjunction
247+ /// is isomorphic to initial policy (*invariant*).
248+ #[ cfg( feature = "compiler" ) ]
249+ fn enumerate_policy_tree ( & self , prob : f64 ) -> Vec < ( f64 , Self ) > {
250+ let mut tapleaf_prob_vec = BTreeSet :: < ( Reverse < OrdF64 > , Self ) > :: new ( ) ;
251+ // Store probability corresponding to policy in the enumerated tree. This is required since
252+ // owing to the current [policy element enumeration algorithm][`Policy::enumerate_pol`],
253+ // two passes of the algorithm might result in same sub-policy showing up. Currently, we
254+ // merge the nodes by adding up the corresponding probabilities for the same policy.
255+ let mut pol_prob_map = HashMap :: < Self , OrdF64 > :: new ( ) ;
256+
257+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( prob) ) , self . clone ( ) ) ) ;
258+ pol_prob_map. insert ( self . clone ( ) , OrdF64 ( prob) ) ;
259+
260+ // Since we know that policy enumeration *must* result in increase in total number of nodes,
261+ // we can maintain the length of the ordered set to check if the
262+ // [enumeration pass][`Policy::enumerate_pol`] results in further policy split or not.
263+ let mut prev_len = 0usize ;
264+ // This is required since we merge some corresponding policy nodes, so we can explicitly
265+ // store the variables
266+ let mut enum_len = tapleaf_prob_vec. len ( ) ;
267+
268+ let mut ret: Vec < ( f64 , Self ) > = vec ! [ ] ;
269+
270+ // Stopping condition: When NONE of the inputs can be further enumerated.
271+ ' outer: loop {
272+ //--- FIND a plausible node ---
273+
274+ let mut prob: Reverse < OrdF64 > = Reverse ( OrdF64 ( 0.0 ) ) ;
275+ let mut curr_policy: & Self = & Policy :: Unsatisfiable ;
276+ let mut curr_pol_replace_vec: Vec < ( f64 , Self ) > = vec ! [ ( prob. 0 . 0 , curr_policy. clone( ) ) ] ;
277+
278+ // The nodes which can't be enumerated further are directly appended to ret and removed
279+ // from the ordered set.
280+ let mut to_del: Vec < ( f64 , Self ) > = vec ! [ ] ;
281+ for ( i, ( p, pol) ) in tapleaf_prob_vec. iter ( ) . enumerate ( ) {
282+ curr_pol_replace_vec = pol. enumerate_pol ( p. 0 . 0 ) ;
283+ enum_len += curr_pol_replace_vec. len ( ) - 1 ; // A disjunctive node should have seperated this into more nodes
284+ if prev_len < enum_len {
285+ // Plausible node found
286+ prob = * p;
287+ curr_policy = pol;
288+ break ;
289+ } else if i == tapleaf_prob_vec. len ( ) - 1 {
290+ // No enumerable node found i.e. STOP
291+ // Move all the elements to final return set
292+ ret. append ( & mut to_del) ;
293+ ret. push ( ( p. 0 . 0 , pol. clone ( ) ) ) ;
294+ break ' outer;
295+ } else {
296+ // Either node is enumerable, or we have
297+ // Mark all non-enumerable nodes to remove
298+ to_del. push ( ( p. 0 . 0 , pol. clone ( ) ) ) ;
299+ }
300+ }
301+
302+ // --- Sanity Checks ---
303+ if enum_len > MAX_COMPILATION_LEAVES || curr_policy == & Policy :: Unsatisfiable {
304+ break ;
305+ }
306+
307+ // If total number of nodes are in limits, we remove the current node and replace it
308+ // with children nodes
309+
310+ // Remove current node
311+ tapleaf_prob_vec. remove ( & ( prob, curr_policy. clone ( ) ) ) ;
312+ // Remove marked nodes (minor optimization)
313+ for ( p, pol) in to_del {
314+ tapleaf_prob_vec. remove ( & ( Reverse ( OrdF64 ( p) ) , pol. clone ( ) ) ) ;
315+ ret. push ( ( p, pol. clone ( ) ) ) ;
316+ }
317+
318+ // Append node if not previously exists, else update the respective probability
319+ for ( p, policy) in curr_pol_replace_vec {
320+ match pol_prob_map. get ( & policy) {
321+ Some ( prev_prob) => {
322+ tapleaf_prob_vec. remove ( & ( Reverse ( * prev_prob) , policy. clone ( ) ) ) ;
323+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( prev_prob. 0 + p) ) , policy. clone ( ) ) ) ;
324+ pol_prob_map. insert ( policy. clone ( ) , OrdF64 ( prev_prob. 0 + p) ) ;
325+ }
326+ None => {
327+ tapleaf_prob_vec. insert ( ( Reverse ( OrdF64 ( p) ) , policy. clone ( ) ) ) ;
328+ pol_prob_map. insert ( policy. clone ( ) , OrdF64 ( p) ) ;
329+ }
330+ }
331+ }
332+ // --- Update --- total sub-policies count (considering no merging of nodes)
333+ prev_len = enum_len;
334+ }
335+
336+ ret
337+ }
338+
213339 /// Extract the internal_key from policy tree.
214340 #[ cfg( feature = "compiler" ) ]
215341 fn extract_key ( self , unspendable_key : Option < Pk > ) -> Result < ( Pk , Policy < Pk > ) , Error > {
@@ -306,6 +432,56 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
306432 }
307433 }
308434
435+ /// Compile the [`Policy`] into a [`Tr`][`Descriptor::Tr`] Descriptor, with policy-enumeration
436+ /// by [`Policy::enumerate_policy_tree`].
437+ ///
438+ /// ### TapTree compilation
439+ ///
440+ /// The policy tree constructed by root-level disjunctions over [`Or`][`Policy::Or`] and
441+ /// [`Thresh`][`Policy::Threshold`](k, ..n..) which is flattened into a vector (with respective
442+ /// probabilities derived from odds) of policies.
443+ /// For example, the policy `thresh(1,or(pk(A),pk(B)),and(or(pk(C),pk(D)),pk(E)))` gives the vector
444+ /// `[pk(A),pk(B),and(or(pk(C),pk(D)),pk(E)))]`.
445+ ///
446+ /// ### Policy enumeration
447+ ///
448+ /// Refer to [`Policy::enumerate_policy_tree`] for the current strategy implemented.
449+ #[ cfg( feature = "compiler" ) ]
450+ pub fn compile_tr_private_experimental (
451+ & self ,
452+ unspendable_key : Option < Pk > ,
453+ ) -> Result < Descriptor < Pk > , Error > {
454+ self . is_valid ( ) ?; // Check for validity
455+ match self . is_safe_nonmalleable ( ) {
456+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
457+ ( _, false ) => Err ( Error :: from (
458+ CompilerError :: ImpossibleNonMalleableCompilation ,
459+ ) ) ,
460+ _ => {
461+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
462+ let tree = Descriptor :: new_tr (
463+ internal_key,
464+ match policy {
465+ Policy :: Trivial => None ,
466+ policy => {
467+ let leaf_compilations: Vec < _ > = policy
468+ . enumerate_policy_tree ( 1.0 )
469+ . into_iter ( )
470+ . filter ( |x| x. 1 != Policy :: Unsatisfiable )
471+ . map ( |( prob, ref pol) | {
472+ ( OrdF64 ( prob) , compiler:: best_compilation ( pol) . unwrap ( ) )
473+ } )
474+ . collect ( ) ;
475+ let taptree = with_huffman_tree :: < Pk > ( leaf_compilations) . unwrap ( ) ;
476+ Some ( taptree)
477+ }
478+ } ,
479+ ) ?;
480+ Ok ( tree)
481+ }
482+ }
483+ }
484+
309485 /// Compile the [`Policy`] into desc_ctx [`Descriptor`]
310486 ///
311487 /// In case of [Tr][`DescriptorCtx::Tr`], `internal_key` is used for the Taproot comilation when
@@ -944,3 +1120,30 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
9441120 . 1 ;
9451121 Ok ( node)
9461122}
1123+
1124+ /// Enumerate a [Thresh][`Policy::Threshold`](k, ..n..) into `n` different thresh.
1125+ ///
1126+ /// ## Strategy
1127+ ///
1128+ /// `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}))`
1129+ /// by the simple argument that choosing `k` conditions from `n` available conditions might not contain
1130+ /// any one of the conditions exclusively.
1131+ #[ cfg( feature = "compiler" ) ]
1132+ fn generate_combination < Pk : MiniscriptKey > (
1133+ policy_vec : & Vec < Policy < Pk > > ,
1134+ prob : f64 ,
1135+ k : usize ,
1136+ ) -> Vec < ( f64 , Policy < Pk > ) > {
1137+ debug_assert ! ( k <= policy_vec. len( ) ) ;
1138+
1139+ let mut ret: Vec < ( f64 , Policy < Pk > ) > = vec ! [ ] ;
1140+ for i in 0 ..policy_vec. len ( ) {
1141+ let mut policies: Vec < Policy < Pk > > = policy_vec. clone ( ) ;
1142+ policies. remove ( i) ;
1143+ ret. push ( (
1144+ prob / policy_vec. len ( ) as f64 ,
1145+ Policy :: < Pk > :: Threshold ( k, policies) ,
1146+ ) ) ;
1147+ }
1148+ ret
1149+ }
0 commit comments