@@ -85,11 +85,12 @@ use crate::ln::features::InvoiceRequestFeatures;
85
85
use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
86
86
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
87
87
use 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 } ;
90
91
use 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 } ;
93
94
use crate :: onion_message:: BlindedPath ;
94
95
use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
95
96
use crate :: util:: string:: PrintableString ;
@@ -343,7 +344,7 @@ impl Refund {
343
344
///
344
345
/// [`payer_id`]: Self::payer_id
345
346
pub fn metadata ( & self ) -> & [ u8 ] {
346
- self . contents . payer . 0 . as_bytes ( ) . map ( |bytes| bytes . as_slice ( ) ) . unwrap_or ( & [ ] )
347
+ self . contents . metadata ( )
347
348
}
348
349
349
350
/// A chain that the refund is valid for.
@@ -455,6 +456,10 @@ impl RefundContents {
455
456
}
456
457
}
457
458
459
+ fn metadata ( & self ) -> & [ u8 ] {
460
+ self . payer . 0 . as_bytes ( ) . map ( |bytes| bytes. as_slice ( ) ) . unwrap_or ( & [ ] )
461
+ }
462
+
458
463
pub ( super ) fn chain ( & self ) -> ChainHash {
459
464
self . chain . unwrap_or_else ( || self . implied_chain ( ) )
460
465
}
@@ -463,6 +468,22 @@ impl RefundContents {
463
468
ChainHash :: using_genesis_block ( Network :: Bitcoin )
464
469
}
465
470
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
+
466
487
pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
467
488
let payer = PayerTlvStreamRef {
468
489
metadata : self . payer . 0 . as_bytes ( ) ,
@@ -640,7 +661,9 @@ mod tests {
640
661
use bitcoin:: secp256k1:: { KeyPair , Secp256k1 , SecretKey } ;
641
662
use core:: convert:: TryFrom ;
642
663
use core:: time:: Duration ;
664
+ use crate :: chain:: keysinterface:: KeyMaterial ;
643
665
use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
666
+ use crate :: ln:: inbound_payment:: ExpandedKey ;
644
667
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
645
668
use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
646
669
use crate :: offers:: offer:: OfferTlvStreamRef ;
@@ -726,6 +749,118 @@ mod tests {
726
749
}
727
750
}
728
751
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
+
729
864
#[ test]
730
865
fn builds_refund_with_absolute_expiry ( ) {
731
866
let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
0 commit comments