1 // This file is Copyright its original authors, visible in version control
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
10 //! Data structures and encoding for `offer` messages.
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.
19 //! extern crate bitcoin;
20 //! extern crate core;
21 //! extern crate lightning;
23 //! use core::convert::TryFrom;
24 //! use core::num::NonZeroU64;
25 //! use core::time::Duration;
27 //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
28 //! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
29 //! use lightning::offers::parse::Bolt12ParseError;
30 //! use lightning::util::ser::{Readable, Writeable};
32 //! # use lightning::blinded_path::BlindedPath;
33 //! # #[cfg(feature = "std")]
34 //! # use std::time::SystemTime;
36 //! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
37 //! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
39 //! # #[cfg(feature = "std")]
40 //! # fn build() -> Result<(), Bolt12ParseError> {
41 //! let secp_ctx = Secp256k1::new();
42 //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
43 //! let pubkey = PublicKey::from(keys);
45 //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
46 //! let offer = OfferBuilder::new("coffee, large".to_string(), pubkey)
47 //! .amount_msats(20_000)
48 //! .supported_quantity(Quantity::Unbounded)
49 //! .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
50 //! .issuer("Foo Bar".to_string())
51 //! .path(create_blinded_path())
52 //! .path(create_another_blinded_path())
55 //! // Encode as a bech32 string for use in a QR code.
56 //! let encoded_offer = offer.to_string();
58 //! // Parse from a bech32 string after scanning from a QR code.
59 //! let offer = encoded_offer.parse::<Offer>()?;
61 //! // Encode offer as raw bytes.
62 //! let mut bytes = Vec::new();
63 //! offer.write(&mut bytes).unwrap();
65 //! // Decode raw bytes into an offer.
66 //! let offer = Offer::try_from(bytes)?;
73 //! If constructing an [`Offer`] for use with a [`ChannelManager`], use
74 //! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
76 //! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
77 //! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
79 use bitcoin::blockdata::constants::ChainHash;
80 use bitcoin::network::constants::Network;
81 use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
82 use core::convert::TryFrom;
83 use core::num::NonZeroU64;
85 use core::str::FromStr;
86 use core::time::Duration;
87 use crate::sign::EntropySource;
89 use crate::blinded_path::BlindedPath;
90 use crate::ln::channelmanager::PaymentId;
91 use crate::ln::features::OfferFeatures;
92 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
93 use crate::ln::msgs::MAX_VALUE_MSAT;
94 use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
95 use crate::offers::merkle::TlvStream;
96 use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
97 use crate::offers::signer::{Metadata, MetadataMaterial, self};
98 use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
99 use crate::util::string::PrintableString;
101 use crate::prelude::*;
103 #[cfg(feature = "std")]
104 use std::time::SystemTime;
106 pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
108 /// Builds an [`Offer`] for the "offer to be paid" flow.
110 /// See [module-level documentation] for usage.
112 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
114 /// [module-level documentation]: self
115 pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> {
116 offer: OfferContents,
117 metadata_strategy: core::marker::PhantomData<M>,
118 secp_ctx: Option<&'a Secp256k1<T>>,
121 /// Indicates how [`Offer::metadata`] may be set.
123 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
124 pub trait MetadataStrategy {}
126 /// [`Offer::metadata`] may be explicitly set or left empty.
128 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
129 pub struct ExplicitMetadata {}
131 /// [`Offer::metadata`] will be derived.
133 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
134 pub struct DerivedMetadata {}
136 impl MetadataStrategy for ExplicitMetadata {}
137 impl MetadataStrategy for DerivedMetadata {}
139 impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
140 /// Creates a new builder for an offer setting the [`Offer::description`] and using the
141 /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
142 /// while the offer is valid.
144 /// Use a different pubkey per offer to avoid correlating offers.
148 /// If constructing an [`Offer`] for use with a [`ChannelManager`], use
149 /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
151 /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
152 /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
153 pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
155 offer: OfferContents {
156 chains: None, metadata: None, amount: None, description,
157 features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
158 supported_quantity: Quantity::One, signing_pubkey,
160 metadata_strategy: core::marker::PhantomData,
165 /// Sets the [`Offer::metadata`] to the given bytes.
167 /// Successive calls to this method will override the previous setting.
168 pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, Bolt12SemanticError> {
169 self.offer.metadata = Some(Metadata::Bytes(metadata));
174 impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
175 /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing
176 /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides
177 /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
178 /// provided `node_id` is used for the signing pubkey.
180 /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
181 /// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
184 /// [`InvoiceRequest::verify`]: crate::offers::invoice_request::InvoiceRequest::verify
185 /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
186 pub fn deriving_signing_pubkey<ES: Deref>(
187 description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
188 secp_ctx: &'a Secp256k1<T>
189 ) -> Self where ES::Target: EntropySource {
190 let nonce = Nonce::from_entropy_source(entropy_source);
191 let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
192 let metadata = Metadata::DerivedSigningPubkey(derivation_material);
194 offer: OfferContents {
195 chains: None, metadata: Some(metadata), amount: None, description,
196 features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
197 supported_quantity: Quantity::One, signing_pubkey: node_id,
199 metadata_strategy: core::marker::PhantomData,
200 secp_ctx: Some(secp_ctx),
205 impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
206 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
207 /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
209 /// See [`Offer::chains`] on how this relates to the payment currency.
211 /// Successive calls to this method will add another chain hash.
212 pub fn chain(self, network: Network) -> Self {
213 self.chain_hash(ChainHash::using_genesis_block(network))
216 /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
217 /// [`Network::Bitcoin`] is assumed to be the only one supported.
219 /// See [`Offer::chains`] on how this relates to the payment currency.
221 /// Successive calls to this method will add another chain hash.
222 pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
223 let chains = self.offer.chains.get_or_insert_with(Vec::new);
224 if !chains.contains(&chain) {
231 /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
233 /// Successive calls to this method will override the previous setting.
234 pub fn amount_msats(self, amount_msats: u64) -> Self {
235 self.amount(Amount::Bitcoin { amount_msats })
238 /// Sets the [`Offer::amount`].
240 /// Successive calls to this method will override the previous setting.
241 pub(super) fn amount(mut self, amount: Amount) -> Self {
242 self.offer.amount = Some(amount);
246 /// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
247 /// already passed is valid and can be checked for using [`Offer::is_expired`].
249 /// Successive calls to this method will override the previous setting.
250 pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
251 self.offer.absolute_expiry = Some(absolute_expiry);
255 /// Sets the [`Offer::issuer`].
257 /// Successive calls to this method will override the previous setting.
258 pub fn issuer(mut self, issuer: String) -> Self {
259 self.offer.issuer = Some(issuer);
263 /// Adds a blinded path to [`Offer::paths`]. Must include at least one path if only connected by
264 /// private channels or if [`Offer::signing_pubkey`] is not a public node id.
266 /// Successive calls to this method will add another blinded path. Caller is responsible for not
267 /// adding duplicate paths.
268 pub fn path(mut self, path: BlindedPath) -> Self {
269 self.offer.paths.get_or_insert_with(Vec::new).push(path);
273 /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
274 /// [`Quantity::One`].
276 /// Successive calls to this method will override the previous setting.
277 pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
278 self.offer.supported_quantity = quantity;
282 /// Builds an [`Offer`] from the builder's settings.
283 pub fn build(mut self) -> Result<Offer, Bolt12SemanticError> {
284 match self.offer.amount {
285 Some(Amount::Bitcoin { amount_msats }) => {
286 if amount_msats > MAX_VALUE_MSAT {
287 return Err(Bolt12SemanticError::InvalidAmount);
290 Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
294 if let Some(chains) = &self.offer.chains {
295 if chains.len() == 1 && chains[0] == self.offer.implied_chain() {
296 self.offer.chains = None;
300 Ok(self.build_without_checks())
303 fn build_without_checks(mut self) -> Offer {
304 // Create the metadata for stateless verification of an InvoiceRequest.
305 if let Some(mut metadata) = self.offer.metadata.take() {
306 if metadata.has_derivation_material() {
307 if self.offer.paths.is_none() {
308 metadata = metadata.without_keys();
311 let mut tlv_stream = self.offer.as_tlv_stream();
312 debug_assert_eq!(tlv_stream.metadata, None);
313 tlv_stream.metadata = None;
314 if metadata.derives_recipient_keys() {
315 tlv_stream.node_id = None;
318 let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
319 metadata = derived_metadata;
320 if let Some(keys) = keys {
321 self.offer.signing_pubkey = keys.public_key();
325 self.offer.metadata = Some(metadata);
328 let mut bytes = Vec::new();
329 self.offer.write(&mut bytes).unwrap();
331 Offer { bytes, contents: self.offer }
336 impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
337 fn features_unchecked(mut self, features: OfferFeatures) -> Self {
338 self.offer.features = features;
342 pub(super) fn build_unchecked(self) -> Offer {
343 self.build_without_checks()
347 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
349 /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
350 /// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
351 /// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
353 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
356 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
358 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
359 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
360 #[derive(Clone, Debug)]
361 #[cfg_attr(test, derive(PartialEq))]
363 // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
365 pub(super) bytes: Vec<u8>,
366 pub(super) contents: OfferContents,
369 /// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
370 /// [`Bolt12Invoice`].
372 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
373 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
374 #[derive(Clone, Debug)]
375 #[cfg_attr(test, derive(PartialEq))]
376 pub(super) struct OfferContents {
377 chains: Option<Vec<ChainHash>>,
378 metadata: Option<Metadata>,
379 amount: Option<Amount>,
381 features: OfferFeatures,
382 absolute_expiry: Option<Duration>,
383 issuer: Option<String>,
384 paths: Option<Vec<BlindedPath>>,
385 supported_quantity: Quantity,
386 signing_pubkey: PublicKey,
389 macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
390 // TODO: Return a slice once ChainHash has constants.
391 // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
392 // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
393 /// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
394 /// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
395 /// for the selected chain.
396 pub fn chains(&$self) -> Vec<bitcoin::blockdata::constants::ChainHash> {
400 // TODO: Link to corresponding method in `InvoiceRequest`.
401 /// Opaque bytes set by the originator. Useful for authentication and validating fields since it
402 /// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
403 pub fn metadata(&$self) -> Option<&Vec<u8>> {
407 /// The minimum amount required for a successful payment of a single item.
408 pub fn amount(&$self) -> Option<&$crate::offers::offer::Amount> {
412 /// A complete description of the purpose of the payment. Intended to be displayed to the user
413 /// but with the caveat that it has not been verified in any way.
414 pub fn description(&$self) -> $crate::util::string::PrintableString {
415 $contents.description()
418 /// Features pertaining to the offer.
419 pub fn offer_features(&$self) -> &$crate::ln::features::OfferFeatures {
420 &$contents.features()
423 /// Duration since the Unix epoch when an invoice should no longer be requested.
425 /// If `None`, the offer does not expire.
426 pub fn absolute_expiry(&$self) -> Option<core::time::Duration> {
427 $contents.absolute_expiry()
430 /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
431 /// displayed to the user but with the caveat that it has not been verified in any way.
432 pub fn issuer(&$self) -> Option<$crate::util::string::PrintableString> {
436 /// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
437 /// recipient privacy by obfuscating its node id.
438 pub fn paths(&$self) -> &[$crate::blinded_path::BlindedPath] {
442 /// The quantity of items supported.
443 pub fn supported_quantity(&$self) -> $crate::offers::offer::Quantity {
444 $contents.supported_quantity()
447 /// The public key used by the recipient to sign invoices.
448 pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
449 $contents.signing_pubkey()
454 offer_accessors!(self, self.contents);
456 pub(super) fn implied_chain(&self) -> ChainHash {
457 self.contents.implied_chain()
460 /// Returns whether the given chain is supported by the offer.
461 pub fn supports_chain(&self, chain: ChainHash) -> bool {
462 self.contents.supports_chain(chain)
465 /// Whether the offer has expired.
466 #[cfg(feature = "std")]
467 pub fn is_expired(&self) -> bool {
468 self.contents.is_expired()
471 /// Returns whether the given quantity is valid for the offer.
472 pub fn is_valid_quantity(&self, quantity: u64) -> bool {
473 self.contents.is_valid_quantity(quantity)
476 /// Returns whether a quantity is expected in an [`InvoiceRequest`] for the offer.
478 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
479 pub fn expects_quantity(&self) -> bool {
480 self.contents.expects_quantity()
483 /// Similar to [`Offer::request_invoice`] except it:
484 /// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
486 /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
487 /// such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
488 /// requested using a base [`ExpandedKey`] from which the payer id was derived, and
489 /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
490 /// be used when sending the payment for the requested invoice.
492 /// Useful to protect the sender's privacy.
494 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
496 /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
497 /// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
498 /// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
499 /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
500 pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
501 &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
502 payment_id: PaymentId
503 ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
505 ES::Target: EntropySource,
507 if self.offer_features().requires_unknown_bits() {
508 return Err(Bolt12SemanticError::UnknownRequiredFeatures);
511 Ok(InvoiceRequestBuilder::deriving_payer_id(
512 self, expanded_key, entropy_source, secp_ctx, payment_id
516 /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
517 /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request.
519 /// Useful for recurring payments using the same `payer_id` with different invoices.
521 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
523 /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
524 pub fn request_invoice_deriving_metadata<ES: Deref>(
525 &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
526 payment_id: PaymentId
527 ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
529 ES::Target: EntropySource,
531 if self.offer_features().requires_unknown_bits() {
532 return Err(Bolt12SemanticError::UnknownRequiredFeatures);
535 Ok(InvoiceRequestBuilder::deriving_metadata(
536 self, payer_id, expanded_key, entropy_source, payment_id
540 /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
541 /// which will be reflected in the `Bolt12Invoice` response.
543 /// The `metadata` is useful for including information about the derivation of `payer_id` such
544 /// that invoice response handling can be stateless. Also serves as payer-provided entropy while
545 /// hashing in the signature calculation.
547 /// This should not leak any information such as by using a simple BIP-32 derivation path.
548 /// Otherwise, payments may be correlated.
550 /// Errors if the offer contains unknown required features.
552 /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
554 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
555 pub fn request_invoice(
556 &self, metadata: Vec<u8>, payer_id: PublicKey
557 ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
558 if self.offer_features().requires_unknown_bits() {
559 return Err(Bolt12SemanticError::UnknownRequiredFeatures);
562 Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
566 pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
567 self.contents.as_tlv_stream()
571 impl AsRef<[u8]> for Offer {
572 fn as_ref(&self) -> &[u8] {
578 pub fn chains(&self) -> Vec<ChainHash> {
579 self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
582 pub fn implied_chain(&self) -> ChainHash {
583 ChainHash::using_genesis_block(Network::Bitcoin)
586 pub fn supports_chain(&self, chain: ChainHash) -> bool {
587 self.chains().contains(&chain)
590 pub fn metadata(&self) -> Option<&Vec<u8>> {
591 self.metadata.as_ref().and_then(|metadata| metadata.as_bytes())
594 pub fn amount(&self) -> Option<&Amount> {
598 pub fn description(&self) -> PrintableString {
599 PrintableString(&self.description)
602 pub fn features(&self) -> &OfferFeatures {
606 pub fn absolute_expiry(&self) -> Option<Duration> {
610 #[cfg(feature = "std")]
611 pub(super) fn is_expired(&self) -> bool {
612 match self.absolute_expiry {
613 Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
614 Ok(elapsed) => elapsed > seconds_from_epoch,
621 pub fn issuer(&self) -> Option<PrintableString> {
622 self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
625 pub fn paths(&self) -> &[BlindedPath] {
626 self.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
629 pub(super) fn check_amount_msats_for_quantity(
630 &self, amount_msats: Option<u64>, quantity: Option<u64>
631 ) -> Result<(), Bolt12SemanticError> {
632 let offer_amount_msats = match self.amount {
634 Some(Amount::Bitcoin { amount_msats }) => amount_msats,
635 Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
638 if !self.expects_quantity() || quantity.is_some() {
639 let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
640 .ok_or(Bolt12SemanticError::InvalidAmount)?;
641 let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
643 if amount_msats < expected_amount_msats {
644 return Err(Bolt12SemanticError::InsufficientAmount);
647 if amount_msats > MAX_VALUE_MSAT {
648 return Err(Bolt12SemanticError::InvalidAmount);
655 pub fn supported_quantity(&self) -> Quantity {
656 self.supported_quantity
659 pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), Bolt12SemanticError> {
660 let expects_quantity = self.expects_quantity();
662 None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
663 Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
664 Some(quantity) if !self.is_valid_quantity(quantity) => {
665 Err(Bolt12SemanticError::InvalidQuantity)
671 fn is_valid_quantity(&self, quantity: u64) -> bool {
672 match self.supported_quantity {
673 Quantity::Bounded(n) => quantity <= n.get(),
674 Quantity::Unbounded => quantity > 0,
675 Quantity::One => quantity == 1,
679 fn expects_quantity(&self) -> bool {
680 match self.supported_quantity {
681 Quantity::Bounded(_) => true,
682 Quantity::Unbounded => true,
683 Quantity::One => false,
687 pub(super) fn signing_pubkey(&self) -> PublicKey {
691 /// Verifies that the offer metadata was produced from the offer in the TLV stream.
692 pub(super) fn verify<T: secp256k1::Signing>(
693 &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
694 ) -> Result<Option<KeyPair>, ()> {
695 match self.metadata() {
697 let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
698 match record.r#type {
699 OFFER_METADATA_TYPE => false,
700 OFFER_NODE_ID_TYPE => {
701 !self.metadata.as_ref().unwrap().derives_recipient_keys()
706 signer::verify_recipient_metadata(
707 metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
714 pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
715 let (currency, amount) = match &self.amount {
716 None => (None, None),
717 Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),
718 Some(Amount::Currency { iso4217_code, amount }) => (
719 Some(iso4217_code), Some(*amount)
724 if self.features == OfferFeatures::empty() { None } else { Some(&self.features) }
728 chains: self.chains.as_ref(),
729 metadata: self.metadata(),
732 description: Some(&self.description),
734 absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
735 paths: self.paths.as_ref(),
736 issuer: self.issuer.as_ref(),
737 quantity_max: self.supported_quantity.to_tlv_record(),
738 node_id: Some(&self.signing_pubkey),
743 impl Writeable for Offer {
744 fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
745 WithoutLength(&self.bytes).write(writer)
749 impl Writeable for OfferContents {
750 fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
751 self.as_tlv_stream().write(writer)
755 /// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or
756 /// another currency.
757 #[derive(Clone, Debug, PartialEq)]
759 /// An amount of bitcoin.
761 /// The amount in millisatoshi.
764 /// An amount of currency specified using ISO 4712.
766 /// The currency that the amount is denominated in.
767 iso4217_code: CurrencyCode,
768 /// The amount in the currency unit adjusted by the ISO 4712 exponent (e.g., USD cents).
773 /// An ISO 4712 three-letter currency code (e.g., USD).
774 pub type CurrencyCode = [u8; 3];
776 /// Quantity of items supported by an [`Offer`].
777 #[derive(Clone, Copy, Debug, PartialEq)]
779 /// Up to a specific number of items (inclusive). Use when more than one item can be requested
780 /// but is limited (e.g., because of per customer or inventory limits).
782 /// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
785 /// One or more items. Use when more than one item can be requested without any limit.
787 /// Only one item. Use when only a single item can be requested.
792 fn to_tlv_record(&self) -> Option<u64> {
794 Quantity::Bounded(n) => Some(n.get()),
795 Quantity::Unbounded => Some(0),
796 Quantity::One => None,
801 /// Valid type range for offer TLV records.
802 pub(super) const OFFER_TYPES: core::ops::Range<u64> = 1..80;
804 /// TLV record type for [`Offer::metadata`].
805 const OFFER_METADATA_TYPE: u64 = 4;
807 /// TLV record type for [`Offer::signing_pubkey`].
808 const OFFER_NODE_ID_TYPE: u64 = 22;
810 tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
811 (2, chains: (Vec<ChainHash>, WithoutLength)),
812 (OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
813 (6, currency: CurrencyCode),
814 (8, amount: (u64, HighZeroBytesDroppedBigSize)),
815 (10, description: (String, WithoutLength)),
816 (12, features: (OfferFeatures, WithoutLength)),
817 (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
818 (16, paths: (Vec<BlindedPath>, WithoutLength)),
819 (18, issuer: (String, WithoutLength)),
820 (20, quantity_max: (u64, HighZeroBytesDroppedBigSize)),
821 (OFFER_NODE_ID_TYPE, node_id: PublicKey),
824 impl Bech32Encode for Offer {
825 const BECH32_HRP: &'static str = "lno";
828 impl FromStr for Offer {
829 type Err = Bolt12ParseError;
831 fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
832 Self::from_bech32_str(s)
836 impl TryFrom<Vec<u8>> for Offer {
837 type Error = Bolt12ParseError;
839 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
840 let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
841 let ParsedMessage { bytes, tlv_stream } = offer;
842 let contents = OfferContents::try_from(tlv_stream)?;
843 Ok(Offer { bytes, contents })
847 impl TryFrom<OfferTlvStream> for OfferContents {
848 type Error = Bolt12SemanticError;
850 fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
852 chains, metadata, currency, amount, description, features, absolute_expiry, paths,
853 issuer, quantity_max, node_id,
856 let metadata = metadata.map(|metadata| Metadata::Bytes(metadata));
858 let amount = match (currency, amount) {
859 (None, None) => None,
860 (None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
861 return Err(Bolt12SemanticError::InvalidAmount);
863 (None, Some(amount_msats)) => Some(Amount::Bitcoin { amount_msats }),
864 (Some(_), None) => return Err(Bolt12SemanticError::MissingAmount),
865 (Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
868 let description = match description {
869 None => return Err(Bolt12SemanticError::MissingDescription),
870 Some(description) => description,
873 let features = features.unwrap_or_else(OfferFeatures::empty);
875 let absolute_expiry = absolute_expiry
876 .map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
878 let supported_quantity = match quantity_max {
879 None => Quantity::One,
880 Some(0) => Quantity::Unbounded,
881 Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
884 let signing_pubkey = match node_id {
885 None => return Err(Bolt12SemanticError::MissingSigningPubkey),
886 Some(node_id) => node_id,
890 chains, metadata, amount, description, features, absolute_expiry, issuer, paths,
891 supported_quantity, signing_pubkey,
896 impl core::fmt::Display for Offer {
897 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
898 self.fmt_bech32_str(f)
904 use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
906 use bitcoin::blockdata::constants::ChainHash;
907 use bitcoin::network::constants::Network;
908 use bitcoin::secp256k1::Secp256k1;
909 use core::convert::TryFrom;
910 use core::num::NonZeroU64;
911 use core::time::Duration;
912 use crate::blinded_path::{BlindedHop, BlindedPath};
913 use crate::sign::KeyMaterial;
914 use crate::ln::features::OfferFeatures;
915 use crate::ln::inbound_payment::ExpandedKey;
916 use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
917 use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
918 use crate::offers::test_utils::*;
919 use crate::util::ser::{BigSize, Writeable};
920 use crate::util::string::PrintableString;
923 fn builds_offer_with_defaults() {
924 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
926 let mut buffer = Vec::new();
927 offer.write(&mut buffer).unwrap();
929 assert_eq!(offer.bytes, buffer.as_slice());
930 assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
931 assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
932 assert_eq!(offer.metadata(), None);
933 assert_eq!(offer.amount(), None);
934 assert_eq!(offer.description(), PrintableString("foo"));
935 assert_eq!(offer.offer_features(), &OfferFeatures::empty());
936 assert_eq!(offer.absolute_expiry(), None);
937 #[cfg(feature = "std")]
938 assert!(!offer.is_expired());
939 assert_eq!(offer.paths(), &[]);
940 assert_eq!(offer.issuer(), None);
941 assert_eq!(offer.supported_quantity(), Quantity::One);
942 assert_eq!(offer.signing_pubkey(), pubkey(42));
945 offer.as_tlv_stream(),
951 description: Some(&String::from("foo")),
953 absolute_expiry: None,
957 node_id: Some(&pubkey(42)),
961 if let Err(e) = Offer::try_from(buffer) {
962 panic!("error parsing offer: {:?}", e);
967 fn builds_offer_with_chains() {
968 let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
969 let testnet = ChainHash::using_genesis_block(Network::Testnet);
971 let offer = OfferBuilder::new("foo".into(), pubkey(42))
972 .chain(Network::Bitcoin)
975 assert!(offer.supports_chain(mainnet));
976 assert_eq!(offer.chains(), vec![mainnet]);
977 assert_eq!(offer.as_tlv_stream().chains, None);
979 let offer = OfferBuilder::new("foo".into(), pubkey(42))
980 .chain(Network::Testnet)
983 assert!(offer.supports_chain(testnet));
984 assert_eq!(offer.chains(), vec![testnet]);
985 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
987 let offer = OfferBuilder::new("foo".into(), pubkey(42))
988 .chain(Network::Testnet)
989 .chain(Network::Testnet)
992 assert!(offer.supports_chain(testnet));
993 assert_eq!(offer.chains(), vec![testnet]);
994 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
996 let offer = OfferBuilder::new("foo".into(), pubkey(42))
997 .chain(Network::Bitcoin)
998 .chain(Network::Testnet)
1001 assert!(offer.supports_chain(mainnet));
1002 assert!(offer.supports_chain(testnet));
1003 assert_eq!(offer.chains(), vec![mainnet, testnet]);
1004 assert_eq!(offer.as_tlv_stream().chains, Some(&vec![mainnet, testnet]));
1008 fn builds_offer_with_metadata() {
1009 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1010 .metadata(vec![42; 32]).unwrap()
1013 assert_eq!(offer.metadata(), Some(&vec![42; 32]));
1014 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32]));
1016 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1017 .metadata(vec![42; 32]).unwrap()
1018 .metadata(vec![43; 32]).unwrap()
1021 assert_eq!(offer.metadata(), Some(&vec![43; 32]));
1022 assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
1026 fn builds_offer_with_metadata_derived() {
1027 let desc = "foo".to_string();
1028 let node_id = recipient_pubkey();
1029 let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
1030 let entropy = FixedEntropy {};
1031 let secp_ctx = Secp256k1::new();
1033 let offer = OfferBuilder
1034 ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
1037 assert_eq!(offer.signing_pubkey(), node_id);
1039 let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1041 .sign(payer_sign).unwrap();
1042 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
1044 // Fails verification with altered offer field
1045 let mut tlv_stream = offer.as_tlv_stream();
1046 tlv_stream.amount = Some(100);
1048 let mut encoded_offer = Vec::new();
1049 tlv_stream.write(&mut encoded_offer).unwrap();
1051 let invoice_request = Offer::try_from(encoded_offer).unwrap()
1052 .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1054 .sign(payer_sign).unwrap();
1055 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1057 // Fails verification with altered metadata
1058 let mut tlv_stream = offer.as_tlv_stream();
1059 let metadata = tlv_stream.metadata.unwrap().iter().copied().rev().collect();
1060 tlv_stream.metadata = Some(&metadata);
1062 let mut encoded_offer = Vec::new();
1063 tlv_stream.write(&mut encoded_offer).unwrap();
1065 let invoice_request = Offer::try_from(encoded_offer).unwrap()
1066 .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1068 .sign(payer_sign).unwrap();
1069 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1073 fn builds_offer_with_derived_signing_pubkey() {
1074 let desc = "foo".to_string();
1075 let node_id = recipient_pubkey();
1076 let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
1077 let entropy = FixedEntropy {};
1078 let secp_ctx = Secp256k1::new();
1080 let blinded_path = BlindedPath {
1081 introduction_node_id: pubkey(40),
1082 blinding_point: pubkey(41),
1084 BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
1085 BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
1089 let offer = OfferBuilder
1090 ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
1094 assert_ne!(offer.signing_pubkey(), node_id);
1096 let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1098 .sign(payer_sign).unwrap();
1099 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
1101 // Fails verification with altered offer field
1102 let mut tlv_stream = offer.as_tlv_stream();
1103 tlv_stream.amount = Some(100);
1105 let mut encoded_offer = Vec::new();
1106 tlv_stream.write(&mut encoded_offer).unwrap();
1108 let invoice_request = Offer::try_from(encoded_offer).unwrap()
1109 .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1111 .sign(payer_sign).unwrap();
1112 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1114 // Fails verification with altered signing pubkey
1115 let mut tlv_stream = offer.as_tlv_stream();
1116 let signing_pubkey = pubkey(1);
1117 tlv_stream.node_id = Some(&signing_pubkey);
1119 let mut encoded_offer = Vec::new();
1120 tlv_stream.write(&mut encoded_offer).unwrap();
1122 let invoice_request = Offer::try_from(encoded_offer).unwrap()
1123 .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1125 .sign(payer_sign).unwrap();
1126 assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1130 fn builds_offer_with_amount() {
1131 let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };
1132 let currency_amount = Amount::Currency { iso4217_code: *b"USD", amount: 10 };
1134 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1138 let tlv_stream = offer.as_tlv_stream();
1139 assert_eq!(offer.amount(), Some(&bitcoin_amount));
1140 assert_eq!(tlv_stream.amount, Some(1000));
1141 assert_eq!(tlv_stream.currency, None);
1143 let builder = OfferBuilder::new("foo".into(), pubkey(42))
1144 .amount(currency_amount.clone());
1145 let tlv_stream = builder.offer.as_tlv_stream();
1146 assert_eq!(builder.offer.amount, Some(currency_amount.clone()));
1147 assert_eq!(tlv_stream.amount, Some(10));
1148 assert_eq!(tlv_stream.currency, Some(b"USD"));
1149 match builder.build() {
1150 Ok(_) => panic!("expected error"),
1151 Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
1154 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1155 .amount(currency_amount.clone())
1156 .amount(bitcoin_amount.clone())
1159 let tlv_stream = offer.as_tlv_stream();
1160 assert_eq!(tlv_stream.amount, Some(1000));
1161 assert_eq!(tlv_stream.currency, None);
1163 let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
1164 match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
1165 Ok(_) => panic!("expected error"),
1166 Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
1171 fn builds_offer_with_features() {
1172 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1173 .features_unchecked(OfferFeatures::unknown())
1176 assert_eq!(offer.offer_features(), &OfferFeatures::unknown());
1177 assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
1179 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1180 .features_unchecked(OfferFeatures::unknown())
1181 .features_unchecked(OfferFeatures::empty())
1184 assert_eq!(offer.offer_features(), &OfferFeatures::empty());
1185 assert_eq!(offer.as_tlv_stream().features, None);
1189 fn builds_offer_with_absolute_expiry() {
1190 let future_expiry = Duration::from_secs(u64::max_value());
1191 let past_expiry = Duration::from_secs(0);
1193 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1194 .absolute_expiry(future_expiry)
1197 #[cfg(feature = "std")]
1198 assert!(!offer.is_expired());
1199 assert_eq!(offer.absolute_expiry(), Some(future_expiry));
1200 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
1202 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1203 .absolute_expiry(future_expiry)
1204 .absolute_expiry(past_expiry)
1207 #[cfg(feature = "std")]
1208 assert!(offer.is_expired());
1209 assert_eq!(offer.absolute_expiry(), Some(past_expiry));
1210 assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
1214 fn builds_offer_with_paths() {
1217 introduction_node_id: pubkey(40),
1218 blinding_point: pubkey(41),
1220 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
1221 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
1225 introduction_node_id: pubkey(40),
1226 blinding_point: pubkey(41),
1228 BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
1229 BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
1234 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1235 .path(paths[0].clone())
1236 .path(paths[1].clone())
1239 let tlv_stream = offer.as_tlv_stream();
1240 assert_eq!(offer.paths(), paths.as_slice());
1241 assert_eq!(offer.signing_pubkey(), pubkey(42));
1242 assert_ne!(pubkey(42), pubkey(44));
1243 assert_eq!(tlv_stream.paths, Some(&paths));
1244 assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
1248 fn builds_offer_with_issuer() {
1249 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1250 .issuer("bar".into())
1253 assert_eq!(offer.issuer(), Some(PrintableString("bar")));
1254 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar")));
1256 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1257 .issuer("bar".into())
1258 .issuer("baz".into())
1261 assert_eq!(offer.issuer(), Some(PrintableString("baz")));
1262 assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("baz")));
1266 fn builds_offer_with_supported_quantity() {
1267 let one = NonZeroU64::new(1).unwrap();
1268 let ten = NonZeroU64::new(10).unwrap();
1270 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1271 .supported_quantity(Quantity::One)
1274 let tlv_stream = offer.as_tlv_stream();
1275 assert_eq!(offer.supported_quantity(), Quantity::One);
1276 assert_eq!(tlv_stream.quantity_max, None);
1278 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1279 .supported_quantity(Quantity::Unbounded)
1282 let tlv_stream = offer.as_tlv_stream();
1283 assert_eq!(offer.supported_quantity(), Quantity::Unbounded);
1284 assert_eq!(tlv_stream.quantity_max, Some(0));
1286 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1287 .supported_quantity(Quantity::Bounded(ten))
1290 let tlv_stream = offer.as_tlv_stream();
1291 assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
1292 assert_eq!(tlv_stream.quantity_max, Some(10));
1294 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1295 .supported_quantity(Quantity::Bounded(one))
1298 let tlv_stream = offer.as_tlv_stream();
1299 assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
1300 assert_eq!(tlv_stream.quantity_max, Some(1));
1302 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1303 .supported_quantity(Quantity::Bounded(ten))
1304 .supported_quantity(Quantity::One)
1307 let tlv_stream = offer.as_tlv_stream();
1308 assert_eq!(offer.supported_quantity(), Quantity::One);
1309 assert_eq!(tlv_stream.quantity_max, None);
1313 fn fails_requesting_invoice_with_unknown_required_features() {
1314 match OfferBuilder::new("foo".into(), pubkey(42))
1315 .features_unchecked(OfferFeatures::unknown())
1317 .request_invoice(vec![1; 32], pubkey(43))
1319 Ok(_) => panic!("expected error"),
1320 Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
1325 fn parses_offer_with_chains() {
1326 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1327 .chain(Network::Bitcoin)
1328 .chain(Network::Testnet)
1331 if let Err(e) = offer.to_string().parse::<Offer>() {
1332 panic!("error parsing offer: {:?}", e);
1337 fn parses_offer_with_amount() {
1338 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1339 .amount(Amount::Bitcoin { amount_msats: 1000 })
1342 if let Err(e) = offer.to_string().parse::<Offer>() {
1343 panic!("error parsing offer: {:?}", e);
1346 let mut tlv_stream = offer.as_tlv_stream();
1347 tlv_stream.amount = Some(1000);
1348 tlv_stream.currency = Some(b"USD");
1350 let mut encoded_offer = Vec::new();
1351 tlv_stream.write(&mut encoded_offer).unwrap();
1353 if let Err(e) = Offer::try_from(encoded_offer) {
1354 panic!("error parsing offer: {:?}", e);
1357 let mut tlv_stream = offer.as_tlv_stream();
1358 tlv_stream.amount = None;
1359 tlv_stream.currency = Some(b"USD");
1361 let mut encoded_offer = Vec::new();
1362 tlv_stream.write(&mut encoded_offer).unwrap();
1364 match Offer::try_from(encoded_offer) {
1365 Ok(_) => panic!("expected error"),
1366 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
1369 let mut tlv_stream = offer.as_tlv_stream();
1370 tlv_stream.amount = Some(MAX_VALUE_MSAT + 1);
1371 tlv_stream.currency = None;
1373 let mut encoded_offer = Vec::new();
1374 tlv_stream.write(&mut encoded_offer).unwrap();
1376 match Offer::try_from(encoded_offer) {
1377 Ok(_) => panic!("expected error"),
1378 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
1383 fn parses_offer_with_description() {
1384 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1385 if let Err(e) = offer.to_string().parse::<Offer>() {
1386 panic!("error parsing offer: {:?}", e);
1389 let mut tlv_stream = offer.as_tlv_stream();
1390 tlv_stream.description = None;
1392 let mut encoded_offer = Vec::new();
1393 tlv_stream.write(&mut encoded_offer).unwrap();
1395 match Offer::try_from(encoded_offer) {
1396 Ok(_) => panic!("expected error"),
1398 assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
1404 fn parses_offer_with_paths() {
1405 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1407 introduction_node_id: pubkey(40),
1408 blinding_point: pubkey(41),
1410 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
1411 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
1415 introduction_node_id: pubkey(40),
1416 blinding_point: pubkey(41),
1418 BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
1419 BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
1424 if let Err(e) = offer.to_string().parse::<Offer>() {
1425 panic!("error parsing offer: {:?}", e);
1428 let mut builder = OfferBuilder::new("foo".into(), pubkey(42));
1429 builder.offer.paths = Some(vec![]);
1431 let offer = builder.build().unwrap();
1432 if let Err(e) = offer.to_string().parse::<Offer>() {
1433 panic!("error parsing offer: {:?}", e);
1438 fn parses_offer_with_quantity() {
1439 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1440 .supported_quantity(Quantity::One)
1443 if let Err(e) = offer.to_string().parse::<Offer>() {
1444 panic!("error parsing offer: {:?}", e);
1447 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1448 .supported_quantity(Quantity::Unbounded)
1451 if let Err(e) = offer.to_string().parse::<Offer>() {
1452 panic!("error parsing offer: {:?}", e);
1455 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1456 .supported_quantity(Quantity::Bounded(NonZeroU64::new(10).unwrap()))
1459 if let Err(e) = offer.to_string().parse::<Offer>() {
1460 panic!("error parsing offer: {:?}", e);
1463 let offer = OfferBuilder::new("foo".into(), pubkey(42))
1464 .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
1467 if let Err(e) = offer.to_string().parse::<Offer>() {
1468 panic!("error parsing offer: {:?}", e);
1473 fn parses_offer_with_node_id() {
1474 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1475 if let Err(e) = offer.to_string().parse::<Offer>() {
1476 panic!("error parsing offer: {:?}", e);
1479 let mut tlv_stream = offer.as_tlv_stream();
1480 tlv_stream.node_id = None;
1482 let mut encoded_offer = Vec::new();
1483 tlv_stream.write(&mut encoded_offer).unwrap();
1485 match Offer::try_from(encoded_offer) {
1486 Ok(_) => panic!("expected error"),
1488 assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
1494 fn fails_parsing_offer_with_extra_tlv_records() {
1495 let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
1497 let mut encoded_offer = Vec::new();
1498 offer.write(&mut encoded_offer).unwrap();
1499 BigSize(80).write(&mut encoded_offer).unwrap();
1500 BigSize(32).write(&mut encoded_offer).unwrap();
1501 [42u8; 32].write(&mut encoded_offer).unwrap();
1503 match Offer::try_from(encoded_offer) {
1504 Ok(_) => panic!("expected error"),
1505 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1512 use super::{Bolt12ParseError, Offer};
1513 use bitcoin::bech32;
1514 use crate::ln::msgs::DecodeError;
1517 fn encodes_offer_as_bech32_without_checksum() {
1518 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
1519 let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
1520 let reencoded_offer = offer.to_string();
1521 dbg!(reencoded_offer.parse::<Offer>().unwrap());
1522 assert_eq!(reencoded_offer, encoded_offer);
1526 fn parses_bech32_encoded_offers() {
1528 // BOLT 12 test vectors
1529 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1530 "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1531 "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
1532 "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
1534 for encoded_offer in &offers {
1535 if let Err(e) = encoded_offer.parse::<Offer>() {
1536 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
1542 fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
1544 // BOLT 12 test vectors
1545 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
1546 "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
1547 "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1548 "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1549 "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1551 for encoded_offer in &offers {
1552 match encoded_offer.parse::<Offer>() {
1553 Ok(_) => panic!("Valid offer: {}", encoded_offer),
1554 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
1561 fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
1562 let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
1563 match encoded_offer.parse::<Offer>() {
1564 Ok(_) => panic!("Valid offer: {}", encoded_offer),
1565 Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
1570 fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
1571 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
1572 match encoded_offer.parse::<Offer>() {
1573 Ok(_) => panic!("Valid offer: {}", encoded_offer),
1574 Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
1579 fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
1580 let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
1581 match encoded_offer.parse::<Offer>() {
1582 Ok(_) => panic!("Valid offer: {}", encoded_offer),
1583 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),