@@ -24,6 +24,7 @@ use bitcoin::hashes::{hash160, ripemd160};
24
24
#[ cfg( feature = "compiler" ) ]
25
25
use {
26
26
crate :: descriptor:: TapTree ,
27
+ crate :: miniscript:: limits:: MAX_TAPLEAFS_PER_TAPTREE ,
27
28
crate :: miniscript:: ScriptContext ,
28
29
crate :: policy:: compiler:: CompilerError ,
29
30
crate :: policy:: compiler:: OrdF64 ,
@@ -186,9 +187,14 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
186
187
/// B C
187
188
///
188
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_TAPLEAFS_PER_TAPTREE`].
189
195
#[ cfg( feature = "compiler" ) ]
190
196
fn to_tapleaf_prob_vec ( & self , prob : f64 ) -> Vec < ( f64 , Policy < Pk > ) > {
191
- match * self {
197
+ match self {
192
198
Policy :: Or ( ref subs) => {
193
199
let total_odds: usize = subs. iter ( ) . map ( |( ref k, _) | k) . sum ( ) ;
194
200
subs. iter ( )
@@ -198,17 +204,141 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
198
204
. flatten ( )
199
205
. collect :: < Vec < _ > > ( )
200
206
}
201
- Policy :: Threshold ( k, ref subs) if k == 1 => {
207
+ Policy :: Threshold ( k, ref subs) if * k == 1 => {
202
208
let total_odds = subs. len ( ) ;
203
209
subs. iter ( )
204
210
. map ( |policy| policy. to_tapleaf_prob_vec ( prob / total_odds as f64 ) )
205
211
. flatten ( )
206
212
. collect :: < Vec < _ > > ( )
207
213
}
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( ) ) ] ,
209
241
}
210
242
}
211
243
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
+
212
342
/// Extract the internal_key from policy tree.
213
343
#[ cfg( feature = "compiler" ) ]
214
344
fn extract_key ( self , unspendable_key : Option < Pk > ) -> Result < ( Pk , Policy < Pk > ) , Error > {
@@ -305,6 +435,56 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
305
435
}
306
436
}
307
437
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
+
308
488
/// Compile the [`Policy`] into desc_ctx [`Descriptor`]
309
489
///
310
490
/// In case of [Tr][`DescriptorCtx::Tr`], `internal_key` is used for the Taproot comilation when
@@ -508,15 +688,17 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
508
688
fn num_tap_leaves ( & self ) -> usize {
509
689
match self {
510
690
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
+ }
512
694
_ => 1 ,
513
695
}
514
696
}
515
697
516
698
/// Check on the number of TapLeaves
517
699
#[ 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 {
520
702
return Err ( errstr ( "Too many Tapleaves" ) ) ;
521
703
}
522
704
Ok ( ( ) )
@@ -939,12 +1121,32 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
939
1121
. pop ( )
940
1122
. expect ( "huffman tree algorithm is broken" )
941
1123
. 1 ;
1124
+ Ok ( node)
1125
+ }
942
1126
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.
943
1134
#[ 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
+ ) ) ;
949
1150
}
1151
+ ret
950
1152
}
0 commit comments