@@ -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 ;
@@ -542,6 +543,24 @@ impl OfferContents {
542543 self . signing_pubkey
543544 }
544545
546+ /// Verifies that the offer metadata was produced from the offer in the TLV stream.
547+ pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
548+ match & self . metadata {
549+ Some ( metadata) => {
550+ let tlv_stream = tlv_stream. range ( OFFER_TYPES ) . filter ( |record| {
551+ match record. r#type {
552+ // TODO: Assert value bytes == metadata?
553+ OFFER_METADATA_TYPE => false ,
554+ OFFER_NODE_ID_TYPE => false ,
555+ _ => true ,
556+ }
557+ } ) ;
558+ signer:: verify_metadata ( metadata, key, tlv_stream)
559+ } ,
560+ None => false ,
561+ }
562+ }
563+
545564 pub ( super ) fn as_tlv_stream ( & self ) -> OfferTlvStreamRef {
546565 let ( currency, amount) = match & self . amount {
547566 None => ( None , None ) ,
@@ -629,9 +648,18 @@ impl Quantity {
629648 }
630649}
631650
632- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
651+ /// Valid type range for offer TLV records.
652+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
653+
654+ /// TLV record type for [`Offer::metadata`].
655+ const OFFER_METADATA_TYPE : u64 = 4 ;
656+
657+ /// TLV record type for [`Offer::signing_pubkey`].
658+ const OFFER_NODE_ID_TYPE : u64 = 22 ;
659+
660+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
633661 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
634- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
662+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
635663 ( 6 , currency: CurrencyCode ) ,
636664 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
637665 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -640,7 +668,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
640668 ( 16 , paths: ( Vec <BlindedPath >, WithoutLength ) ) ,
641669 ( 18 , issuer: ( String , WithoutLength ) ) ,
642670 ( 20 , quantity_max: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
643- ( 22 , node_id: PublicKey ) ,
671+ ( OFFER_NODE_ID_TYPE , node_id: PublicKey ) ,
644672} ) ;
645673
646674impl Bech32Encode for Offer {
@@ -728,9 +756,12 @@ mod tests {
728756 use core:: convert:: TryFrom ;
729757 use core:: num:: NonZeroU64 ;
730758 use core:: time:: Duration ;
759+ use crate :: chain:: keysinterface:: KeyMaterial ;
731760 use crate :: ln:: features:: OfferFeatures ;
761+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
732762 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
733763 use crate :: offers:: parse:: { ParseError , SemanticError } ;
764+ use crate :: offers:: signer:: DerivedPubkey ;
734765 use crate :: offers:: test_utils:: * ;
735766 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
736767 use crate :: util:: ser:: { BigSize , Writeable } ;
@@ -839,6 +870,82 @@ mod tests {
839870 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
840871 }
841872
873+ #[ test]
874+ fn builds_offer_with_metadata_derived ( ) {
875+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
876+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
877+
878+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
879+ . amount_msats ( 1000 )
880+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
881+ . build ( ) . unwrap ( ) ;
882+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
883+ assert_eq ! ( offer. signing_pubkey( ) , recipient_pubkey( ) ) ;
884+
885+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
886+ . build ( ) . unwrap ( )
887+ . sign ( payer_sign) . unwrap ( ) ;
888+ assert ! ( invoice_request. verify( & expanded_key) ) ;
889+
890+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
891+ tlv_stream. amount = Some ( 100 ) ;
892+
893+ let mut encoded_offer = Vec :: new ( ) ;
894+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
895+
896+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
897+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
898+ . build ( ) . unwrap ( )
899+ . sign ( payer_sign) . unwrap ( ) ;
900+ assert ! ( !invoice_request. verify( & expanded_key) ) ;
901+
902+ match OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
903+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
904+ . metadata_derived ( & expanded_key, nonce)
905+ {
906+ Ok ( _) => panic ! ( "expected error" ) ,
907+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
908+ }
909+ }
910+
911+ #[ test]
912+ fn builds_offer_with_derived_signing_pubkey ( ) {
913+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
914+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
915+
916+ let recipient_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
917+ let offer = OfferBuilder :: deriving_signing_pubkey ( "foo" . into ( ) , recipient_pubkey)
918+ . amount_msats ( 1000 )
919+ . build ( ) . unwrap ( ) ;
920+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
921+ assert_eq ! ( offer. signing_pubkey( ) , expanded_key. signing_pubkey_for_offer( nonce) ) ;
922+
923+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
924+ . build ( ) . unwrap ( )
925+ . sign ( payer_sign) . unwrap ( ) ;
926+ assert ! ( invoice_request. verify( & expanded_key) ) ;
927+
928+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
929+ tlv_stream. amount = Some ( 100 ) ;
930+
931+ let mut encoded_offer = Vec :: new ( ) ;
932+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
933+
934+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
935+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
936+ . build ( ) . unwrap ( )
937+ . sign ( payer_sign) . unwrap ( ) ;
938+ assert ! ( !invoice_request. verify( & expanded_key) ) ;
939+
940+ let recipient_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
941+ match OfferBuilder :: deriving_signing_pubkey ( "foo" . into ( ) , recipient_pubkey)
942+ . metadata_derived ( & expanded_key, nonce)
943+ {
944+ Ok ( _) => panic ! ( "expected error" ) ,
945+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
946+ }
947+ }
948+
842949 #[ test]
843950 fn builds_offer_with_amount ( ) {
844951 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments