@@ -557,3 +557,350 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
557557 } )
558558 }
559559}
560+
561+ #[ cfg( test) ]
562+ mod tests {
563+ use crate :: blinded_path:: { BlindedHop , BlindedPath , IntroductionNode } ;
564+ use crate :: ln:: features:: { Bolt12InvoiceFeatures , OfferFeatures } ;
565+ use crate :: ln:: inbound_payment:: ExpandedKey ;
566+ use crate :: offers:: invoice:: InvoiceTlvStreamRef ;
567+ use crate :: offers:: merkle;
568+ use crate :: offers:: merkle:: { SignatureTlvStreamRef , TaggedHash } ;
569+ use crate :: offers:: offer:: { Offer , OfferBuilder , OfferTlvStreamRef , Quantity } ;
570+ use crate :: offers:: parse:: Bolt12SemanticError ;
571+ use crate :: offers:: static_invoice:: {
572+ StaticInvoice , StaticInvoiceBuilder , DEFAULT_RELATIVE_EXPIRY , SIGNATURE_TAG ,
573+ } ;
574+ use crate :: offers:: test_utils:: * ;
575+ use crate :: sign:: KeyMaterial ;
576+ use crate :: util:: ser:: { Iterable , Writeable } ;
577+ use bitcoin:: blockdata:: constants:: ChainHash ;
578+ use bitcoin:: secp256k1:: Secp256k1 ;
579+ use bitcoin:: Network ;
580+ use core:: time:: Duration ;
581+
582+ type FullInvoiceTlvStreamRef < ' a > =
583+ ( OfferTlvStreamRef < ' a > , InvoiceTlvStreamRef < ' a > , SignatureTlvStreamRef < ' a > ) ;
584+
585+ impl StaticInvoice {
586+ fn as_tlv_stream ( & self ) -> FullInvoiceTlvStreamRef {
587+ let ( offer_tlv_stream, invoice_tlv_stream) = self . contents . as_tlv_stream ( ) ;
588+ (
589+ offer_tlv_stream,
590+ invoice_tlv_stream,
591+ SignatureTlvStreamRef { signature : Some ( & self . signature ) } ,
592+ )
593+ }
594+ }
595+
596+ fn blinded_path ( ) -> BlindedPath {
597+ BlindedPath {
598+ introduction_node : IntroductionNode :: NodeId ( pubkey ( 40 ) ) ,
599+ blinding_point : pubkey ( 41 ) ,
600+ blinded_hops : vec ! [
601+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
602+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
603+ ] ,
604+ }
605+ }
606+
607+ #[ test]
608+ fn builds_invoice_for_offer_with_defaults ( ) {
609+ let node_id = recipient_pubkey ( ) ;
610+ let payment_paths = payment_paths ( ) ;
611+ let now = now ( ) ;
612+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
613+ let entropy = FixedEntropy { } ;
614+ let secp_ctx = Secp256k1 :: new ( ) ;
615+
616+ let offer =
617+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
618+ . path ( blinded_path ( ) )
619+ . build ( )
620+ . unwrap ( ) ;
621+
622+ let invoice = StaticInvoiceBuilder :: for_offer_using_derived_keys (
623+ & offer,
624+ payment_paths. clone ( ) ,
625+ vec ! [ blinded_path( ) ] ,
626+ now,
627+ & expanded_key,
628+ & secp_ctx,
629+ )
630+ . unwrap ( )
631+ . build_and_sign ( & secp_ctx)
632+ . unwrap ( ) ;
633+
634+ let mut buffer = Vec :: new ( ) ;
635+ invoice. write ( & mut buffer) . unwrap ( ) ;
636+
637+ assert_eq ! ( invoice. bytes, buffer. as_slice( ) ) ;
638+ assert ! ( invoice. metadata( ) . is_some( ) ) ;
639+ assert_eq ! ( invoice. amount( ) , None ) ;
640+ assert_eq ! ( invoice. description( ) , None ) ;
641+ assert_eq ! ( invoice. offer_features( ) , & OfferFeatures :: empty( ) ) ;
642+ assert_eq ! ( invoice. absolute_expiry( ) , None ) ;
643+ assert_eq ! ( invoice. offer_message_paths( ) , & [ blinded_path( ) ] ) ;
644+ assert_eq ! ( invoice. message_paths( ) , & [ blinded_path( ) ] ) ;
645+ assert_eq ! ( invoice. issuer( ) , None ) ;
646+ assert_eq ! ( invoice. supported_quantity( ) , Quantity :: One ) ;
647+ assert_ne ! ( invoice. signing_pubkey( ) , recipient_pubkey( ) ) ;
648+ assert_eq ! ( invoice. chain( ) , ChainHash :: using_genesis_block( Network :: Bitcoin ) ) ;
649+ assert_eq ! ( invoice. payment_paths( ) , payment_paths. as_slice( ) ) ;
650+ assert_eq ! ( invoice. created_at( ) , now) ;
651+ assert_eq ! ( invoice. relative_expiry( ) , DEFAULT_RELATIVE_EXPIRY ) ;
652+ #[ cfg( feature = "std" ) ]
653+ assert ! ( !invoice. is_expired( ) ) ;
654+ assert ! ( invoice. fallbacks( ) . is_empty( ) ) ;
655+ assert_eq ! ( invoice. invoice_features( ) , & Bolt12InvoiceFeatures :: empty( ) ) ;
656+
657+ let offer_signing_pubkey = offer. signing_pubkey ( ) . unwrap ( ) ;
658+ let message = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & invoice. bytes ) ;
659+ assert ! (
660+ merkle:: verify_signature( & invoice. signature, & message, offer_signing_pubkey) . is_ok( )
661+ ) ;
662+
663+ let paths = vec ! [ blinded_path( ) ] ;
664+ let metadata = vec ! [ 42 ; 16 ] ;
665+ assert_eq ! (
666+ invoice. as_tlv_stream( ) ,
667+ (
668+ OfferTlvStreamRef {
669+ chains: None ,
670+ metadata: Some ( & metadata) ,
671+ currency: None ,
672+ amount: None ,
673+ description: None ,
674+ features: None ,
675+ absolute_expiry: None ,
676+ paths: Some ( & paths) ,
677+ issuer: None ,
678+ quantity_max: None ,
679+ node_id: Some ( & offer_signing_pubkey) ,
680+ } ,
681+ InvoiceTlvStreamRef {
682+ paths: Some ( Iterable ( payment_paths. iter( ) . map( |( _, path) | path) ) ) ,
683+ blindedpay: Some ( Iterable ( payment_paths. iter( ) . map( |( payinfo, _) | payinfo) ) ) ,
684+ created_at: Some ( now. as_secs( ) ) ,
685+ relative_expiry: None ,
686+ payment_hash: None ,
687+ amount: None ,
688+ fallbacks: None ,
689+ features: None ,
690+ node_id: Some ( & offer_signing_pubkey) ,
691+ message_paths: Some ( & paths) ,
692+ } ,
693+ SignatureTlvStreamRef { signature: Some ( & invoice. signature( ) ) } ,
694+ )
695+ ) ;
696+
697+ if let Err ( e) = StaticInvoice :: try_from ( buffer) {
698+ panic ! ( "error parsing invoice: {:?}" , e) ;
699+ }
700+ }
701+
702+ #[ cfg( feature = "std" ) ]
703+ #[ test]
704+ fn builds_invoice_from_offer_with_expiration ( ) {
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 future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
712+ let past_expiry = Duration :: from_secs ( 0 ) ;
713+
714+ let valid_offer =
715+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
716+ . path ( blinded_path ( ) )
717+ . absolute_expiry ( future_expiry)
718+ . build ( )
719+ . unwrap ( ) ;
720+
721+ let invoice = StaticInvoiceBuilder :: for_offer_using_derived_keys (
722+ & valid_offer,
723+ payment_paths ( ) ,
724+ vec ! [ blinded_path( ) ] ,
725+ now,
726+ & expanded_key,
727+ & secp_ctx,
728+ )
729+ . unwrap ( )
730+ . build_and_sign ( & secp_ctx)
731+ . unwrap ( ) ;
732+ assert ! ( !invoice. is_expired( ) ) ;
733+ assert_eq ! ( invoice. absolute_expiry( ) , Some ( future_expiry) ) ;
734+
735+ let expired_offer =
736+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
737+ . path ( blinded_path ( ) )
738+ . absolute_expiry ( past_expiry)
739+ . build ( )
740+ . unwrap ( ) ;
741+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
742+ & expired_offer,
743+ payment_paths ( ) ,
744+ vec ! [ blinded_path( ) ] ,
745+ now,
746+ & expanded_key,
747+ & secp_ctx,
748+ )
749+ . unwrap ( )
750+ . build_and_sign ( & secp_ctx)
751+ {
752+ assert_eq ! ( e, Bolt12SemanticError :: AlreadyExpired ) ;
753+ } else {
754+ panic ! ( "expected error" )
755+ }
756+ }
757+
758+ #[ test]
759+ fn fails_build_with_missing_paths ( ) {
760+ let node_id = recipient_pubkey ( ) ;
761+ let now = now ( ) ;
762+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
763+ let entropy = FixedEntropy { } ;
764+ let secp_ctx = Secp256k1 :: new ( ) ;
765+
766+ let valid_offer =
767+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
768+ . path ( blinded_path ( ) )
769+ . build ( )
770+ . unwrap ( ) ;
771+
772+ // Error if payment paths are missing.
773+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
774+ & valid_offer,
775+ Vec :: new ( ) ,
776+ vec ! [ blinded_path( ) ] ,
777+ now,
778+ & expanded_key,
779+ & secp_ctx,
780+ ) {
781+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
782+ } else {
783+ panic ! ( "expected error" )
784+ }
785+
786+ // Error if message paths are missing.
787+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
788+ & valid_offer,
789+ payment_paths ( ) ,
790+ Vec :: new ( ) ,
791+ now,
792+ & expanded_key,
793+ & secp_ctx,
794+ ) {
795+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
796+ } else {
797+ panic ! ( "expected error" )
798+ }
799+
800+ // Error if offer paths are missing.
801+ let mut offer_without_paths = valid_offer. clone ( ) ;
802+ let mut offer_tlv_stream = offer_without_paths. as_tlv_stream ( ) ;
803+ offer_tlv_stream. paths . take ( ) ;
804+ let mut buffer = Vec :: new ( ) ;
805+ offer_tlv_stream. write ( & mut buffer) . unwrap ( ) ;
806+ offer_without_paths = Offer :: try_from ( buffer) . unwrap ( ) ;
807+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
808+ & offer_without_paths,
809+ payment_paths ( ) ,
810+ vec ! [ blinded_path( ) ] ,
811+ now,
812+ & expanded_key,
813+ & secp_ctx,
814+ ) {
815+ assert_eq ! ( e, Bolt12SemanticError :: MissingPaths ) ;
816+ } else {
817+ panic ! ( "expected error" )
818+ }
819+ }
820+
821+ #[ test]
822+ fn fails_build_offer_signing_pubkey ( ) {
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 valid_offer =
830+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
831+ . path ( blinded_path ( ) )
832+ . build ( )
833+ . unwrap ( ) ;
834+
835+ // Error if offer signing pubkey is missing.
836+ let mut offer_missing_signing_pubkey = valid_offer. clone ( ) ;
837+ let mut offer_tlv_stream = offer_missing_signing_pubkey. as_tlv_stream ( ) ;
838+ offer_tlv_stream. node_id . take ( ) ;
839+ let mut buffer = Vec :: new ( ) ;
840+ offer_tlv_stream. write ( & mut buffer) . unwrap ( ) ;
841+ offer_missing_signing_pubkey = Offer :: try_from ( buffer) . unwrap ( ) ;
842+
843+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
844+ & offer_missing_signing_pubkey,
845+ payment_paths ( ) ,
846+ vec ! [ blinded_path( ) ] ,
847+ now,
848+ & expanded_key,
849+ & secp_ctx,
850+ ) {
851+ assert_eq ! ( e, Bolt12SemanticError :: MissingSigningPubkey ) ;
852+ } else {
853+ panic ! ( "expected error" )
854+ }
855+
856+ // Error if the offer's metadata cannot be verified.
857+ let offer = OfferBuilder :: new ( recipient_pubkey ( ) )
858+ . path ( blinded_path ( ) )
859+ . metadata ( vec ! [ 42 ; 32 ] )
860+ . unwrap ( )
861+ . build ( )
862+ . unwrap ( ) ;
863+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
864+ & offer,
865+ payment_paths ( ) ,
866+ vec ! [ blinded_path( ) ] ,
867+ now,
868+ & expanded_key,
869+ & secp_ctx,
870+ ) {
871+ assert_eq ! ( e, Bolt12SemanticError :: InvalidMetadata ) ;
872+ } else {
873+ panic ! ( "expected error" )
874+ }
875+ }
876+
877+ #[ test]
878+ fn fails_building_with_extra_offer_chains ( ) {
879+ let node_id = recipient_pubkey ( ) ;
880+ let now = now ( ) ;
881+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
882+ let entropy = FixedEntropy { } ;
883+ let secp_ctx = Secp256k1 :: new ( ) ;
884+
885+ let offer_with_extra_chain =
886+ OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, & entropy, & secp_ctx)
887+ . path ( blinded_path ( ) )
888+ . chain ( Network :: Bitcoin )
889+ . chain ( Network :: Testnet )
890+ . build ( )
891+ . unwrap ( ) ;
892+
893+ if let Err ( e) = StaticInvoiceBuilder :: for_offer_using_derived_keys (
894+ & offer_with_extra_chain,
895+ payment_paths ( ) ,
896+ vec ! [ blinded_path( ) ] ,
897+ now,
898+ & expanded_key,
899+ & secp_ctx,
900+ ) {
901+ assert_eq ! ( e, Bolt12SemanticError :: UnexpectedChain ) ;
902+ } else {
903+ panic ! ( "expected error" )
904+ }
905+ }
906+ }
0 commit comments