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 , WithoutSignatures , self } ;
28
+ use crate :: offers:: offer:: { Amount , 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
- use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , SeekReadable , WithoutLength , Writeable , Writer } ;
33
+ use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , Iterable , SeekReadable , WithoutLength , Writeable , Writer } ;
31
34
32
35
use crate :: prelude:: * ;
33
36
@@ -38,6 +41,161 @@ 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
+ invreq_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 , payment_paths : Vec < ( BlindedPath , BlindedPayInfo ) > ,
61
+ created_at : Duration , payment_hash : PaymentHash
62
+ ) -> Result < Self , SemanticError > {
63
+ if payment_paths. is_empty ( ) {
64
+ return Err ( SemanticError :: MissingPaths ) ;
65
+ }
66
+
67
+ let amount_msats = match invoice_request. amount_msats ( ) {
68
+ Some ( amount_msats) => amount_msats,
69
+ None => match invoice_request. contents . offer . amount ( ) {
70
+ Some ( Amount :: Bitcoin { amount_msats } ) => {
71
+ amount_msats * invoice_request. quantity ( ) . unwrap_or ( 1 )
72
+ } ,
73
+ Some ( Amount :: Currency { .. } ) => return Err ( SemanticError :: UnsupportedCurrency ) ,
74
+ None => return Err ( SemanticError :: MissingAmount ) ,
75
+ } ,
76
+ } ;
77
+
78
+ Ok ( Self {
79
+ invreq_bytes : & invoice_request. bytes ,
80
+ invoice : InvoiceContents :: ForOffer {
81
+ invoice_request : invoice_request. contents . clone ( ) ,
82
+ fields : InvoiceFields {
83
+ payment_paths, created_at, relative_expiry : None , payment_hash, amount_msats,
84
+ fallbacks : None , features : Bolt12InvoiceFeatures :: empty ( ) ,
85
+ signing_pubkey : invoice_request. contents . offer . signing_pubkey ( ) ,
86
+ } ,
87
+ } ,
88
+ } )
89
+ }
90
+
91
+ /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
92
+ /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
93
+ ///
94
+ /// Successive calls to this method will override the previous setting.
95
+ pub fn relative_expiry ( mut self , relative_expiry_secs : u32 ) -> Self {
96
+ let relative_expiry = Duration :: from_secs ( relative_expiry_secs as u64 ) ;
97
+ self . invoice . fields_mut ( ) . relative_expiry = Some ( relative_expiry) ;
98
+ self
99
+ }
100
+
101
+ /// Adds a P2WSH address to [`Invoice::fallbacks`].
102
+ ///
103
+ /// Successive calls to this method will add another address. Caller is responsible for not
104
+ /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
105
+ pub fn fallback_v0_p2wsh ( mut self , script_hash : & WScriptHash ) -> Self {
106
+ let address = FallbackAddress {
107
+ version : WitnessVersion :: V0 . to_num ( ) ,
108
+ program : Vec :: from ( & script_hash. into_inner ( ) [ ..] ) ,
109
+ } ;
110
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
111
+ self
112
+ }
113
+
114
+ /// Adds a P2WPKH address to [`Invoice::fallbacks`].
115
+ ///
116
+ /// Successive calls to this method will add another address. Caller is responsible for not
117
+ /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
118
+ pub fn fallback_v0_p2wpkh ( mut self , pubkey_hash : & WPubkeyHash ) -> Self {
119
+ let address = FallbackAddress {
120
+ version : WitnessVersion :: V0 . to_num ( ) ,
121
+ program : Vec :: from ( & pubkey_hash. into_inner ( ) [ ..] ) ,
122
+ } ;
123
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
124
+ self
125
+ }
126
+
127
+ /// Adds a P2TR address to [`Invoice::fallbacks`].
128
+ ///
129
+ /// Successive calls to this method will add another address. Caller is responsible for not
130
+ /// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
131
+ pub fn fallback_v1_p2tr_tweaked ( mut self , output_key : & TweakedPublicKey ) -> Self {
132
+ let address = FallbackAddress {
133
+ version : WitnessVersion :: V1 . to_num ( ) ,
134
+ program : Vec :: from ( & output_key. serialize ( ) [ ..] ) ,
135
+ } ;
136
+ self . invoice . fields_mut ( ) . fallbacks . get_or_insert_with ( Vec :: new) . push ( address) ;
137
+ self
138
+ }
139
+
140
+ /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
141
+ pub fn allow_mpp ( mut self ) -> Self {
142
+ self . invoice . fields_mut ( ) . features . set_basic_mpp_optional ( ) ;
143
+ self
144
+ }
145
+
146
+ /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
147
+ /// [`UnsignedInvoice::sign`].
148
+ pub fn build ( self ) -> Result < UnsignedInvoice < ' a > , SemanticError > {
149
+ #[ cfg( feature = "std" ) ] {
150
+ if self . invoice . is_offer_or_refund_expired ( ) {
151
+ return Err ( SemanticError :: AlreadyExpired ) ;
152
+ }
153
+ }
154
+
155
+ let InvoiceBuilder { invreq_bytes, invoice } = self ;
156
+ Ok ( UnsignedInvoice { invreq_bytes, invoice } )
157
+ }
158
+ }
159
+
160
+ /// A semantically valid [`Invoice`] that hasn't been signed.
161
+ pub struct UnsignedInvoice < ' a > {
162
+ invreq_bytes : & ' a Vec < u8 > ,
163
+ invoice : InvoiceContents ,
164
+ }
165
+
166
+ impl < ' a > UnsignedInvoice < ' a > {
167
+ /// Signs the invoice using the given function.
168
+ pub fn sign < F , E > ( self , sign : F ) -> Result < Invoice , SignError < E > >
169
+ where
170
+ F : FnOnce ( & Message ) -> Result < Signature , E >
171
+ {
172
+ // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
173
+ // have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
174
+ // `RefundContents`.
175
+ let ( _, _, _, invoice_tlv_stream) = self . invoice . as_tlv_stream ( ) ;
176
+ let invoice_request_bytes = WithoutSignatures ( self . invreq_bytes ) ;
177
+ let unsigned_tlv_stream = ( invoice_request_bytes, invoice_tlv_stream) ;
178
+
179
+ let mut bytes = Vec :: new ( ) ;
180
+ unsigned_tlv_stream. write ( & mut bytes) . unwrap ( ) ;
181
+
182
+ let pubkey = self . invoice . fields ( ) . signing_pubkey ;
183
+ let signature = merkle:: sign_message ( sign, SIGNATURE_TAG , & bytes, pubkey) ?;
184
+
185
+ // Append the signature TLV record to the bytes.
186
+ let signature_tlv_stream = SignatureTlvStreamRef {
187
+ signature : Some ( & signature) ,
188
+ } ;
189
+ signature_tlv_stream. write ( & mut bytes) . unwrap ( ) ;
190
+
191
+ Ok ( Invoice {
192
+ bytes,
193
+ contents : self . invoice ,
194
+ signature,
195
+ } )
196
+ }
197
+ }
198
+
41
199
/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
42
200
///
43
201
/// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
@@ -199,6 +357,15 @@ impl Invoice {
199
357
}
200
358
201
359
impl InvoiceContents {
360
+ /// Whether the original offer or refund has expired.
361
+ #[ cfg( feature = "std" ) ]
362
+ fn is_offer_or_refund_expired ( & self ) -> bool {
363
+ match self {
364
+ InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. offer . is_expired ( ) ,
365
+ InvoiceContents :: ForRefund { refund, .. } => refund. is_expired ( ) ,
366
+ }
367
+ }
368
+
202
369
fn chain ( & self ) -> ChainHash {
203
370
match self {
204
371
InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. chain ( ) ,
@@ -212,6 +379,44 @@ impl InvoiceContents {
212
379
InvoiceContents :: ForRefund { fields, .. } => fields,
213
380
}
214
381
}
382
+
383
+ fn fields_mut ( & mut self ) -> & mut InvoiceFields {
384
+ match self {
385
+ InvoiceContents :: ForOffer { fields, .. } => fields,
386
+ InvoiceContents :: ForRefund { fields, .. } => fields,
387
+ }
388
+ }
389
+
390
+ fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef {
391
+ let ( payer, offer, invoice_request) = match self {
392
+ InvoiceContents :: ForOffer { invoice_request, .. } => invoice_request. as_tlv_stream ( ) ,
393
+ InvoiceContents :: ForRefund { refund, .. } => refund. as_tlv_stream ( ) ,
394
+ } ;
395
+ let invoice = self . fields ( ) . as_tlv_stream ( ) ;
396
+
397
+ ( payer, offer, invoice_request, invoice)
398
+ }
399
+ }
400
+
401
+ impl InvoiceFields {
402
+ fn as_tlv_stream ( & self ) -> InvoiceTlvStreamRef {
403
+ let features = {
404
+ if self . features == Bolt12InvoiceFeatures :: empty ( ) { None }
405
+ else { Some ( & self . features ) }
406
+ } ;
407
+
408
+ InvoiceTlvStreamRef {
409
+ paths : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( path, _) | path) ) ) ,
410
+ blindedpay : Some ( Iterable ( self . payment_paths . iter ( ) . map ( |( _, payinfo) | payinfo) ) ) ,
411
+ created_at : Some ( self . created_at . as_secs ( ) ) ,
412
+ relative_expiry : self . relative_expiry . map ( |duration| duration. as_secs ( ) as u32 ) ,
413
+ payment_hash : Some ( & self . payment_hash ) ,
414
+ amount : Some ( self . amount_msats ) ,
415
+ fallbacks : self . fallbacks . as_ref ( ) ,
416
+ features,
417
+ node_id : Some ( & self . signing_pubkey ) ,
418
+ }
419
+ }
215
420
}
216
421
217
422
impl Writeable for Invoice {
@@ -230,8 +435,8 @@ impl TryFrom<Vec<u8>> for Invoice {
230
435
}
231
436
232
437
tlv_stream ! ( InvoiceTlvStream , InvoiceTlvStreamRef , 160 ..240 , {
233
- ( 160 , paths: ( Vec <BlindedPath >, WithoutLength ) ) ,
234
- ( 162 , blindedpay: ( Vec <BlindedPayInfo >, WithoutLength ) ) ,
438
+ ( 160 , paths: ( Vec <BlindedPath >, WithoutLength , Iterable < ' a , BlindedPathIter < ' a> , BlindedPath > ) ) ,
439
+ ( 162 , blindedpay: ( Vec <BlindedPayInfo >, WithoutLength , Iterable < ' a , BlindedPayInfoIter < ' a> , BlindedPayInfo > ) ) ,
235
440
( 164 , created_at: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
236
441
( 166 , relative_expiry: ( u32 , HighZeroBytesDroppedBigSize ) ) ,
237
442
( 168 , payment_hash: PaymentHash ) ,
@@ -241,7 +446,17 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
241
446
( 176 , node_id: PublicKey ) ,
242
447
} ) ;
243
448
244
- /// Information needed to route a payment across a [`BlindedPath`] hop.
449
+ type BlindedPathIter < ' a > = core:: iter:: Map <
450
+ core:: slice:: Iter < ' a , ( BlindedPath , BlindedPayInfo ) > ,
451
+ for <' r > fn ( & ' r ( BlindedPath , BlindedPayInfo ) ) -> & ' r BlindedPath ,
452
+ > ;
453
+
454
+ type BlindedPayInfoIter < ' a > = core:: iter:: Map <
455
+ core:: slice:: Iter < ' a , ( BlindedPath , BlindedPayInfo ) > ,
456
+ for <' r > fn ( & ' r ( BlindedPath , BlindedPayInfo ) ) -> & ' r BlindedPayInfo ,
457
+ > ;
458
+
459
+ /// Information needed to route a payment across a [`BlindedPath`].
245
460
#[ derive( Debug , PartialEq ) ]
246
461
pub struct BlindedPayInfo {
247
462
fee_base_msat : u32 ,
@@ -288,6 +503,13 @@ impl SeekReadable for FullInvoiceTlvStream {
288
503
type PartialInvoiceTlvStream =
289
504
( PayerTlvStream , OfferTlvStream , InvoiceRequestTlvStream , InvoiceTlvStream ) ;
290
505
506
+ type PartialInvoiceTlvStreamRef < ' a > = (
507
+ PayerTlvStreamRef < ' a > ,
508
+ OfferTlvStreamRef < ' a > ,
509
+ InvoiceRequestTlvStreamRef < ' a > ,
510
+ InvoiceTlvStreamRef < ' a > ,
511
+ ) ;
512
+
291
513
impl TryFrom < ParsedMessage < FullInvoiceTlvStream > > for Invoice {
292
514
type Error = ParseError ;
293
515
0 commit comments