@@ -97,11 +97,11 @@ use bitcoin::blockdata::constants::ChainHash;
97
97
use bitcoin:: hash_types:: { WPubkeyHash , WScriptHash } ;
98
98
use bitcoin:: hashes:: Hash ;
99
99
use bitcoin:: network:: constants:: Network ;
100
- use bitcoin:: secp256k1:: { Message , PublicKey , Secp256k1 , self } ;
100
+ use bitcoin:: secp256k1:: { KeyPair , Message , PublicKey , Secp256k1 , self } ;
101
101
use bitcoin:: secp256k1:: schnorr:: Signature ;
102
102
use bitcoin:: util:: address:: { Address , Payload , WitnessVersion } ;
103
103
use bitcoin:: util:: schnorr:: TweakedPublicKey ;
104
- use core:: convert:: TryFrom ;
104
+ use core:: convert:: { Infallible , TryFrom } ;
105
105
use core:: time:: Duration ;
106
106
use crate :: io;
107
107
use crate :: ln:: PaymentHash ;
@@ -136,28 +136,31 @@ pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "
136
136
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
137
137
/// [`Refund`]: crate::offers::refund::Refund
138
138
/// [module-level documentation]: self
139
- pub struct InvoiceBuilder < ' a > {
139
+ pub struct InvoiceBuilder < ' a , S : SigningPubkeyStrategy > {
140
140
invreq_bytes : & ' a Vec < u8 > ,
141
141
invoice : InvoiceContents ,
142
+ keys : Option < KeyPair > ,
143
+ signing_pubkey_strategy : core:: marker:: PhantomData < S > ,
142
144
}
143
145
144
- impl < ' a > InvoiceBuilder < ' a > {
146
+ /// Indicates how [`Invoice::signing_pubkey`] was set.
147
+ pub trait SigningPubkeyStrategy { }
148
+
149
+ /// [`Invoice::signing_pubkey`] was explicitly set.
150
+ pub struct ExplicitSigningPubkey { }
151
+
152
+ /// [`Invoice::signing_pubkey`] was derived.
153
+ pub struct DerivedSigningPubkey { }
154
+
155
+ impl SigningPubkeyStrategy for ExplicitSigningPubkey { }
156
+ impl SigningPubkeyStrategy for DerivedSigningPubkey { }
157
+
158
+ impl < ' a > InvoiceBuilder < ' a , ExplicitSigningPubkey > {
145
159
pub ( super ) fn for_offer (
146
160
invoice_request : & ' a InvoiceRequest , payment_paths : Vec < ( BlindedPath , BlindedPayInfo ) > ,
147
161
created_at : Duration , payment_hash : PaymentHash
148
162
) -> Result < Self , SemanticError > {
149
- let amount_msats = match invoice_request. amount_msats ( ) {
150
- Some ( amount_msats) => amount_msats,
151
- None => match invoice_request. contents . inner . offer . amount ( ) {
152
- Some ( Amount :: Bitcoin { amount_msats } ) => {
153
- amount_msats. checked_mul ( invoice_request. quantity ( ) . unwrap_or ( 1 ) )
154
- . ok_or ( SemanticError :: InvalidAmount ) ?
155
- } ,
156
- Some ( Amount :: Currency { .. } ) => return Err ( SemanticError :: UnsupportedCurrency ) ,
157
- None => return Err ( SemanticError :: MissingAmount ) ,
158
- } ,
159
- } ;
160
-
163
+ let amount_msats = Self :: check_amount_msats ( invoice_request) ?;
161
164
let contents = InvoiceContents :: ForOffer {
162
165
invoice_request : invoice_request. contents . clone ( ) ,
163
166
fields : InvoiceFields {
@@ -167,7 +170,7 @@ impl<'a> InvoiceBuilder<'a> {
167
170
} ,
168
171
} ;
169
172
170
- Self :: new ( & invoice_request. bytes , contents)
173
+ Self :: new ( & invoice_request. bytes , contents, None )
171
174
}
172
175
173
176
pub ( super ) fn for_refund (
@@ -183,15 +186,57 @@ impl<'a> InvoiceBuilder<'a> {
183
186
} ,
184
187
} ;
185
188
186
- Self :: new ( & refund. bytes , contents)
189
+ Self :: new ( & refund. bytes , contents, None )
187
190
}
191
+ }
188
192
189
- fn new ( invreq_bytes : & ' a Vec < u8 > , contents : InvoiceContents ) -> Result < Self , SemanticError > {
193
+ impl < ' a > InvoiceBuilder < ' a , DerivedSigningPubkey > {
194
+ pub ( super ) fn for_offer_using_keys (
195
+ invoice_request : & ' a InvoiceRequest , payment_paths : Vec < ( BlindedPath , BlindedPayInfo ) > ,
196
+ created_at : Duration , payment_hash : PaymentHash , keys : KeyPair
197
+ ) -> Result < Self , SemanticError > {
198
+ let amount_msats = Self :: check_amount_msats ( invoice_request) ?;
199
+ let contents = InvoiceContents :: ForOffer {
200
+ invoice_request : invoice_request. contents . clone ( ) ,
201
+ fields : InvoiceFields {
202
+ payment_paths, created_at, relative_expiry : None , payment_hash, amount_msats,
203
+ fallbacks : None , features : Bolt12InvoiceFeatures :: empty ( ) ,
204
+ signing_pubkey : invoice_request. contents . inner . offer . signing_pubkey ( ) ,
205
+ } ,
206
+ } ;
207
+
208
+ Self :: new ( & invoice_request. bytes , contents, Some ( keys) )
209
+ }
210
+ }
211
+
212
+ impl < ' a , S : SigningPubkeyStrategy > InvoiceBuilder < ' a , S > {
213
+ fn check_amount_msats ( invoice_request : & InvoiceRequest ) -> Result < u64 , SemanticError > {
214
+ match invoice_request. amount_msats ( ) {
215
+ Some ( amount_msats) => Ok ( amount_msats) ,
216
+ None => match invoice_request. contents . inner . offer . amount ( ) {
217
+ Some ( Amount :: Bitcoin { amount_msats } ) => {
218
+ amount_msats. checked_mul ( invoice_request. quantity ( ) . unwrap_or ( 1 ) )
219
+ . ok_or ( SemanticError :: InvalidAmount )
220
+ } ,
221
+ Some ( Amount :: Currency { .. } ) => Err ( SemanticError :: UnsupportedCurrency ) ,
222
+ None => Err ( SemanticError :: MissingAmount ) ,
223
+ } ,
224
+ }
225
+ }
226
+
227
+ fn new (
228
+ invreq_bytes : & ' a Vec < u8 > , contents : InvoiceContents , keys : Option < KeyPair >
229
+ ) -> Result < Self , SemanticError > {
190
230
if contents. fields ( ) . payment_paths . is_empty ( ) {
191
231
return Err ( SemanticError :: MissingPaths ) ;
192
232
}
193
233
194
- Ok ( Self { invreq_bytes, invoice : contents } )
234
+ Ok ( Self {
235
+ invreq_bytes,
236
+ invoice : contents,
237
+ keys,
238
+ signing_pubkey_strategy : core:: marker:: PhantomData ,
239
+ } )
195
240
}
196
241
197
242
/// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
@@ -248,7 +293,9 @@ impl<'a> InvoiceBuilder<'a> {
248
293
self . invoice . fields_mut ( ) . features . set_basic_mpp_optional ( ) ;
249
294
self
250
295
}
296
+ }
251
297
298
+ impl < ' a > InvoiceBuilder < ' a , ExplicitSigningPubkey > {
252
299
/// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
253
300
/// [`UnsignedInvoice::sign`].
254
301
pub fn build ( self ) -> Result < UnsignedInvoice < ' a > , SemanticError > {
@@ -258,11 +305,36 @@ impl<'a> InvoiceBuilder<'a> {
258
305
}
259
306
}
260
307
261
- let InvoiceBuilder { invreq_bytes, invoice } = self ;
308
+ let InvoiceBuilder { invreq_bytes, invoice, .. } = self ;
262
309
Ok ( UnsignedInvoice { invreq_bytes, invoice } )
263
310
}
264
311
}
265
312
313
+ impl < ' a > InvoiceBuilder < ' a , DerivedSigningPubkey > {
314
+ /// Builds a signed [`Invoice`] after checking for valid semantics.
315
+ pub fn build_and_sign < T : secp256k1:: Signing > (
316
+ self , secp_ctx : & Secp256k1 < T >
317
+ ) -> Result < Invoice , SemanticError > {
318
+ #[ cfg( feature = "std" ) ] {
319
+ if self . invoice . is_offer_or_refund_expired ( ) {
320
+ return Err ( SemanticError :: AlreadyExpired ) ;
321
+ }
322
+ }
323
+
324
+ let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self ;
325
+ let keys = match & invoice {
326
+ InvoiceContents :: ForOffer { .. } => keys. unwrap ( ) ,
327
+ InvoiceContents :: ForRefund { .. } => unreachable ! ( ) ,
328
+ } ;
329
+
330
+ let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice } ;
331
+ let invoice = unsigned_invoice
332
+ . sign :: < _ , Infallible > ( |digest| Ok ( secp_ctx. sign_schnorr_no_aux_rand ( digest, & keys) ) )
333
+ . unwrap ( ) ;
334
+ Ok ( invoice)
335
+ }
336
+ }
337
+
266
338
/// A semantically valid [`Invoice`] that hasn't been signed.
267
339
pub struct UnsignedInvoice < ' a > {
268
340
invreq_bytes : & ' a Vec < u8 > ,
@@ -551,7 +623,10 @@ impl InvoiceContents {
551
623
} ,
552
624
} ;
553
625
554
- signer:: verify_metadata ( metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
626
+ match signer:: verify_metadata ( metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
627
+ Ok ( _) => true ,
628
+ Err ( ( ) ) => false ,
629
+ }
555
630
}
556
631
557
632
fn derives_keys ( & self ) -> bool {
@@ -831,15 +906,18 @@ mod tests {
831
906
use bitcoin:: util:: schnorr:: TweakedPublicKey ;
832
907
use core:: convert:: TryFrom ;
833
908
use core:: time:: Duration ;
834
- use crate :: ln :: msgs :: DecodeError ;
909
+ use crate :: chain :: keysinterface :: KeyMaterial ;
835
910
use crate :: ln:: features:: Bolt12InvoiceFeatures ;
911
+ use crate :: ln:: inbound_payment:: ExpandedKey ;
912
+ use crate :: ln:: msgs:: DecodeError ;
836
913
use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
837
914
use crate :: offers:: merkle:: { SignError , SignatureTlvStreamRef , self } ;
838
915
use crate :: offers:: offer:: { OfferBuilder , OfferTlvStreamRef , Quantity } ;
839
916
use crate :: offers:: parse:: { ParseError , SemanticError } ;
840
917
use crate :: offers:: payer:: PayerTlvStreamRef ;
841
918
use crate :: offers:: refund:: RefundBuilder ;
842
919
use crate :: offers:: test_utils:: * ;
920
+ use crate :: onion_message:: { BlindedHop , BlindedPath } ;
843
921
use crate :: util:: ser:: { BigSize , Iterable , Writeable } ;
844
922
845
923
trait ToBytes {
@@ -1084,6 +1162,67 @@ mod tests {
1084
1162
}
1085
1163
}
1086
1164
1165
+ #[ test]
1166
+ fn builds_invoice_from_offer_using_derived_keys ( ) {
1167
+ let desc = "foo" . to_string ( ) ;
1168
+ let node_id = recipient_pubkey ( ) ;
1169
+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
1170
+ let entropy = FixedEntropy { } ;
1171
+ let secp_ctx = Secp256k1 :: new ( ) ;
1172
+
1173
+ let blinded_path = BlindedPath {
1174
+ introduction_node_id : pubkey ( 40 ) ,
1175
+ blinding_point : pubkey ( 41 ) ,
1176
+ blinded_hops : vec ! [
1177
+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
1178
+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
1179
+ ] ,
1180
+ } ;
1181
+
1182
+ let offer = OfferBuilder
1183
+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
1184
+ . amount_msats ( 1000 )
1185
+ . path ( blinded_path)
1186
+ . build ( ) . unwrap ( ) ;
1187
+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1188
+ . build ( ) . unwrap ( )
1189
+ . sign ( payer_sign) . unwrap ( ) ;
1190
+
1191
+ if let Err ( e) = invoice_request
1192
+ . verify_and_respond_using_derived_keys_no_std (
1193
+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1194
+ )
1195
+ . unwrap ( )
1196
+ . build_and_sign ( & secp_ctx)
1197
+ {
1198
+ panic ! ( "error building invoice: {:?}" , e) ;
1199
+ }
1200
+
1201
+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 41 ; 32 ] ) ) ;
1202
+ match invoice_request. verify_and_respond_using_derived_keys_no_std (
1203
+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1204
+ ) {
1205
+ Ok ( _) => panic ! ( "expected error" ) ,
1206
+ Err ( e) => assert_eq ! ( e, SemanticError :: InvalidMetadata ) ,
1207
+ }
1208
+
1209
+ let desc = "foo" . to_string ( ) ;
1210
+ let offer = OfferBuilder
1211
+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
1212
+ . amount_msats ( 1000 )
1213
+ . build ( ) . unwrap ( ) ;
1214
+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1215
+ . build ( ) . unwrap ( )
1216
+ . sign ( payer_sign) . unwrap ( ) ;
1217
+
1218
+ match invoice_request. verify_and_respond_using_derived_keys_no_std (
1219
+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1220
+ ) {
1221
+ Ok ( _) => panic ! ( "expected error" ) ,
1222
+ Err ( e) => assert_eq ! ( e, SemanticError :: InvalidMetadata ) ,
1223
+ }
1224
+ }
1225
+
1087
1226
#[ test]
1088
1227
fn builds_invoice_with_relative_expiry ( ) {
1089
1228
let now = now ( ) ;
0 commit comments