1212use crate :: blinded_path:: BlindedPath ;
1313use crate :: io;
1414use crate :: ln:: features:: { Bolt12InvoiceFeatures , OfferFeatures } ;
15+ use crate :: ln:: inbound_payment:: ExpandedKey ;
1516use crate :: ln:: msgs:: DecodeError ;
1617use crate :: offers:: invoice:: {
1718 check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, BlindedPathIter ,
1819 BlindedPayInfo , BlindedPayInfoIter , FallbackAddress , InvoiceTlvStream , InvoiceTlvStreamRef ,
1920 SIGNATURE_TAG ,
2021} ;
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+ } ;
2429use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
2530use crate :: util:: ser:: {
2631 HighZeroBytesDroppedBigSize , Iterable , SeekReadable , WithoutLength , Writeable , Writer ,
@@ -29,7 +34,7 @@ use crate::util::string::PrintableString;
2934use bitcoin:: address:: Address ;
3035use bitcoin:: blockdata:: constants:: ChainHash ;
3136use bitcoin:: secp256k1:: schnorr:: Signature ;
32- use bitcoin:: secp256k1:: PublicKey ;
37+ use bitcoin:: secp256k1:: { self , Keypair , PublicKey , Secp256k1 } ;
3338use core:: time:: Duration ;
3439
3540#[ cfg( feature = "std" ) ]
@@ -73,6 +78,96 @@ struct InvoiceContents {
7378 message_paths : Vec < BlindedPath > ,
7479}
7580
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+
76171macro_rules! invoice_accessors { ( $self: ident, $contents: expr) => {
77172 /// The chain that must be used when paying the invoice. [`StaticInvoice`]s currently can only be
78173 /// created from offers that support a single chain.
@@ -148,6 +243,68 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
148243 }
149244} }
150245
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+
151308impl StaticInvoice {
152309 invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
153310 invoice_accessors ! ( self , self . contents) ;
@@ -159,6 +316,57 @@ impl StaticInvoice {
159316}
160317
161318impl 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+
162370 fn chain ( & self ) -> ChainHash {
163371 debug_assert_eq ! ( self . offer. chains( ) . len( ) , 1 ) ;
164372 self . offer . chains ( ) . first ( ) . cloned ( ) . unwrap_or_else ( || self . offer . implied_chain ( ) )
@@ -264,6 +472,8 @@ impl SeekReadable for FullInvoiceTlvStream {
264472
265473type PartialInvoiceTlvStream = ( OfferTlvStream , InvoiceTlvStream ) ;
266474
475+ type PartialInvoiceTlvStreamRef < ' a > = ( OfferTlvStreamRef < ' a > , InvoiceTlvStreamRef < ' a > ) ;
476+
267477impl TryFrom < ParsedMessage < FullInvoiceTlvStream > > for StaticInvoice {
268478 type Error = Bolt12ParseError ;
269479
0 commit comments