@@ -85,11 +85,12 @@ use crate::ln::features::InvoiceRequestFeatures;
8585use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
8686use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
8787use crate :: offers:: invoice:: { BlindedPayInfo , InvoiceBuilder } ;
88- use crate :: offers:: invoice_request:: { InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
89- use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
88+ use crate :: offers:: invoice_request:: { INVOICE_REQUEST_PAYER_ID_TYPE , INVOICE_REQUEST_TYPES , InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
89+ use crate :: offers:: merkle:: TlvStream ;
90+ use crate :: offers:: offer:: { OFFER_TYPES , OfferTlvStream , OfferTlvStreamRef } ;
9091use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
91- use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
92- use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
92+ use crate :: offers:: payer:: { PAYER_METADATA_TYPE , PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
93+ use crate :: offers:: signer:: { Metadata , MetadataMaterial , self } ;
9394use crate :: onion_message:: BlindedPath ;
9495use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
9596use crate :: util:: string:: PrintableString ;
@@ -343,7 +344,7 @@ impl Refund {
343344 ///
344345 /// [`payer_id`]: Self::payer_id
345346 pub fn metadata ( & self ) -> & [ u8 ] {
346- self . contents . payer . 0 . as_bytes ( ) . map ( |bytes| bytes . as_slice ( ) ) . unwrap_or ( & [ ] )
347+ self . contents . metadata ( )
347348 }
348349
349350 /// A chain that the refund is valid for.
@@ -455,6 +456,10 @@ impl RefundContents {
455456 }
456457 }
457458
459+ fn metadata ( & self ) -> & [ u8 ] {
460+ self . payer . 0 . as_bytes ( ) . map ( |bytes| bytes. as_slice ( ) ) . unwrap_or ( & [ ] )
461+ }
462+
458463 pub ( super ) fn chain ( & self ) -> ChainHash {
459464 self . chain . unwrap_or_else ( || self . implied_chain ( ) )
460465 }
@@ -463,6 +468,22 @@ impl RefundContents {
463468 ChainHash :: using_genesis_block ( Network :: Bitcoin )
464469 }
465470
471+ /// Verifies that the payer metadata was produced from the refund in the TLV stream.
472+ pub ( super ) fn verify < T : secp256k1:: Signing > (
473+ & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey , secp_ctx : & Secp256k1 < T >
474+ ) -> bool {
475+ let offer_records = tlv_stream. clone ( ) . range ( OFFER_TYPES ) ;
476+ let invreq_records = tlv_stream. range ( INVOICE_REQUEST_TYPES ) . filter ( |record| {
477+ match record. r#type {
478+ PAYER_METADATA_TYPE => false , // Should be outside range
479+ INVOICE_REQUEST_PAYER_ID_TYPE => !self . payer . 0 . derives_keys ( ) ,
480+ _ => true ,
481+ }
482+ } ) ;
483+ let tlv_stream = offer_records. chain ( invreq_records) ;
484+ signer:: verify_metadata ( self . metadata ( ) , key, IV_BYTES , self . payer_id , tlv_stream, secp_ctx)
485+ }
486+
466487 pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
467488 let payer = PayerTlvStreamRef {
468489 metadata : self . payer . 0 . as_bytes ( ) ,
@@ -640,7 +661,9 @@ mod tests {
640661 use bitcoin:: secp256k1:: { KeyPair , Secp256k1 , SecretKey } ;
641662 use core:: convert:: TryFrom ;
642663 use core:: time:: Duration ;
664+ use crate :: chain:: keysinterface:: KeyMaterial ;
643665 use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
666+ use crate :: ln:: inbound_payment:: ExpandedKey ;
644667 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
645668 use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
646669 use crate :: offers:: offer:: OfferTlvStreamRef ;
@@ -726,6 +749,118 @@ mod tests {
726749 }
727750 }
728751
752+ #[ test]
753+ fn builds_refund_with_metadata_derived ( ) {
754+ let desc = "foo" . to_string ( ) ;
755+ let node_id = payer_pubkey ( ) ;
756+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
757+ let entropy = FixedEntropy { } ;
758+ let secp_ctx = Secp256k1 :: new ( ) ;
759+
760+ let refund = RefundBuilder
761+ :: deriving_payer_id ( desc, node_id, & expanded_key, & entropy, & secp_ctx, 1000 )
762+ . unwrap ( )
763+ . build ( ) . unwrap ( ) ;
764+ assert_eq ! ( refund. payer_id( ) , node_id) ;
765+
766+ // Fails verification with altered fields
767+ let invoice = refund
768+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
769+ . unwrap ( )
770+ . build ( ) . unwrap ( )
771+ . sign ( recipient_sign) . unwrap ( ) ;
772+ assert ! ( invoice. verify( & expanded_key, & secp_ctx) ) ;
773+
774+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
775+ tlv_stream. 2 . amount = Some ( 2000 ) ;
776+
777+ let mut encoded_refund = Vec :: new ( ) ;
778+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
779+
780+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
781+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
782+ . unwrap ( )
783+ . build ( ) . unwrap ( )
784+ . sign ( recipient_sign) . unwrap ( ) ;
785+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
786+
787+ // Fails verification with altered metadata
788+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
789+ let metadata = tlv_stream. 0 . metadata . unwrap ( ) . iter ( ) . copied ( ) . rev ( ) . collect ( ) ;
790+ tlv_stream. 0 . metadata = Some ( & metadata) ;
791+
792+ let mut encoded_refund = Vec :: new ( ) ;
793+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
794+
795+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
796+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
797+ . unwrap ( )
798+ . build ( ) . unwrap ( )
799+ . sign ( recipient_sign) . unwrap ( ) ;
800+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
801+ }
802+
803+ #[ test]
804+ fn builds_refund_with_derived_payer_id ( ) {
805+ let desc = "foo" . to_string ( ) ;
806+ let node_id = payer_pubkey ( ) ;
807+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
808+ let entropy = FixedEntropy { } ;
809+ let secp_ctx = Secp256k1 :: new ( ) ;
810+
811+ let blinded_path = BlindedPath {
812+ introduction_node_id : pubkey ( 40 ) ,
813+ blinding_point : pubkey ( 41 ) ,
814+ blinded_hops : vec ! [
815+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
816+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
817+ ] ,
818+ } ;
819+
820+ let refund = RefundBuilder
821+ :: deriving_payer_id ( desc, node_id, & expanded_key, & entropy, & secp_ctx, 1000 )
822+ . unwrap ( )
823+ . path ( blinded_path)
824+ . build ( ) . unwrap ( ) ;
825+ assert_ne ! ( refund. payer_id( ) , node_id) ;
826+
827+ let invoice = refund
828+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
829+ . unwrap ( )
830+ . build ( ) . unwrap ( )
831+ . sign ( recipient_sign) . unwrap ( ) ;
832+ assert ! ( invoice. verify( & expanded_key, & secp_ctx) ) ;
833+
834+ // Fails verification with altered fields
835+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
836+ tlv_stream. 2 . amount = Some ( 2000 ) ;
837+
838+ let mut encoded_refund = Vec :: new ( ) ;
839+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
840+
841+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
842+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
843+ . unwrap ( )
844+ . build ( ) . unwrap ( )
845+ . sign ( recipient_sign) . unwrap ( ) ;
846+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
847+
848+ // Fails verification with altered payer_id
849+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
850+ let payer_id = pubkey ( 1 ) ;
851+ tlv_stream. 2 . payer_id = Some ( & payer_id) ;
852+
853+ let mut encoded_refund = Vec :: new ( ) ;
854+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
855+
856+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
857+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
858+ . unwrap ( )
859+ . build ( ) . unwrap ( )
860+ . sign ( recipient_sign) . unwrap ( ) ;
861+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
862+ }
863+
729864 #[ test]
730865 fn builds_refund_with_absolute_expiry ( ) {
731866 let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
0 commit comments