f800274c973341d0ed33fd9c89dffc8fcb475b19
[rust-lightning] / lightning / src / offers / refund.rs
1 // This file is Copyright its original authors, visible in version control
2 // history.
3 //
4 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7 // You may not use this file except in accordance with one or both of these
8 // licenses.
9
10 //! Data structures and encoding for refunds.
11 //!
12 //! A [`Refund`] is an "offer for money" and is typically constructed by a merchant and presented
13 //! directly to the customer. The recipient responds with an [`Invoice`] to be paid.
14 //!
15 //! This is an [`InvoiceRequest`] produced *not* in response to an [`Offer`].
16 //!
17 //! [`Invoice`]: crate::offers::invoice::Invoice
18 //! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
19 //! [`Offer`]: crate::offers::offer::Offer
20 //!
21 //! ```ignore
22 //! extern crate bitcoin;
23 //! extern crate core;
24 //! extern crate lightning;
25 //!
26 //! use core::convert::TryFrom;
27 //! use core::time::Duration;
28 //!
29 //! use bitcoin::network::constants::Network;
30 //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
31 //! use lightning::offers::parse::ParseError;
32 //! use lightning::offers::refund::{Refund, RefundBuilder};
33 //! use lightning::util::ser::{Readable, Writeable};
34 //!
35 //! # use lightning::onion_message::BlindedPath;
36 //! # #[cfg(feature = "std")]
37 //! # use std::time::SystemTime;
38 //! #
39 //! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
40 //! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
41 //! #
42 //! # #[cfg(feature = "std")]
43 //! # fn build() -> Result<(), ParseError> {
44 //! let secp_ctx = Secp256k1::new();
45 //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
46 //! let pubkey = PublicKey::from(keys);
47 //!
48 //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
49 //! let refund = RefundBuilder::new("coffee, large".to_string(), vec![1; 32], pubkey, 20_000)?
50 //!     .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
51 //!     .issuer("Foo Bar".to_string())
52 //!     .path(create_blinded_path())
53 //!     .path(create_another_blinded_path())
54 //!     .chain(Network::Bitcoin)
55 //!     .payer_note("refund for order #12345".to_string())
56 //!     .build()?;
57 //!
58 //! // Encode as a bech32 string for use in a QR code.
59 //! let encoded_refund = refund.to_string();
60 //!
61 //! // Parse from a bech32 string after scanning from a QR code.
62 //! let refund = encoded_refund.parse::<Refund>()?;
63 //!
64 //! // Encode refund as raw bytes.
65 //! let mut bytes = Vec::new();
66 //! refund.write(&mut bytes).unwrap();
67 //!
68 //! // Decode raw bytes into an refund.
69 //! let refund = Refund::try_from(bytes)?;
70 //! # Ok(())
71 //! # }
72 //! ```
73
74 use bitcoin::blockdata::constants::ChainHash;
75 use bitcoin::network::constants::Network;
76 use bitcoin::secp256k1::PublicKey;
77 use core::convert::TryFrom;
78 use core::str::FromStr;
79 use core::time::Duration;
80 use crate::io;
81 use crate::ln::PaymentHash;
82 use crate::ln::features::InvoiceRequestFeatures;
83 use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
84 use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
85 use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
86 use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
87 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
88 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
89 use crate::onion_message::BlindedPath;
90 use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
91 use crate::util::string::PrintableString;
92
93 use crate::prelude::*;
94
95 #[cfg(feature = "std")]
96 use std::time::SystemTime;
97
98 /// Builds a [`Refund`] for the "offer for money" flow.
99 ///
100 /// See [module-level documentation] for usage.
101 ///
102 /// [module-level documentation]: self
103 pub struct RefundBuilder {
104         refund: RefundContents,
105 }
106
107 impl RefundBuilder {
108         /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
109         /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
110         ///
111         /// Additionally, sets the required [`Refund::description`], [`Refund::metadata`], and
112         /// [`Refund::amount_msats`].
113         pub fn new(
114                 description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
115         ) -> Result<Self, SemanticError> {
116                 if amount_msats > MAX_VALUE_MSAT {
117                         return Err(SemanticError::InvalidAmount);
118                 }
119
120                 let refund = RefundContents {
121                         payer: PayerContents(metadata), metadata: None, description, absolute_expiry: None,
122                         issuer: None, paths: None, chain: None, amount_msats,
123                         features: InvoiceRequestFeatures::empty(), quantity: None, payer_id, payer_note: None,
124                 };
125
126                 Ok(RefundBuilder { refund })
127         }
128
129         /// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
130         /// already passed is valid and can be checked for using [`Refund::is_expired`].
131         ///
132         /// Successive calls to this method will override the previous setting.
133         pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
134                 self.refund.absolute_expiry = Some(absolute_expiry);
135                 self
136         }
137
138         /// Sets the [`Refund::issuer`].
139         ///
140         /// Successive calls to this method will override the previous setting.
141         pub fn issuer(mut self, issuer: String) -> Self {
142                 self.refund.issuer = Some(issuer);
143                 self
144         }
145
146         /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected
147         /// by private channels or if [`Refund::payer_id`] is not a public node id.
148         ///
149         /// Successive calls to this method will add another blinded path. Caller is responsible for not
150         /// adding duplicate paths.
151         pub fn path(mut self, path: BlindedPath) -> Self {
152                 self.refund.paths.get_or_insert_with(Vec::new).push(path);
153                 self
154         }
155
156         /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not
157         /// called, [`Network::Bitcoin`] is assumed.
158         ///
159         /// Successive calls to this method will override the previous setting.
160         pub fn chain(mut self, network: Network) -> Self {
161                 self.refund.chain = Some(ChainHash::using_genesis_block(network));
162                 self
163         }
164
165         /// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful
166         /// when the refund pertains to an [`Invoice`] that paid for more than one item from an
167         /// [`Offer`] as specified by [`InvoiceRequest::quantity`].
168         ///
169         /// Successive calls to this method will override the previous setting.
170         ///
171         /// [`Invoice`]: crate::offers::invoice::Invoice
172         /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
173         /// [`Offer`]: crate::offers::offer::Offer
174         pub fn quantity(mut self, quantity: u64) -> Self {
175                 self.refund.quantity = Some(quantity);
176                 self
177         }
178
179         /// Sets the [`Refund::payer_note`].
180         ///
181         /// Successive calls to this method will override the previous setting.
182         pub fn payer_note(mut self, payer_note: String) -> Self {
183                 self.refund.payer_note = Some(payer_note);
184                 self
185         }
186
187         /// Builds a [`Refund`] after checking for valid semantics.
188         pub fn build(mut self) -> Result<Refund, SemanticError> {
189                 if self.refund.chain() == self.refund.implied_chain() {
190                         self.refund.chain = None;
191                 }
192
193                 let mut bytes = Vec::new();
194                 self.refund.write(&mut bytes).unwrap();
195
196                 Ok(Refund {
197                         bytes,
198                         contents: self.refund,
199                 })
200         }
201 }
202
203 #[cfg(test)]
204 impl RefundBuilder {
205         fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
206                 self.refund.features = features;
207                 self
208         }
209 }
210
211 /// A `Refund` is a request to send an [`Invoice`] without a preceding [`Offer`].
212 ///
213 /// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
214 /// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
215 /// bitcoin ATM.
216 ///
217 /// [`Invoice`]: crate::offers::invoice::Invoice
218 /// [`Offer`]: crate::offers::offer::Offer
219 #[derive(Clone, Debug)]
220 pub struct Refund {
221         pub(super) bytes: Vec<u8>,
222         pub(super) contents: RefundContents,
223 }
224
225 /// The contents of a [`Refund`], which may be shared with an [`Invoice`].
226 ///
227 /// [`Invoice`]: crate::offers::invoice::Invoice
228 #[derive(Clone, Debug)]
229 pub(super) struct RefundContents {
230         payer: PayerContents,
231         // offer fields
232         metadata: Option<Vec<u8>>,
233         description: String,
234         absolute_expiry: Option<Duration>,
235         issuer: Option<String>,
236         paths: Option<Vec<BlindedPath>>,
237         // invoice_request fields
238         chain: Option<ChainHash>,
239         amount_msats: u64,
240         features: InvoiceRequestFeatures,
241         quantity: Option<u64>,
242         payer_id: PublicKey,
243         payer_note: Option<String>,
244 }
245
246 impl Refund {
247         /// A complete description of the purpose of the refund. Intended to be displayed to the user
248         /// but with the caveat that it has not been verified in any way.
249         pub fn description(&self) -> PrintableString {
250                 PrintableString(&self.contents.description)
251         }
252
253         /// Duration since the Unix epoch when an invoice should no longer be sent.
254         ///
255         /// If `None`, the refund does not expire.
256         pub fn absolute_expiry(&self) -> Option<Duration> {
257                 self.contents.absolute_expiry
258         }
259
260         /// Whether the refund has expired.
261         #[cfg(feature = "std")]
262         pub fn is_expired(&self) -> bool {
263                 self.contents.is_expired()
264         }
265
266         /// The issuer of the refund, possibly beginning with `user@domain` or `domain`. Intended to be
267         /// displayed to the user but with the caveat that it has not been verified in any way.
268         pub fn issuer(&self) -> Option<PrintableString> {
269                 self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
270         }
271
272         /// Paths to the sender originating from publicly reachable nodes. Blinded paths provide sender
273         /// privacy by obfuscating its node id.
274         pub fn paths(&self) -> &[BlindedPath] {
275                 self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
276         }
277
278         /// An unpredictable series of bytes, typically containing information about the derivation of
279         /// [`payer_id`].
280         ///
281         /// [`payer_id`]: Self::payer_id
282         pub fn metadata(&self) -> &[u8] {
283                 &self.contents.payer.0
284         }
285
286         /// A chain that the refund is valid for.
287         pub fn chain(&self) -> ChainHash {
288                 self.contents.chain.unwrap_or_else(|| self.contents.implied_chain())
289         }
290
291         /// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
292         ///
293         /// [`chain`]: Self::chain
294         pub fn amount_msats(&self) -> u64 {
295                 self.contents.amount_msats
296         }
297
298         /// Features pertaining to requesting an invoice.
299         pub fn features(&self) -> &InvoiceRequestFeatures {
300                 &self.contents.features
301         }
302
303         /// The quantity of an item that refund is for.
304         pub fn quantity(&self) -> Option<u64> {
305                 self.contents.quantity
306         }
307
308         /// A public node id to send to in the case where there are no [`paths`]. Otherwise, a possibly
309         /// transient pubkey.
310         ///
311         /// [`paths`]: Self::paths
312         pub fn payer_id(&self) -> PublicKey {
313                 self.contents.payer_id
314         }
315
316         /// Payer provided note to include in the invoice.
317         pub fn payer_note(&self) -> Option<PrintableString> {
318                 self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
319         }
320
321         /// Creates an [`Invoice`] for the refund with the given required fields.
322         ///
323         /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
324         /// calling this method in `std` builds. For `no-std` builds, a final [`Duration`] parameter
325         /// must be given, which is used to set [`Invoice::created_at`] since [`std::time::SystemTime`]
326         /// is not available.
327         ///
328         /// The caller is expected to remember the preimage of `payment_hash` in order to
329         /// claim a payment for the invoice.
330         ///
331         /// The `signing_pubkey` is required to sign the invoice since refunds are not in response to an
332         /// offer, which does have a `signing_pubkey`.
333         ///
334         /// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It
335         /// must contain one or more elements.
336         ///
337         /// Errors if the request contains unknown required features.
338         ///
339         /// [`Invoice`]: crate::offers::invoice::Invoice
340         /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
341         pub fn respond_with(
342                 &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
343                 signing_pubkey: PublicKey,
344                 #[cfg(any(test, not(feature = "std")))]
345                 created_at: Duration
346         ) -> Result<InvoiceBuilder, SemanticError> {
347                 if self.features().requires_unknown_bits() {
348                         return Err(SemanticError::UnknownRequiredFeatures);
349                 }
350
351                 #[cfg(all(not(test), feature = "std"))]
352                 let created_at = std::time::SystemTime::now()
353                         .duration_since(std::time::SystemTime::UNIX_EPOCH)
354                         .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
355
356                 InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
357         }
358
359         #[cfg(test)]
360         fn as_tlv_stream(&self) -> RefundTlvStreamRef {
361                 self.contents.as_tlv_stream()
362         }
363 }
364
365 impl AsRef<[u8]> for Refund {
366         fn as_ref(&self) -> &[u8] {
367                 &self.bytes
368         }
369 }
370
371 impl RefundContents {
372         #[cfg(feature = "std")]
373         pub(super) fn is_expired(&self) -> bool {
374                 match self.absolute_expiry {
375                         Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
376                                 Ok(elapsed) => elapsed > seconds_from_epoch,
377                                 Err(_) => false,
378                         },
379                         None => false,
380                 }
381         }
382
383         pub(super) fn chain(&self) -> ChainHash {
384                 self.chain.unwrap_or_else(|| self.implied_chain())
385         }
386
387         pub fn implied_chain(&self) -> ChainHash {
388                 ChainHash::using_genesis_block(Network::Bitcoin)
389         }
390
391         pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
392                 let payer = PayerTlvStreamRef {
393                         metadata: Some(&self.payer.0),
394                 };
395
396                 let offer = OfferTlvStreamRef {
397                         chains: None,
398                         metadata: self.metadata.as_ref(),
399                         currency: None,
400                         amount: None,
401                         description: Some(&self.description),
402                         features: None,
403                         absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
404                         paths: self.paths.as_ref(),
405                         issuer: self.issuer.as_ref(),
406                         quantity_max: None,
407                         node_id: None,
408                 };
409
410                 let features = {
411                         if self.features == InvoiceRequestFeatures::empty() { None }
412                         else { Some(&self.features) }
413                 };
414
415                 let invoice_request = InvoiceRequestTlvStreamRef {
416                         chain: self.chain.as_ref(),
417                         amount: Some(self.amount_msats),
418                         features,
419                         quantity: self.quantity,
420                         payer_id: Some(&self.payer_id),
421                         payer_note: self.payer_note.as_ref(),
422                 };
423
424                 (payer, offer, invoice_request)
425         }
426 }
427
428 impl Writeable for Refund {
429         fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
430                 WithoutLength(&self.bytes).write(writer)
431         }
432 }
433
434 impl Writeable for RefundContents {
435         fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
436                 self.as_tlv_stream().write(writer)
437         }
438 }
439
440 type RefundTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
441
442 type RefundTlvStreamRef<'a> = (
443         PayerTlvStreamRef<'a>,
444         OfferTlvStreamRef<'a>,
445         InvoiceRequestTlvStreamRef<'a>,
446 );
447
448 impl SeekReadable for RefundTlvStream {
449         fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
450                 let payer = SeekReadable::read(r)?;
451                 let offer = SeekReadable::read(r)?;
452                 let invoice_request = SeekReadable::read(r)?;
453
454                 Ok((payer, offer, invoice_request))
455         }
456 }
457
458 impl Bech32Encode for Refund {
459         const BECH32_HRP: &'static str = "lnr";
460 }
461
462 impl FromStr for Refund {
463         type Err = ParseError;
464
465         fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
466                 Refund::from_bech32_str(s)
467         }
468 }
469
470 impl TryFrom<Vec<u8>> for Refund {
471         type Error = ParseError;
472
473         fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
474                 let refund = ParsedMessage::<RefundTlvStream>::try_from(bytes)?;
475                 let ParsedMessage { bytes, tlv_stream } = refund;
476                 let contents = RefundContents::try_from(tlv_stream)?;
477
478                 Ok(Refund { bytes, contents })
479         }
480 }
481
482 impl TryFrom<RefundTlvStream> for RefundContents {
483         type Error = SemanticError;
484
485         fn try_from(tlv_stream: RefundTlvStream) -> Result<Self, Self::Error> {
486                 let (
487                         PayerTlvStream { metadata: payer_metadata },
488                         OfferTlvStream {
489                                 chains, metadata, currency, amount: offer_amount, description,
490                                 features: offer_features, absolute_expiry, paths, issuer, quantity_max, node_id,
491                         },
492                         InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
493                 ) = tlv_stream;
494
495                 let payer = match payer_metadata {
496                         None => return Err(SemanticError::MissingPayerMetadata),
497                         Some(metadata) => PayerContents(metadata),
498                 };
499
500                 if chains.is_some() {
501                         return Err(SemanticError::UnexpectedChain);
502                 }
503
504                 if currency.is_some() || offer_amount.is_some() {
505                         return Err(SemanticError::UnexpectedAmount);
506                 }
507
508                 let description = match description {
509                         None => return Err(SemanticError::MissingDescription),
510                         Some(description) => description,
511                 };
512
513                 if offer_features.is_some() {
514                         return Err(SemanticError::UnexpectedFeatures);
515                 }
516
517                 let absolute_expiry = absolute_expiry.map(Duration::from_secs);
518
519                 if quantity_max.is_some() {
520                         return Err(SemanticError::UnexpectedQuantity);
521                 }
522
523                 if node_id.is_some() {
524                         return Err(SemanticError::UnexpectedSigningPubkey);
525                 }
526
527                 let amount_msats = match amount {
528                         None => return Err(SemanticError::MissingAmount),
529                         Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
530                                 return Err(SemanticError::InvalidAmount);
531                         },
532                         Some(amount_msats) => amount_msats,
533                 };
534
535                 let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
536
537                 let payer_id = match payer_id {
538                         None => return Err(SemanticError::MissingPayerId),
539                         Some(payer_id) => payer_id,
540                 };
541
542                 // TODO: Should metadata be included?
543                 Ok(RefundContents {
544                         payer, metadata, description, absolute_expiry, issuer, paths, chain, amount_msats,
545                         features, quantity, payer_id, payer_note,
546                 })
547         }
548 }
549
550 impl core::fmt::Display for Refund {
551         fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
552                 self.fmt_bech32_str(f)
553         }
554 }
555
556 #[cfg(test)]
557 mod tests {
558         use super::{Refund, RefundBuilder, RefundTlvStreamRef};
559
560         use bitcoin::blockdata::constants::ChainHash;
561         use bitcoin::network::constants::Network;
562         use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
563         use core::convert::TryFrom;
564         use core::time::Duration;
565         use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
566         use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
567         use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
568         use crate::offers::offer::OfferTlvStreamRef;
569         use crate::offers::parse::{ParseError, SemanticError};
570         use crate::offers::payer::PayerTlvStreamRef;
571         use crate::onion_message::{BlindedHop, BlindedPath};
572         use crate::util::ser::{BigSize, Writeable};
573         use crate::util::string::PrintableString;
574
575         fn payer_pubkey() -> PublicKey {
576                 let secp_ctx = Secp256k1::new();
577                 KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()).public_key()
578         }
579
580         fn pubkey(byte: u8) -> PublicKey {
581                 let secp_ctx = Secp256k1::new();
582                 PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
583         }
584
585         fn privkey(byte: u8) -> SecretKey {
586                 SecretKey::from_slice(&[byte; 32]).unwrap()
587         }
588
589         trait ToBytes {
590                 fn to_bytes(&self) -> Vec<u8>;
591         }
592
593         impl<'a> ToBytes for RefundTlvStreamRef<'a> {
594                 fn to_bytes(&self) -> Vec<u8> {
595                         let mut buffer = Vec::new();
596                         self.write(&mut buffer).unwrap();
597                         buffer
598                 }
599         }
600
601         #[test]
602         fn builds_refund_with_defaults() {
603                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
604                         .build().unwrap();
605
606                 let mut buffer = Vec::new();
607                 refund.write(&mut buffer).unwrap();
608
609                 assert_eq!(refund.bytes, buffer.as_slice());
610                 assert_eq!(refund.metadata(), &[1; 32]);
611                 assert_eq!(refund.description(), PrintableString("foo"));
612                 assert_eq!(refund.absolute_expiry(), None);
613                 #[cfg(feature = "std")]
614                 assert!(!refund.is_expired());
615                 assert_eq!(refund.paths(), &[]);
616                 assert_eq!(refund.issuer(), None);
617                 assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
618                 assert_eq!(refund.amount_msats(), 1000);
619                 assert_eq!(refund.features(), &InvoiceRequestFeatures::empty());
620                 assert_eq!(refund.payer_id(), payer_pubkey());
621                 assert_eq!(refund.payer_note(), None);
622
623                 assert_eq!(
624                         refund.as_tlv_stream(),
625                         (
626                                 PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
627                                 OfferTlvStreamRef {
628                                         chains: None,
629                                         metadata: None,
630                                         currency: None,
631                                         amount: None,
632                                         description: Some(&String::from("foo")),
633                                         features: None,
634                                         absolute_expiry: None,
635                                         paths: None,
636                                         issuer: None,
637                                         quantity_max: None,
638                                         node_id: None,
639                                 },
640                                 InvoiceRequestTlvStreamRef {
641                                         chain: None,
642                                         amount: Some(1000),
643                                         features: None,
644                                         quantity: None,
645                                         payer_id: Some(&payer_pubkey()),
646                                         payer_note: None,
647                                 },
648                         ),
649                 );
650
651                 if let Err(e) = Refund::try_from(buffer) {
652                         panic!("error parsing refund: {:?}", e);
653                 }
654         }
655
656         #[test]
657         fn fails_building_refund_with_invalid_amount() {
658                 match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) {
659                         Ok(_) => panic!("expected error"),
660                         Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
661                 }
662         }
663
664         #[test]
665         fn builds_refund_with_absolute_expiry() {
666                 let future_expiry = Duration::from_secs(u64::max_value());
667                 let past_expiry = Duration::from_secs(0);
668
669                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
670                         .absolute_expiry(future_expiry)
671                         .build()
672                         .unwrap();
673                 let (_, tlv_stream, _) = refund.as_tlv_stream();
674                 #[cfg(feature = "std")]
675                 assert!(!refund.is_expired());
676                 assert_eq!(refund.absolute_expiry(), Some(future_expiry));
677                 assert_eq!(tlv_stream.absolute_expiry, Some(future_expiry.as_secs()));
678
679                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
680                         .absolute_expiry(future_expiry)
681                         .absolute_expiry(past_expiry)
682                         .build()
683                         .unwrap();
684                 let (_, tlv_stream, _) = refund.as_tlv_stream();
685                 #[cfg(feature = "std")]
686                 assert!(refund.is_expired());
687                 assert_eq!(refund.absolute_expiry(), Some(past_expiry));
688                 assert_eq!(tlv_stream.absolute_expiry, Some(past_expiry.as_secs()));
689         }
690
691         #[test]
692         fn builds_refund_with_paths() {
693                 let paths = vec![
694                         BlindedPath {
695                                 introduction_node_id: pubkey(40),
696                                 blinding_point: pubkey(41),
697                                 blinded_hops: vec![
698                                         BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
699                                         BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
700                                 ],
701                         },
702                         BlindedPath {
703                                 introduction_node_id: pubkey(40),
704                                 blinding_point: pubkey(41),
705                                 blinded_hops: vec![
706                                         BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
707                                         BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
708                                 ],
709                         },
710                 ];
711
712                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
713                         .path(paths[0].clone())
714                         .path(paths[1].clone())
715                         .build()
716                         .unwrap();
717                 let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream();
718                 assert_eq!(refund.paths(), paths.as_slice());
719                 assert_eq!(refund.payer_id(), pubkey(42));
720                 assert_ne!(pubkey(42), pubkey(44));
721                 assert_eq!(offer_tlv_stream.paths, Some(&paths));
722                 assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42)));
723         }
724
725         #[test]
726         fn builds_refund_with_issuer() {
727                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
728                         .issuer("bar".into())
729                         .build()
730                         .unwrap();
731                 let (_, tlv_stream, _) = refund.as_tlv_stream();
732                 assert_eq!(refund.issuer(), Some(PrintableString("bar")));
733                 assert_eq!(tlv_stream.issuer, Some(&String::from("bar")));
734
735                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
736                         .issuer("bar".into())
737                         .issuer("baz".into())
738                         .build()
739                         .unwrap();
740                 let (_, tlv_stream, _) = refund.as_tlv_stream();
741                 assert_eq!(refund.issuer(), Some(PrintableString("baz")));
742                 assert_eq!(tlv_stream.issuer, Some(&String::from("baz")));
743         }
744
745         #[test]
746         fn builds_refund_with_chain() {
747                 let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
748                 let testnet = ChainHash::using_genesis_block(Network::Testnet);
749
750                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
751                         .chain(Network::Bitcoin)
752                         .build().unwrap();
753                 let (_, _, tlv_stream) = refund.as_tlv_stream();
754                 assert_eq!(refund.chain(), mainnet);
755                 assert_eq!(tlv_stream.chain, None);
756
757                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
758                         .chain(Network::Testnet)
759                         .build().unwrap();
760                 let (_, _, tlv_stream) = refund.as_tlv_stream();
761                 assert_eq!(refund.chain(), testnet);
762                 assert_eq!(tlv_stream.chain, Some(&testnet));
763
764                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
765                         .chain(Network::Regtest)
766                         .chain(Network::Testnet)
767                         .build().unwrap();
768                 let (_, _, tlv_stream) = refund.as_tlv_stream();
769                 assert_eq!(refund.chain(), testnet);
770                 assert_eq!(tlv_stream.chain, Some(&testnet));
771         }
772
773         #[test]
774         fn builds_refund_with_quantity() {
775                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
776                         .quantity(10)
777                         .build().unwrap();
778                 let (_, _, tlv_stream) = refund.as_tlv_stream();
779                 assert_eq!(refund.quantity(), Some(10));
780                 assert_eq!(tlv_stream.quantity, Some(10));
781
782                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
783                         .quantity(10)
784                         .quantity(1)
785                         .build().unwrap();
786                 let (_, _, tlv_stream) = refund.as_tlv_stream();
787                 assert_eq!(refund.quantity(), Some(1));
788                 assert_eq!(tlv_stream.quantity, Some(1));
789         }
790
791         #[test]
792         fn builds_refund_with_payer_note() {
793                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
794                         .payer_note("bar".into())
795                         .build().unwrap();
796                 let (_, _, tlv_stream) = refund.as_tlv_stream();
797                 assert_eq!(refund.payer_note(), Some(PrintableString("bar")));
798                 assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
799
800                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
801                         .payer_note("bar".into())
802                         .payer_note("baz".into())
803                         .build().unwrap();
804                 let (_, _, tlv_stream) = refund.as_tlv_stream();
805                 assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
806                 assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
807         }
808
809         #[test]
810         fn parses_refund_with_metadata() {
811                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
812                         .build().unwrap();
813                 if let Err(e) = refund.to_string().parse::<Refund>() {
814                         panic!("error parsing refund: {:?}", e);
815                 }
816
817                 let mut tlv_stream = refund.as_tlv_stream();
818                 tlv_stream.0.metadata = None;
819
820                 match Refund::try_from(tlv_stream.to_bytes()) {
821                         Ok(_) => panic!("expected error"),
822                         Err(e) => {
823                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
824                         },
825                 }
826         }
827
828         #[test]
829         fn parses_refund_with_description() {
830                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
831                         .build().unwrap();
832                 if let Err(e) = refund.to_string().parse::<Refund>() {
833                         panic!("error parsing refund: {:?}", e);
834                 }
835
836                 let mut tlv_stream = refund.as_tlv_stream();
837                 tlv_stream.1.description = None;
838
839                 match Refund::try_from(tlv_stream.to_bytes()) {
840                         Ok(_) => panic!("expected error"),
841                         Err(e) => {
842                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
843                         },
844                 }
845         }
846
847         #[test]
848         fn parses_refund_with_amount() {
849                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
850                         .build().unwrap();
851                 if let Err(e) = refund.to_string().parse::<Refund>() {
852                         panic!("error parsing refund: {:?}", e);
853                 }
854
855                 let mut tlv_stream = refund.as_tlv_stream();
856                 tlv_stream.2.amount = None;
857
858                 match Refund::try_from(tlv_stream.to_bytes()) {
859                         Ok(_) => panic!("expected error"),
860                         Err(e) => {
861                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount));
862                         },
863                 }
864
865                 let mut tlv_stream = refund.as_tlv_stream();
866                 tlv_stream.2.amount = Some(MAX_VALUE_MSAT + 1);
867
868                 match Refund::try_from(tlv_stream.to_bytes()) {
869                         Ok(_) => panic!("expected error"),
870                         Err(e) => {
871                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount));
872                         },
873                 }
874         }
875
876         #[test]
877         fn parses_refund_with_payer_id() {
878                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
879                         .build().unwrap();
880                 if let Err(e) = refund.to_string().parse::<Refund>() {
881                         panic!("error parsing refund: {:?}", e);
882                 }
883
884                 let mut tlv_stream = refund.as_tlv_stream();
885                 tlv_stream.2.payer_id = None;
886
887                 match Refund::try_from(tlv_stream.to_bytes()) {
888                         Ok(_) => panic!("expected error"),
889                         Err(e) => {
890                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId));
891                         },
892                 }
893         }
894
895         #[test]
896         fn parses_refund_with_optional_fields() {
897                 let past_expiry = Duration::from_secs(0);
898                 let paths = vec![
899                         BlindedPath {
900                                 introduction_node_id: pubkey(40),
901                                 blinding_point: pubkey(41),
902                                 blinded_hops: vec![
903                                         BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
904                                         BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
905                                 ],
906                         },
907                         BlindedPath {
908                                 introduction_node_id: pubkey(40),
909                                 blinding_point: pubkey(41),
910                                 blinded_hops: vec![
911                                         BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
912                                         BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
913                                 ],
914                         },
915                 ];
916
917                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
918                         .absolute_expiry(past_expiry)
919                         .issuer("bar".into())
920                         .path(paths[0].clone())
921                         .path(paths[1].clone())
922                         .chain(Network::Testnet)
923                         .features_unchecked(InvoiceRequestFeatures::unknown())
924                         .quantity(10)
925                         .payer_note("baz".into())
926                         .build()
927                         .unwrap();
928                 match refund.to_string().parse::<Refund>() {
929                         Ok(refund) => {
930                                 assert_eq!(refund.absolute_expiry(), Some(past_expiry));
931                                 #[cfg(feature = "std")]
932                                 assert!(refund.is_expired());
933                                 assert_eq!(refund.paths(), &paths[..]);
934                                 assert_eq!(refund.issuer(), Some(PrintableString("bar")));
935                                 assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Testnet));
936                                 assert_eq!(refund.features(), &InvoiceRequestFeatures::unknown());
937                                 assert_eq!(refund.quantity(), Some(10));
938                                 assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
939                         },
940                         Err(e) => panic!("error parsing refund: {:?}", e),
941                 }
942         }
943
944         #[test]
945         fn fails_parsing_refund_with_unexpected_fields() {
946                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
947                         .build().unwrap();
948                 if let Err(e) = refund.to_string().parse::<Refund>() {
949                         panic!("error parsing refund: {:?}", e);
950                 }
951
952                 let chains = vec![ChainHash::using_genesis_block(Network::Testnet)];
953                 let mut tlv_stream = refund.as_tlv_stream();
954                 tlv_stream.1.chains = Some(&chains);
955
956                 match Refund::try_from(tlv_stream.to_bytes()) {
957                         Ok(_) => panic!("expected error"),
958                         Err(e) => {
959                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedChain));
960                         },
961                 }
962
963                 let mut tlv_stream = refund.as_tlv_stream();
964                 tlv_stream.1.currency = Some(&b"USD");
965                 tlv_stream.1.amount = Some(1000);
966
967                 match Refund::try_from(tlv_stream.to_bytes()) {
968                         Ok(_) => panic!("expected error"),
969                         Err(e) => {
970                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedAmount));
971                         },
972                 }
973
974                 let features = OfferFeatures::unknown();
975                 let mut tlv_stream = refund.as_tlv_stream();
976                 tlv_stream.1.features = Some(&features);
977
978                 match Refund::try_from(tlv_stream.to_bytes()) {
979                         Ok(_) => panic!("expected error"),
980                         Err(e) => {
981                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedFeatures));
982                         },
983                 }
984
985                 let mut tlv_stream = refund.as_tlv_stream();
986                 tlv_stream.1.quantity_max = Some(10);
987
988                 match Refund::try_from(tlv_stream.to_bytes()) {
989                         Ok(_) => panic!("expected error"),
990                         Err(e) => {
991                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
992                         },
993                 }
994
995                 let node_id = payer_pubkey();
996                 let mut tlv_stream = refund.as_tlv_stream();
997                 tlv_stream.1.node_id = Some(&node_id);
998
999                 match Refund::try_from(tlv_stream.to_bytes()) {
1000                         Ok(_) => panic!("expected error"),
1001                         Err(e) => {
1002                                 assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
1003                         },
1004                 }
1005         }
1006
1007         #[test]
1008         fn fails_parsing_refund_with_extra_tlv_records() {
1009                 let secp_ctx = Secp256k1::new();
1010                 let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
1011                 let refund = RefundBuilder::new("foo".into(), vec![1; 32], keys.public_key(), 1000).unwrap()
1012                         .build().unwrap();
1013
1014                 let mut encoded_refund = Vec::new();
1015                 refund.write(&mut encoded_refund).unwrap();
1016                 BigSize(1002).write(&mut encoded_refund).unwrap();
1017                 BigSize(32).write(&mut encoded_refund).unwrap();
1018                 [42u8; 32].write(&mut encoded_refund).unwrap();
1019
1020                 match Refund::try_from(encoded_refund) {
1021                         Ok(_) => panic!("expected error"),
1022                         Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1023                 }
1024         }
1025 }