68
68
69
69
use bitcoin:: blockdata:: constants:: ChainHash ;
70
70
use bitcoin:: network:: constants:: Network ;
71
- use bitcoin:: secp256k1:: PublicKey ;
71
+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , self } ;
72
72
use core:: convert:: TryFrom ;
73
73
use core:: num:: NonZeroU64 ;
74
+ use core:: ops:: Deref ;
74
75
use core:: str:: FromStr ;
75
76
use core:: time:: Duration ;
77
+ use crate :: chain:: keysinterface:: EntropySource ;
76
78
use crate :: io;
77
79
use crate :: ln:: features:: OfferFeatures ;
80
+ use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
78
81
use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
79
82
use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
80
83
use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
84
+ use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
81
85
use crate :: onion_message:: BlindedPath ;
82
86
use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
83
87
use crate :: util:: string:: PrintableString ;
@@ -87,30 +91,89 @@ use crate::prelude::*;
87
91
#[ cfg( feature = "std" ) ]
88
92
use std:: time:: SystemTime ;
89
93
94
+ const IV_BYTES : & [ u8 ; IV_LEN ] = b"LDK Offer ~~~~~~" ;
95
+
90
96
/// Builds an [`Offer`] for the "offer to be paid" flow.
91
97
///
92
98
/// See [module-level documentation] for usage.
93
99
///
94
100
/// [module-level documentation]: self
95
- pub struct OfferBuilder {
101
+ pub struct OfferBuilder < ' a , M : MetadataStrategy , T : secp256k1 :: Signing > {
96
102
offer : OfferContents ,
103
+ metadata_strategy : core:: marker:: PhantomData < M > ,
104
+ secp_ctx : Option < & ' a Secp256k1 < T > > ,
97
105
}
98
106
99
- impl OfferBuilder {
107
+ /// Indicates how [`Offer::metadata`] may be set.
108
+ pub trait MetadataStrategy { }
109
+
110
+ /// [`Offer::metadata`] may be explicitly set or left empty.
111
+ pub struct ExplicitMetadata { }
112
+
113
+ /// [`Offer::metadata`] will be derived.
114
+ pub struct DerivedMetadata { }
115
+
116
+ impl MetadataStrategy for ExplicitMetadata { }
117
+ impl MetadataStrategy for DerivedMetadata { }
118
+
119
+ impl < ' a > OfferBuilder < ' a , ExplicitMetadata , secp256k1:: SignOnly > {
100
120
/// Creates a new builder for an offer setting the [`Offer::description`] and using the
101
121
/// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
102
122
/// while the offer is valid.
103
123
///
104
124
/// Use a different pubkey per offer to avoid correlating offers.
105
125
pub fn new ( description : String , signing_pubkey : PublicKey ) -> Self {
106
- let offer = OfferContents {
107
- chains : None , metadata : None , amount : None , description,
108
- features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
109
- supported_quantity : Quantity :: One , signing_pubkey,
110
- } ;
111
- OfferBuilder { offer }
126
+ OfferBuilder {
127
+ offer : OfferContents {
128
+ chains : None , metadata : None , amount : None , description,
129
+ features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
130
+ supported_quantity : Quantity :: One , signing_pubkey,
131
+ } ,
132
+ metadata_strategy : core:: marker:: PhantomData ,
133
+ secp_ctx : None ,
134
+ }
135
+ }
136
+
137
+ /// Sets the [`Offer::metadata`] to the given bytes.
138
+ ///
139
+ /// Successive calls to this method will override the previous setting.
140
+ pub fn metadata ( mut self , metadata : Vec < u8 > ) -> Result < Self , SemanticError > {
141
+ self . offer . metadata = Some ( Metadata :: Bytes ( metadata) ) ;
142
+ Ok ( self )
112
143
}
144
+ }
113
145
146
+ impl < ' a , T : secp256k1:: Signing > OfferBuilder < ' a , DerivedMetadata , T > {
147
+ /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing
148
+ /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides
149
+ /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
150
+ /// provided `node_id` is used for the signing pubkey.
151
+ ///
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`].
154
+ ///
155
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
156
+ /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
157
+ pub fn deriving_signing_pubkey < ES : Deref > (
158
+ description : String , node_id : PublicKey , expanded_key : & ExpandedKey , entropy_source : ES ,
159
+ secp_ctx : & ' a Secp256k1 < T >
160
+ ) -> Self where ES :: Target : EntropySource {
161
+ let nonce = Nonce :: from_entropy_source ( entropy_source) ;
162
+ let derivation_material = MetadataMaterial :: new ( nonce, expanded_key, IV_BYTES ) ;
163
+ let metadata = Metadata :: DerivedSigningPubkey ( derivation_material) ;
164
+ OfferBuilder {
165
+ offer : OfferContents {
166
+ chains : None , metadata : Some ( metadata) , amount : None , description,
167
+ features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
168
+ supported_quantity : Quantity :: One , signing_pubkey : node_id,
169
+ } ,
170
+ metadata_strategy : core:: marker:: PhantomData ,
171
+ secp_ctx : Some ( secp_ctx) ,
172
+ }
173
+ }
174
+ }
175
+
176
+ impl < ' a , M : MetadataStrategy , T : secp256k1:: Signing > OfferBuilder < ' a , M , T > {
114
177
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
115
178
/// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
116
179
///
@@ -127,14 +190,6 @@ impl OfferBuilder {
127
190
self
128
191
}
129
192
130
- /// Sets the [`Offer::metadata`].
131
- ///
132
- /// Successive calls to this method will override the previous setting.
133
- pub fn metadata ( mut self , metadata : Vec < u8 > ) -> Self {
134
- self . offer . metadata = Some ( metadata) ;
135
- self
136
- }
137
-
138
193
/// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
139
194
///
140
195
/// Successive calls to this method will override the previous setting.
@@ -204,28 +259,50 @@ impl OfferBuilder {
204
259
}
205
260
}
206
261
262
+ Ok ( self . build_without_checks ( ) )
263
+ }
264
+
265
+ fn build_without_checks ( mut self ) -> Offer {
266
+ // Create the metadata for stateless verification of an InvoiceRequest.
267
+ if let Some ( mut metadata) = self . offer . metadata . take ( ) {
268
+ if metadata. has_derivation_material ( ) {
269
+ if self . offer . paths . is_none ( ) {
270
+ metadata = metadata. without_keys ( ) ;
271
+ }
272
+
273
+ let mut tlv_stream = self . offer . as_tlv_stream ( ) ;
274
+ debug_assert_eq ! ( tlv_stream. metadata, None ) ;
275
+ tlv_stream. metadata = None ;
276
+ if metadata. derives_keys ( ) {
277
+ tlv_stream. node_id = None ;
278
+ }
279
+
280
+ let ( derived_metadata, keys) = metadata. derive_from ( tlv_stream, self . secp_ctx ) ;
281
+ metadata = derived_metadata;
282
+ if let Some ( keys) = keys {
283
+ self . offer . signing_pubkey = keys. public_key ( ) ;
284
+ }
285
+ }
286
+
287
+ self . offer . metadata = Some ( metadata) ;
288
+ }
289
+
207
290
let mut bytes = Vec :: new ( ) ;
208
291
self . offer . write ( & mut bytes) . unwrap ( ) ;
209
292
210
- Ok ( Offer {
211
- bytes,
212
- contents : self . offer ,
213
- } )
293
+ Offer { bytes, contents : self . offer }
214
294
}
215
295
}
216
296
217
297
#[ cfg( test) ]
218
- impl OfferBuilder {
298
+ impl < ' a , M : MetadataStrategy , T : secp256k1 :: Signing > OfferBuilder < ' a , M , T > {
219
299
fn features_unchecked ( mut self , features : OfferFeatures ) -> Self {
220
300
self . offer . features = features;
221
301
self
222
302
}
223
303
224
304
pub ( super ) fn build_unchecked ( self ) -> Offer {
225
- let mut bytes = Vec :: new ( ) ;
226
- self . offer . write ( & mut bytes) . unwrap ( ) ;
227
-
228
- Offer { bytes, contents : self . offer }
305
+ self . build_without_checks ( )
229
306
}
230
307
}
231
308
@@ -242,7 +319,8 @@ impl OfferBuilder {
242
319
///
243
320
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
244
321
/// [`Invoice`]: crate::offers::invoice::Invoice
245
- #[ derive( Clone , Debug , PartialEq ) ]
322
+ #[ derive( Clone , Debug ) ]
323
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
246
324
pub struct Offer {
247
325
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
248
326
// fields.
@@ -254,10 +332,11 @@ pub struct Offer {
254
332
///
255
333
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
256
334
/// [`Invoice`]: crate::offers::invoice::Invoice
257
- #[ derive( Clone , Debug , PartialEq ) ]
335
+ #[ derive( Clone , Debug ) ]
336
+ #[ cfg_attr( test, derive( PartialEq ) ) ]
258
337
pub ( super ) struct OfferContents {
259
338
chains : Option < Vec < ChainHash > > ,
260
- metadata : Option < Vec < u8 > > ,
339
+ metadata : Option < Metadata > ,
261
340
amount : Option < Amount > ,
262
341
description : String ,
263
342
features : OfferFeatures ,
@@ -292,7 +371,7 @@ impl Offer {
292
371
/// Opaque bytes set by the originator. Useful for authentication and validating fields since it
293
372
/// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
294
373
pub fn metadata ( & self ) -> Option < & Vec < u8 > > {
295
- self . contents . metadata . as_ref ( )
374
+ self . contents . metadata ( )
296
375
}
297
376
298
377
/// The minimum amount required for a successful payment of a single item.
@@ -406,6 +485,10 @@ impl OfferContents {
406
485
self . chains ( ) . contains ( & chain)
407
486
}
408
487
488
+ pub fn metadata ( & self ) -> Option < & Vec < u8 > > {
489
+ self . metadata . as_ref ( ) . and_then ( |metadata| metadata. as_bytes ( ) )
490
+ }
491
+
409
492
#[ cfg( feature = "std" ) ]
410
493
pub ( super ) fn is_expired ( & self ) -> bool {
411
494
match self . absolute_expiry {
@@ -498,7 +581,7 @@ impl OfferContents {
498
581
499
582
OfferTlvStreamRef {
500
583
chains : self . chains . as_ref ( ) ,
501
- metadata : self . metadata . as_ref ( ) ,
584
+ metadata : self . metadata ( ) ,
502
585
currency,
503
586
amount,
504
587
description : Some ( & self . description ) ,
@@ -616,6 +699,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
616
699
issuer, quantity_max, node_id,
617
700
} = tlv_stream;
618
701
702
+ let metadata = metadata. map ( |metadata| Metadata :: Bytes ( metadata) ) ;
703
+
619
704
let amount = match ( currency, amount) {
620
705
( None , None ) => None ,
621
706
( None , Some ( amount_msats) ) if amount_msats > MAX_VALUE_MSAT => {
@@ -765,15 +850,15 @@ mod tests {
765
850
#[ test]
766
851
fn builds_offer_with_metadata ( ) {
767
852
let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
768
- . metadata ( vec ! [ 42 ; 32 ] )
853
+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
769
854
. build ( )
770
855
. unwrap ( ) ;
771
856
assert_eq ! ( offer. metadata( ) , Some ( & vec![ 42 ; 32 ] ) ) ;
772
857
assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 42 ; 32 ] ) ) ;
773
858
774
859
let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
775
- . metadata ( vec ! [ 42 ; 32 ] )
776
- . metadata ( vec ! [ 43 ; 32 ] )
860
+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
861
+ . metadata ( vec ! [ 43 ; 32 ] ) . unwrap ( )
777
862
. build ( )
778
863
. unwrap ( ) ;
779
864
assert_eq ! ( offer. metadata( ) , Some ( & vec![ 43 ; 32 ] ) ) ;
0 commit comments