12
12
use crate :: blinded_path:: BlindedPath ;
13
13
use crate :: io;
14
14
use crate :: ln:: features:: { Bolt12InvoiceFeatures , OfferFeatures } ;
15
+ use crate :: ln:: inbound_payment:: ExpandedKey ;
15
16
use crate :: ln:: msgs:: DecodeError ;
16
17
use crate :: offers:: invoice:: {
17
18
check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, BlindedPathIter ,
18
19
BlindedPayInfo , BlindedPayInfoIter , FallbackAddress , InvoiceTlvStream , InvoiceTlvStreamRef ,
19
20
} ;
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 } ;
21
+ use crate :: offers:: invoice_macros:: { invoice_accessors_common, invoice_builder_methods_common} ;
22
+ use crate :: offers:: merkle:: {
23
+ self , SignError , SignFn , SignatureTlvStream , SignatureTlvStreamRef , TaggedHash ,
24
+ } ;
25
+ use crate :: offers:: offer:: {
26
+ Amount , Offer , OfferContents , OfferTlvStream , OfferTlvStreamRef , Quantity ,
27
+ } ;
23
28
use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
24
29
use crate :: util:: ser:: {
25
30
HighZeroBytesDroppedBigSize , Iterable , SeekReadable , WithoutLength , Writeable , Writer ,
@@ -28,7 +33,7 @@ use crate::util::string::PrintableString;
28
33
use bitcoin:: address:: Address ;
29
34
use bitcoin:: blockdata:: constants:: ChainHash ;
30
35
use bitcoin:: secp256k1:: schnorr:: Signature ;
31
- use bitcoin:: secp256k1:: PublicKey ;
36
+ use bitcoin:: secp256k1:: { self , Keypair , PublicKey , Secp256k1 } ;
32
37
use core:: time:: Duration ;
33
38
34
39
#[ cfg( feature = "std" ) ]
@@ -75,6 +80,96 @@ struct InvoiceContents {
75
80
message_paths : Vec < BlindedPath > ,
76
81
}
77
82
83
+ /// Builds a [`StaticInvoice`] from an [`Offer`].
84
+ ///
85
+ /// See [module-level documentation] for usage.
86
+ ///
87
+ /// [`Offer`]: crate::offers::offer::Offer
88
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
89
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
90
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
91
+ pub struct StaticInvoiceBuilder < ' a > {
92
+ offer_bytes : & ' a Vec < u8 > ,
93
+ invoice : InvoiceContents ,
94
+ keys : Keypair ,
95
+ }
96
+
97
+ impl < ' a > StaticInvoiceBuilder < ' a > {
98
+ /// Initialize a [`StaticInvoiceBuilder`] from the given [`Offer`].
99
+ ///
100
+ /// Unless [`StaticInvoiceBuilder::relative_expiry`] is set, the invoice will expire 24 hours
101
+ /// after `created_at`.
102
+ pub fn for_offer_using_derived_keys < T : secp256k1:: Signing > (
103
+ offer : & ' a Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
104
+ message_paths : Vec < BlindedPath > , created_at : Duration , expanded_key : & ExpandedKey ,
105
+ secp_ctx : & Secp256k1 < T > ,
106
+ ) -> Result < Self , Bolt12SemanticError > {
107
+ if offer. chains ( ) . len ( ) > 1 {
108
+ return Err ( Bolt12SemanticError :: UnexpectedChain ) ;
109
+ }
110
+
111
+ if payment_paths. is_empty ( ) || message_paths. is_empty ( ) || offer. paths ( ) . is_empty ( ) {
112
+ return Err ( Bolt12SemanticError :: MissingPaths ) ;
113
+ }
114
+
115
+ let offer_signing_pubkey =
116
+ offer. signing_pubkey ( ) . ok_or ( Bolt12SemanticError :: MissingSigningPubkey ) ?;
117
+
118
+ let keys = offer
119
+ . verify ( & expanded_key, & secp_ctx)
120
+ . map_err ( |( ) | Bolt12SemanticError :: InvalidMetadata ) ?
121
+ . 1
122
+ . ok_or ( Bolt12SemanticError :: MissingSigningPubkey ) ?;
123
+
124
+ let signing_pubkey = keys. public_key ( ) ;
125
+ if signing_pubkey != offer_signing_pubkey {
126
+ return Err ( Bolt12SemanticError :: InvalidSigningPubkey ) ;
127
+ }
128
+
129
+ let invoice =
130
+ InvoiceContents :: new ( offer, payment_paths, message_paths, created_at, signing_pubkey) ;
131
+
132
+ Ok ( Self { offer_bytes : & offer. bytes , invoice, keys } )
133
+ }
134
+
135
+ /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
136
+ pub fn build_and_sign < T : secp256k1:: Signing > (
137
+ self , secp_ctx : & Secp256k1 < T > ,
138
+ ) -> Result < StaticInvoice , Bolt12SemanticError > {
139
+ #[ cfg( feature = "std" ) ]
140
+ {
141
+ if self . invoice . is_offer_expired ( ) {
142
+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
143
+ }
144
+ }
145
+
146
+ #[ cfg( not( feature = "std" ) ) ]
147
+ {
148
+ if self . invoice . is_offer_expired_no_std ( self . invoice . created_at ( ) ) {
149
+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
150
+ }
151
+ }
152
+
153
+ let Self { offer_bytes, invoice, keys } = self ;
154
+ let unsigned_invoice = UnsignedStaticInvoice :: new ( & offer_bytes, invoice) ;
155
+ let invoice = unsigned_invoice
156
+ . sign ( |message : & UnsignedStaticInvoice | {
157
+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( message. tagged_hash . as_digest ( ) , & keys) )
158
+ } )
159
+ . unwrap ( ) ;
160
+ Ok ( invoice)
161
+ }
162
+
163
+ invoice_builder_methods_common ! ( self , Self , self . invoice, Self , self , S , StaticInvoice , mut ) ;
164
+ }
165
+
166
+ /// A semantically valid [`StaticInvoice`] that hasn't been signed.
167
+ pub struct UnsignedStaticInvoice {
168
+ bytes : Vec < u8 > ,
169
+ contents : InvoiceContents ,
170
+ tagged_hash : TaggedHash ,
171
+ }
172
+
78
173
macro_rules! invoice_accessors { ( $self: ident, $contents: expr) => {
79
174
/// The chain that must be used when paying the invoice. [`StaticInvoice`]s currently can only be
80
175
/// created from offers that support a single chain.
@@ -150,6 +245,68 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
150
245
}
151
246
} }
152
247
248
+ impl UnsignedStaticInvoice {
249
+ fn new ( offer_bytes : & Vec < u8 > , contents : InvoiceContents ) -> Self {
250
+ let ( _, invoice_tlv_stream) = contents. as_tlv_stream ( ) ;
251
+ let offer_bytes = WithoutLength ( offer_bytes) ;
252
+ let unsigned_tlv_stream = ( offer_bytes, invoice_tlv_stream) ;
253
+
254
+ let mut bytes = Vec :: new ( ) ;
255
+ unsigned_tlv_stream. write ( & mut bytes) . unwrap ( ) ;
256
+
257
+ let tagged_hash = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & bytes) ;
258
+
259
+ Self { contents, tagged_hash, bytes }
260
+ }
261
+
262
+ /// Signs the [`TaggedHash`] of the invoice using the given function.
263
+ ///
264
+ /// Note: The hash computation may have included unknown, odd TLV records.
265
+ pub fn sign < F : SignStaticInvoiceFn > ( mut self , sign : F ) -> Result < StaticInvoice , SignError > {
266
+ let pubkey = self . contents . signing_pubkey ;
267
+ let signature = merkle:: sign_message ( sign, & self , pubkey) ?;
268
+
269
+ // Append the signature TLV record to the bytes.
270
+ let signature_tlv_stream = SignatureTlvStreamRef { signature : Some ( & signature) } ;
271
+ signature_tlv_stream. write ( & mut self . bytes ) . unwrap ( ) ;
272
+
273
+ Ok ( StaticInvoice { bytes : self . bytes , contents : self . contents , signature } )
274
+ }
275
+
276
+ invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
277
+ invoice_accessors ! ( self , self . contents) ;
278
+ }
279
+
280
+ impl AsRef < TaggedHash > for UnsignedStaticInvoice {
281
+ fn as_ref ( & self ) -> & TaggedHash {
282
+ & self . tagged_hash
283
+ }
284
+ }
285
+
286
+ /// A function for signing an [`UnsignedStaticInvoice`].
287
+ pub trait SignStaticInvoiceFn {
288
+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
289
+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ;
290
+ }
291
+
292
+ impl < F > SignStaticInvoiceFn for F
293
+ where
294
+ F : Fn ( & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ,
295
+ {
296
+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
297
+ self ( message)
298
+ }
299
+ }
300
+
301
+ impl < F > SignFn < UnsignedStaticInvoice > for F
302
+ where
303
+ F : SignStaticInvoiceFn ,
304
+ {
305
+ fn sign ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
306
+ self . sign_invoice ( message)
307
+ }
308
+ }
309
+
153
310
impl StaticInvoice {
154
311
invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
155
312
invoice_accessors ! ( self , self . contents) ;
@@ -161,6 +318,57 @@ impl StaticInvoice {
161
318
}
162
319
163
320
impl InvoiceContents {
321
+ #[ cfg( feature = "std" ) ]
322
+ fn is_offer_expired ( & self ) -> bool {
323
+ self . offer . is_expired ( )
324
+ }
325
+
326
+ #[ cfg( not( feature = "std" ) ) ]
327
+ fn is_offer_expired_no_std ( & self , duration_since_epoch : Duration ) -> bool {
328
+ self . offer . is_expired_no_std ( duration_since_epoch)
329
+ }
330
+
331
+ fn new (
332
+ offer : & Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
333
+ message_paths : Vec < BlindedPath > , created_at : Duration , signing_pubkey : PublicKey ,
334
+ ) -> Self {
335
+ Self {
336
+ offer : offer. contents . clone ( ) ,
337
+ payment_paths,
338
+ message_paths,
339
+ created_at,
340
+ relative_expiry : None ,
341
+ fallbacks : None ,
342
+ features : Bolt12InvoiceFeatures :: empty ( ) ,
343
+ signing_pubkey,
344
+ }
345
+ }
346
+
347
+ fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef {
348
+ let features = {
349
+ if self . features == Bolt12InvoiceFeatures :: empty ( ) {
350
+ None
351
+ } else {
352
+ Some ( & self . features )
353
+ }
354
+ } ;
355
+
356
+ let invoice = InvoiceTlvStreamRef {
357
+ paths : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( _, path) | path) ) ) ,
358
+ message_paths : Some ( self . message_paths . as_ref ( ) ) ,
359
+ blindedpay : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( payinfo, _) | payinfo) ) ) ,
360
+ created_at : Some ( self . created_at . as_secs ( ) ) ,
361
+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
362
+ fallbacks : self . fallbacks . as_ref ( ) ,
363
+ features,
364
+ node_id : Some ( & self . signing_pubkey ) ,
365
+ amount : None ,
366
+ payment_hash : None ,
367
+ } ;
368
+
369
+ ( self . offer . as_tlv_stream ( ) , invoice)
370
+ }
371
+
164
372
fn chain ( & self ) -> ChainHash {
165
373
debug_assert_eq ! ( self . offer. chains( ) . len( ) , 1 ) ;
166
374
self . offer . chains ( ) . first ( ) . cloned ( ) . unwrap_or_else ( || self . offer . implied_chain ( ) )
@@ -265,6 +473,8 @@ impl SeekReadable for FullInvoiceTlvStream {
265
473
266
474
type PartialInvoiceTlvStream = ( OfferTlvStream , InvoiceTlvStream ) ;
267
475
476
+ type PartialInvoiceTlvStreamRef < ' a > = ( OfferTlvStreamRef < ' a > , InvoiceTlvStreamRef < ' a > ) ;
477
+
268
478
impl TryFrom < ParsedMessage < FullInvoiceTlvStream > > for StaticInvoice {
269
479
type Error = Bolt12ParseError ;
270
480
0 commit comments