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