@@ -17,9 +17,11 @@ use crate::offers::invoice::{
1717 construct_payment_paths, filter_fallbacks, is_expired, BlindedPathIter , BlindedPayInfo ,
1818 BlindedPayInfoIter , 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,42 +30,80 @@ 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/// Static invoices default to expiring after 24 hours.
3537const DEFAULT_RELATIVE_EXPIRY : Duration = Duration :: from_secs ( 3600 * 24 ) ;
3638
37- /// A `StaticInvoice` is a reusable payment request corresponding to an [`Offer`].
39+ /// Builds a [ `StaticInvoice`] from an [`Offer`].
3840///
39- /// A static invoice may be sent in response to an [`InvoiceRequest`] and includes all the
40- /// information needed to pay the recipient. However, unlike [`Bolt12Invoice`]s, static invoices do
41- /// not provide proof-of-payment. Therefore, [`Bolt12Invoice`]s should be preferred when the
42- /// recipient is online to provide one.
41+ /// See [module-level documentation] for usage.
4342///
44- /// [`Offer`]: crate::offers::offer::Offer
45- /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
46- /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
47- pub struct StaticInvoice {
43+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
44+ pub struct StaticInvoiceBuilder < ' a > {
45+ offer_bytes : & ' a Vec < u8 > ,
46+ invoice : InvoiceContents ,
47+ keys : KeyPair ,
48+ }
49+
50+ impl < ' a > StaticInvoiceBuilder < ' a > {
51+ /// Initialize a [`StaticInvoiceBuilder`] from the given [`Offer`].
52+ ///
53+ /// Unless [`StaticInvoiceBuilder::relative_expiry`] is set, the invoice will expire 24 hours
54+ /// after `created_at`.
55+ pub fn for_offer_using_keys (
56+ offer : & ' a Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > , created_at : Duration ,
57+ keys : KeyPair ,
58+ ) -> Result < Self , Bolt12SemanticError > {
59+ let invoice = InvoiceContents :: new ( offer, payment_paths, created_at, keys. public_key ( ) ) ;
60+ if invoice. payment_paths . is_empty ( ) {
61+ return Err ( Bolt12SemanticError :: MissingPaths ) ;
62+ }
63+ if invoice. offer . chains ( ) . len ( ) > 1 {
64+ return Err ( Bolt12SemanticError :: UnexpectedChain ) ;
65+ }
66+ Ok ( Self { offer_bytes : & offer. bytes , invoice, keys } )
67+ }
68+
69+ /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
70+ pub fn build_and_sign < T : secp256k1:: Signing > (
71+ self , secp_ctx : & Secp256k1 < T > ,
72+ ) -> Result < StaticInvoice , Bolt12SemanticError > {
73+ #[ cfg( feature = "std" ) ]
74+ {
75+ if self . invoice . is_offer_expired ( ) {
76+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
77+ }
78+ }
79+
80+ #[ cfg( not( feature = "std" ) ) ]
81+ {
82+ if self . invoice . is_offer_expired_no_std ( self . invoice . created_at ( ) ) {
83+ return Err ( Bolt12SemanticError :: AlreadyExpired ) ;
84+ }
85+ }
86+
87+ let Self { offer_bytes, invoice, keys } = self ;
88+ let unsigned_invoice = UnsignedStaticInvoice :: new ( & offer_bytes, invoice) ;
89+ let invoice = unsigned_invoice
90+ . sign ( |message : & UnsignedStaticInvoice | {
91+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( message. tagged_hash . as_digest ( ) , & keys) )
92+ } )
93+ . unwrap ( ) ;
94+ Ok ( invoice)
95+ }
96+
97+ invoice_builder_methods_common ! ( self , Self , self . invoice, Self , self , S , StaticInvoice , mut ) ;
98+ }
99+
100+ /// A semantically valid [`StaticInvoice`] that hasn't been signed.
101+ pub struct UnsignedStaticInvoice {
48102 bytes : Vec < u8 > ,
49103 contents : InvoiceContents ,
50- signature : Signature ,
51104 tagged_hash : TaggedHash ,
52105}
53106
54- /// The contents of a [`StaticInvoice`] for responding to an [`Offer`].
55- ///
56- /// [`Offer`]: crate::offers::offer::Offer
57- struct InvoiceContents {
58- offer : OfferContents ,
59- payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
60- created_at : Duration ,
61- relative_expiry : Option < Duration > ,
62- fallbacks : Option < Vec < FallbackAddress > > ,
63- features : Bolt12InvoiceFeatures ,
64- signing_pubkey : PublicKey ,
65- }
66-
67107macro_rules! invoice_accessors { ( $self: ident, $contents: expr) => {
68108 /// The chain that must be used when paying the invoice. [`StaticInvoice`]s currently can only be
69109 /// created from offers that support a single chain.
@@ -132,6 +172,99 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
132172 }
133173} }
134174
175+ impl UnsignedStaticInvoice {
176+ fn new ( offer_bytes : & Vec < u8 > , contents : InvoiceContents ) -> Self {
177+ let mut bytes = Vec :: new ( ) ;
178+ WithoutLength ( offer_bytes) . write ( & mut bytes) . unwrap ( ) ;
179+ contents. as_invoice_fields_tlv_stream ( ) . write ( & mut bytes) . unwrap ( ) ;
180+
181+ let tagged_hash = TaggedHash :: from_valid_tlv_stream_bytes ( SIGNATURE_TAG , & bytes) ;
182+ Self { contents, tagged_hash, bytes }
183+ }
184+
185+ /// Signs the [`TaggedHash`] of the invoice using the given function.
186+ ///
187+ /// Note: The hash computation may have included unknown, odd TLV records.
188+ pub fn sign < F : SignStaticInvoiceFn > ( mut self , sign : F ) -> Result < StaticInvoice , SignError > {
189+ let pubkey = self . contents . signing_pubkey ;
190+ let signature = merkle:: sign_message ( sign, & self , pubkey) ?;
191+
192+ // Append the signature TLV record to the bytes.
193+ let signature_tlv_stream = SignatureTlvStreamRef { signature : Some ( & signature) } ;
194+ signature_tlv_stream. write ( & mut self . bytes ) . unwrap ( ) ;
195+
196+ Ok ( StaticInvoice {
197+ bytes : self . bytes ,
198+ contents : self . contents ,
199+ signature,
200+ tagged_hash : self . tagged_hash ,
201+ } )
202+ }
203+
204+ invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
205+ invoice_accessors ! ( self , self . contents) ;
206+ }
207+
208+ impl AsRef < TaggedHash > for UnsignedStaticInvoice {
209+ fn as_ref ( & self ) -> & TaggedHash {
210+ & self . tagged_hash
211+ }
212+ }
213+
214+ /// A function for signing an [`UnsignedStaticInvoice`].
215+ pub trait SignStaticInvoiceFn {
216+ /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream.
217+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ;
218+ }
219+
220+ impl < F > SignStaticInvoiceFn for F
221+ where
222+ F : Fn ( & UnsignedStaticInvoice ) -> Result < Signature , ( ) > ,
223+ {
224+ fn sign_invoice ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
225+ self ( message)
226+ }
227+ }
228+
229+ impl < F > SignFn < UnsignedStaticInvoice > for F
230+ where
231+ F : SignStaticInvoiceFn ,
232+ {
233+ fn sign ( & self , message : & UnsignedStaticInvoice ) -> Result < Signature , ( ) > {
234+ self . sign_invoice ( message)
235+ }
236+ }
237+
238+ /// A `StaticInvoice` is a reusable payment request corresponding to an [`Offer`].
239+ ///
240+ /// A static invoice may be sent in response to an [`InvoiceRequest`] and includes all the
241+ /// information needed to pay the recipient. However, unlike [`Bolt12Invoice`]s, static invoices do
242+ /// not provide proof-of-payment. Therefore, [`Bolt12Invoice`]s should be preferred when the
243+ /// recipient is online to provide one.
244+ ///
245+ /// [`Offer`]: crate::offers::offer::Offer
246+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
247+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
248+ pub struct StaticInvoice {
249+ bytes : Vec < u8 > ,
250+ contents : InvoiceContents ,
251+ signature : Signature ,
252+ tagged_hash : TaggedHash ,
253+ }
254+
255+ /// The contents of a [`StaticInvoice`] for responding to an [`Offer`].
256+ ///
257+ /// [`Offer`]: crate::offers::offer::Offer
258+ struct InvoiceContents {
259+ offer : OfferContents ,
260+ payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > ,
261+ created_at : Duration ,
262+ relative_expiry : Option < Duration > ,
263+ fallbacks : Option < Vec < FallbackAddress > > ,
264+ features : Bolt12InvoiceFeatures ,
265+ signing_pubkey : PublicKey ,
266+ }
267+
135268impl StaticInvoice {
136269 invoice_accessors_common ! ( self , self . contents, StaticInvoice ) ;
137270 invoice_accessors ! ( self , self . contents) ;
@@ -148,6 +281,51 @@ impl StaticInvoice {
148281}
149282
150283impl InvoiceContents {
284+ #[ cfg( feature = "std" ) ]
285+ fn is_offer_expired ( & self ) -> bool {
286+ self . offer . is_expired ( )
287+ }
288+
289+ #[ cfg( not( feature = "std" ) ) ]
290+ fn is_offer_expired_no_std ( & self , duration_since_epoch : Duration ) -> bool {
291+ self . offer . is_offer_expired_no_std ( duration_since_epoch)
292+ }
293+
294+ fn new (
295+ offer : & Offer , payment_paths : Vec < ( BlindedPayInfo , BlindedPath ) > , created_at : Duration ,
296+ signing_pubkey : PublicKey ,
297+ ) -> Self {
298+ Self {
299+ offer : offer. contents . clone ( ) ,
300+ payment_paths,
301+ created_at,
302+ relative_expiry : None ,
303+ fallbacks : None ,
304+ features : Bolt12InvoiceFeatures :: empty ( ) ,
305+ signing_pubkey,
306+ }
307+ }
308+
309+ fn as_invoice_fields_tlv_stream ( & self ) -> InvoiceTlvStreamRef {
310+ let features = {
311+ if self . features == Bolt12InvoiceFeatures :: empty ( ) {
312+ None
313+ } else {
314+ Some ( & self . features )
315+ }
316+ } ;
317+
318+ InvoiceTlvStreamRef {
319+ paths : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( _, path) | path) ) ) ,
320+ blindedpay : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( payinfo, _) | payinfo) ) ) ,
321+ created_at : Some ( self . created_at . as_secs ( ) ) ,
322+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
323+ fallbacks : self . fallbacks . as_ref ( ) ,
324+ features,
325+ node_id : Some ( & self . signing_pubkey ) ,
326+ }
327+ }
328+
151329 fn chain ( & self ) -> ChainHash {
152330 debug_assert_eq ! ( self . offer. chains( ) . len( ) , 1 ) ;
153331 self . offer . chains ( ) . first ( ) . cloned ( ) . unwrap_or_else ( || self . offer . implied_chain ( ) )
0 commit comments