@@ -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 ;
@@ -341,7 +342,7 @@ impl Refund {
341
342
///
342
343
/// [`payer_id`]: Self::payer_id
343
344
pub fn metadata ( & self ) -> & [ u8 ] {
344
- self . contents . payer . 0 . as_bytes ( ) . map ( |bytes| bytes . as_slice ( ) ) . unwrap_or ( & [ ] )
345
+ self . contents . metadata ( )
345
346
}
346
347
347
348
/// A chain that the refund is valid for.
@@ -453,6 +454,10 @@ impl RefundContents {
453
454
}
454
455
}
455
456
457
+ fn metadata ( & self ) -> & [ u8 ] {
458
+ self . payer . 0 . as_bytes ( ) . map ( |bytes| bytes. as_slice ( ) ) . unwrap_or ( & [ ] )
459
+ }
460
+
456
461
pub ( super ) fn chain ( & self ) -> ChainHash {
457
462
self . chain . unwrap_or_else ( || self . implied_chain ( ) )
458
463
}
@@ -461,6 +466,22 @@ impl RefundContents {
461
466
ChainHash :: using_genesis_block ( Network :: Bitcoin )
462
467
}
463
468
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
+
464
485
pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
465
486
let payer = PayerTlvStreamRef {
466
487
metadata : self . payer . 0 . as_bytes ( ) ,
@@ -638,7 +659,9 @@ mod tests {
638
659
use bitcoin:: secp256k1:: { KeyPair , Secp256k1 , SecretKey } ;
639
660
use core:: convert:: TryFrom ;
640
661
use core:: time:: Duration ;
662
+ use crate :: chain:: keysinterface:: KeyMaterial ;
641
663
use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
664
+ use crate :: ln:: inbound_payment:: ExpandedKey ;
642
665
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
643
666
use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
644
667
use crate :: offers:: offer:: OfferTlvStreamRef ;
@@ -724,6 +747,118 @@ mod tests {
724
747
}
725
748
}
726
749
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
+
727
862
#[ test]
728
863
fn builds_refund_with_absolute_expiry ( ) {
729
864
let future_expiry = Duration :: from_secs ( u64:: max_value ( ) ) ;
0 commit comments