@@ -80,8 +80,9 @@ use crate::ln::features::OfferFeatures;
80
80
use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
81
81
use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
82
82
use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
83
+ use crate :: offers:: merkle:: TlvStream ;
83
84
use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
84
- use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
85
+ use crate :: offers:: signer:: { Metadata , MetadataMaterial , self } ;
85
86
use crate :: onion_message:: BlindedPath ;
86
87
use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
87
88
use crate :: util:: string:: PrintableString ;
@@ -149,10 +150,11 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
149
150
/// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
150
151
/// provided `node_id` is used for the signing pubkey.
151
152
///
152
- /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used to
153
- /// verify that an [`InvoiceRequest`] was produced for the offer given an [`ExpandedKey`].
153
+ /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
154
+ /// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
155
+ /// [`ExpandedKey`].
154
156
///
155
- /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
157
+ /// [`InvoiceRequest::verify `]: crate::offers::invoice_request::InvoiceRequest::verify
156
158
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
157
159
pub fn deriving_signing_pubkey < ES : Deref > (
158
160
description : String , node_id : PublicKey , expanded_key : & ExpandedKey , entropy_source : ES ,
@@ -566,6 +568,27 @@ impl OfferContents {
566
568
self . signing_pubkey
567
569
}
568
570
571
+ /// Verifies that the offer metadata was produced from the offer in the TLV stream.
572
+ pub ( super ) fn verify < T : secp256k1:: Signing > (
573
+ & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey , secp_ctx : & Secp256k1 < T >
574
+ ) -> bool {
575
+ match self . metadata ( ) {
576
+ Some ( metadata) => {
577
+ let tlv_stream = tlv_stream. range ( OFFER_TYPES ) . filter ( |record| {
578
+ match record. r#type {
579
+ OFFER_METADATA_TYPE => false ,
580
+ OFFER_NODE_ID_TYPE => !self . metadata . as_ref ( ) . unwrap ( ) . derives_keys ( ) ,
581
+ _ => true ,
582
+ }
583
+ } ) ;
584
+ signer:: verify_metadata (
585
+ metadata, key, IV_BYTES , self . signing_pubkey ( ) , tlv_stream, secp_ctx
586
+ )
587
+ } ,
588
+ None => false ,
589
+ }
590
+ }
591
+
569
592
pub ( super ) fn as_tlv_stream ( & self ) -> OfferTlvStreamRef {
570
593
let ( currency, amount) = match & self . amount {
571
594
None => ( None , None ) ,
@@ -653,9 +676,18 @@ impl Quantity {
653
676
}
654
677
}
655
678
656
- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
679
+ /// Valid type range for offer TLV records.
680
+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
681
+
682
+ /// TLV record type for [`Offer::metadata`].
683
+ const OFFER_METADATA_TYPE : u64 = 4 ;
684
+
685
+ /// TLV record type for [`Offer::signing_pubkey`].
686
+ const OFFER_NODE_ID_TYPE : u64 = 22 ;
687
+
688
+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
657
689
( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
658
- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
690
+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
659
691
( 6 , currency: CurrencyCode ) ,
660
692
( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
661
693
( 10 , description: ( String , WithoutLength ) ) ,
@@ -664,7 +696,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
664
696
( 16 , paths: ( Vec <BlindedPath >, WithoutLength ) ) ,
665
697
( 18 , issuer: ( String , WithoutLength ) ) ,
666
698
( 20 , quantity_max: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
667
- ( 22 , node_id: PublicKey ) ,
699
+ ( OFFER_NODE_ID_TYPE , node_id: PublicKey ) ,
668
700
} ) ;
669
701
670
702
impl Bech32Encode for Offer {
@@ -751,10 +783,13 @@ mod tests {
751
783
752
784
use bitcoin:: blockdata:: constants:: ChainHash ;
753
785
use bitcoin:: network:: constants:: Network ;
786
+ use bitcoin:: secp256k1:: Secp256k1 ;
754
787
use core:: convert:: TryFrom ;
755
788
use core:: num:: NonZeroU64 ;
756
789
use core:: time:: Duration ;
790
+ use crate :: chain:: keysinterface:: KeyMaterial ;
757
791
use crate :: ln:: features:: OfferFeatures ;
792
+ use crate :: ln:: inbound_payment:: ExpandedKey ;
758
793
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
759
794
use crate :: offers:: parse:: { ParseError , SemanticError } ;
760
795
use crate :: offers:: test_utils:: * ;
@@ -865,6 +900,110 @@ mod tests {
865
900
assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
866
901
}
867
902
903
+ #[ test]
904
+ fn builds_offer_with_metadata_derived ( ) {
905
+ let desc = "foo" . to_string ( ) ;
906
+ let node_id = recipient_pubkey ( ) ;
907
+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
908
+ let entropy = FixedEntropy { } ;
909
+ let secp_ctx = Secp256k1 :: new ( ) ;
910
+
911
+ let offer = OfferBuilder
912
+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
913
+ . amount_msats ( 1000 )
914
+ . build ( ) . unwrap ( ) ;
915
+ assert_eq ! ( offer. signing_pubkey( ) , node_id) ;
916
+
917
+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
918
+ . build ( ) . unwrap ( )
919
+ . sign ( payer_sign) . unwrap ( ) ;
920
+ assert ! ( invoice_request. verify( & expanded_key, & secp_ctx) ) ;
921
+
922
+ // Fails verification with altered offer field
923
+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
924
+ tlv_stream. amount = Some ( 100 ) ;
925
+
926
+ let mut encoded_offer = Vec :: new ( ) ;
927
+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
928
+
929
+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
930
+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
931
+ . build ( ) . unwrap ( )
932
+ . sign ( payer_sign) . unwrap ( ) ;
933
+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
934
+
935
+ // Fails verification with altered metadata
936
+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
937
+ let metadata = tlv_stream. metadata . unwrap ( ) . iter ( ) . copied ( ) . rev ( ) . collect ( ) ;
938
+ tlv_stream. metadata = Some ( & metadata) ;
939
+
940
+ let mut encoded_offer = Vec :: new ( ) ;
941
+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
942
+
943
+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
944
+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
945
+ . build ( ) . unwrap ( )
946
+ . sign ( payer_sign) . unwrap ( ) ;
947
+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
948
+ }
949
+
950
+ #[ test]
951
+ fn builds_offer_with_derived_signing_pubkey ( ) {
952
+ let desc = "foo" . to_string ( ) ;
953
+ let node_id = recipient_pubkey ( ) ;
954
+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
955
+ let entropy = FixedEntropy { } ;
956
+ let secp_ctx = Secp256k1 :: new ( ) ;
957
+
958
+ let blinded_path = BlindedPath {
959
+ introduction_node_id : pubkey ( 40 ) ,
960
+ blinding_point : pubkey ( 41 ) ,
961
+ blinded_hops : vec ! [
962
+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
963
+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
964
+ ] ,
965
+ } ;
966
+
967
+ let offer = OfferBuilder
968
+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
969
+ . amount_msats ( 1000 )
970
+ . path ( blinded_path)
971
+ . build ( ) . unwrap ( ) ;
972
+ assert_ne ! ( offer. signing_pubkey( ) , node_id) ;
973
+
974
+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
975
+ . build ( ) . unwrap ( )
976
+ . sign ( payer_sign) . unwrap ( ) ;
977
+ assert ! ( invoice_request. verify( & expanded_key, & secp_ctx) ) ;
978
+
979
+ // Fails verification with altered offer field
980
+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
981
+ tlv_stream. amount = Some ( 100 ) ;
982
+
983
+ let mut encoded_offer = Vec :: new ( ) ;
984
+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
985
+
986
+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
987
+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
988
+ . build ( ) . unwrap ( )
989
+ . sign ( payer_sign) . unwrap ( ) ;
990
+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
991
+
992
+ // Fails verification with altered signing pubkey
993
+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
994
+ let signing_pubkey = pubkey ( 1 ) ;
995
+ tlv_stream. node_id = Some ( & signing_pubkey) ;
996
+
997
+ let mut encoded_offer = Vec :: new ( ) ;
998
+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
999
+
1000
+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
1001
+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1002
+ . build ( ) . unwrap ( )
1003
+ . sign ( payer_sign) . unwrap ( ) ;
1004
+ assert ! ( !invoice_request. verify( & expanded_key, & secp_ctx) ) ;
1005
+ }
1006
+
868
1007
#[ test]
869
1008
fn builds_offer_with_amount ( ) {
870
1009
let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments