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