7373
7474use bitcoin:: blockdata:: constants:: ChainHash ;
7575use bitcoin:: network:: constants:: Network ;
76- use bitcoin:: secp256k1:: PublicKey ;
76+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , self } ;
7777use core:: convert:: TryFrom ;
78+ use core:: ops:: Deref ;
7879use core:: str:: FromStr ;
7980use core:: time:: Duration ;
81+ use crate :: chain:: keysinterface:: EntropySource ;
8082use crate :: io;
8183use crate :: ln:: PaymentHash ;
8284use crate :: ln:: features:: InvoiceRequestFeatures ;
85+ use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
8386use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
8487use crate :: offers:: invoice:: { BlindedPayInfo , InvoiceBuilder } ;
8588use crate :: offers:: invoice_request:: { InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
8689use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
8790use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
8891use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
89- use crate :: offers:: signer:: Metadata ;
92+ use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
9093use crate :: onion_message:: BlindedPath ;
9194use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
9295use crate :: util:: string:: PrintableString ;
@@ -96,16 +99,19 @@ use crate::prelude::*;
9699#[ cfg( feature = "std" ) ]
97100use std:: time:: SystemTime ;
98101
102+ const IV_BYTES : & [ u8 ; IV_LEN ] = b"LDK Refund ~~~~~" ;
103+
99104/// Builds a [`Refund`] for the "offer for money" flow.
100105///
101106/// See [module-level documentation] for usage.
102107///
103108/// [module-level documentation]: self
104- pub struct RefundBuilder {
109+ pub struct RefundBuilder < ' a , T : secp256k1 :: Signing > {
105110 refund : RefundContents ,
111+ secp_ctx : Option < & ' a Secp256k1 < T > > ,
106112}
107113
108- impl RefundBuilder {
114+ impl < ' a > RefundBuilder < ' a , secp256k1 :: SignOnly > {
109115 /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
110116 /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
111117 ///
@@ -119,13 +125,47 @@ impl RefundBuilder {
119125 }
120126
121127 let metadata = Metadata :: Bytes ( metadata) ;
122- let refund = RefundContents {
123- payer : PayerContents ( metadata) , description, absolute_expiry : None , issuer : None ,
124- paths : None , chain : None , amount_msats, features : InvoiceRequestFeatures :: empty ( ) ,
125- quantity : None , payer_id, payer_note : None ,
126- } ;
128+ Ok ( Self {
129+ refund : RefundContents {
130+ payer : PayerContents ( metadata) , description, absolute_expiry : None , issuer : None ,
131+ paths : None , chain : None , amount_msats, features : InvoiceRequestFeatures :: empty ( ) ,
132+ quantity : None , payer_id, payer_note : None ,
133+ } ,
134+ secp_ctx : None ,
135+ } )
136+ }
137+ }
127138
128- Ok ( RefundBuilder { refund } )
139+ impl < ' a , T : secp256k1:: Signing > RefundBuilder < ' a , T > {
140+ /// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id
141+ /// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a
142+ /// different payer id for each refund, assuming a different nonce is used. Otherwise, the
143+ /// provided `node_id` is used for the payer id.
144+ ///
145+ /// Also, sets the metadata when [`RefundBuilder::build`] is called such that it can be used to
146+ /// verify that an [`InvoiceRequest`] was produced for the refund given an [`ExpandedKey`].
147+ ///
148+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
149+ /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
150+ pub fn deriving_payer_id < ES : Deref > (
151+ description : String , node_id : PublicKey , expanded_key : & ExpandedKey , entropy_source : ES ,
152+ secp_ctx : & ' a Secp256k1 < T > , amount_msats : u64
153+ ) -> Result < Self , SemanticError > where ES :: Target : EntropySource {
154+ if amount_msats > MAX_VALUE_MSAT {
155+ return Err ( SemanticError :: InvalidAmount ) ;
156+ }
157+
158+ let nonce = Nonce :: from_entropy_source ( entropy_source) ;
159+ let derivation_material = MetadataMaterial :: new ( nonce, expanded_key, IV_BYTES ) ;
160+ let metadata = Metadata :: DerivedSigningPubkey ( derivation_material) ;
161+ Ok ( Self {
162+ refund : RefundContents {
163+ payer : PayerContents ( metadata) , description, absolute_expiry : None , issuer : None ,
164+ paths : None , chain : None , amount_msats, features : InvoiceRequestFeatures :: empty ( ) ,
165+ quantity : None , payer_id : node_id, payer_note : None ,
166+ } ,
167+ secp_ctx : Some ( secp_ctx) ,
168+ } )
129169 }
130170
131171 /// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
@@ -192,18 +232,36 @@ impl RefundBuilder {
192232 self . refund . chain = None ;
193233 }
194234
235+ // Create the metadata for stateless verification of an Invoice.
236+ if self . refund . payer . 0 . has_derivation_material ( ) {
237+ let mut metadata = core:: mem:: take ( & mut self . refund . payer . 0 ) ;
238+
239+ if self . refund . paths . is_none ( ) {
240+ metadata = metadata. without_keys ( ) ;
241+ }
242+
243+ let mut tlv_stream = self . refund . as_tlv_stream ( ) ;
244+ tlv_stream. 0 . metadata = None ;
245+ tlv_stream. 2 . payer_id = None ;
246+
247+ let ( derived_metadata, keys) = metadata. derive_from ( tlv_stream, self . secp_ctx ) ;
248+ metadata = derived_metadata;
249+ if let Some ( keys) = keys {
250+ self . refund . payer_id = keys. public_key ( ) ;
251+ }
252+
253+ self . refund . payer . 0 = metadata;
254+ }
255+
195256 let mut bytes = Vec :: new ( ) ;
196257 self . refund . write ( & mut bytes) . unwrap ( ) ;
197258
198- Ok ( Refund {
199- bytes,
200- contents : self . refund ,
201- } )
259+ Ok ( Refund { bytes, contents : self . refund } )
202260 }
203261}
204262
205263#[ cfg( test) ]
206- impl RefundBuilder {
264+ impl < ' a , T : secp256k1 :: Signing > RefundBuilder < ' a , T > {
207265 fn features_unchecked ( mut self , features : InvoiceRequestFeatures ) -> Self {
208266 self . refund . features = features;
209267 self
@@ -283,7 +341,7 @@ impl Refund {
283341 ///
284342 /// [`payer_id`]: Self::payer_id
285343 pub fn metadata ( & self ) -> & [ u8 ] {
286- & self . contents . payer . 0 . as_bytes ( ) . unwrap ( ) [ .. ]
344+ self . contents . payer . 0 . as_bytes ( ) . map ( |bytes| bytes . as_slice ( ) ) . unwrap_or ( & [ ] )
287345 }
288346
289347 /// A chain that the refund is valid for.
0 commit comments