@@ -83,11 +83,12 @@ use crate::ln::features::InvoiceRequestFeatures;
8383use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
8484use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
8585use crate :: offers:: invoice:: { BlindedPayInfo , InvoiceBuilder } ;
86- use crate :: offers:: invoice_request:: { InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
87- use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
86+ use crate :: offers:: invoice_request:: { INVOICE_REQUEST_PAYER_ID_TYPE , INVOICE_REQUEST_TYPES , InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
87+ use crate :: offers:: merkle:: TlvStream ;
88+ use crate :: offers:: offer:: { OFFER_TYPES , OfferTlvStream , OfferTlvStreamRef } ;
8889use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
89- use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
90- use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey } ;
90+ use crate :: offers:: payer:: { PAYER_METADATA_TYPE , PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
91+ use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey , self } ;
9192use crate :: onion_message:: BlindedPath ;
9293use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
9394use crate :: util:: string:: PrintableString ;
@@ -461,6 +462,20 @@ impl RefundContents {
461462 ChainHash :: using_genesis_block ( Network :: Bitcoin )
462463 }
463464
465+ /// Verifies that the payer metadata was produced from the refund in the TLV stream.
466+ pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
467+ let offer_records = tlv_stream. clone ( ) . range ( OFFER_TYPES ) ;
468+ let invreq_records = tlv_stream. range ( INVOICE_REQUEST_TYPES ) . filter ( |record| {
469+ match record. r#type {
470+ PAYER_METADATA_TYPE => false , // Should be outside range
471+ INVOICE_REQUEST_PAYER_ID_TYPE => false ,
472+ _ => true ,
473+ }
474+ } ) ;
475+ let tlv_stream = offer_records. chain ( invreq_records) ;
476+ signer:: verify_metadata ( & self . payer . 0 , key, tlv_stream)
477+ }
478+
464479 pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
465480 let payer = PayerTlvStreamRef {
466481 metadata : Some ( & self . payer . 0 ) ,
@@ -638,12 +653,15 @@ mod tests {
638653 use bitcoin:: secp256k1:: { KeyPair , Secp256k1 , SecretKey } ;
639654 use core:: convert:: TryFrom ;
640655 use core:: time:: Duration ;
656+ use crate :: chain:: keysinterface:: KeyMaterial ;
641657 use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
658+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
642659 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
643660 use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
644661 use crate :: offers:: offer:: OfferTlvStreamRef ;
645662 use crate :: offers:: parse:: { ParseError , SemanticError } ;
646663 use crate :: offers:: payer:: PayerTlvStreamRef ;
664+ use crate :: offers:: signer:: DerivedPubkey ;
647665 use crate :: offers:: test_utils:: * ;
648666 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
649667 use crate :: util:: ser:: { BigSize , Writeable } ;
@@ -724,6 +742,87 @@ mod tests {
724742 }
725743 }
726744
745+ #[ test]
746+ fn builds_refund_with_metadata_derived ( ) {
747+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
748+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
749+
750+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
751+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
752+ . build ( ) . unwrap ( ) ;
753+ assert_eq ! ( refund. metadata( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
754+ assert_eq ! ( refund. payer_id( ) , payer_pubkey( ) ) ;
755+
756+ let invoice = refund
757+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
758+ . unwrap ( )
759+ . build ( ) . unwrap ( )
760+ . sign ( recipient_sign) . unwrap ( ) ;
761+ assert ! ( invoice. verify( & expanded_key) ) ;
762+
763+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
764+ tlv_stream. 2 . amount = Some ( 2000 ) ;
765+
766+ let mut encoded_refund = Vec :: new ( ) ;
767+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
768+
769+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
770+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
771+ . unwrap ( )
772+ . build ( ) . unwrap ( )
773+ . sign ( recipient_sign) . unwrap ( ) ;
774+ assert ! ( !invoice. verify( & expanded_key) ) ;
775+
776+ match RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
777+ . metadata_derived ( & expanded_key, nonce) . unwrap ( )
778+ . metadata_derived ( & expanded_key, nonce)
779+ {
780+ Ok ( _) => panic ! ( "expected error" ) ,
781+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
782+ }
783+ }
784+
785+ #[ test]
786+ fn builds_refund_with_derived_payer_id ( ) {
787+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
788+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
789+ let keys = expanded_key. signing_keypair_for_offer ( nonce) ;
790+
791+ let payer_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
792+ let refund = RefundBuilder :: deriving_payer_id ( "foo" . into ( ) , payer_pubkey, 1000 ) . unwrap ( )
793+ . build ( ) . unwrap ( ) ;
794+ assert_eq ! ( refund. metadata( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
795+ assert_eq ! ( refund. payer_id( ) , keys. public_key( ) ) ;
796+
797+ let invoice = refund
798+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
799+ . unwrap ( )
800+ . build ( ) . unwrap ( )
801+ . sign ( recipient_sign) . unwrap ( ) ;
802+ assert ! ( invoice. verify( & expanded_key) ) ;
803+
804+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
805+ tlv_stream. 2 . amount = Some ( 2000 ) ;
806+
807+ let mut encoded_refund = Vec :: new ( ) ;
808+ tlv_stream. write ( & mut encoded_refund) . unwrap ( ) ;
809+
810+ let invoice = Refund :: try_from ( encoded_refund) . unwrap ( )
811+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
812+ . unwrap ( )
813+ . build ( ) . unwrap ( )
814+ . sign ( recipient_sign) . unwrap ( ) ;
815+ assert ! ( !invoice. verify( & expanded_key) ) ;
816+
817+ let payer_pubkey = DerivedPubkey :: new ( & expanded_key, nonce) ;
818+ match RefundBuilder :: deriving_payer_id ( "foo" . into ( ) , payer_pubkey, 1000 ) . unwrap ( )
819+ . metadata_derived ( & expanded_key, nonce)
820+ {
821+ Ok ( _) => panic ! ( "expected error" ) ,
822+ Err ( e) => assert_eq ! ( e, SemanticError :: UnexpectedMetadata ) ,
823+ }
824+ }
825+
727826 #[ test]
728827 fn builds_refund_with_absolute_expiry ( ) {
729828 let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
0 commit comments