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