@@ -522,9 +522,14 @@ impl OMNameResolver {
522522 ///
523523 /// If an [`Offer`] is found, it, as well as the [`PaymentId`] and original `name` passed to
524524 /// [`Self::resolve_name`] are returned.
525+ ///
526+ /// If the proof is invalid and there are no remaining queries for this name, or if the proof is
527+ /// valid and does not contain a valid BIP 353 entry or BOLT 12 [`Offer`], `Err` will be
528+ /// returned with the set of [`HumanReadableName`] and [`PaymentId`] requests which should be
529+ /// considered failed.
525530 pub fn handle_dnssec_proof_for_offer (
526531 & self , msg : DNSSECProof , context : DNSResolverContext ,
527- ) -> Option < ( Vec < ( HumanReadableName , PaymentId ) > , Offer ) > {
532+ ) -> Result < ( Vec < ( HumanReadableName , PaymentId ) > , Offer ) , Vec < ( HumanReadableName , PaymentId ) > > {
528533 let ( completed_requests, uri) = self . handle_dnssec_proof_for_uri ( msg, context) ?;
529534 if let Some ( ( _onchain, params) ) = uri. split_once ( '?' ) {
530535 for param in params. split ( '&' ) {
@@ -535,13 +540,13 @@ impl OMNameResolver {
535540 } ;
536541 if k. eq_ignore_ascii_case ( "lno" ) {
537542 if let Ok ( offer) = Offer :: from_str ( v) {
538- return Some ( ( completed_requests, offer) ) ;
543+ return Ok ( ( completed_requests, offer) ) ;
539544 }
540- return None ;
545+ return Err ( Vec :: new ( ) ) ;
541546 }
542547 }
543548 }
544- None
549+ Err ( Vec :: new ( ) )
545550 }
546551
547552 /// Handles a [`DNSSECProof`] message, attempting to verify it and match it against any pending
@@ -553,14 +558,18 @@ impl OMNameResolver {
553558 /// Note that a single proof for a wildcard DNS entry may complete several requests for
554559 /// different [`HumanReadableName`]s.
555560 ///
561+ /// If the proof is invalid and there are no remaining queries for this name, or if the proof is
562+ /// valid and does not contain a valid BIP 353 entry, `Err` will be returned with the set of
563+ /// [`HumanReadableName`] and [`PaymentId`] requests which should be considered failed.
564+ ///
556565 /// This method is useful for those who handle bitcoin: URIs already, handling more than just
557566 /// BOLT12 [`Offer`]s.
558567 pub fn handle_dnssec_proof_for_uri (
559568 & self , msg : DNSSECProof , context : DNSResolverContext ,
560- ) -> Option < ( Vec < ( HumanReadableName , PaymentId ) > , String ) > {
569+ ) -> Result < ( Vec < ( HumanReadableName , PaymentId ) > , String ) , Vec < ( HumanReadableName , PaymentId ) > > {
561570 let DNSSECProof { name : answer_name, proof } = msg;
562571 let mut pending_resolves = self . pending_resolves . lock ( ) . unwrap ( ) ;
563- if let hash_map:: Entry :: Occupied ( entry) = pending_resolves. entry ( answer_name) {
572+ if let hash_map:: Entry :: Occupied ( mut entry) = pending_resolves. entry ( answer_name) {
564573 if !entry. get ( ) . iter ( ) . any ( |query| query. context == context) {
565574 // If we don't have any pending queries with the context included in the blinded
566575 // path (implying someone sent us this response not using the blinded path we gave
@@ -570,66 +579,113 @@ impl OMNameResolver {
570579 // If there was at least one query with the same context, we go ahead and complete
571580 // all queries for the same name, as there's no point in waiting for another proof
572581 // for the same name.
573- return None ;
582+ return Err ( Vec :: new ( ) ) ;
574583 }
575- let parsed_rrs = parse_rr_stream ( & proof) ;
576- let validated_rrs =
577- parsed_rrs. as_ref ( ) . and_then ( |rrs| verify_rr_stream ( rrs) . map_err ( |_| & ( ) ) ) ;
578- if let Ok ( validated_rrs) = validated_rrs {
579- #[ allow( unused_assignments, unused_mut) ]
580- let mut time = self . latest_block_time . load ( Ordering :: Acquire ) as u64 ;
581- #[ cfg( all( feature = "std" , not( fuzzing) ) ) ]
582- {
583- use std:: time:: { SystemTime , UNIX_EPOCH } ;
584- let now = SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) ;
585- time = now. expect ( "Time must be > 1970" ) . as_secs ( ) ;
586- }
587- if time != 0 {
588- // Block times may be up to two hours in the future and some time into the past
589- // (we assume no more than two hours, though the actual limits are rather
590- // complicated).
591- // Thus, we have to let the proof times be rather fuzzy.
592- let max_time_offset =
593- if cfg ! ( all( feature = "std" , not( fuzzing) ) ) { 0 } else { 60 * 2 } ;
594- if validated_rrs. valid_from > time + max_time_offset {
595- return None ;
584+ let mut valid_resolution = false ;
585+ let res = parse_rr_stream ( & proof)
586+ . and_then ( |rrs| {
587+ if let Some ( verified_rrs) = self . verify_dnssec_proof_for_rrs ( entry. key ( ) , & rrs) {
588+ valid_resolution = true ;
589+ self . map_rrs_to_uri ( verified_rrs) . ok_or ( ( ) )
590+ } else {
591+ Err ( ( ) )
596592 }
597- if validated_rrs. expires < time - max_time_offset {
598- return None ;
593+ } ) ;
594+ if valid_resolution {
595+ let requests = entry. remove_entry ( ) ;
596+ match res {
597+ Ok ( txt) => {
598+ Ok ( ( requests. 1 . into_iter ( ) . map ( |r| ( r. name , r. payment_id ) ) . collect ( ) , txt) )
599599 }
600+ Err ( ( ) ) => Err ( Vec :: new ( ) ) ,
600601 }
601- let resolved_rrs = validated_rrs. resolve_name ( & entry. key ( ) ) ;
602- if resolved_rrs. is_empty ( ) {
603- return None ;
602+ } else {
603+ let mut failed_resolutions = Vec :: new ( ) ;
604+ entry. get_mut ( ) . retain_mut ( |query| {
605+ if query. context == context {
606+ query. pending_queries = query. pending_queries . saturating_sub ( 1 ) ;
607+ if query. pending_queries == 0 {
608+ failed_resolutions. push ( ( query. name , query. payment_id ) ) ;
609+ false
610+ } else {
611+ true
612+ }
613+ } else {
614+ true
615+ }
616+ } ) ;
617+
618+ if entry. get ( ) . is_empty ( ) {
619+ entry. remove_entry ( ) ;
604620 }
621+ Err ( failed_resolutions)
622+ }
623+ } else {
624+ Err ( Vec :: new ( ) )
625+ }
626+ }
605627
606- let ( _, requests) = entry. remove_entry ( ) ;
607-
608- const URI_PREFIX : & str = "bitcoin:" ;
609- let mut candidate_records = resolved_rrs
610- . iter ( )
611- . filter_map (
612- |rr| if let RR :: Txt ( txt) = rr { Some ( txt. data . as_vec ( ) ) } else { None } ,
613- )
614- . filter_map ( |data| String :: from_utf8 ( data) . ok ( ) )
615- . filter ( |data_string| data_string. len ( ) > URI_PREFIX . len ( ) )
616- . filter ( |data_string| {
617- let pfx = & data_string. as_bytes ( ) [ ..URI_PREFIX . len ( ) ] ;
618- pfx. eq_ignore_ascii_case ( URI_PREFIX . as_bytes ( ) )
619- } ) ;
620- // Check that there is exactly one TXT record that begins with
621- // bitcoin: as required by BIP 353 (and is valid UTF-8).
622- match ( candidate_records. next ( ) , candidate_records. next ( ) ) {
623- ( Some ( txt) , None ) => {
624- let completed_requests =
625- requests. into_iter ( ) . map ( |r| ( r. name , r. payment_id ) ) . collect ( ) ;
626- return Some ( ( completed_requests, txt) ) ;
627- } ,
628- _ => { } ,
628+ fn verify_dnssec_proof_for_rrs < ' a > (
629+ & self , resolved_name : & Name , rrs : & ' a [ RR ] ,
630+ ) -> Option < Vec < & ' a RR > > {
631+ let validated_rrs = verify_rr_stream ( rrs) ;
632+ if let Ok ( validated_rrs) = validated_rrs {
633+ #[ allow( unused_assignments, unused_mut) ]
634+ let mut time = self . latest_block_time . load ( Ordering :: Acquire ) as u64 ;
635+ #[ cfg( all( feature = "std" , not( fuzzing) ) ) ]
636+ {
637+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
638+ let now = SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) ;
639+ time = now. expect ( "Time must be > 1970" ) . as_secs ( ) ;
640+ }
641+ if time != 0 {
642+ // Block times may be up to two hours in the future and some time into the past
643+ // (we assume no more than two hours, though the actual limits are rather
644+ // complicated).
645+ // Thus, we have to let the proof times be rather fuzzy.
646+ let max_time_offset =
647+ if cfg ! ( all( feature = "std" , not( fuzzing) ) ) { 0 } else { 60 * 2 } ;
648+ if validated_rrs. valid_from > time + max_time_offset {
649+ return None ;
650+ }
651+ if validated_rrs. expires < time - max_time_offset {
652+ return None ;
629653 }
630654 }
655+ let resolved_rrs = validated_rrs. resolve_name ( resolved_name) ;
656+ if resolved_rrs. is_empty ( ) {
657+ return None ;
658+ }
659+
660+ Some ( resolved_rrs)
661+ } else {
662+ None
663+ }
664+ }
665+
666+ fn map_rrs_to_uri (
667+ & self , resolved_rrs : Vec < & RR > ,
668+ ) -> Option < String > {
669+ const URI_PREFIX : & str = "bitcoin:" ;
670+ let mut candidate_records = resolved_rrs
671+ . iter ( )
672+ . filter_map (
673+ |rr| if let RR :: Txt ( txt) = rr { Some ( txt. data . as_vec ( ) ) } else { None } ,
674+ )
675+ . filter_map ( |data| String :: from_utf8 ( data) . ok ( ) )
676+ . filter ( |data_string| data_string. len ( ) > URI_PREFIX . len ( ) )
677+ . filter ( |data_string| {
678+ let pfx = & data_string. as_bytes ( ) [ ..URI_PREFIX . len ( ) ] ;
679+ pfx. eq_ignore_ascii_case ( URI_PREFIX . as_bytes ( ) )
680+ } ) ;
681+ // Check that there is exactly one TXT record that begins with
682+ // bitcoin: as required by BIP 353 (and is valid UTF-8).
683+ match ( candidate_records. next ( ) , candidate_records. next ( ) ) {
684+ ( Some ( txt) , None ) => {
685+ Some ( txt)
686+ } ,
687+ _ => None ,
631688 }
632- None
633689 }
634690
635691 /// Handles a [`DNSSECError`] message, indicating that one of the resolvers we sent a
0 commit comments