@@ -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 ;
@@ -341,7 +342,7 @@ impl Refund {
341342 ///
342343 /// [`payer_id`]: Self::payer_id
343344 pub fn metadata ( & self ) -> & [ u8 ] {
344- self . contents . payer . 0 . as_bytes ( ) . map ( |bytes| bytes . as_slice ( ) ) . unwrap_or ( & [ ] )
345+ self . contents . metadata ( )
345346 }
346347
347348 /// A chain that the refund is valid for.
@@ -453,6 +454,10 @@ impl RefundContents {
453454 }
454455 }
455456
457+ fn metadata ( & self ) -> & [ u8 ] {
458+ self . payer . 0 . as_bytes ( ) . map ( |bytes| bytes. as_slice ( ) ) . unwrap_or ( & [ ] )
459+ }
460+
456461 pub ( super ) fn chain ( & self ) -> ChainHash {
457462 self . chain . unwrap_or_else ( || self . implied_chain ( ) )
458463 }
@@ -461,6 +466,22 @@ impl RefundContents {
461466 ChainHash :: using_genesis_block ( Network :: Bitcoin )
462467 }
463468
469+ /// Verifies that the payer metadata was produced from the refund in the TLV stream.
470+ pub ( super ) fn verify < T : secp256k1:: Signing > (
471+ & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey , secp_ctx : & Secp256k1 < T >
472+ ) -> bool {
473+ let offer_records = tlv_stream. clone ( ) . range ( OFFER_TYPES ) ;
474+ let invreq_records = tlv_stream. range ( INVOICE_REQUEST_TYPES ) . filter ( |record| {
475+ match record. r#type {
476+ PAYER_METADATA_TYPE => false , // Should be outside range
477+ INVOICE_REQUEST_PAYER_ID_TYPE => false ,
478+ _ => true ,
479+ }
480+ } ) ;
481+ let tlv_stream = offer_records. chain ( invreq_records) ;
482+ signer:: verify_metadata ( self . metadata ( ) , key, IV_BYTES , self . payer_id , tlv_stream, secp_ctx)
483+ }
484+
464485 pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
465486 let payer = PayerTlvStreamRef {
466487 metadata : self . payer . 0 . as_bytes ( ) ,
@@ -638,7 +659,9 @@ mod tests {
638659 use bitcoin:: secp256k1:: { KeyPair , Secp256k1 , SecretKey } ;
639660 use core:: convert:: TryFrom ;
640661 use core:: time:: Duration ;
662+ use crate :: chain:: keysinterface:: KeyMaterial ;
641663 use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
664+ use crate :: ln:: inbound_payment:: ExpandedKey ;
642665 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
643666 use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
644667 use crate :: offers:: offer:: OfferTlvStreamRef ;
@@ -724,6 +747,118 @@ mod tests {
724747 }
725748 }
726749
750+ #[ test]
751+ fn builds_refund_with_metadata_derived ( ) {
752+ let desc = "foo" . to_string ( ) ;
753+ let node_id = payer_pubkey ( ) ;
754+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
755+ let entropy = FixedEntropy { } ;
756+ let secp_ctx = Secp256k1 :: new ( ) ;
757+
758+ let refund = RefundBuilder
759+ :: deriving_payer_id ( desc, node_id, & expanded_key, & entropy, & secp_ctx, 1000 )
760+ . unwrap ( )
761+ . build ( ) . unwrap ( ) ;
762+ assert_eq ! ( refund. payer_id( ) , node_id) ;
763+
764+ // Fails verification with altered fields
765+ let invoice = refund
766+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
767+ . unwrap ( )
768+ . build ( ) . unwrap ( )
769+ . sign ( recipient_sign) . unwrap ( ) ;
770+ assert ! ( invoice. verify( & expanded_key, & secp_ctx) ) ;
771+
772+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
773+ tlv_stream. 2 . amount = Some ( 2000 ) ;
774+
775+ let mut encoded_refund = Vec :: new ( ) ;
776+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
777+
778+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
779+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
780+ . unwrap ( )
781+ . build ( ) . unwrap ( )
782+ . sign ( recipient_sign) . unwrap ( ) ;
783+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
784+
785+ // Fails verification with altered metadata
786+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
787+ let metadata = tlv_stream. 0 . metadata . unwrap ( ) . iter ( ) . copied ( ) . rev ( ) . collect ( ) ;
788+ tlv_stream. 0 . metadata = Some ( & metadata) ;
789+
790+ let mut encoded_refund = Vec :: new ( ) ;
791+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
792+
793+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
794+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
795+ . unwrap ( )
796+ . build ( ) . unwrap ( )
797+ . sign ( recipient_sign) . unwrap ( ) ;
798+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
799+ }
800+
801+ #[ test]
802+ fn builds_refund_with_derived_payer_id ( ) {
803+ let desc = "foo" . to_string ( ) ;
804+ let node_id = payer_pubkey ( ) ;
805+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
806+ let entropy = FixedEntropy { } ;
807+ let secp_ctx = Secp256k1 :: new ( ) ;
808+
809+ let blinded_path = BlindedPath {
810+ introduction_node_id : pubkey ( 40 ) ,
811+ blinding_point : pubkey ( 41 ) ,
812+ blinded_hops : vec ! [
813+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
814+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
815+ ] ,
816+ } ;
817+
818+ let refund = RefundBuilder
819+ :: deriving_payer_id ( desc, node_id, & expanded_key, & entropy, & secp_ctx, 1000 )
820+ . unwrap ( )
821+ . path ( blinded_path)
822+ . build ( ) . unwrap ( ) ;
823+ assert_ne ! ( refund. payer_id( ) , node_id) ;
824+
825+ let invoice = refund
826+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
827+ . unwrap ( )
828+ . build ( ) . unwrap ( )
829+ . sign ( recipient_sign) . unwrap ( ) ;
830+ assert ! ( invoice. verify( & expanded_key, & secp_ctx) ) ;
831+
832+ // Fails verification with altered fields
833+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
834+ tlv_stream. 2 . amount = Some ( 2000 ) ;
835+
836+ let mut encoded_refund = Vec :: new ( ) ;
837+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
838+
839+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
840+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
841+ . unwrap ( )
842+ . build ( ) . unwrap ( )
843+ . sign ( recipient_sign) . unwrap ( ) ;
844+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
845+
846+ // Fails verification with altered payer_id
847+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
848+ let payer_id = pubkey ( 1 ) ;
849+ tlv_stream. 2 . payer_id = Some ( & payer_id) ;
850+
851+ let mut encoded_refund = Vec :: new ( ) ;
852+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
853+
854+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
855+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
856+ . unwrap ( )
857+ . build ( ) . unwrap ( )
858+ . sign ( recipient_sign) . unwrap ( ) ;
859+ assert ! ( !invoice. verify( & expanded_key, & secp_ctx) ) ;
860+ }
861+
727862 #[ test]
728863 fn builds_refund_with_absolute_expiry ( ) {
729864 let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
0 commit comments