@@ -17,9 +17,11 @@ use crate::offers::invoice::{
17
17
construct_payment_paths, filter_fallbacks, BlindedPathIter , BlindedPayInfo , BlindedPayInfoIter ,
18
18
FallbackAddress , SIGNATURE_TAG ,
19
19
} ;
20
- use crate :: offers:: invoice_macros:: invoice_accessors_common;
21
- use crate :: offers:: merkle:: { self , SignatureTlvStream , TaggedHash } ;
22
- use crate :: offers:: offer:: { Amount , OfferContents , OfferTlvStream , Quantity } ;
20
+ use crate :: offers:: invoice_macros:: { invoice_accessors_common, invoice_builder_methods_common} ;
21
+ use crate :: offers:: merkle:: {
22
+ self , SignError , SignFn , SignatureTlvStream , SignatureTlvStreamRef , TaggedHash ,
23
+ } ;
24
+ use crate :: offers:: offer:: { Amount , Offer , OfferContents , OfferTlvStream , Quantity } ;
23
25
use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
24
26
use crate :: util:: ser:: {
25
27
HighZeroBytesDroppedBigSize , Iterable , SeekReadable , WithoutLength , Writeable , Writer ,
@@ -28,7 +30,7 @@ use crate::util::string::PrintableString;
28
30
use bitcoin:: address:: Address ;
29
31
use bitcoin:: blockdata:: constants:: ChainHash ;
30
32
use bitcoin:: secp256k1:: schnorr:: Signature ;
31
- use bitcoin:: secp256k1:: PublicKey ;
33
+ use bitcoin:: secp256k1:: { self , KeyPair , PublicKey , Secp256k1 } ;
32
34
use core:: time:: Duration ;
33
35
34
36
#[ cfg( feature = "std" ) ]
@@ -72,6 +74,90 @@ struct InvoiceContents {
72
74
message_paths : Vec < BlindedPath > ,
73
75
}
74
76
77
+ /// Builds a [`StaticInvoice`] from an [`Offer`].
78
+ ///
79
+ /// See [module-level documentation] for usage.
80
+ ///
81
+ /// [`Offer`]: crate::offers::offer::Offer
82
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
83
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
84
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
85
+ pub struct StaticInvoiceBuilder < ' a > {
86
+ offer_bytes : & ' a Vec < u8 > ,
87
+ invoice : InvoiceContents ,
88
+ keys : KeyPair ,
89
+ }
90
+
91
+ impl < ' a > StaticInvoiceBuilder < ' a > {
92
+ /// Initialize a [`StaticInvoiceBuilder`] from the given [`Offer`].
93
+ ///
94
+ /// Unless [`StaticInvoiceBuilder::relative_expiry`] is set, the invoice will expire 24 hours
95
+ /// after `created_at`.
96
+ pub fn for_offer_using_keys (
97
+ offer : & ' a Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
98
+ message_paths : Vec < BlindedPath > , created_at : Duration , keys : KeyPair ,
99
+ ) -> Result < Self , Bolt12SemanticError > {
100
+ let offer_signing_pubkey =
101
+ offer. signing_pubkey ( ) . ok_or ( Bolt12SemanticError :: MissingSigningPubkey ) ?;
102
+ let signing_pubkey = keys. public_key ( ) ;
103
+ if signing_pubkey != offer_signing_pubkey {
104
+ return Err ( Bolt12SemanticError :: InvalidSigningPubkey ) ;
105
+ }
106
+
107
+ let invoice =
108
+ InvoiceContents :: new ( offer, payment_paths, message_paths, created_at, signing_pubkey) ;
109
+
110
+ if invoice. payment_paths . is_empty ( )
111
+ || invoice. message_paths . is_empty ( )
112
+ || offer. paths ( ) . is_empty ( )
113
+ {
114
+ return Err ( Bolt12SemanticError :: MissingPaths ) ;
115
+ }
116
+ if invoice. offer . chains ( ) . len ( ) > 1 {
117
+ return Err ( Bolt12SemanticError :: UnexpectedChain ) ;
118
+ }
119
+
120
+ Ok ( Self { offer_bytes : & offer. bytes , invoice, keys } )
121
+ }
122
+
123
+ /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
124
+ pub fn build_and_sign < T : secp256k1:: Signing > (
125
+ self , secp_ctx : & Secp256k1 < T > ,
126
+ ) -> Result < StaticInvoice , Bolt12SemanticError > {
127
+ #[ cfg( feature = "std" ) ]
128
+ {
129
+ if self . invoice . is_offer_expired ( ) {
130
+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
131
+ }
132
+ }
133
+
134
+ #[ cfg( not( feature = "std" ) ) ]
135
+ {
136
+ if self . invoice . is_offer_expired_no_std ( self . invoice . created_at ( ) ) {
137
+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
138
+ }
139
+ }
140
+
141
+ let Self { offer_bytes, invoice, keys } = self ;
142
+ let unsigned_invoice = UnsignedStaticInvoice :: new ( & offer_bytes, invoice) ;
143
+ let invoice = unsigned_invoice
144
+ . sign ( |message : & UnsignedStaticInvoice | {
145
+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( message. tagged_hash . as_digest ( ) , & keys) )
146
+ } )
147
+ . unwrap ( ) ;
148
+ Ok ( invoice)
149
+ }
150
+
151
+ invoice_builder_methods_common ! ( self , Self , self . invoice, Self , self , S , StaticInvoice , mut ) ;
152
+ }
153
+
154
+ /// A semantically valid [`StaticInvoice`] that hasn't been signed.
155
+ pub struct UnsignedStaticInvoice {
156
+ bytes : Vec < u8 > ,
157
+ contents : InvoiceContents ,
158
+ tagged_hash : TaggedHash ,
159
+ }
160
+
75
161
macro_rules! invoice_accessors { ( $self: ident, $contents: expr) => {
76
162
/// The chain that must be used when paying the invoice. [`StaticInvoice`]s currently can only be
77
163
/// created from offers that support a single chain.
@@ -147,6 +233,64 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
147
233
}
148
234
} }
149
235
236
+ impl UnsignedStaticInvoice {
237
+ fn new ( offer_bytes : & Vec < u8 > , contents : InvoiceContents ) -> Self {
238
+ let mut bytes = Vec :: new ( ) ;
239
+ WithoutLength ( offer_bytes) . write ( & mut bytes) . unwrap ( ) ;
240
+ contents. as_invoice_fields_tlv_stream ( ) . write ( & mut bytes) . unwrap ( ) ;
241
+
242
+ let tagged_hash = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & bytes) ;
243
+ Self { contents, tagged_hash, bytes }
244
+ }
245
+
246
+ /// Signs the [`TaggedHash`] of the invoice using the given function.
247
+ ///
248
+ /// Note: The hash computation may have included unknown, odd TLV records.
249
+ pub fn sign < F : SignStaticInvoiceFn > ( mut self , sign : F ) -> Result < StaticInvoice , SignError > {
250
+ let pubkey = self . contents . signing_pubkey ;
251
+ let signature = merkle:: sign_message ( sign, & self , pubkey) ?;
252
+
253
+ // Append the signature TLV record to the bytes.
254
+ let signature_tlv_stream = SignatureTlvStreamRef { signature : Some ( & signature) } ;
255
+ signature_tlv_stream. write ( & mut self . bytes ) . unwrap ( ) ;
256
+
257
+ Ok ( StaticInvoice { bytes : self . bytes , contents : self . contents , signature } )
258
+ }
259
+
260
+ invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
261
+ invoice_accessors ! ( self , self . contents) ;
262
+ }
263
+
264
+ impl AsRef < TaggedHash > for UnsignedStaticInvoice {
265
+ fn as_ref ( & self ) -> & TaggedHash {
266
+ & self . tagged_hash
267
+ }
268
+ }
269
+
270
+ /// A function for signing an [`UnsignedStaticInvoice`].
271
+ pub trait SignStaticInvoiceFn {
272
+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
273
+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ;
274
+ }
275
+
276
+ impl < F > SignStaticInvoiceFn for F
277
+ where
278
+ F : Fn ( & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ,
279
+ {
280
+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
281
+ self ( message)
282
+ }
283
+ }
284
+
285
+ impl < F > SignFn < UnsignedStaticInvoice > for F
286
+ where
287
+ F : SignStaticInvoiceFn ,
288
+ {
289
+ fn sign ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
290
+ self . sign_invoice ( message)
291
+ }
292
+ }
293
+
150
294
impl StaticInvoice {
151
295
invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
152
296
invoice_accessors ! ( self , self . contents) ;
@@ -158,6 +302,53 @@ impl StaticInvoice {
158
302
}
159
303
160
304
impl InvoiceContents {
305
+ #[ cfg( feature = "std" ) ]
306
+ fn is_offer_expired ( & self ) -> bool {
307
+ self . offer . is_expired ( )
308
+ }
309
+
310
+ #[ cfg( not( feature = "std" ) ) ]
311
+ fn is_offer_expired_no_std ( & self , duration_since_epoch : Duration ) -> bool {
312
+ self . offer . is_expired_no_std ( duration_since_epoch)
313
+ }
314
+
315
+ fn new (
316
+ offer : & Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
317
+ message_paths : Vec < BlindedPath > , created_at : Duration , signing_pubkey : PublicKey ,
318
+ ) -> Self {
319
+ Self {
320
+ offer : offer. contents . clone ( ) ,
321
+ payment_paths,
322
+ message_paths,
323
+ created_at,
324
+ relative_expiry : None ,
325
+ fallbacks : None ,
326
+ features : Bolt12InvoiceFeatures :: empty ( ) ,
327
+ signing_pubkey,
328
+ }
329
+ }
330
+
331
+ fn as_invoice_fields_tlv_stream ( & self ) -> InvoiceTlvStreamRef {
332
+ let features = {
333
+ if self . features == Bolt12InvoiceFeatures :: empty ( ) {
334
+ None
335
+ } else {
336
+ Some ( & self . features )
337
+ }
338
+ } ;
339
+
340
+ InvoiceTlvStreamRef {
341
+ payment_paths : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( _, path) | path) ) ) ,
342
+ message_paths : Some ( self . message_paths . as_ref ( ) ) ,
343
+ blindedpay : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( payinfo, _) | payinfo) ) ) ,
344
+ created_at : Some ( self . created_at . as_secs ( ) ) ,
345
+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
346
+ fallbacks : self . fallbacks . as_ref ( ) ,
347
+ features,
348
+ node_id : Some ( & self . signing_pubkey ) ,
349
+ }
350
+ }
351
+
161
352
fn chain ( & self ) -> ChainHash {
162
353
debug_assert_eq ! ( self . offer. chains( ) . len( ) , 1 ) ;
163
354
self . offer . chains ( ) . first ( ) . cloned ( ) . unwrap_or_else ( || self . offer . implied_chain ( ) )
@@ -292,7 +483,7 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for StaticInvoice {
292
483
let pubkey = contents. signing_pubkey ;
293
484
merkle:: verify_signature ( & signature, & tagged_hash, pubkey) ?;
294
485
295
- Ok ( StaticInvoice { bytes, contents, signature, tagged_hash } )
486
+ Ok ( StaticInvoice { bytes, contents, signature } )
296
487
}
297
488
}
298
489
@@ -328,9 +519,23 @@ impl TryFrom<(OfferTlvStream, InvoiceTlvStream)> for InvoiceContents {
328
519
329
520
let signing_pubkey = match node_id {
330
521
None => return Err ( Bolt12SemanticError :: MissingSigningPubkey ) ,
331
- Some ( node_id) => node_id,
522
+ Some ( node_id) => {
523
+ let offer_node_id =
524
+ offer_tlv_stream. node_id . ok_or ( Bolt12SemanticError :: MissingSigningPubkey ) ?;
525
+ if node_id != offer_node_id {
526
+ return Err ( Bolt12SemanticError :: InvalidSigningPubkey ) ;
527
+ }
528
+ node_id
529
+ } ,
332
530
} ;
333
531
532
+ if offer_tlv_stream. paths . is_none ( ) {
533
+ return Err ( Bolt12SemanticError :: MissingPaths ) ;
534
+ }
535
+ if offer_tlv_stream. chains . as_ref ( ) . map_or ( 0 , |chains| chains. len ( ) ) > 1 {
536
+ return Err ( Bolt12SemanticError :: UnexpectedChain ) ;
537
+ }
538
+
334
539
Ok ( InvoiceContents {
335
540
offer : OfferContents :: try_from ( offer_tlv_stream) ?,
336
541
payment_paths,
0 commit comments