@@ -42,6 +42,14 @@ use crate::miniscript::types::extra_props::TimelockInfo;
42
42
use crate :: prelude:: * ;
43
43
use crate :: { errstr, Error , ForEach , ForEachKey , MiniscriptKey } ;
44
44
45
+ /// [`TapTree`] -> ([`Policy`], satisfaction cost) cache
46
+ #[ cfg( feature = "compiler" ) ]
47
+ type PolicyTapCache < Pk > = BTreeMap < TapTree < Pk > , ( Policy < Pk > , f64 ) > ;
48
+
49
+ /// [`Miniscript`] -> leaf probability in policy cache
50
+ #[ cfg( feature = "compiler" ) ]
51
+ type MsTapCache < Pk > = BTreeMap < TapTree < Pk > , f64 > ;
52
+
45
53
/// Concrete policy which corresponds directly to a Miniscript structure,
46
54
/// and whose disjunctions are annotated with satisfaction probabilities
47
55
/// to assist the compiler
@@ -285,6 +293,69 @@ impl<Pk: MiniscriptKey> Policy<Pk> {
285
293
}
286
294
}
287
295
296
+ /// Compile [`Policy`] into a [`TapTree Descriptor`][`Descriptor::Tr`]
297
+ ///
298
+ ///
299
+ /// This follows the heuristic as described in [`with_huffman_tree_eff`]
300
+ #[ cfg( feature = "compiler" ) ]
301
+ pub fn compile_tr ( & self , unspendable_key : Option < Pk > ) -> Result < Descriptor < Pk > , Error > {
302
+ self . is_valid ( ) ?; // Check for validity
303
+ match self . is_safe_nonmalleable ( ) {
304
+ ( false , _) => Err ( Error :: from ( CompilerError :: TopLevelNonSafe ) ) ,
305
+ ( _, false ) => Err ( Error :: from (
306
+ CompilerError :: ImpossibleNonMalleableCompilation ,
307
+ ) ) ,
308
+ _ => {
309
+ let ( internal_key, policy) = self . clone ( ) . extract_key ( unspendable_key) ?;
310
+ let tree = Descriptor :: new_tr (
311
+ internal_key,
312
+ match policy {
313
+ Policy :: Trivial => None ,
314
+ policy => {
315
+ let mut policy_cache = PolicyTapCache :: < Pk > :: new ( ) ;
316
+ let mut ms_cache = MsTapCache :: < Pk > :: new ( ) ;
317
+ // Obtain the policy compilations and populate the respective caches for
318
+ // creating the huffman tree later on
319
+ let vec_policies: Vec < _ > = policy. to_tapleaf_prob_vec ( 1.0 ) ;
320
+ let mut leaf_compilations: Vec < Arc < Miniscript < Pk , Tap > > > = vec ! [ ] ;
321
+ for ( prob, pol) in vec_policies {
322
+ // policy corresponding to the key (replaced by unsatisfiable) is skipped
323
+ if pol == Policy :: Unsatisfiable {
324
+ continue ;
325
+ }
326
+ let compilation = compiler:: best_compilation_sat :: < Pk , Tap > ( & pol) ?;
327
+ compilation. 0 . sanity_check ( ) ?;
328
+ let leaf_comp = TapTree :: Leaf ( compilation. 0 . clone ( ) ) ;
329
+ policy_cache. insert (
330
+ TapTree :: Leaf ( Arc :: clone ( & compilation. 0 ) ) ,
331
+ ( pol. clone ( ) , compilation. 1 ) , // (policy, sat_cost)
332
+ ) ;
333
+ // In case we hit duplication compilations for sub-policies, we add
334
+ // their respective probabilities without pushing the node back again.
335
+ match ms_cache. get ( & leaf_comp) {
336
+ Some ( p) => {
337
+ ms_cache. insert ( leaf_comp, p + prob) ;
338
+ }
339
+ None => {
340
+ ms_cache. insert ( leaf_comp, prob) ;
341
+ leaf_compilations. push ( compilation. 0 ) ;
342
+ }
343
+ } ;
344
+ }
345
+ let taptree = with_huffman_tree_eff (
346
+ leaf_compilations,
347
+ & mut policy_cache,
348
+ & mut ms_cache,
349
+ ) ?;
350
+ Some ( taptree)
351
+ }
352
+ } ,
353
+ ) ?;
354
+ Ok ( tree)
355
+ }
356
+ }
357
+ }
358
+
288
359
/// Compile the descriptor into an optimized `Miniscript` representation
289
360
#[ cfg( feature = "compiler" ) ]
290
361
pub fn compile < Ctx : ScriptContext > ( & self ) -> Result < Miniscript < Pk , Ctx > , CompilerError > {
@@ -827,6 +898,50 @@ where
827
898
}
828
899
}
829
900
901
+ /// Average satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes at some depth having
902
+ /// probabilities corresponding to the (sub)policies they're compiled from.
903
+ ///
904
+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
905
+ /// the size of control block at depth + the script size.
906
+ #[ cfg( feature = "compiler" ) ]
907
+ fn at_depth_taptree_cost < Pk : MiniscriptKey > (
908
+ tr : & TapTree < Pk > ,
909
+ ms_cache : & MsTapCache < Pk > ,
910
+ policy_cache : & PolicyTapCache < Pk > ,
911
+ depth : u32 ,
912
+ ) -> f64 {
913
+ match * tr {
914
+ TapTree :: Tree ( ref l, ref r) => {
915
+ at_depth_taptree_cost ( l, ms_cache, policy_cache, depth + 1 )
916
+ + at_depth_taptree_cost ( r, ms_cache, policy_cache, depth + 1 )
917
+ }
918
+ TapTree :: Leaf ( ref ms) => {
919
+ let prob = ms_cache
920
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
921
+ . expect ( "Probability should exist for the given ms" ) ;
922
+ let sat_cost = policy_cache
923
+ . get ( & TapTree :: Leaf ( Arc :: clone ( ms) ) )
924
+ . expect ( "Cost should exist for the given ms" )
925
+ . 1 ;
926
+ prob * ( ms. script_size ( ) as f64 + sat_cost + 32.0 * depth as f64 )
927
+ }
928
+ }
929
+ }
930
+
931
+ /// Average net satisfaction cost for [`TapTree`] with the leaf [`Miniscript`] nodes having
932
+ /// probabilities corresponding to the (sub)policies they're compiled from.
933
+ ///
934
+ /// Average satisfaction cost for [`TapTree`] over script-spend paths is probability times
935
+ /// the size of control block + the script size.
936
+ #[ cfg( feature = "compiler" ) ]
937
+ fn taptree_cost < Pk : MiniscriptKey > (
938
+ tr : & TapTree < Pk > ,
939
+ ms_cache : & MsTapCache < Pk > ,
940
+ policy_cache : & PolicyTapCache < Pk > ,
941
+ ) -> f64 {
942
+ at_depth_taptree_cost ( tr, ms_cache, policy_cache, 0 )
943
+ }
944
+
830
945
/// Create a Huffman Tree from compiled [Miniscript] nodes
831
946
#[ cfg( feature = "compiler" ) ]
832
947
fn with_huffman_tree < Pk : MiniscriptKey > (
@@ -857,3 +972,124 @@ fn with_huffman_tree<Pk: MiniscriptKey>(
857
972
. 1 ;
858
973
Ok ( node)
859
974
}
975
+
976
+ /// Create a [`TapTree`] from the a list of [`Miniscript`]s having corresponding satisfaction
977
+ /// cost and probability.
978
+ ///
979
+ /// Given that satisfaction probability and cost for each script is known, constructing the
980
+ /// [`TapTree`] as a huffman tree over the net cost (as defined in [`taptree_cost`]) is
981
+ /// the optimal one.
982
+ /// For finding the optimal policy to taptree compilation, we are required to search
983
+ /// exhaustively over all policies which have the same leaf policies. Owing to the exponential
984
+ /// blow-up for such a method, we use a heuristic where we augment the merge to check if the
985
+ /// compilation of a new (sub)policy into a [`TapTree::Leaf`] with the policy corresponding to
986
+ /// the nodes as children is better than [`TapTree::Tree`] with the nodes as children.
987
+ ///
988
+ /// # Assumption
989
+ ///
990
+ /// We have no two duplicate policies/ compilations in the given list.
991
+ /// In any other case, we'd need to re-engineer the node-merging algorithm here to gracefully
992
+ /// handle duplicate intermediate policies/ miniscript compilations by dis-disambiguating them.
993
+ #[ cfg( feature = "compiler" ) ]
994
+ fn with_huffman_tree_eff < Pk : MiniscriptKey > (
995
+ ms : Vec < Arc < Miniscript < Pk , Tap > > > ,
996
+ policy_cache : & mut PolicyTapCache < Pk > ,
997
+ ms_cache : & mut MsTapCache < Pk > ,
998
+ ) -> Result < TapTree < Pk > , Error > {
999
+ let mut node_weights = BinaryHeap :: < ( Reverse < OrdF64 > , OrdF64 , TapTree < Pk > ) > :: new ( ) ; // (cost, branch_prob, tree)
1000
+ // Populate the heap with each `ms` as a TapLeaf, and the respective cost fields
1001
+ for script in ms {
1002
+ let wt = OrdF64 ( taptree_cost (
1003
+ & TapTree :: Leaf ( Arc :: clone ( & script) ) ,
1004
+ ms_cache,
1005
+ policy_cache,
1006
+ ) ) ;
1007
+ let prob = OrdF64 (
1008
+ * ms_cache
1009
+ . get ( & TapTree :: Leaf ( Arc :: clone ( & script) ) )
1010
+ . expect ( "Probability should exist for the given ms" ) ,
1011
+ ) ;
1012
+ node_weights. push ( ( Reverse ( wt) , prob, TapTree :: Leaf ( Arc :: clone ( & script) ) ) ) ;
1013
+ }
1014
+ if node_weights. is_empty ( ) {
1015
+ return Err ( errstr ( "Empty Miniscript compilation" ) ) ;
1016
+ }
1017
+ while node_weights. len ( ) > 1 {
1018
+ // Obtain the two least-weighted nodes from the heap for merging
1019
+ let ( _prev_cost1, p1, ms1) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
1020
+ let ( _prev_cost2, p2, ms2) = node_weights. pop ( ) . expect ( "len must atleast be two" ) ;
1021
+
1022
+ // Retrieve the respective policies
1023
+ let ( left_pol, _c1) = policy_cache
1024
+ . get ( & ms1)
1025
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
1026
+ . clone ( ) ;
1027
+
1028
+ let ( right_pol, _c2) = policy_cache
1029
+ . get ( & ms2)
1030
+ . ok_or_else ( || errstr ( "No corresponding policy found" ) ) ?
1031
+ . clone ( ) ;
1032
+
1033
+ // Create a parent policy with the respective node TapTrees as children (with odds
1034
+ // weighted approximately in ratio to their probabilities)
1035
+ let parent_policy = Policy :: Or ( vec ! [
1036
+ ( ( p1. 0 * 1e4 ) . round( ) as usize , left_pol) ,
1037
+ ( ( p2. 0 * 1e4 ) . round( ) as usize , right_pol) ,
1038
+ ] ) ;
1039
+
1040
+ // Obtain compilation for the parent policy
1041
+ let ( parent_compilation, parent_sat_cost) =
1042
+ compiler:: best_compilation_sat :: < Pk , Tap > ( & parent_policy) ?;
1043
+
1044
+ // Probability of the parent node being satisfied equals the probability of either
1045
+ // nodes to be satisfied. Since we weight the odds appropriately, the children nodes
1046
+ // still have approximately the same probabilities
1047
+ let p = p1. 0 + p2. 0 ;
1048
+ // Inserting parent policy's weights (sat_cost and probability) for later usage, assuming
1049
+ // we don't hit duplicate policy/ compilation here.
1050
+ ms_cache. insert ( TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) , p) ;
1051
+ policy_cache. insert (
1052
+ TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1053
+ ( parent_policy. clone ( ) , parent_sat_cost) ,
1054
+ ) ;
1055
+
1056
+ let parent_cost = OrdF64 ( taptree_cost (
1057
+ & TapTree :: Leaf ( Arc :: clone ( & parent_compilation) ) ,
1058
+ ms_cache,
1059
+ policy_cache,
1060
+ ) ) ;
1061
+ let children_cost = OrdF64 (
1062
+ taptree_cost ( & ms1, ms_cache, policy_cache) + taptree_cost ( & ms2, ms_cache, policy_cache) ,
1063
+ ) ;
1064
+
1065
+ // Merge the children nodes into either TapLeaf of the parent compilation or TapTree
1066
+ // children nodes accordingly. Note that in case the costs are same, we prefer to compile
1067
+ // them to TapTree of children nodes considering privacy guarantees.
1068
+ node_weights. push (
1069
+ if parent_cost < children_cost && parent_compilation. sanity_check ( ) . is_ok ( ) {
1070
+ let node = TapTree :: Leaf ( Arc :: from ( parent_compilation) ) ;
1071
+ ( Reverse ( parent_cost) , OrdF64 ( p) , node)
1072
+ } else {
1073
+ ms_cache. insert (
1074
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1075
+ p,
1076
+ ) ;
1077
+ policy_cache. insert (
1078
+ TapTree :: Tree ( Arc :: from ( ms1. clone ( ) ) , Arc :: from ( ms2. clone ( ) ) ) ,
1079
+ ( parent_policy, parent_sat_cost) ,
1080
+ ) ;
1081
+ (
1082
+ Reverse ( children_cost) ,
1083
+ OrdF64 ( p) ,
1084
+ TapTree :: Tree ( Arc :: from ( ms1) , Arc :: from ( ms2) ) ,
1085
+ )
1086
+ } ,
1087
+ ) ;
1088
+ }
1089
+ debug_assert ! ( node_weights. len( ) == 1 ) ;
1090
+ let node = node_weights
1091
+ . pop ( )
1092
+ . expect ( "huffman tree algorithm is broken" )
1093
+ . 2 ;
1094
+ Ok ( node)
1095
+ }
0 commit comments