@@ -563,59 +563,103 @@ mod tests {
563563
564564 #[ test]
565565 fn regression_895 ( ) {
566- // Tests a pathological descriptor whose cheapest satisfaction would require mixing
567- // timelocks, although a more expensive satisfaction is available that avoids the
568- // timelock mixing. This descriptor is accepted by rust-miniscript but its satisfier
569- // cannot produce a satisfaction for it.
566+ // Tests a pathological descriptor whose cheapest satisfaction involves computing a
567+ // dissatisfaction containing a timelock. Such dissatisfactions exist because the
568+ // `and_v` fragment, uniquely, has a dissatisfaction that includes the satisfaction
569+ // of its left child. (Check sat_dissat.rs and search 'dissat: ' to see how the
570+ // dissatisfactions of every fragment are computed. You will see that exactly one,
571+ // and_v, uses the satisfaction of a child.)
570572 //
571- // Prior to PR #895, the satisfaction logic would yield the invalid timelock-mixing
572- // satisfaction. After PR #895, it yields no satisfaction at all.
573- //
574- // The correct behavior is arguably to find the more expensive satisfaction. Doing
575- // this would would require some sort of backtracking in the satisfier and is highly
576- // nontrivial. Since triggering this bug requires generating a satisfaction in a
577- // context where both a height-based and time-based timelock are available, an
578- // impossible situation, it is probably not worth fixing.
573+ // Prior to PR #895, the satisfier did not keep track of timelocks that were used
574+ // for dissatisfactions. This can lead to incorrect plans, as demonstrated in the
575+ // below test vectors.
579576
580- // Setup: an unavailable key , used to make some branches unsatisfiable from the POV
577+ // Setup: unavailable keys , used to make some branches unsatisfiable from the POV
581578 // of the satisfier (but not the typechecker).
582- let unavailable_key = PublicKey :: from_str (
583- "02eb64639a17f7334bb5a1a3aad857d6fec65faef439db3de72f85c88bc2906ad3" ,
584- )
585- . unwrap ( ) ;
579+ let available_keys: [ PublicKey ; _] = [
580+ "02eb64639a17f7334bb5a1a3aad857d6fec65faef439db3de72f85c88bc2906ad1"
581+ . parse ( )
582+ . unwrap ( ) ,
583+ "02eb64639a17f7334bb5a1a3aad857d6fec65faef439db3de72f85c88bc2906ad3"
584+ . parse ( )
585+ . unwrap ( ) ,
586+ ] ;
587+ let available_key_map = {
588+ let dummy_sig = bitcoin:: ecdsa:: Signature :: sighash_all (
589+ secp256k1:: ecdsa:: Signature :: from_compact ( & [ 1 ; 64 ] ) . unwrap ( ) ,
590+ ) ;
591+ let mut map = std:: collections:: BTreeMap :: new ( ) ;
592+ map. insert (
593+ crate :: DefiniteDescriptorKey :: new ( available_keys[ 0 ] . into ( ) ) . unwrap ( ) ,
594+ dummy_sig,
595+ ) ;
596+ map. insert (
597+ crate :: DefiniteDescriptorKey :: new ( available_keys[ 1 ] . into ( ) ) . unwrap ( ) ,
598+ dummy_sig,
599+ ) ;
600+ map
601+ } ;
602+
603+ // Generate a large pile of distinct unavailable keys.
604+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
605+ let unavailable_keys: Vec < PublicKey > =
606+ core:: iter:: successors ( Some ( available_keys[ 0 ] ) , |prev| {
607+ prev. inner
608+ . add_exp_tweak ( & secp, & secp256k1:: Scalar :: ONE )
609+ . ok ( )
610+ . map ( PublicKey :: new)
611+ } )
612+ . skip ( 1 )
613+ . take ( 20 )
614+ . collect ( ) ;
615+ assert_eq ! ( unavailable_keys. len( ) , 20 ) ; // sanity-check the above loop
616+
617+ // Create a fragment which is very expensive to dissatisfy.
618+ let expensive_threshold = format ! (
619+ "thresh(10,pk({}),{})" ,
620+ unavailable_keys[ 1 ] , // we need key[0] below
621+ unavailable_keys
622+ . iter( )
623+ . skip( 2 )
624+ . map( |k| format!( "a:pkh({k})" ) )
625+ . collect:: <Vec <_>>( )
626+ . join( "," ) ,
627+ ) ;
628+
586629 // Setup: a satisfier that thinks that both a height-based and time-based timelock
587630 // are available. This is needed for the second test.
588631 let satisfier = (
632+ available_key_map,
589633 absolute:: LockTime :: from_height ( 1000 ) . unwrap ( ) ,
590634 absolute:: LockTime :: from_time ( 2000000000 ) . unwrap ( ) ,
591635 ) ;
592636
593637 // Construct a script that would mix timelocks:
594638 // or_b(
595639 // n:or_i(
596- // and_v(v:after(144),pk()), // dissatisfied by a height-based timelock
597- // thresh(3,pk(),s:pk(),s:pk()) // dissatisfied by 3empty sigs (more expensive)
640+ // and_v(v:after(144),and_v(v: pk(),pk())), // dissatisfied by a height-based timelock, a sig, and 0
641+ // expensive_threshold // dissatisfied by a giant pile of pubkeyhash preimages
598642 // ),
599- // sdv: after(50) // satisfied by a lower height-based timelock
643+ // ajt:and_v(v: after(50),v:pk({})))) // satisfied by a lower height-based timelock and a sig
600644 // )
601645 //
602- // Here the or_i cannot be satisfied because none of the keys are available , so it
646+ // Here the or_i cannot be satisfied due to missing keys on both branches , so it
603647 // must be dissatisfied (and the after(50) branch must be satisfied). However, there
604648 // are two dissatisfactions for the or_i: one which dissatisfies the first branch,
605649 // by using the height-based timelock, and one which dissatisfies the second branch,
606650 // which is ignored since it's the more expensive of the two possibilities.
607651 //
608- // We therefore take both the after(144) and after(50) branches, and the resulting
609- // plan should show after(144) since it's the higher one. However, prior to #895,
610- // we "did not notice" the after(144) since it appears as part of a dissatisfaction .
652+ // Since the first branch's dissatisfaction is HASSIG but the second branch's is not,
653+ // the satisfier is required to dissatisfy the second branch to avoid malleability.
654+ // However, because of #976, it does not .
611655 //
612- // Unrelatedly: the fact that the LHS of the `or_i` has a malleable dissatisfaction
613- // means that the whole script is malleable, and the fact that this parses at all is
614- // an instance of https://github.com/rust-bitcoin/rust-miniscript/issues/734
615-
656+ // Itstead, it takes both the after(144) and after(50) branches, and the resulting
657+ // plan should show after(144) since it's the higher one. However, prior to #895,
658+ // we "did not notice" the after(144) since it appears as part of a dissatisfaction,
659+ // leading to a plan that did not match the actual timelock requirement.
616660 let descriptor_str = format ! (
617- "wsh(or_b(n:or_i(and_v(v:after(144),pk({})),thresh(3, pk({}),s:pk({ }),s:pk({}))),sdv :after(50)))" ,
618- unavailable_key , unavailable_key , unavailable_key , unavailable_key ,
661+ "wsh(or_b(n:or_i(and_v(v:after(144),and_v(v: pk({}), pk({}))),{expensive_threshold }),ajt:and_v(v :after(50),v:pk({}) )))" ,
662+ available_keys [ 0 ] , unavailable_keys [ 0 ] , available_keys [ 1 ] ,
619663 ) ;
620664 // Need DefiniteDescriptorKey https://github.com/rust-bitcoin/rust-miniscript/issues/927
621665 let descriptor =
@@ -625,19 +669,23 @@ mod tests {
625669 assert_eq ! ( plan. absolute_timelock, Some ( absolute:: LockTime :: from_height( 144 ) . unwrap( ) ) , ) ;
626670
627671 // Same descriptor as above, except that now we use a time-based timelock rather than a
628- // lower height-based one. This time the "ideal" behavior would be that once we get to
629- // the final time-based timelock, we somehow backtrack and then use the more-expensive
630- // dissatisfaction choice for the `or_i`. (The type system guarantees that such a choice
631- // exists; otherwise the whole script would be flagged as mixing timelocks.)
672+ // lower height-based one.
673+ //
674+ // Again, we wind up taking both timelock branches. Prior to #895, we would do this because
675+ // we did not track the after(144) at all. After #895, we do it because our timelock logic
676+ // handles mixed timelocks by silently clobbering one of them. (See the implementation of
677+ // `concatenate_rev`, which calls `cmp::max` on the timelocks. This clobbers a locktime.
678+ // See #979 for an explanation of this.)
679+ //
680+ // This time the "ideal" behavior would be that we track best satisfactions for both time-
681+ // and height-based locktimes, and whenever we are forced into a conflict we throw one away.
682+ // (The type system guarantees that we will always have at least one satisfaction left;
683+ // otherwise the whole script would be flagged as mixing timelocks.)
632684 //
633- // However, our code architecture doesn't let us backtrack like this, and it's only possible
634- // to get into this situation if (a) you have a pathological script like this, and (b) you
635- // call .plan() or .satisfy() with both a height-based and time-based timelock set (which
636- // is impossible for any actual transaction). So for now we just use this unit test to
637- // document the behavior.
685+ // For now we just use this unit test to document the behavior.
638686 let descriptor_str = format ! (
639- "wsh(or_b(n:or_i(and_v(v:after(144),pk({})),thresh(2, pk({}),s:pk({}))),sdv: after(1000000000)))" ,
640- unavailable_key , unavailable_key , unavailable_key ,
687+ "wsh(or_b(n:or_i(and_v(v:after(144),and_v(v: pk({}), pk({}))),{expensive_threshold}),ajt:and_v(v: after(1000000000),v:pk({}) )))" ,
688+ available_keys [ 0 ] , unavailable_keys [ 0 ] , available_keys [ 1 ] ,
641689 ) ;
642690 let descriptor =
643691 Descriptor :: < crate :: DefiniteDescriptorKey > :: from_str ( & descriptor_str) . unwrap ( ) ;
0 commit comments