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