73
73
74
74
use bitcoin:: blockdata:: constants:: ChainHash ;
75
75
use bitcoin:: network:: constants:: Network ;
76
- use bitcoin:: secp256k1:: PublicKey ;
76
+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , self } ;
77
77
use core:: convert:: TryFrom ;
78
+ use core:: ops:: Deref ;
78
79
use core:: str:: FromStr ;
79
80
use core:: time:: Duration ;
81
+ use crate :: chain:: keysinterface:: EntropySource ;
80
82
use crate :: io;
81
83
use crate :: ln:: PaymentHash ;
82
84
use crate :: ln:: features:: InvoiceRequestFeatures ;
85
+ use crate :: ln:: inbound_payment:: { ExpandedKey , IV_LEN , Nonce } ;
83
86
use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
84
87
use crate :: offers:: invoice:: { BlindedPayInfo , InvoiceBuilder } ;
85
88
use crate :: offers:: invoice_request:: { InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
86
89
use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
87
90
use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
88
91
use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
89
- use crate :: offers:: signer:: Metadata ;
92
+ use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
90
93
use crate :: onion_message:: BlindedPath ;
91
94
use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
92
95
use crate :: util:: string:: PrintableString ;
@@ -96,16 +99,19 @@ use crate::prelude::*;
96
99
#[ cfg( feature = "std" ) ]
97
100
use std:: time:: SystemTime ;
98
101
102
+ const IV_BYTES : & [ u8 ; IV_LEN ] = b"LDK Refund ~~~~~" ;
103
+
99
104
/// Builds a [`Refund`] for the "offer for money" flow.
100
105
///
101
106
/// See [module-level documentation] for usage.
102
107
///
103
108
/// [module-level documentation]: self
104
- pub struct RefundBuilder {
109
+ pub struct RefundBuilder < ' a , T : secp256k1 :: Signing > {
105
110
refund : RefundContents ,
111
+ secp_ctx : Option < & ' a Secp256k1 < T > > ,
106
112
}
107
113
108
- impl RefundBuilder {
114
+ impl < ' a > RefundBuilder < ' a , secp256k1 :: SignOnly > {
109
115
/// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
110
116
/// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
111
117
///
@@ -119,13 +125,47 @@ impl RefundBuilder {
119
125
}
120
126
121
127
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
+ }
127
138
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
+ } )
129
169
}
130
170
131
171
/// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
@@ -192,18 +232,36 @@ impl RefundBuilder {
192
232
self . refund . chain = None ;
193
233
}
194
234
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
+
195
256
let mut bytes = Vec :: new ( ) ;
196
257
self . refund . write ( & mut bytes) . unwrap ( ) ;
197
258
198
- Ok ( Refund {
199
- bytes,
200
- contents : self . refund ,
201
- } )
259
+ Ok ( Refund { bytes, contents : self . refund } )
202
260
}
203
261
}
204
262
205
263
#[ cfg( test) ]
206
- impl RefundBuilder {
264
+ impl < ' a , T : secp256k1 :: Signing > RefundBuilder < ' a , T > {
207
265
fn features_unchecked ( mut self , features : InvoiceRequestFeatures ) -> Self {
208
266
self . refund . features = features;
209
267
self
@@ -283,7 +341,7 @@ impl Refund {
283
341
///
284
342
/// [`payer_id`]: Self::payer_id
285
343
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 ( & [ ] )
287
345
}
288
346
289
347
/// A chain that the refund is valid for.
0 commit comments