10
10
//! Data structures and encoding for `invoice` messages.
11
11
12
12
use bitcoin:: blockdata:: constants:: ChainHash ;
13
+ use bitcoin:: hash_types:: { WPubkeyHash , WScriptHash } ;
14
+ use bitcoin:: hashes:: Hash ;
13
15
use bitcoin:: network:: constants:: Network ;
14
- use bitcoin:: secp256k1:: PublicKey ;
16
+ use bitcoin:: secp256k1:: { Message , PublicKey } ;
15
17
use bitcoin:: secp256k1:: schnorr:: Signature ;
16
18
use bitcoin:: util:: address:: { Address , Payload , WitnessVersion } ;
19
+ use bitcoin:: util:: schnorr:: TweakedPublicKey ;
17
20
use core:: convert:: TryFrom ;
18
21
use core:: time:: Duration ;
19
22
use crate :: io;
20
23
use crate :: ln:: PaymentHash ;
21
24
use crate :: ln:: features:: { BlindedHopFeatures , Bolt12InvoiceFeatures } ;
22
25
use crate :: ln:: msgs:: DecodeError ;
23
- use crate :: offers:: invoice_request:: { InvoiceRequestContents , InvoiceRequestTlvStream } ;
24
- use crate :: offers:: merkle:: { SignatureTlvStream , self } ;
25
- use crate :: offers:: offer:: OfferTlvStream ;
26
+ use crate :: offers:: invoice_request:: { InvoiceRequest , InvoiceRequestContents , InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
27
+ use crate :: offers:: merkle:: { SignError , SignatureTlvStream , SignatureTlvStreamRef , self } ;
28
+ use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
26
29
use crate :: offers:: parse:: { ParseError , ParsedMessage , SemanticError } ;
27
- use crate :: offers:: payer:: PayerTlvStream ;
30
+ use crate :: offers:: payer:: { PayerTlvStream , PayerTlvStreamRef } ;
28
31
use crate :: offers:: refund:: RefundContents ;
29
32
use crate :: onion_message:: BlindedPath ;
30
33
use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , SeekReadable , WithoutLength , Writeable , Writer } ;
@@ -38,6 +41,164 @@ const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
38
41
39
42
const SIGNATURE_TAG : & ' static str = concat ! ( "lightning" , "invoice" , "signature" ) ;
40
43
44
+ /// Builds an [`Invoice`] from either:
45
+ /// - an [`InvoiceRequest`] for the "offer to be paid" flow or
46
+ /// - a [`Refund`] for the "offer for money" flow.
47
+ ///
48
+ /// See [module-level documentation] for usage.
49
+ ///
50
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
51
+ /// [`Refund`]: crate::offers::refund::Refund
52
+ /// [module-level documentation]: self
53
+ pub struct InvoiceBuilder < ' a > {
54
+ bytes : & ' a Vec < u8 > ,
55
+ invoice : InvoiceContents ,
56
+ }
57
+
58
+ impl < ' a > InvoiceBuilder < ' a > {
59
+ pub ( super ) fn for_offer (
60
+ invoice_request : & ' a InvoiceRequest , paths : Vec < BlindedPath > , payinfo : Vec < BlindedPayInfo > ,
61
+ created_at : Duration , payment_hash : PaymentHash
62
+ ) -> Result < Self , SemanticError > {
63
+ if paths. is_empty ( ) {
64
+ return Err ( SemanticError :: MissingPaths ) ;
65
+ }
66
+
67
+ if paths. len ( ) != payinfo. len ( ) {
68
+ return Err ( SemanticError :: InvalidPayInfo ) ;
69
+ }
70
+
71
+ Ok ( Self {
72
+ bytes : & invoice_request. bytes ,
73
+ invoice : InvoiceContents :: ForOffer {
74
+ invoice_request : invoice_request. contents . clone ( ) ,
75
+ fields : InvoiceFields {
76
+ paths, payinfo, created_at, relative_expiry : None , payment_hash,
77
+ amount_msats : invoice_request. amount_msats ( ) , fallbacks : None ,
78
+ features : Bolt12InvoiceFeatures :: empty ( ) , code : None ,
79
+ } ,
80
+ } ,
81
+ } )
82
+ }
83
+
84
+ /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
85
+ /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
86
+ ///
87
+ /// Successive calls to this method will override the previous setting.
88
+ pub fn relative_expiry ( mut self , relative_expiry_secs : u32 ) -> Self {
89
+ let relative_expiry = Duration :: from_secs ( relative_expiry_secs as u64 ) ;
90
+ self . invoice . fields_mut ( ) . relative_expiry = Some ( relative_expiry) ;
91
+ self
92
+ }
93
+
94
+ /// Adds a P2WSH address to [`Invoice::fallbacks`].
95
+ ///
96
+ /// Successive calls to this method will add another address. Caller is responsible for not
97
+ /// adding duplicate addresses.
98
+ pub fn fallback_v0_p2wsh ( mut self , script_hash : & WScriptHash ) -> Self {
99
+ let address = FallbackAddress {
100
+ version : WitnessVersion :: V0 ,
101
+ program : Vec :: from ( & script_hash. into_inner ( ) [ ..] ) ,
102
+ } ;
103
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
104
+ self
105
+ }
106
+
107
+ /// Adds a P2WPKH address to [`Invoice::fallbacks`].
108
+ ///
109
+ /// Successive calls to this method will add another address. Caller is responsible for not
110
+ /// adding duplicate addresses.
111
+ pub fn fallback_v0_p2wpkh ( mut self , pubkey_hash : & WPubkeyHash ) -> Self {
112
+ let address = FallbackAddress {
113
+ version : WitnessVersion :: V0 ,
114
+ program : Vec :: from ( & pubkey_hash. into_inner ( ) [ ..] ) ,
115
+ } ;
116
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
117
+ self
118
+ }
119
+
120
+ /// Adds a P2TR address to [`Invoice::fallbacks`].
121
+ ///
122
+ /// Successive calls to this method will add another address. Caller is responsible for not
123
+ /// adding duplicate addresses.
124
+ pub fn fallback_v1_p2tr_tweaked ( mut self , output_key : & TweakedPublicKey ) -> Self {
125
+ let address = FallbackAddress {
126
+ version : WitnessVersion :: V1 ,
127
+ program : Vec :: from ( & output_key. serialize ( ) [ ..] ) ,
128
+ } ;
129
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
130
+ self
131
+ }
132
+
133
+ /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
134
+ ///
135
+ /// A subsequent call to [`InvoiceBuilder::mpp_required`] will override this setting.
136
+ pub fn mpp_optional ( mut self ) -> Self {
137
+ self . invoice . fields_mut ( ) . features . set_basic_mpp_optional ( ) ;
138
+ self
139
+ }
140
+
141
+ /// Sets [`Invoice::features`] to indicate MPP should be used. Otherwise, MPP is disallowed.
142
+ ///
143
+ /// A subsequent call to [`InvoiceBuilder::mpp_optional`] will override this setting.
144
+ pub fn mpp_required ( mut self ) -> Self {
145
+ self . invoice . fields_mut ( ) . features . set_basic_mpp_required ( ) ;
146
+ self
147
+ }
148
+
149
+ /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
150
+ /// [`UnsignedInvoice::sign`].
151
+ pub fn build ( self ) -> Result < UnsignedInvoice < ' a > , SemanticError > {
152
+ #[ cfg( feature = "std" ) ] {
153
+ if self . invoice . is_offer_or_refund_expired ( ) {
154
+ return Err ( SemanticError :: AlreadyExpired ) ;
155
+ }
156
+ }
157
+
158
+ let InvoiceBuilder { bytes, invoice } = self ;
159
+ Ok ( UnsignedInvoice { bytes, invoice } )
160
+ }
161
+ }
162
+
163
+ /// A semantically valid [`Invoice`] that hasn't been signed.
164
+ pub struct UnsignedInvoice < ' a > {
165
+ bytes : & ' a Vec < u8 > ,
166
+ invoice : InvoiceContents ,
167
+ }
168
+
169
+ impl < ' a > UnsignedInvoice < ' a > {
170
+ /// Signs the invoice using the given function.
171
+ pub fn sign < F , E > ( self , sign : F ) -> Result < Invoice , SignError < E > >
172
+ where
173
+ F : FnOnce ( & Message ) -> Result < Signature , E >
174
+ {
175
+ // Use the invoice_request bytes instead of the invoice_reqeuest TLV stream as the latter
176
+ // may have contained unknown TLV records, which are not stored in `InvoiceRequestContents`
177
+ // or `RefundContents`.
178
+ let ( _, _, _, invoice_tlv_stream) = self . invoice . as_tlv_stream ( ) ;
179
+ let invoice_request_bytes = WithoutLength ( self . bytes ) ;
180
+ let unsigned_tlv_stream = ( invoice_request_bytes, invoice_tlv_stream) ;
181
+
182
+ let mut bytes = Vec :: new ( ) ;
183
+ unsigned_tlv_stream. write ( & mut bytes) . unwrap ( ) ;
184
+
185
+ let pubkey = self . invoice . signing_pubkey ( ) ;
186
+ let signature = merkle:: sign_message ( sign, SIGNATURE_TAG , & bytes, pubkey) ?;
187
+
188
+ // Append the signature TLV record to the bytes.
189
+ let signature_tlv_stream = SignatureTlvStreamRef {
190
+ signature : Some ( & signature) ,
191
+ } ;
192
+ signature_tlv_stream. write ( & mut bytes) . unwrap ( ) ;
193
+
194
+ Ok ( Invoice {
195
+ bytes,
196
+ contents : self . invoice ,
197
+ signature : Some ( signature) ,
198
+ } )
199
+ }
200
+ }
201
+
41
202
/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
42
203
///
43
204
/// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
@@ -199,19 +360,75 @@ impl Invoice {
199
360
}
200
361
201
362
impl InvoiceContents {
363
+ /// Whether the original offer or refund has expired.
364
+ #[ cfg( feature = "std" ) ]
365
+ fn is_offer_or_refund_expired ( & self ) -> bool {
366
+ match self {
367
+ InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. offer . is_expired ( ) ,
368
+ InvoiceContents :: ForRefund { refund, .. } => refund. is_expired ( ) ,
369
+ }
370
+ }
371
+
202
372
fn chain ( & self ) -> ChainHash {
203
373
match self {
204
374
InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. chain ( ) ,
205
375
InvoiceContents :: ForRefund { refund, .. } => refund. chain ( ) ,
206
376
}
207
377
}
208
378
379
+ fn signing_pubkey ( & self ) -> PublicKey {
380
+ match self {
381
+ InvoiceContents :: ForOffer { invoice_request, .. } => {
382
+ invoice_request. offer . signing_pubkey ( )
383
+ } ,
384
+ InvoiceContents :: ForRefund { .. } => unreachable ! ( ) ,
385
+ }
386
+ }
387
+
209
388
fn fields ( & self ) -> & InvoiceFields {
210
389
match self {
211
390
InvoiceContents :: ForOffer { fields, .. } => fields,
212
391
InvoiceContents :: ForRefund { fields, .. } => fields,
213
392
}
214
393
}
394
+
395
+ fn fields_mut ( & mut self ) -> & mut InvoiceFields {
396
+ match self {
397
+ InvoiceContents :: ForOffer { fields, .. } => fields,
398
+ InvoiceContents :: ForRefund { fields, .. } => fields,
399
+ }
400
+ }
401
+
402
+ fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef {
403
+ let ( payer, offer, invoice_request) = match self {
404
+ InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. as_tlv_stream ( ) ,
405
+ InvoiceContents :: ForRefund { refund, .. } => refund. as_tlv_stream ( ) ,
406
+ } ;
407
+ let invoice = self . fields ( ) . as_tlv_stream ( ) ;
408
+
409
+ ( payer, offer, invoice_request, invoice)
410
+ }
411
+ }
412
+
413
+ impl InvoiceFields {
414
+ fn as_tlv_stream ( & self ) -> InvoiceTlvStreamRef {
415
+ let features = {
416
+ if self . features == Bolt12InvoiceFeatures :: empty ( ) { None }
417
+ else { Some ( & self . features ) }
418
+ } ;
419
+
420
+ InvoiceTlvStreamRef {
421
+ paths : Some ( & self . paths ) ,
422
+ blindedpay : Some ( & self . payinfo ) ,
423
+ created_at : Some ( self . created_at . as_secs ( ) ) ,
424
+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
425
+ payment_hash : Some ( & self . payment_hash ) ,
426
+ amount : Some ( self . amount_msats ) ,
427
+ fallbacks : self . fallbacks . as_ref ( ) ,
428
+ features,
429
+ code : self . code . as_ref ( ) ,
430
+ }
431
+ }
215
432
}
216
433
217
434
impl Writeable for Invoice {
@@ -288,6 +505,13 @@ impl SeekReadable for FullInvoiceTlvStream {
288
505
type PartialInvoiceTlvStream =
289
506
( PayerTlvStream , OfferTlvStream , InvoiceRequestTlvStream , InvoiceTlvStream ) ;
290
507
508
+ type PartialInvoiceTlvStreamRef < ' a > = (
509
+ PayerTlvStreamRef < ' a > ,
510
+ OfferTlvStreamRef < ' a > ,
511
+ InvoiceRequestTlvStreamRef < ' a > ,
512
+ InvoiceTlvStreamRef < ' a > ,
513
+ ) ;
514
+
291
515
impl TryFrom < ParsedMessage < FullInvoiceTlvStream > > for Invoice {
292
516
type Error = ParseError ;
293
517
0 commit comments