@@ -80,8 +80,9 @@ use crate::ln::features::OfferFeatures;
8080use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
8181use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
8282use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
83+ use crate :: offers:: merkle:: TlvStream ;
8384use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
84- use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
85+ use crate :: offers:: signer:: { Metadata , MetadataMaterial , self } ;
8586use crate :: onion_message:: BlindedPath ;
8687use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
8788use crate :: util:: string:: PrintableString ;
@@ -149,10 +150,11 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
149150 /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
150151 /// provided `node_id` is used for the signing pubkey.
151152 ///
152- /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used to
153- /// verify that an [`InvoiceRequest`] was produced for the offer given an [`ExpandedKey`].
153+ /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
154+ /// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
155+ /// [`ExpandedKey`].
154156 ///
155- /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
157+ /// [`InvoiceRequest::verify `]: crate::offers::invoice_request::InvoiceRequest::verify
156158 /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
157159 pub fn deriving_signing_pubkey < ES : Deref > (
158160 description : String , node_id : PublicKey , expanded_key : & ExpandedKey , entropy_source : ES ,
@@ -564,6 +566,27 @@ impl OfferContents {
564566 self . signing_pubkey
565567 }
566568
569+ /// Verifies that the offer metadata was produced from the offer in the TLV stream.
570+ pub ( super ) fn verify < T : secp256k1:: Signing > (
571+ & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey , secp_ctx : & Secp256k1 < T >
572+ ) -> bool {
573+ match self . metadata ( ) {
574+ Some ( metadata) => {
575+ let tlv_stream = tlv_stream. range ( OFFER_TYPES ) . filter ( |record| {
576+ match record. r#type {
577+ OFFER_METADATA_TYPE => false ,
578+ OFFER_NODE_ID_TYPE => false ,
579+ _ => true ,
580+ }
581+ } ) ;
582+ signer:: verify_metadata (
583+ metadata, key, IV_BYTES , self . signing_pubkey ( ) , tlv_stream, secp_ctx
584+ )
585+ } ,
586+ None => false ,
587+ }
588+ }
589+
567590 pub ( super ) fn as_tlv_stream ( & self ) -> OfferTlvStreamRef {
568591 let ( currency, amount) = match & self . amount {
569592 None => ( None , None ) ,
@@ -651,9 +674,18 @@ impl Quantity {
651674 }
652675}
653676
654- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
677+ /// Valid type range for offer TLV records.
678+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
679+
680+ /// TLV record type for [`Offer::metadata`].
681+ const OFFER_METADATA_TYPE : u64 = 4 ;
682+
683+ /// TLV record type for [`Offer::signing_pubkey`].
684+ const OFFER_NODE_ID_TYPE : u64 = 22 ;
685+
686+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
655687 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
656- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
688+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
657689 ( 6 , currency: CurrencyCode ) ,
658690 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
659691 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -662,7 +694,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
662694 ( 16 , paths: ( Vec <BlindedPath >, WithoutLength ) ) ,
663695 ( 18 , issuer: ( String , WithoutLength ) ) ,
664696 ( 20 , quantity_max: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
665- ( 22 , node_id: PublicKey ) ,
697+ ( OFFER_NODE_ID_TYPE , node_id: PublicKey ) ,
666698} ) ;
667699
668700impl Bech32Encode for Offer {
@@ -749,10 +781,13 @@ mod tests {
749781
750782 use bitcoin:: blockdata:: constants:: ChainHash ;
751783 use bitcoin:: network:: constants:: Network ;
784+ use bitcoin:: secp256k1:: Secp256k1 ;
752785 use core:: convert:: TryFrom ;
753786 use core:: num:: NonZeroU64 ;
754787 use core:: time:: Duration ;
788+ use crate :: chain:: keysinterface:: KeyMaterial ;
755789 use crate :: ln:: features:: OfferFeatures ;
790+ use crate :: ln:: inbound_payment:: ExpandedKey ;
756791 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
757792 use crate :: offers:: parse:: { ParseError , SemanticError } ;
758793 use crate :: offers:: test_utils:: * ;
@@ -863,6 +898,110 @@ mod tests {
863898 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
864899 }
865900
901+ #[ test]
902+ fn builds_offer_with_metadata_derived ( ) {
903+ let desc = "foo" . to_string ( ) ;
904+ let node_id = recipient_pubkey ( ) ;
905+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
906+ let entropy = FixedEntropy { } ;
907+ let secp_ctx = Secp256k1 :: new ( ) ;
908+
909+ let offer = OfferBuilder
910+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
911+ . amount_msats ( 1000 )
912+ . build ( ) . unwrap ( ) ;
913+ assert_eq ! ( offer. signing_pubkey( ) , node_id) ;
914+
915+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
916+ . build ( ) . unwrap ( )
917+ . sign ( payer_sign) . unwrap ( ) ;
918+ assert ! ( invoice_request. verify( & expanded_key, & secp_ctx) ) ;
919+
920+ // Fails verification with altered offer field
921+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
922+ tlv_stream. amount = Some ( 100 ) ;
923+
924+ let mut encoded_offer = Vec :: new ( ) ;
925+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
926+
927+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
928+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
929+ . build ( ) . unwrap ( )
930+ . sign ( payer_sign) . unwrap ( ) ;
931+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
932+
933+ // Fails verification with altered metadata
934+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
935+ let metadata = tlv_stream. metadata . unwrap ( ) . iter ( ) . copied ( ) . rev ( ) . collect ( ) ;
936+ tlv_stream. metadata = Some ( & metadata) ;
937+
938+ let mut encoded_offer = Vec :: new ( ) ;
939+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
940+
941+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
942+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
943+ . build ( ) . unwrap ( )
944+ . sign ( payer_sign) . unwrap ( ) ;
945+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
946+ }
947+
948+ #[ test]
949+ fn builds_offer_with_derived_signing_pubkey ( ) {
950+ let desc = "foo" . to_string ( ) ;
951+ let node_id = recipient_pubkey ( ) ;
952+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
953+ let entropy = FixedEntropy { } ;
954+ let secp_ctx = Secp256k1 :: new ( ) ;
955+
956+ let blinded_path = BlindedPath {
957+ introduction_node_id : pubkey ( 40 ) ,
958+ blinding_point : pubkey ( 41 ) ,
959+ blinded_hops : vec ! [
960+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
961+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
962+ ] ,
963+ } ;
964+
965+ let offer = OfferBuilder
966+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
967+ . amount_msats ( 1000 )
968+ . path ( blinded_path)
969+ . build ( ) . unwrap ( ) ;
970+ assert_ne ! ( offer. signing_pubkey( ) , node_id) ;
971+
972+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
973+ . build ( ) . unwrap ( )
974+ . sign ( payer_sign) . unwrap ( ) ;
975+ assert ! ( invoice_request. verify( & expanded_key, & secp_ctx) ) ;
976+
977+ // Fails verification with altered offer field
978+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
979+ tlv_stream. amount = Some ( 100 ) ;
980+
981+ let mut encoded_offer = Vec :: new ( ) ;
982+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
983+
984+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
985+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
986+ . build ( ) . unwrap ( )
987+ . sign ( payer_sign) . unwrap ( ) ;
988+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
989+
990+ // Fails verification with altered signing pubkey
991+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
992+ let signing_pubkey = pubkey ( 1 ) ;
993+ tlv_stream. node_id = Some ( & signing_pubkey) ;
994+
995+ let mut encoded_offer = Vec :: new ( ) ;
996+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
997+
998+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
999+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1000+ . build ( ) . unwrap ( )
1001+ . sign ( payer_sign) . unwrap ( ) ;
1002+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
1003+ }
1004+
8661005 #[ test]
8671006 fn builds_offer_with_amount ( ) {
8681007 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments