6868
6969use bitcoin:: blockdata:: constants:: ChainHash ;
7070use bitcoin:: network:: constants:: Network ;
71- use bitcoin:: secp256k1:: PublicKey ;
71+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , self } ;
7272use core:: convert:: TryFrom ;
7373use core:: num:: NonZeroU64 ;
74+ use core:: ops:: Deref ;
7475use core:: str:: FromStr ;
7576use core:: time:: Duration ;
77+ use crate :: chain:: keysinterface:: EntropySource ;
7678use crate :: io;
7779use crate :: ln:: features:: OfferFeatures ;
80+ use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
7881use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
7982use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
8083use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
84+ use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
8185use crate :: onion_message:: BlindedPath ;
8286use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
8387use crate :: util:: string:: PrintableString ;
@@ -87,30 +91,89 @@ use crate::prelude::*;
8791#[ cfg( feature = "std" ) ]
8892use std:: time:: SystemTime ;
8993
94+ const IV_BYTES : & [ u8 ; IV_LEN ] = b"LDK Offer ~~~~~~" ;
95+
9096/// Builds an [`Offer`] for the "offer to be paid" flow.
9197///
9298/// See [module-level documentation] for usage.
9399///
94100/// [module-level documentation]: self
95- pub struct OfferBuilder {
101+ pub struct OfferBuilder < ' a , M : MetadataStrategy , T : secp256k1 :: Signing > {
96102 offer : OfferContents ,
103+ metadata_strategy : core:: marker:: PhantomData < M > ,
104+ secp_ctx : Option < & ' a Secp256k1 < T > > ,
97105}
98106
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 > {
100120 /// Creates a new builder for an offer setting the [`Offer::description`] and using the
101121 /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
102122 /// while the offer is valid.
103123 ///
104124 /// Use a different pubkey per offer to avoid correlating offers.
105125 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 )
112143 }
144+ }
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+ }
113175
176+ impl < ' a , M : MetadataStrategy , T : secp256k1:: Signing > OfferBuilder < ' a , M , T > {
114177 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
115178 /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
116179 ///
@@ -127,14 +190,6 @@ impl OfferBuilder {
127190 self
128191 }
129192
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-
138193 /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
139194 ///
140195 /// Successive calls to this method will override the previous setting.
@@ -204,28 +259,48 @@ impl OfferBuilder {
204259 }
205260 }
206261
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+ tlv_stream. node_id = None ;
277+
278+ let ( derived_metadata, keys) = metadata. derive_from ( tlv_stream, self . secp_ctx ) ;
279+ metadata = derived_metadata;
280+ if let Some ( keys) = keys {
281+ self . offer . signing_pubkey = keys. public_key ( ) ;
282+ }
283+ }
284+
285+ self . offer . metadata = Some ( metadata) ;
286+ }
287+
207288 let mut bytes = Vec :: new ( ) ;
208289 self . offer . write ( & mut bytes) . unwrap ( ) ;
209290
210- Ok ( Offer {
211- bytes,
212- contents : self . offer ,
213- } )
291+ Offer { bytes, contents : self . offer }
214292 }
215293}
216294
217295#[ cfg( test) ]
218- impl OfferBuilder {
296+ impl < ' a , M : MetadataStrategy , T : secp256k1 :: Signing > OfferBuilder < ' a , M , T > {
219297 fn features_unchecked ( mut self , features : OfferFeatures ) -> Self {
220298 self . offer . features = features;
221299 self
222300 }
223301
224302 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 }
303+ self . build_without_checks ( )
229304 }
230305}
231306
@@ -242,7 +317,8 @@ impl OfferBuilder {
242317///
243318/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
244319/// [`Invoice`]: crate::offers::invoice::Invoice
245- #[ derive( Clone , Debug , PartialEq ) ]
320+ #[ derive( Clone , Debug ) ]
321+ #[ cfg_attr( test, derive( PartialEq ) ) ]
246322pub struct Offer {
247323 // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
248324 // fields.
@@ -254,10 +330,11 @@ pub struct Offer {
254330///
255331/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
256332/// [`Invoice`]: crate::offers::invoice::Invoice
257- #[ derive( Clone , Debug , PartialEq ) ]
333+ #[ derive( Clone , Debug ) ]
334+ #[ cfg_attr( test, derive( PartialEq ) ) ]
258335pub ( super ) struct OfferContents {
259336 chains : Option < Vec < ChainHash > > ,
260- metadata : Option < Vec < u8 > > ,
337+ metadata : Option < Metadata > ,
261338 amount : Option < Amount > ,
262339 description : String ,
263340 features : OfferFeatures ,
@@ -292,7 +369,7 @@ impl Offer {
292369 /// Opaque bytes set by the originator. Useful for authentication and validating fields since it
293370 /// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
294371 pub fn metadata ( & self ) -> Option < & Vec < u8 > > {
295- self . contents . metadata . as_ref ( )
372+ self . contents . metadata ( )
296373 }
297374
298375 /// The minimum amount required for a successful payment of a single item.
@@ -406,6 +483,10 @@ impl OfferContents {
406483 self . chains ( ) . contains ( & chain)
407484 }
408485
486+ pub fn metadata ( & self ) -> Option < & Vec < u8 > > {
487+ self . metadata . as_ref ( ) . and_then ( |metadata| metadata. as_bytes ( ) )
488+ }
489+
409490 #[ cfg( feature = "std" ) ]
410491 pub ( super ) fn is_expired ( & self ) -> bool {
411492 match self . absolute_expiry {
@@ -498,7 +579,7 @@ impl OfferContents {
498579
499580 OfferTlvStreamRef {
500581 chains : self . chains . as_ref ( ) ,
501- metadata : self . metadata . as_ref ( ) ,
582+ metadata : self . metadata ( ) ,
502583 currency,
503584 amount,
504585 description : Some ( & self . description ) ,
@@ -616,6 +697,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
616697 issuer, quantity_max, node_id,
617698 } = tlv_stream;
618699
700+ let metadata = metadata. map ( |metadata| Metadata :: Bytes ( metadata) ) ;
701+
619702 let amount = match ( currency, amount) {
620703 ( None , None ) => None ,
621704 ( None , Some ( amount_msats) ) if amount_msats > MAX_VALUE_MSAT => {
@@ -765,15 +848,15 @@ mod tests {
765848 #[ test]
766849 fn builds_offer_with_metadata ( ) {
767850 let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
768- . metadata ( vec ! [ 42 ; 32 ] )
851+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
769852 . build ( )
770853 . unwrap ( ) ;
771854 assert_eq ! ( offer. metadata( ) , Some ( & vec![ 42 ; 32 ] ) ) ;
772855 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 42 ; 32 ] ) ) ;
773856
774857 let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
775- . metadata ( vec ! [ 42 ; 32 ] )
776- . metadata ( vec ! [ 43 ; 32 ] )
858+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
859+ . metadata ( vec ! [ 43 ; 32 ] ) . unwrap ( )
777860 . build ( )
778861 . unwrap ( ) ;
779862 assert_eq ! ( offer. metadata( ) , Some ( & vec![ 43 ; 32 ] ) ) ;
0 commit comments