@@ -187,9 +187,14 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
187
187
/// B C
188
188
///
189
189
/// 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`].
190
195
#[ cfg( feature = "compiler" ) ]
191
196
fn to_tapleaf_prob_vec ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
192
- match * self {
197
+ match self {
193
198
Policy :: Or ( ref subs) => {
194
199
let total_odds: usize = subs. iter ( ) . map ( |( ref k, _) | k) . sum ( ) ;
195
200
subs. iter ( )
@@ -199,17 +204,138 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
199
204
. flatten ( )
200
205
. collect :: < Vec < _ > > ( )
201
206
}
202
- Policy :: Threshold ( k, ref subs) if k == 1 => {
207
+ Policy :: Threshold ( k, ref subs) if * k == 1 => {
203
208
let total_odds = subs. len ( ) ;
204
209
subs. iter ( )
205
210
. map ( |policy| policy. to_tapleaf_prob_vec ( prob / total_odds as f64 ) )
206
211
. flatten ( )
207
212
. collect :: < Vec < _ > > ( )
208
213
}
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( ) ) ] ,
210
239
}
211
240
}
212
241
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
+
213
339
/// Extract the internal_key from policy tree.
214
340
#[ cfg( feature = "compiler" ) ]
215
341
fn extract_key ( self , unspendable_key : Option < Pk > ) -> Result < ( Pk , Policy < Pk > ) , Error > {
@@ -306,6 +432,56 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
306
432
}
307
433
}
308
434
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
+
309
485
/// Compile the [`Policy`] into desc_ctx [`Descriptor`]
310
486
///
311
487
/// 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>(
944
1120
. 1 ;
945
1121
Ok ( node)
946
1122
}
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