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