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