@@ -548,3 +548,304 @@ impl TryFrom<(OfferTlvStream, InvoiceTlvStream)> for InvoiceContents {
548548 } )
549549 }
550550}
551+
552+ #[ cfg( test) ]
553+ mod tests {
554+ use crate :: blinded_path:: { BlindedHop , BlindedPath , IntroductionNode } ;
555+ use crate :: ln:: features:: { Bolt12InvoiceFeatures , OfferFeatures } ;
556+ use crate :: ln:: inbound_payment:: ExpandedKey ;
557+ use crate :: offers:: invoice:: SIGNATURE_TAG ;
558+ use crate :: offers:: merkle;
559+ use crate :: offers:: merkle:: TaggedHash ;
560+ use crate :: offers:: offer:: { Offer , OfferBuilder , Quantity } ;
561+ use crate :: offers:: parse:: Bolt12SemanticError ;
562+ use crate :: offers:: static_invoice:: {
563+ StaticInvoice , StaticInvoiceBuilder , DEFAULT_RELATIVE_EXPIRY ,
564+ } ;
565+ use crate :: offers:: test_utils:: * ;
566+ use crate :: sign:: KeyMaterial ;
567+ use crate :: util:: ser:: Writeable ;
568+ use bitcoin:: blockdata:: constants:: ChainHash ;
569+ use bitcoin:: network:: constants:: Network ;
570+ use bitcoin:: secp256k1:: Secp256k1 ;
571+ use core:: time:: Duration ;
572+
573+ fn blinded_path ( ) -> BlindedPath {
574+ BlindedPath {
575+ introduction_node : IntroductionNode :: NodeId ( pubkey ( 40 ) ) ,
576+ blinding_point : pubkey ( 41 ) ,
577+ blinded_hops : vec ! [
578+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
579+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
580+ ] ,
581+ }
582+ }
583+
584+ #[ test]
585+ fn builds_invoice_for_offer_with_defaults ( ) {
586+ let node_id = recipient_pubkey ( ) ;
587+ let payment_paths = payment_paths ( ) ;
588+ let now = now ( ) ;
589+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
590+ let entropy = FixedEntropy { } ;
591+ let secp_ctx = Secp256k1 :: new ( ) ;
592+
593+ let offer =
594+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
595+ . path ( blinded_path ( ) )
596+ . build ( )
597+ . unwrap ( ) ;
598+
599+ let ( _offer_id, keys_opt) = offer. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
600+ let invoice = StaticInvoiceBuilder :: for_offer_using_keys (
601+ & offer,
602+ payment_paths. clone ( ) ,
603+ vec ! [ blinded_path( ) ] ,
604+ now,
605+ keys_opt. unwrap ( ) ,
606+ )
607+ . unwrap ( )
608+ . build_and_sign ( & secp_ctx)
609+ . unwrap ( ) ;
610+
611+ let mut buffer = Vec :: new ( ) ;
612+ invoice. write ( & mut buffer) . unwrap ( ) ;
613+
614+ assert_eq ! ( invoice. bytes, buffer. as_slice( ) ) ;
615+ assert ! ( invoice. metadata( ) . is_some( ) ) ;
616+ assert_eq ! ( invoice. amount( ) , None ) ;
617+ assert_eq ! ( invoice. description( ) , None ) ;
618+ assert_eq ! ( invoice. offer_features( ) , & OfferFeatures :: empty( ) ) ;
619+ assert_eq ! ( invoice. absolute_expiry( ) , None ) ;
620+ assert_eq ! ( invoice. offer_message_paths( ) , & [ blinded_path( ) ] ) ;
621+ assert_eq ! ( invoice. message_paths( ) , & [ blinded_path( ) ] ) ;
622+ assert_eq ! ( invoice. issuer( ) , None ) ;
623+ assert_eq ! ( invoice. supported_quantity( ) , Quantity :: One ) ;
624+ assert_ne ! ( invoice. signing_pubkey( ) , recipient_pubkey( ) ) ;
625+ assert_eq ! ( invoice. chain( ) , ChainHash :: using_genesis_block( Network :: Bitcoin ) ) ;
626+ assert_eq ! ( invoice. payment_paths( ) , payment_paths. as_slice( ) ) ;
627+ assert_eq ! ( invoice. created_at( ) , now) ;
628+ assert_eq ! ( invoice. relative_expiry( ) , DEFAULT_RELATIVE_EXPIRY ) ;
629+ #[ cfg( feature = "std" ) ]
630+ assert ! ( !invoice. is_expired( ) ) ;
631+ assert_eq ! ( invoice. fallbacks( ) , vec![ ] ) ;
632+ assert_eq ! ( invoice. invoice_features( ) , & Bolt12InvoiceFeatures :: empty( ) ) ;
633+
634+ let message = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & invoice. bytes ) ;
635+ assert ! ( merkle:: verify_signature(
636+ & invoice. signature,
637+ & message,
638+ keys_opt. unwrap( ) . public_key( )
639+ )
640+ . is_ok( ) ) ;
641+
642+ if let Err ( e) = StaticInvoice :: try_from ( buffer) {
643+ panic ! ( "error parsing invoice: {:?}" , e) ;
644+ }
645+ }
646+
647+ #[ cfg( feature = "std" ) ]
648+ #[ test]
649+ fn builds_invoice_from_offer_with_expiration ( ) {
650+ let node_id = recipient_pubkey ( ) ;
651+ let now = now ( ) ;
652+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
653+ let entropy = FixedEntropy { } ;
654+ let secp_ctx = Secp256k1 :: new ( ) ;
655+
656+ let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
657+ let past_expiry = Duration :: from_secs ( 0 ) ;
658+
659+ let valid_offer =
660+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
661+ . path ( blinded_path ( ) )
662+ . absolute_expiry ( future_expiry)
663+ . build ( )
664+ . unwrap ( ) ;
665+
666+ let ( _offer_id, keys_opt) = valid_offer. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
667+ let invoice = StaticInvoiceBuilder :: for_offer_using_keys (
668+ & valid_offer,
669+ payment_paths ( ) ,
670+ vec ! [ blinded_path( ) ] ,
671+ now,
672+ keys_opt. unwrap ( ) ,
673+ )
674+ . unwrap ( )
675+ . build_and_sign ( & secp_ctx)
676+ . unwrap ( ) ;
677+ assert ! ( !invoice. is_expired( ) ) ;
678+ assert_eq ! ( invoice. absolute_expiry( ) , Some ( future_expiry) ) ;
679+
680+ let expired_offer =
681+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
682+ . path ( blinded_path ( ) )
683+ . absolute_expiry ( past_expiry)
684+ . build ( )
685+ . unwrap ( ) ;
686+ let ( _offer_id, keys_opt) = expired_offer. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
687+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
688+ & expired_offer,
689+ payment_paths ( ) ,
690+ vec ! [ blinded_path( ) ] ,
691+ now,
692+ keys_opt. unwrap ( ) ,
693+ )
694+ . unwrap ( )
695+ . build_and_sign ( & secp_ctx)
696+ {
697+ assert_eq ! ( e, Bolt12SemanticError :: AlreadyExpired ) ;
698+ } else {
699+ panic ! ( "expected error" )
700+ }
701+ }
702+
703+ #[ test]
704+ fn fails_build_with_missing_paths ( ) {
705+ let node_id = recipient_pubkey ( ) ;
706+ let now = now ( ) ;
707+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
708+ let entropy = FixedEntropy { } ;
709+ let secp_ctx = Secp256k1 :: new ( ) ;
710+
711+ let valid_offer =
712+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
713+ . path ( blinded_path ( ) )
714+ . build ( )
715+ . unwrap ( ) ;
716+ let ( _offer_id, keys_opt) = valid_offer. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
717+
718+ // Error if payment paths are missing.
719+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
720+ & valid_offer,
721+ Vec :: new ( ) ,
722+ vec ! [ blinded_path( ) ] ,
723+ now,
724+ keys_opt. unwrap ( ) ,
725+ ) {
726+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
727+ } else {
728+ panic ! ( "expected error" )
729+ }
730+
731+ // Error if message paths are missing.
732+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
733+ & valid_offer,
734+ payment_paths ( ) ,
735+ Vec :: new ( ) ,
736+ now,
737+ keys_opt. unwrap ( ) ,
738+ ) {
739+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
740+ } else {
741+ panic ! ( "expected error" )
742+ }
743+
744+ // Error if offer paths are missing.
745+ let mut offer_without_paths = valid_offer. clone ( ) ;
746+ let mut offer_tlv_stream = offer_without_paths. as_tlv_stream ( ) ;
747+ offer_tlv_stream. paths . take ( ) ;
748+ let mut buffer = Vec :: new ( ) ;
749+ offer_tlv_stream. write ( & mut buffer) . unwrap ( ) ;
750+ offer_without_paths = Offer :: try_from ( buffer) . unwrap ( ) ;
751+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
752+ & offer_without_paths,
753+ payment_paths ( ) ,
754+ vec ! [ blinded_path( ) ] ,
755+ now,
756+ keys_opt. unwrap ( ) ,
757+ ) {
758+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
759+ } else {
760+ panic ! ( "expected error" )
761+ }
762+ }
763+
764+ #[ test]
765+ fn fails_build_offer_signing_pubkey ( ) {
766+ let node_id = recipient_pubkey ( ) ;
767+ let now = now ( ) ;
768+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
769+ let entropy = FixedEntropy { } ;
770+ let secp_ctx = Secp256k1 :: new ( ) ;
771+
772+ let valid_offer =
773+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
774+ . path ( blinded_path ( ) )
775+ . build ( )
776+ . unwrap ( ) ;
777+ let ( _offer_id, keys_opt) = valid_offer. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
778+
779+ // Error if offer signing pubkey is missing.
780+ let mut offer_missing_signing_pubkey = valid_offer. clone ( ) ;
781+ let mut offer_tlv_stream = offer_missing_signing_pubkey. as_tlv_stream ( ) ;
782+ offer_tlv_stream. node_id . take ( ) ;
783+ let mut buffer = Vec :: new ( ) ;
784+ offer_tlv_stream. write ( & mut buffer) . unwrap ( ) ;
785+ offer_missing_signing_pubkey = Offer :: try_from ( buffer) . unwrap ( ) ;
786+
787+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
788+ & offer_missing_signing_pubkey,
789+ payment_paths ( ) ,
790+ vec ! [ blinded_path( ) ] ,
791+ now,
792+ keys_opt. unwrap ( ) ,
793+ ) {
794+ assert_eq ! ( e, Bolt12SemanticError :: MissingSigningPubkey ) ;
795+ } else {
796+ panic ! ( "expected error" )
797+ }
798+
799+ // Error if the offer's signing pubkey doesn't match the invoice's.
800+ let mut offer_invalid_signing_pubkey = valid_offer. clone ( ) ;
801+ let mut offer_tlv_stream = offer_invalid_signing_pubkey. as_tlv_stream ( ) ;
802+ let invalid_node_id = payer_pubkey ( ) ;
803+ offer_tlv_stream. node_id = Some ( & invalid_node_id) ;
804+ let mut buffer = Vec :: new ( ) ;
805+ offer_tlv_stream. write ( & mut buffer) . unwrap ( ) ;
806+ offer_invalid_signing_pubkey = Offer :: try_from ( buffer) . unwrap ( ) ;
807+
808+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
809+ & offer_invalid_signing_pubkey,
810+ payment_paths ( ) ,
811+ vec ! [ blinded_path( ) ] ,
812+ now,
813+ keys_opt. unwrap ( ) ,
814+ ) {
815+ assert_eq ! ( e, Bolt12SemanticError :: InvalidSigningPubkey ) ;
816+ } else {
817+ panic ! ( "expected error" )
818+ }
819+ }
820+
821+ #[ test]
822+ fn fails_build_with_extra_offer_chains ( ) {
823+ let node_id = recipient_pubkey ( ) ;
824+ let now = now ( ) ;
825+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
826+ let entropy = FixedEntropy { } ;
827+ let secp_ctx = Secp256k1 :: new ( ) ;
828+
829+ let offer_with_extra_chain =
830+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
831+ . path ( blinded_path ( ) )
832+ . chain ( Network :: Bitcoin )
833+ . chain ( Network :: Testnet )
834+ . build ( )
835+ . unwrap ( ) ;
836+ let ( _offer_id, keys_opt) =
837+ offer_with_extra_chain. verify ( & expanded_key, & secp_ctx) . unwrap ( ) ;
838+
839+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_keys (
840+ & offer_with_extra_chain,
841+ payment_paths ( ) ,
842+ vec ! [ blinded_path( ) ] ,
843+ now,
844+ keys_opt. unwrap ( ) ,
845+ ) {
846+ assert_eq ! ( e, Bolt12SemanticError :: UnexpectedChain ) ;
847+ } else {
848+ panic ! ( "expected error" )
849+ }
850+ }
851+ }
0 commit comments