@@ -72,13 +72,13 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
72
72
use bitcoin:: hashes:: sha256:: Hash as Sha256 ;
73
73
use bitcoin:: network:: constants:: Network ;
74
74
use bitcoin:: secp256k1:: PublicKey ;
75
- use core:: convert:: TryFrom ;
75
+ use core:: convert:: { TryFrom , TryInto } ;
76
76
use core:: num:: NonZeroU64 ;
77
77
use core:: str:: FromStr ;
78
78
use core:: time:: Duration ;
79
79
use crate :: io;
80
80
use crate :: ln:: features:: OfferFeatures ;
81
- use crate :: ln:: inbound_payment:: ExpandedKey ;
81
+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
82
82
use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
83
83
use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
84
84
use crate :: offers:: merkle:: TlvStream ;
@@ -99,16 +99,16 @@ use std::time::SystemTime;
99
99
/// [module-level documentation]: self
100
100
pub struct OfferBuilder {
101
101
offer : OfferContents ,
102
- hmac : Option < HmacEngine < Sha256 > > ,
102
+ metadata_material : Option < ( Nonce , HmacEngine < Sha256 > ) > ,
103
103
}
104
104
105
105
///
106
- pub enum SigningPubkey {
106
+ pub enum SigningPubkey < ' a > {
107
107
Explicit ( PublicKey ) ,
108
- Derived ,
108
+ Derived ( & ' a ExpandedKey , Nonce ) ,
109
109
}
110
110
111
- impl From < PublicKey > for SigningPubkey {
111
+ impl < ' a > From < PublicKey > for SigningPubkey < ' a > {
112
112
fn from ( pubkey : PublicKey ) -> Self {
113
113
SigningPubkey :: Explicit ( pubkey)
114
114
}
@@ -121,16 +121,20 @@ impl OfferBuilder {
121
121
///
122
122
/// Use a different pubkey per offer to avoid correlating offers.
123
123
pub fn new ( description : String , signing_pubkey : SigningPubkey ) -> Self {
124
- let signing_pubkey = match signing_pubkey {
125
- SigningPubkey :: Explicit ( pubkey) => Some ( pubkey) ,
126
- SigningPubkey :: Derived => None ,
124
+ let ( metadata_material, signing_pubkey) = match signing_pubkey {
125
+ SigningPubkey :: Explicit ( pubkey) => ( None , Some ( pubkey) ) ,
126
+ SigningPubkey :: Derived ( expanded_key, nonce) => {
127
+ let metadata_material = ( nonce, expanded_key. hmac_for_offer ( nonce) ) ;
128
+ let signing_pubkey = expanded_key. signing_pubkey_for_offer ( nonce) ;
129
+ ( Some ( metadata_material) , Some ( signing_pubkey) )
130
+ } ,
127
131
} ;
128
132
let offer = OfferContents {
129
133
chains : None , metadata : None , amount : None , description,
130
134
features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
131
135
supported_quantity : Quantity :: One , signing_pubkey,
132
136
} ;
133
- OfferBuilder { offer, hmac : None }
137
+ OfferBuilder { offer, metadata_material }
134
138
}
135
139
136
140
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -159,21 +163,21 @@ impl OfferBuilder {
159
163
}
160
164
161
165
self . offer . metadata = Some ( metadata) ;
162
- self . hmac = None ;
166
+ self . metadata_material = None ;
163
167
Ok ( self )
164
168
}
165
169
166
170
/// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
167
171
/// calling [`OfferBuilder::build`]. Allows for stateless verification of an [`InvoiceRequest`].
168
172
///
169
173
/// Successive calls to this method will override the previous setting and any previous calls to
170
- /// [`OfferBuilder::metadata`]. Must be called if the builder was constructed with
171
- /// [`SigningPubkey::Derived`] in order to derive [`Offer::signing_pubkey`] .
174
+ /// [`OfferBuilder::metadata`]. Does not need to be called if the builder was constructed with
175
+ /// [`SigningPubkey::Derived`].
172
176
///
173
177
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
174
- pub fn metadata_derived ( mut self , key : & ExpandedKey ) -> Self {
178
+ pub fn metadata_derived ( mut self , key : & ExpandedKey , nonce : Nonce ) -> Self {
175
179
self . offer . metadata = None ;
176
- self . hmac = Some ( key. hmac_for_offer ( ) ) ;
180
+ self . metadata_material = Some ( ( nonce , key. hmac_for_offer ( nonce ) ) ) ;
177
181
self
178
182
}
179
183
@@ -246,14 +250,17 @@ impl OfferBuilder {
246
250
}
247
251
}
248
252
249
- // Created the metadata for stateless verification and derive the signing_pubkey, if needed .
250
- if let Some ( mut hmac) = self . hmac {
253
+ // Created the metadata for stateless verification.
254
+ if let Some ( ( nonce , mut hmac) ) = self . metadata_material {
251
255
debug_assert ! ( self . offer. metadata. is_none( ) ) ;
252
256
let mut tlv_stream = self . offer . as_tlv_stream ( ) ;
253
257
tlv_stream. node_id = None ;
254
258
tlv_stream. write ( & mut hmac) . unwrap ( ) ;
255
259
256
- self . offer . metadata = Some ( Hmac :: from_engine ( hmac) . into_inner ( ) . to_vec ( ) ) ;
260
+ let mut metadata = nonce. as_slice ( ) . to_vec ( ) ;
261
+ metadata. extend_from_slice ( & Hmac :: from_engine ( hmac) . into_inner ( ) ) ;
262
+
263
+ self . offer . metadata = Some ( metadata) ;
257
264
}
258
265
259
266
if self . offer . signing_pubkey . is_none ( ) {
@@ -542,7 +549,12 @@ impl OfferContents {
542
549
pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
543
550
match & self . metadata {
544
551
Some ( metadata) => {
545
- let mut hmac = key. hmac_for_offer ( ) ;
552
+ let mut hmac = if metadata. len ( ) < Nonce :: LENGTH {
553
+ return false ;
554
+ } else {
555
+ let nonce = Nonce ( metadata[ ..Nonce :: LENGTH ] . try_into ( ) . unwrap ( ) ) ;
556
+ key. hmac_for_offer ( nonce)
557
+ } ;
546
558
547
559
for record in tlv_stream. range ( OFFER_TYPES ) {
548
560
match record. r#type {
@@ -553,7 +565,7 @@ impl OfferContents {
553
565
}
554
566
}
555
567
556
- metadata == & Hmac :: from_engine ( hmac) . into_inner ( )
568
+ & metadata[ Nonce :: LENGTH .. ] == & Hmac :: from_engine ( hmac) . into_inner ( )
557
569
} ,
558
570
None => false ,
559
571
}
@@ -746,7 +758,7 @@ impl core::fmt::Display for Offer {
746
758
747
759
#[ cfg( test) ]
748
760
mod tests {
749
- use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity } ;
761
+ use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity , SigningPubkey } ;
750
762
751
763
use bitcoin:: blockdata:: constants:: ChainHash ;
752
764
use bitcoin:: network:: constants:: Network ;
@@ -757,7 +769,7 @@ mod tests {
757
769
use core:: time:: Duration ;
758
770
use crate :: chain:: keysinterface:: KeyMaterial ;
759
771
use crate :: ln:: features:: OfferFeatures ;
760
- use crate :: ln:: inbound_payment:: ExpandedKey ;
772
+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
761
773
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
762
774
use crate :: offers:: parse:: { ParseError , SemanticError } ;
763
775
use crate :: onion_message:: { BlindedHop , BlindedPath } ;
@@ -900,7 +912,7 @@ mod tests {
900
912
fn builds_offer_with_metadata_derived ( ) {
901
913
let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
902
914
let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
903
- . metadata_derived ( & keys)
915
+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
904
916
. amount_msats ( 1000 )
905
917
. build ( ) . unwrap ( )
906
918
. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
@@ -909,7 +921,7 @@ mod tests {
909
921
assert ! ( invoice_request. verify( & keys) ) ;
910
922
911
923
let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
912
- . metadata_derived ( & keys)
924
+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
913
925
. amount_msats ( 1000 )
914
926
. build ( ) . unwrap ( ) ;
915
927
let mut tlv_stream = offer. as_tlv_stream ( ) ;
@@ -925,6 +937,36 @@ mod tests {
925
937
assert ! ( !invoice_request. verify( & keys) ) ;
926
938
}
927
939
940
+ #[ test]
941
+ fn builds_offer_with_signing_pubkey_derived ( ) {
942
+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
943
+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
944
+
945
+ let recipient_pubkey = SigningPubkey :: Derived ( & keys, nonce) ;
946
+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey)
947
+ . amount_msats ( 1000 )
948
+ . build ( ) . unwrap ( ) ;
949
+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
950
+ assert_eq ! ( offer. signing_pubkey( ) , keys. signing_pubkey_for_offer( nonce) ) ;
951
+
952
+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
953
+ . build ( ) . unwrap ( )
954
+ . sign ( payer_sign) . unwrap ( ) ;
955
+ assert ! ( invoice_request. verify( & keys) ) ;
956
+
957
+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
958
+ tlv_stream. amount = Some ( 100 ) ;
959
+
960
+ let mut encoded_offer = Vec :: new ( ) ;
961
+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
962
+
963
+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
964
+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
965
+ . build ( ) . unwrap ( )
966
+ . sign ( payer_sign) . unwrap ( ) ;
967
+ assert ! ( !invoice_request. verify( & keys) ) ;
968
+ }
969
+
928
970
#[ test]
929
971
fn builds_offer_with_amount ( ) {
930
972
let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments