@@ -17,9 +17,11 @@ use crate::offers::invoice::{
1717 construct_payment_paths, filter_fallbacks, BlindedPathIter , BlindedPayInfo , BlindedPayInfoIter ,
1818 FallbackAddress , SIGNATURE_TAG ,
1919} ;
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 } ;
2325use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
2426use crate :: util:: ser:: {
2527 HighZeroBytesDroppedBigSize , Iterable , SeekReadable , WithoutLength , Writeable , Writer ,
@@ -28,7 +30,7 @@ use crate::util::string::PrintableString;
2830use bitcoin:: address:: Address ;
2931use bitcoin:: blockdata:: constants:: ChainHash ;
3032use bitcoin:: secp256k1:: schnorr:: Signature ;
31- use bitcoin:: secp256k1:: PublicKey ;
33+ use bitcoin:: secp256k1:: { self , KeyPair , PublicKey , Secp256k1 } ;
3234use core:: time:: Duration ;
3335
3436#[ cfg( feature = "std" ) ]
@@ -72,6 +74,88 @@ struct InvoiceContents {
7274 message_paths : Vec < BlindedPath > ,
7375}
7476
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+ if offer. chains ( ) . len ( ) > 1 {
101+ return Err ( Bolt12SemanticError :: UnexpectedChain ) ;
102+ }
103+
104+ if payment_paths. is_empty ( ) || message_paths. is_empty ( ) || offer. paths ( ) . is_empty ( ) {
105+ return Err ( Bolt12SemanticError :: MissingPaths ) ;
106+ }
107+
108+ let offer_signing_pubkey =
109+ offer. signing_pubkey ( ) . ok_or ( Bolt12SemanticError :: MissingSigningPubkey ) ?;
110+ let signing_pubkey = keys. public_key ( ) ;
111+ if signing_pubkey != offer_signing_pubkey {
112+ return Err ( Bolt12SemanticError :: InvalidSigningPubkey ) ;
113+ }
114+
115+ let invoice =
116+ InvoiceContents :: new ( offer, payment_paths, message_paths, created_at, signing_pubkey) ;
117+
118+ Ok ( Self { offer_bytes : & offer. bytes , invoice, keys } )
119+ }
120+
121+ /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
122+ pub fn build_and_sign < T : secp256k1:: Signing > (
123+ self , secp_ctx : & Secp256k1 < T > ,
124+ ) -> Result < StaticInvoice , Bolt12SemanticError > {
125+ #[ cfg( feature = "std" ) ]
126+ {
127+ if self . invoice . is_offer_expired ( ) {
128+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
129+ }
130+ }
131+
132+ #[ cfg( not( feature = "std" ) ) ]
133+ {
134+ if self . invoice . is_offer_expired_no_std ( self . invoice . created_at ( ) ) {
135+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
136+ }
137+ }
138+
139+ let Self { offer_bytes, invoice, keys } = self ;
140+ let unsigned_invoice = UnsignedStaticInvoice :: new ( & offer_bytes, invoice) ;
141+ let invoice = unsigned_invoice
142+ . sign ( |message : & UnsignedStaticInvoice | {
143+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( message. tagged_hash . as_digest ( ) , & keys) )
144+ } )
145+ . unwrap ( ) ;
146+ Ok ( invoice)
147+ }
148+
149+ invoice_builder_methods_common ! ( self , Self , self . invoice, Self , self , S , StaticInvoice , mut ) ;
150+ }
151+
152+ /// A semantically valid [`StaticInvoice`] that hasn't been signed.
153+ pub struct UnsignedStaticInvoice {
154+ bytes : Vec < u8 > ,
155+ contents : InvoiceContents ,
156+ tagged_hash : TaggedHash ,
157+ }
158+
75159macro_rules! invoice_accessors { ( $self: ident, $contents: expr) => {
76160 /// The chain that must be used when paying the invoice. [`StaticInvoice`]s currently can only be
77161 /// created from offers that support a single chain.
@@ -147,6 +231,64 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
147231 }
148232} }
149233
234+ impl UnsignedStaticInvoice {
235+ fn new ( offer_bytes : & Vec < u8 > , contents : InvoiceContents ) -> Self {
236+ let mut bytes = Vec :: new ( ) ;
237+ WithoutLength ( offer_bytes) . write ( & mut bytes) . unwrap ( ) ;
238+ contents. as_invoice_fields_tlv_stream ( ) . write ( & mut bytes) . unwrap ( ) ;
239+
240+ let tagged_hash = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & bytes) ;
241+ Self { contents, tagged_hash, bytes }
242+ }
243+
244+ /// Signs the [`TaggedHash`] of the invoice using the given function.
245+ ///
246+ /// Note: The hash computation may have included unknown, odd TLV records.
247+ pub fn sign < F : SignStaticInvoiceFn > ( mut self , sign : F ) -> Result < StaticInvoice , SignError > {
248+ let pubkey = self . contents . signing_pubkey ;
249+ let signature = merkle:: sign_message ( sign, & self , pubkey) ?;
250+
251+ // Append the signature TLV record to the bytes.
252+ let signature_tlv_stream = SignatureTlvStreamRef { signature : Some ( & signature) } ;
253+ signature_tlv_stream. write ( & mut self . bytes ) . unwrap ( ) ;
254+
255+ Ok ( StaticInvoice { bytes : self . bytes , contents : self . contents , signature } )
256+ }
257+
258+ invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
259+ invoice_accessors ! ( self , self . contents) ;
260+ }
261+
262+ impl AsRef < TaggedHash > for UnsignedStaticInvoice {
263+ fn as_ref ( & self ) -> & TaggedHash {
264+ & self . tagged_hash
265+ }
266+ }
267+
268+ /// A function for signing an [`UnsignedStaticInvoice`].
269+ pub trait SignStaticInvoiceFn {
270+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
271+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ;
272+ }
273+
274+ impl < F > SignStaticInvoiceFn for F
275+ where
276+ F : Fn ( & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ,
277+ {
278+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
279+ self ( message)
280+ }
281+ }
282+
283+ impl < F > SignFn < UnsignedStaticInvoice > for F
284+ where
285+ F : SignStaticInvoiceFn ,
286+ {
287+ fn sign ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
288+ self . sign_invoice ( message)
289+ }
290+ }
291+
150292impl StaticInvoice {
151293 invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
152294 invoice_accessors ! ( self , self . contents) ;
@@ -158,6 +300,53 @@ impl StaticInvoice {
158300}
159301
160302impl InvoiceContents {
303+ #[ cfg( feature = "std" ) ]
304+ fn is_offer_expired ( & self ) -> bool {
305+ self . offer . is_expired ( )
306+ }
307+
308+ #[ cfg( not( feature = "std" ) ) ]
309+ fn is_offer_expired_no_std ( & self , duration_since_epoch : Duration ) -> bool {
310+ self . offer . is_expired_no_std ( duration_since_epoch)
311+ }
312+
313+ fn new (
314+ offer : & Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
315+ message_paths : Vec < BlindedPath > , created_at : Duration , signing_pubkey : PublicKey ,
316+ ) -> Self {
317+ Self {
318+ offer : offer. contents . clone ( ) ,
319+ payment_paths,
320+ message_paths,
321+ created_at,
322+ relative_expiry : None ,
323+ fallbacks : None ,
324+ features : Bolt12InvoiceFeatures :: empty ( ) ,
325+ signing_pubkey,
326+ }
327+ }
328+
329+ fn as_invoice_fields_tlv_stream ( & self ) -> InvoiceTlvStreamRef {
330+ let features = {
331+ if self . features == Bolt12InvoiceFeatures :: empty ( ) {
332+ None
333+ } else {
334+ Some ( & self . features )
335+ }
336+ } ;
337+
338+ InvoiceTlvStreamRef {
339+ payment_paths : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( _, path) | path) ) ) ,
340+ message_paths : Some ( self . message_paths . as_ref ( ) ) ,
341+ blindedpay : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( payinfo, _) | payinfo) ) ) ,
342+ created_at : Some ( self . created_at . as_secs ( ) ) ,
343+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
344+ fallbacks : self . fallbacks . as_ref ( ) ,
345+ features,
346+ node_id : Some ( & self . signing_pubkey ) ,
347+ }
348+ }
349+
161350 fn chain ( & self ) -> ChainHash {
162351 debug_assert_eq ! ( self . offer. chains( ) . len( ) , 1 ) ;
163352 self . offer . chains ( ) . first ( ) . cloned ( ) . unwrap_or_else ( || self . offer . implied_chain ( ) )
0 commit comments