b5efd6be5a80e42aedd293345c352cb42f99687c
[rust-lightning] / lightning / src / offers / offer.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 `offer` messages.
11 //!
12 //! An [`Offer`] represents an "offer to be paid." It is typically constructed by a merchant and
13 //! published as a QR code to be scanned by a customer. The customer uses the offer to request an
14 //! invoice from the merchant to be paid.
15 //!
16 //! ```
17 //! extern crate bitcoin;
18 //! extern crate core;
19 //! extern crate lightning;
20 //!
21 //! use core::num::NonZeroU64;
22 //! use core::time::Duration;
23 //!
24 //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
25 //! use lightning::offers::offer::{Amount, OfferBuilder};
26 //!
27 //! # use bitcoin::secp256k1;
28 //! # use lightning::onion_message::BlindedPath;
29 //! # #[cfg(feature = "std")]
30 //! # use std::time::SystemTime;
31 //! #
32 //! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
33 //! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
34 //! #
35 //! # #[cfg(feature = "std")]
36 //! # fn build() -> Result<(), secp256k1::Error> {
37 //! let secp_ctx = Secp256k1::new();
38 //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
39 //! let pubkey = PublicKey::from(keys);
40 //!
41 //! let one_item = NonZeroU64::new(1).unwrap();
42 //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
43 //! let offer = OfferBuilder::new("coffee, large".to_string(), pubkey)
44 //!     .amount(Amount::Bitcoin { amount_msats: 20_000 })
45 //!     .quantity_range(one_item..)
46 //!     .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
47 //!     .issuer("Foo Bar".to_string())
48 //!     .path(create_blinded_path())
49 //!     .path(create_another_blinded_path())
50 //!     .build()
51 //!     .unwrap();
52 //! # Ok(())
53 //! # }
54 //! ```
55
56 use bitcoin::blockdata::constants::ChainHash;
57 use bitcoin::network::constants::Network;
58 use bitcoin::secp256k1::PublicKey;
59 use core::num::NonZeroU64;
60 use core::ops::{Bound, RangeBounds};
61 use core::time::Duration;
62 use io;
63 use ln::features::OfferFeatures;
64 use ln::msgs::MAX_VALUE_MSAT;
65 use onion_message::BlindedPath;
66 use util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
67
68 use prelude::*;
69
70 #[cfg(feature = "std")]
71 use std::time::SystemTime;
72
73 /// Builds an [`Offer`] for the "offer to be paid" flow.
74 ///
75 /// See [module-level documentation] for usage.
76 ///
77 /// [module-level documentation]: self
78 pub struct OfferBuilder {
79         offer: OfferContents,
80 }
81
82 impl OfferBuilder {
83         /// Creates a new builder for an offer setting the [`Offer::description`] and using the
84         /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
85         /// while the offer is valid.
86         ///
87         /// Use a different pubkey per offer to avoid correlating offers.
88         pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
89                 let offer = OfferContents {
90                         chains: None, metadata: None, amount: None, description,
91                         features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
92                         quantity_min: None, quantity_max: None, signing_pubkey: Some(signing_pubkey),
93                 };
94                 OfferBuilder { offer }
95         }
96
97         /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
98         /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
99         ///
100         /// Successive calls to this method will add another chain hash.
101         pub fn chain(mut self, network: Network) -> Self {
102                 let chains = self.offer.chains.get_or_insert_with(Vec::new);
103                 let chain = ChainHash::using_genesis_block(network);
104                 if !chains.contains(&chain) {
105                         chains.push(chain);
106                 }
107
108                 self
109         }
110
111         /// Sets the [`Offer::metadata`].
112         ///
113         /// Successive calls to this method will override the previous setting.
114         pub fn metadata(mut self, metadata: Vec<u8>) -> Self {
115                 self.offer.metadata = Some(metadata);
116                 self
117         }
118
119         /// Sets the [`Offer::amount`].
120         ///
121         /// Successive calls to this method will override the previous setting.
122         pub fn amount(mut self, amount: Amount) -> Self {
123                 self.offer.amount = Some(amount);
124                 self
125         }
126
127         /// Sets the [`Offer::features`].
128         ///
129         /// Successive calls to this method will override the previous setting.
130         #[cfg(test)]
131         pub fn features(mut self, features: OfferFeatures) -> Self {
132                 self.offer.features = features;
133                 self
134         }
135
136         /// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
137         /// already passed is valid and can be checked for using [`Offer::is_expired`].
138         ///
139         /// Successive calls to this method will override the previous setting.
140         pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
141                 self.offer.absolute_expiry = Some(absolute_expiry);
142                 self
143         }
144
145         /// Sets the [`Offer::issuer`].
146         ///
147         /// Successive calls to this method will override the previous setting.
148         pub fn issuer(mut self, issuer: String) -> Self {
149                 self.offer.issuer = Some(issuer);
150                 self
151         }
152
153         /// Adds a blinded path to [`Offer::paths`]. Must include one path if only connected by private
154         /// channels.
155         ///
156         /// Successive calls to this method will add another blinded path. Caller is responsible for not
157         /// adding duplicate paths.
158         pub fn path(mut self, path: BlindedPath) -> Self {
159                 self.offer.paths.get_or_insert_with(Vec::new).push(path);
160                 self
161         }
162
163         /// Sets a fixed quantity of items for [`Offer::quantity_min`] and [`Offer::quantity_max`]. If
164         /// not set, `1` is assumed.
165         ///
166         /// Successive calls to this method or [`quantity_range`] will override the previous setting.
167         ///
168         /// [`quantity_range`]: Self::quantity_range
169         pub fn quantity_fixed(mut self, quantity: NonZeroU64) -> Self {
170                 let quantity = Some(quantity.get()).filter(|quantity| *quantity != 1);
171                 self.offer.quantity_min = quantity;
172                 self.offer.quantity_max = quantity;
173                 self
174         }
175
176         /// Sets a quantity range of items for [`Offer::quantity_min`] and [`Offer::quantity_max`]. If
177         /// not set, `1` is assumed.
178         ///
179         /// Successive calls to this method or [`quantity_fixed`] will override the previous setting.
180         ///
181         /// [`quantity_fixed`]: Self::quantity_fixed
182         pub fn quantity_range<R: RangeBounds<NonZeroU64>>(mut self, quantity: R) -> Self {
183                 self.offer.quantity_min = match quantity.start_bound() {
184                         Bound::Included(n) => Some(n.get()),
185                         Bound::Excluded(_) => panic!("Bound::Excluded not supported for start_bound"),
186                         Bound::Unbounded => Some(1),
187                 };
188                 self.offer.quantity_max = match quantity.end_bound() {
189                         Bound::Included(n) => Some(n.get()),
190                         Bound::Excluded(n) => Some(n.get() - 1),
191                         Bound::Unbounded => None,
192                 };
193
194                 // Use a minimal encoding whenever 1 can be inferred.
195                 if let Some(1) = self.offer.quantity_min {
196                         match self.offer.quantity_max {
197                                 Some(1) => {
198                                         self.offer.quantity_min = None;
199                                         self.offer.quantity_max = None;
200                                 },
201                                 Some(_) => {
202                                         self.offer.quantity_min = None;
203                                 },
204                                 None => {},
205                         }
206                 }
207
208                 self
209         }
210
211         /// Builds an [`Offer`] from the builder's settings.
212         pub fn build(self) -> Result<Offer, ()> {
213                 if let Some(Amount::Currency { .. }) = self.offer.amount {
214                         return Err(());
215                 }
216
217                 if self.offer.amount_msats() > MAX_VALUE_MSAT {
218                         return Err(());
219                 }
220
221                 if self.offer.quantity_min() > self.offer.quantity_max() {
222                         return Err(());
223                 }
224
225                 let mut bytes = Vec::new();
226                 self.offer.write(&mut bytes).unwrap();
227
228                 Ok(Offer {
229                         bytes,
230                         contents: self.offer,
231                 })
232         }
233 }
234
235 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
236 ///
237 /// An offer is precursor to an `InvoiceRequest`. A merchant publishes an offer from which a
238 /// customer may request an `Invoice` for a specific quantity and using an amount enough to cover
239 /// that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
240 ///
241 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
242 /// latter.
243 ///
244 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
245 #[derive(Clone, Debug)]
246 pub struct Offer {
247         // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
248         // fields.
249         bytes: Vec<u8>,
250         contents: OfferContents,
251 }
252
253 /// The contents of an [`Offer`], which may be shared with an `InvoiceRequest` or an `Invoice`.
254 #[derive(Clone, Debug)]
255 pub(crate) struct OfferContents {
256         chains: Option<Vec<ChainHash>>,
257         metadata: Option<Vec<u8>>,
258         amount: Option<Amount>,
259         description: String,
260         features: OfferFeatures,
261         absolute_expiry: Option<Duration>,
262         issuer: Option<String>,
263         paths: Option<Vec<BlindedPath>>,
264         quantity_min: Option<u64>,
265         quantity_max: Option<u64>,
266         signing_pubkey: Option<PublicKey>,
267 }
268
269 impl Offer {
270         // TODO: Return a slice once ChainHash has constants.
271         // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
272         // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
273         /// The chains that may be used when paying a requested invoice.
274         pub fn chains(&self) -> Vec<ChainHash> {
275                 self.contents.chains
276                         .as_ref()
277                         .cloned()
278                         .unwrap_or_else(|| vec![ChainHash::using_genesis_block(Network::Bitcoin)])
279         }
280
281         // TODO: Link to corresponding method in `InvoiceRequest`.
282         /// Metadata set by the originator. Useful for authentication and validating fields since it is
283         /// reflected in `invoice_request` messages along with all the other fields from the `offer`.
284         pub fn metadata(&self) -> Option<&Vec<u8>> {
285                 self.contents.metadata.as_ref()
286         }
287
288         /// The minimum amount required for a successful payment of a single item.
289         pub fn amount(&self) -> Option<&Amount> {
290                 self.contents.amount.as_ref()
291         }
292
293         /// A complete description of the purpose of the payment.
294         pub fn description(&self) -> &str {
295                 &self.contents.description
296         }
297
298         /// Features pertaining to the offer.
299         pub fn features(&self) -> &OfferFeatures {
300                 &self.contents.features
301         }
302
303         /// Duration since the Unix epoch when an invoice should no longer be requested.
304         ///
305         /// If `None`, the offer does not expire.
306         pub fn absolute_expiry(&self) -> Option<Duration> {
307                 self.contents.absolute_expiry
308         }
309
310         /// Whether the offer has expired.
311         #[cfg(feature = "std")]
312         pub fn is_expired(&self) -> bool {
313                 match self.absolute_expiry() {
314                         Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
315                                 Ok(elapsed) => elapsed > seconds_from_epoch,
316                                 Err(_) => false,
317                         },
318                         None => false,
319                 }
320         }
321
322         /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
323         /// displayed to the user, so should be human-readable.
324         pub fn issuer(&self) -> Option<&str> {
325                 self.contents.issuer.as_ref().map(|issuer| issuer.as_str())
326         }
327
328         /// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
329         /// recipient privacy by obfuscating its node id.
330         pub fn paths(&self) -> &[BlindedPath] {
331                 self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
332         }
333
334         /// The minimum quantity of items supported.
335         pub fn quantity_min(&self) -> u64 {
336                 self.contents.quantity_min()
337         }
338
339         /// The maximum quantity of items supported.
340         pub fn quantity_max(&self) -> u64 {
341                 self.contents.quantity_max()
342         }
343
344         /// The public key used by the recipient to sign invoices.
345         pub fn signing_pubkey(&self) -> PublicKey {
346                 self.contents.signing_pubkey.unwrap()
347         }
348
349         #[cfg(test)]
350         fn as_tlv_stream(&self) -> OfferTlvStreamRef {
351                 self.contents.as_tlv_stream()
352         }
353 }
354
355 impl OfferContents {
356         pub fn amount_msats(&self) -> u64 {
357                 self.amount.as_ref().map(Amount::as_msats).unwrap_or(0)
358         }
359
360         pub fn quantity_min(&self) -> u64 {
361                 self.quantity_min.unwrap_or(1)
362         }
363
364         pub fn quantity_max(&self) -> u64 {
365                 self.quantity_max.unwrap_or_else(||
366                         self.quantity_min.map_or(1, |_| u64::max_value()))
367         }
368
369         fn as_tlv_stream(&self) -> OfferTlvStreamRef {
370                 let (currency, amount) = match &self.amount {
371                         None => (None, None),
372                         Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),
373                         Some(Amount::Currency { iso4217_code, amount }) => (
374                                 Some(iso4217_code), Some(*amount)
375                         ),
376                 };
377
378                 let features = {
379                         if self.features == OfferFeatures::empty() { None } else { Some(&self.features) }
380                 };
381
382                 OfferTlvStreamRef {
383                         chains: self.chains.as_ref(),
384                         metadata: self.metadata.as_ref(),
385                         currency,
386                         amount,
387                         description: Some(&self.description),
388                         features,
389                         absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
390                         paths: self.paths.as_ref(),
391                         issuer: self.issuer.as_ref(),
392                         quantity_min: self.quantity_min,
393                         quantity_max: self.quantity_max,
394                         node_id: self.signing_pubkey.as_ref(),
395                 }
396         }
397 }
398
399 impl Writeable for OfferContents {
400         fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
401                 self.as_tlv_stream().write(writer)
402         }
403 }
404
405 /// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
406 /// another currency.
407 #[derive(Clone, Debug, PartialEq)]
408 pub enum Amount {
409         /// An amount of bitcoin.
410         Bitcoin {
411                 /// The amount in millisatoshi.
412                 amount_msats: u64,
413         },
414         /// An amount of currency specified using ISO 4712.
415         Currency {
416                 /// The currency that the amount is denominated in.
417                 iso4217_code: CurrencyCode,
418                 /// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
419                 amount: u64,
420         },
421 }
422
423 impl Amount {
424         /// Returns the amount in millisatoshi.
425         pub fn as_msats(&self) -> u64 {
426                 match self {
427                         Amount::Currency { .. } => unimplemented!(),
428                         Amount::Bitcoin { amount_msats } => *amount_msats,
429                 }
430         }
431 }
432
433 /// An ISO 4712 three-letter currency code (e.g., USD).
434 pub type CurrencyCode = [u8; 3];
435
436 tlv_stream!(OfferTlvStream, OfferTlvStreamRef, {
437         (2, chains: (Vec<ChainHash>, WithoutLength)),
438         (4, metadata: (Vec<u8>, WithoutLength)),
439         (6, currency: CurrencyCode),
440         (8, amount: (u64, HighZeroBytesDroppedBigSize)),
441         (10, description: (String, WithoutLength)),
442         (12, features: OfferFeatures),
443         (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
444         (16, paths: (Vec<BlindedPath>, WithoutLength)),
445         (18, issuer: (String, WithoutLength)),
446         (20, quantity_min: (u64, HighZeroBytesDroppedBigSize)),
447         (22, quantity_max: (u64, HighZeroBytesDroppedBigSize)),
448         (24, node_id: PublicKey),
449 });
450
451 #[cfg(test)]
452 mod tests {
453         use super::{Amount, OfferBuilder};
454
455         use bitcoin::blockdata::constants::ChainHash;
456         use bitcoin::network::constants::Network;
457         use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
458         use core::num::NonZeroU64;
459         use core::time::Duration;
460         use ln::features::OfferFeatures;
461         use ln::msgs::MAX_VALUE_MSAT;
462         use onion_message::{BlindedHop, BlindedPath};
463         use util::ser::Writeable;
464
465         fn pubkey(byte: u8) -> PublicKey {
466                 let secp_ctx = Secp256k1::new();
467                 PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
468         }
469
470         fn privkey(byte: u8) -> SecretKey {
471                 SecretKey::from_slice(&[byte; 32]).unwrap()
472         }
473
474         #[test]
475         fn builds_offer_with_defaults() {
476                 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
477                 let tlv_stream = offer.as_tlv_stream();
478                 let mut buffer = Vec::new();
479                 offer.contents.write(&mut buffer).unwrap();
480
481                 assert_eq!(offer.bytes, buffer.as_slice());
482                 assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
483                 assert_eq!(offer.metadata(), None);
484                 assert_eq!(offer.amount(), None);
485                 assert_eq!(offer.description(), "foo");
486                 assert_eq!(offer.features(), &OfferFeatures::empty());
487                 assert_eq!(offer.absolute_expiry(), None);
488                 #[cfg(feature = "std")]
489                 assert!(!offer.is_expired());
490                 assert_eq!(offer.paths(), &[]);
491                 assert_eq!(offer.issuer(), None);
492                 assert_eq!(offer.quantity_min(), 1);
493                 assert_eq!(offer.quantity_max(), 1);
494                 assert_eq!(offer.signing_pubkey(), pubkey(42));
495
496                 assert_eq!(tlv_stream.chains, None);
497                 assert_eq!(tlv_stream.metadata, None);
498                 assert_eq!(tlv_stream.currency, None);
499                 assert_eq!(tlv_stream.amount, None);
500                 assert_eq!(tlv_stream.description, Some(&String::from("foo")));
501                 assert_eq!(tlv_stream.features, None);
502                 assert_eq!(tlv_stream.absolute_expiry, None);
503                 assert_eq!(tlv_stream.paths, None);
504                 assert_eq!(tlv_stream.issuer, None);
505                 assert_eq!(tlv_stream.quantity_min, None);
506                 assert_eq!(tlv_stream.quantity_max, None);
507                 assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
508         }
509
510         #[test]
511         fn builds_offer_with_chains() {
512                 let chain = ChainHash::using_genesis_block(Network::Bitcoin);
513                 let chains = vec![
514                         ChainHash::using_genesis_block(Network::Bitcoin),
515                         ChainHash::using_genesis_block(Network::Testnet),
516                 ];
517
518                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
519                         .chain(Network::Bitcoin)
520                         .build()
521                         .unwrap();
522                 assert_eq!(offer.chains(), vec![chain]);
523                 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![chain]));
524
525                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
526                         .chain(Network::Bitcoin)
527                         .chain(Network::Bitcoin)
528                         .build()
529                         .unwrap();
530                 assert_eq!(offer.chains(), vec![chain]);
531                 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![chain]));
532
533                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
534                         .chain(Network::Bitcoin)
535                         .chain(Network::Testnet)
536                         .build()
537                         .unwrap();
538                 assert_eq!(offer.chains(), chains);
539                 assert_eq!(offer.as_tlv_stream().chains, Some(&chains));
540         }
541
542         #[test]
543         fn builds_offer_with_metadata() {
544                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
545                         .metadata(vec![42; 32])
546                         .build()
547                         .unwrap();
548                 assert_eq!(offer.metadata(), Some(&vec![42; 32]));
549                 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32]));
550
551                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
552                         .metadata(vec![42; 32])
553                         .metadata(vec![43; 32])
554                         .build()
555                         .unwrap();
556                 assert_eq!(offer.metadata(), Some(&vec![43; 32]));
557                 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
558         }
559
560         #[test]
561         fn builds_offer_with_amount() {
562                 let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };
563                 let currency_amount = Amount::Currency { iso4217_code: *b"USD", amount: 10 };
564
565                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
566                         .amount(bitcoin_amount.clone())
567                         .build()
568                         .unwrap();
569                 let tlv_stream = offer.as_tlv_stream();
570                 assert_eq!(offer.amount(), Some(&bitcoin_amount));
571                 assert_eq!(tlv_stream.amount, Some(1000));
572                 assert_eq!(tlv_stream.currency, None);
573
574                 let builder = OfferBuilder::new("foo".into(), pubkey(42))
575                         .amount(currency_amount.clone());
576                 let tlv_stream = builder.offer.as_tlv_stream();
577                 assert_eq!(builder.offer.amount.as_ref(), Some(&currency_amount));
578                 assert_eq!(tlv_stream.amount, Some(10));
579                 assert_eq!(tlv_stream.currency, Some(b"USD"));
580                 match builder.build() {
581                         Ok(_) => panic!("expected error"),
582                         Err(e) => assert_eq!(e, ()),
583                 }
584
585                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
586                         .amount(currency_amount.clone())
587                         .amount(bitcoin_amount.clone())
588                         .build()
589                         .unwrap();
590                 let tlv_stream = offer.as_tlv_stream();
591                 assert_eq!(tlv_stream.amount, Some(1000));
592                 assert_eq!(tlv_stream.currency, None);
593
594                 let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
595                 match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
596                         Ok(_) => panic!("expected error"),
597                         Err(e) => assert_eq!(e, ()),
598                 }
599         }
600
601         #[test]
602         fn builds_offer_with_features() {
603                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
604                         .features(OfferFeatures::unknown())
605                         .build()
606                         .unwrap();
607                 assert_eq!(offer.features(), &OfferFeatures::unknown());
608                 assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
609
610                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
611                         .features(OfferFeatures::unknown())
612                         .features(OfferFeatures::empty())
613                         .build()
614                         .unwrap();
615                 assert_eq!(offer.features(), &OfferFeatures::empty());
616                 assert_eq!(offer.as_tlv_stream().features, None);
617         }
618
619         #[test]
620         fn builds_offer_with_absolute_expiry() {
621                 let future_expiry = Duration::from_secs(u64::max_value());
622                 let past_expiry = Duration::from_secs(0);
623
624                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
625                         .absolute_expiry(future_expiry)
626                         .build()
627                         .unwrap();
628                 #[cfg(feature = "std")]
629                 assert!(!offer.is_expired());
630                 assert_eq!(offer.absolute_expiry(), Some(future_expiry));
631                 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
632
633                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
634                         .absolute_expiry(future_expiry)
635                         .absolute_expiry(past_expiry)
636                         .build()
637                         .unwrap();
638                 #[cfg(feature = "std")]
639                 assert!(offer.is_expired());
640                 assert_eq!(offer.absolute_expiry(), Some(past_expiry));
641                 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
642         }
643
644         #[test]
645         fn builds_offer_with_paths() {
646                 let paths = vec![
647                         BlindedPath {
648                                 introduction_node_id: pubkey(40),
649                                 blinding_point: pubkey(41),
650                                 blinded_hops: vec![
651                                         BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
652                                         BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
653                                 ],
654                         },
655                         BlindedPath {
656                                 introduction_node_id: pubkey(40),
657                                 blinding_point: pubkey(41),
658                                 blinded_hops: vec![
659                                         BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
660                                         BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
661                                 ],
662                         },
663                 ];
664
665                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
666                         .path(paths[0].clone())
667                         .path(paths[1].clone())
668                         .build()
669                         .unwrap();
670                 let tlv_stream = offer.as_tlv_stream();
671                 assert_eq!(offer.paths(), paths.as_slice());
672                 assert_eq!(offer.signing_pubkey(), pubkey(42));
673                 assert_ne!(pubkey(42), pubkey(44));
674                 assert_eq!(tlv_stream.paths, Some(&paths));
675                 assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
676         }
677
678         #[test]
679         fn builds_offer_with_issuer() {
680                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
681                         .issuer("bar".into())
682                         .build()
683                         .unwrap();
684                 assert_eq!(offer.issuer(), Some("bar"));
685                 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar")));
686
687                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
688                         .issuer("bar".into())
689                         .issuer("baz".into())
690                         .build()
691                         .unwrap();
692                 assert_eq!(offer.issuer(), Some("baz"));
693                 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("baz")));
694         }
695
696         #[test]
697         fn builds_offer_with_fixed_quantity() {
698                 let one = NonZeroU64::new(1).unwrap();
699                 let five = NonZeroU64::new(5).unwrap();
700                 let ten = NonZeroU64::new(10).unwrap();
701
702                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
703                         .quantity_fixed(one)
704                         .build()
705                         .unwrap();
706                 let tlv_stream = offer.as_tlv_stream();
707                 assert_eq!(offer.quantity_min(), 1);
708                 assert_eq!(offer.quantity_max(), 1);
709                 assert_eq!(tlv_stream.quantity_min, None);
710                 assert_eq!(tlv_stream.quantity_max, None);
711
712                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
713                         .quantity_fixed(ten)
714                         .build()
715                         .unwrap();
716                 let tlv_stream = offer.as_tlv_stream();
717                 assert_eq!(offer.quantity_min(), 10);
718                 assert_eq!(offer.quantity_max(), 10);
719                 assert_eq!(tlv_stream.quantity_min, Some(10));
720                 assert_eq!(tlv_stream.quantity_max, Some(10));
721
722                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
723                         .quantity_fixed(ten)
724                         .quantity_fixed(five)
725                         .build()
726                         .unwrap();
727                 let tlv_stream = offer.as_tlv_stream();
728                 assert_eq!(offer.quantity_min(), 5);
729                 assert_eq!(offer.quantity_max(), 5);
730                 assert_eq!(tlv_stream.quantity_min, Some(5));
731                 assert_eq!(tlv_stream.quantity_max, Some(5));
732
733                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
734                         .quantity_range(..ten)
735                         .quantity_fixed(five)
736                         .build()
737                         .unwrap();
738                 let tlv_stream = offer.as_tlv_stream();
739                 assert_eq!(offer.quantity_min(), 5);
740                 assert_eq!(offer.quantity_max(), 5);
741                 assert_eq!(tlv_stream.quantity_min, Some(5));
742                 assert_eq!(tlv_stream.quantity_max, Some(5));
743         }
744
745         #[test]
746         fn builds_offer_with_quantity_range() {
747                 let one = NonZeroU64::new(1).unwrap();
748                 let five = NonZeroU64::new(5).unwrap();
749                 let ten = NonZeroU64::new(10).unwrap();
750
751                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
752                         .quantity_range(..)
753                         .build()
754                         .unwrap();
755                 let tlv_stream = offer.as_tlv_stream();
756                 assert_eq!(offer.quantity_min(), 1);
757                 assert_eq!(offer.quantity_max(), u64::max_value());
758                 assert_eq!(tlv_stream.quantity_min, Some(1));
759                 assert_eq!(tlv_stream.quantity_max, None);
760
761                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
762                         .quantity_range(..ten)
763                         .build()
764                         .unwrap();
765                 let tlv_stream = offer.as_tlv_stream();
766                 assert_eq!(offer.quantity_min(), 1);
767                 assert_eq!(offer.quantity_max(), 9);
768                 assert_eq!(tlv_stream.quantity_min, None);
769                 assert_eq!(tlv_stream.quantity_max, Some(9));
770
771                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
772                         .quantity_range(one..ten)
773                         .build()
774                         .unwrap();
775                 let tlv_stream = offer.as_tlv_stream();
776                 assert_eq!(offer.quantity_min(), 1);
777                 assert_eq!(offer.quantity_max(), 9);
778                 assert_eq!(tlv_stream.quantity_min, None);
779                 assert_eq!(tlv_stream.quantity_max, Some(9));
780
781                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
782                         .quantity_range(five..=ten)
783                         .build()
784                         .unwrap();
785                 let tlv_stream = offer.as_tlv_stream();
786                 assert_eq!(offer.quantity_min(), 5);
787                 assert_eq!(offer.quantity_max(), 10);
788                 assert_eq!(tlv_stream.quantity_min, Some(5));
789                 assert_eq!(tlv_stream.quantity_max, Some(10));
790
791                 let one = NonZeroU64::new(1).unwrap();
792                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
793                         .quantity_range(one..=one)
794                         .build()
795                         .unwrap();
796                 let tlv_stream = offer.as_tlv_stream();
797                 assert_eq!(offer.quantity_min(), 1);
798                 assert_eq!(offer.quantity_max(), 1);
799                 assert_eq!(tlv_stream.quantity_min, None);
800                 assert_eq!(tlv_stream.quantity_max, None);
801
802                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
803                         .quantity_range(five..=five)
804                         .build()
805                         .unwrap();
806                 let tlv_stream = offer.as_tlv_stream();
807                 assert_eq!(offer.quantity_min(), 5);
808                 assert_eq!(offer.quantity_max(), 5);
809                 assert_eq!(tlv_stream.quantity_min, Some(5));
810                 assert_eq!(tlv_stream.quantity_max, Some(5));
811
812                 let offer = OfferBuilder::new("foo".into(), pubkey(42))
813                         .quantity_fixed(five)
814                         .quantity_range(..ten)
815                         .build()
816                         .unwrap();
817                 let tlv_stream = offer.as_tlv_stream();
818                 assert_eq!(offer.quantity_min(), 1);
819                 assert_eq!(offer.quantity_max(), 9);
820                 assert_eq!(tlv_stream.quantity_min, None);
821                 assert_eq!(tlv_stream.quantity_max, Some(9));
822
823                 assert!(OfferBuilder::new("foo".into(), pubkey(42))
824                         .quantity_range(ten..five)
825                         .build()
826                         .is_err()
827                 );
828         }
829 }