@@ -78,8 +78,9 @@ use crate::ln::features::OfferFeatures;
7878use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
7979use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
8080use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
81+ use crate :: offers:: merkle:: TlvStream ;
8182use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
82- use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey } ;
83+ use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey , self } ;
8384use crate :: onion_message:: BlindedPath ;
8485use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
8586use crate :: util:: string:: PrintableString ;
@@ -543,6 +544,24 @@ impl OfferContents {
543544 self . signing_pubkey
544545 }
545546
547+ /// Verifies that the offer metadata was produced from the offer in the TLV stream.
548+ pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
549+ match & self . metadata {
550+ Some ( metadata) => {
551+ let tlv_stream = tlv_stream. range ( OFFER_TYPES ) . filter ( |record| {
552+ match record. r#type {
553+ // TODO: Assert value bytes == metadata?
554+ OFFER_METADATA_TYPE => false ,
555+ OFFER_NODE_ID_TYPE => false ,
556+ _ => true ,
557+ }
558+ } ) ;
559+ signer:: verify_metadata ( metadata, key, tlv_stream)
560+ } ,
561+ None => false ,
562+ }
563+ }
564+
546565 pub ( super ) fn as_tlv_stream ( & self ) -> OfferTlvStreamRef {
547566 let ( currency, amount) = match & self . amount {
548567 None => ( None , None ) ,
@@ -630,9 +649,18 @@ impl Quantity {
630649 }
631650}
632651
633- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
652+ /// Valid type range for offer TLV records.
653+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
654+
655+ /// TLV record type for [`Offer::metadata`].
656+ const OFFER_METADATA_TYPE : u64 = 4 ;
657+
658+ /// TLV record type for [`Offer::signing_pubkey`].
659+ const OFFER_NODE_ID_TYPE : u64 = 22 ;
660+
661+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
634662 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
635- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
663+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
636664 ( 6 , currency: CurrencyCode ) ,
637665 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
638666 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -641,7 +669,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
641669 ( 16 , paths: ( Vec <BlindedPath >, WithoutLength ) ) ,
642670 ( 18 , issuer: ( String , WithoutLength ) ) ,
643671 ( 20 , quantity_max: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
644- ( 22 , node_id: PublicKey ) ,
672+ ( OFFER_NODE_ID_TYPE , node_id: PublicKey ) ,
645673} ) ;
646674
647675impl Bech32Encode for Offer {
@@ -729,9 +757,12 @@ mod tests {
729757 use core:: convert:: TryFrom ;
730758 use core:: num:: NonZeroU64 ;
731759 use core:: time:: Duration ;
760+ use crate :: chain:: keysinterface:: KeyMaterial ;
732761 use crate :: ln:: features:: OfferFeatures ;
762+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
733763 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
734764 use crate :: offers:: parse:: { ParseError , SemanticError } ;
765+ use crate :: offers:: signer:: DerivedPubkey ;
735766 use crate :: offers:: test_utils:: * ;
736767 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
737768 use crate :: util:: ser:: { BigSize , Writeable } ;
@@ -840,6 +871,82 @@ mod tests {
840871 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
841872 }
842873
874+ #[ test]
875+ fn builds_offer_with_metadata_derived ( ) {
876+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
877+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
878+
879+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
880+ . amount_msats ( 1000 )
881+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
882+ . build ( ) . unwrap ( ) ;
883+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
884+ assert_eq ! ( offer. signing_pubkey( ) , recipient_pubkey( ) ) ;
885+
886+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
887+ . build ( ) . unwrap ( )
888+ . sign ( payer_sign) . unwrap ( ) ;
889+ assert ! ( invoice_request. verify( & expanded_key) ) ;
890+
891+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
892+ tlv_stream. amount = Some ( 100 ) ;
893+
894+ let mut encoded_offer = Vec :: new ( ) ;
895+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
896+
897+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
898+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
899+ . build ( ) . unwrap ( )
900+ . sign ( payer_sign) . unwrap ( ) ;
901+ assert ! ( !invoice_request. verify( & expanded_key) ) ;
902+
903+ match OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
904+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
905+ . metadata_derived ( & expanded_key, nonce)
906+ {
907+ Ok ( _) => panic ! ( "expected error" ) ,
908+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
909+ }
910+ }
911+
912+ #[ test]
913+ fn builds_offer_with_derived_signing_pubkey ( ) {
914+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
915+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
916+
917+ let recipient_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
918+ let offer = OfferBuilder :: deriving_signing_pubkey ( "foo" . into ( ) , recipient_pubkey)
919+ . amount_msats ( 1000 )
920+ . build ( ) . unwrap ( ) ;
921+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
922+ assert_eq ! ( offer. signing_pubkey( ) , expanded_key. signing_pubkey_for_offer( nonce) ) ;
923+
924+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
925+ . build ( ) . unwrap ( )
926+ . sign ( payer_sign) . unwrap ( ) ;
927+ assert ! ( invoice_request. verify( & expanded_key) ) ;
928+
929+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
930+ tlv_stream. amount = Some ( 100 ) ;
931+
932+ let mut encoded_offer = Vec :: new ( ) ;
933+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
934+
935+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
936+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
937+ . build ( ) . unwrap ( )
938+ . sign ( payer_sign) . unwrap ( ) ;
939+ assert ! ( !invoice_request. verify( & expanded_key) ) ;
940+
941+ let recipient_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
942+ match OfferBuilder :: deriving_signing_pubkey ( "foo" . into ( ) , recipient_pubkey)
943+ . metadata_derived ( & expanded_key, nonce)
944+ {
945+ Ok ( _) => panic ! ( "expected error" ) ,
946+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
947+ }
948+ }
949+
843950 #[ test]
844951 fn builds_offer_with_amount ( ) {
845952 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments