1use crate::blinded_path::message::BlindedMessagePath;
81use crate::io;
82use crate::ln::channelmanager::PaymentId;
83use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
84use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
85use crate::offers::merkle::{TaggedHash, TlvRecord, TlvStream};
86use crate::offers::nonce::Nonce;
87use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
88use crate::offers::signer::{self, Metadata, MetadataMaterial};
89use crate::onion_message::dns_resolution::HumanReadableName;
90use crate::types::features::OfferFeatures;
91use crate::types::string::PrintableString;
92use crate::util::ser::{
93 CursorReadable, HighZeroBytesDroppedBigSize, LengthLimitedRead, LengthReadable, Readable,
94 WithoutLength, Writeable, Writer,
95};
96use bitcoin::constants::ChainHash;
97use bitcoin::network::Network;
98use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1};
99use core::borrow::Borrow;
100use core::hash::{Hash, Hasher};
101use core::num::NonZeroU64;
102use core::str::FromStr;
103use core::time::Duration;
104
105#[cfg(not(c_bindings))]
106use crate::offers::invoice_request::InvoiceRequestBuilder;
107#[cfg(c_bindings)]
108use crate::offers::invoice_request::InvoiceRequestWithDerivedPayerSigningPubkeyBuilder;
109
110#[allow(unused_imports)]
111use crate::prelude::*;
112
113use bitcoin::hex::impl_fmt_traits;
114#[cfg(feature = "std")]
115use std::time::SystemTime;
116
117pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
118pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Offer v2~~~~";
119
120#[derive(Clone, Copy, Eq, PartialEq)]
122pub struct OfferId(pub [u8; 32]);
123
124impl OfferId {
125 const ID_TAG: &'static str = "LDK Offer ID";
126
127 fn from_valid_offer_tlv_stream(bytes: &[u8]) -> Self {
128 let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(Self::ID_TAG, bytes);
129 Self(tagged_hash.to_bytes())
130 }
131
132 pub(super) fn from_valid_bolt12_tlv_stream(bytes: &[u8]) -> Self {
133 let tlv_stream = Offer::tlv_stream_iter(bytes);
134 let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream);
135 Self(tagged_hash.to_bytes())
136 }
137}
138
139impl Borrow<[u8]> for OfferId {
140 fn borrow(&self) -> &[u8] {
141 &self.0[..]
142 }
143}
144
145impl_fmt_traits! {
146 impl fmt_traits for OfferId {
147 const LENGTH: usize = 32;
148 }
149}
150
151impl Writeable for OfferId {
152 fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
153 self.0.write(w)
154 }
155}
156
157impl Readable for OfferId {
158 fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
159 Ok(OfferId(Readable::read(r)?))
160 }
161}
162
163pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> {
171 offer: OfferContents,
172 metadata_strategy: core::marker::PhantomData<M>,
173 secp_ctx: Option<&'a Secp256k1<T>>,
174}
175
176#[cfg(c_bindings)]
182#[derive(Clone)]
183pub struct OfferWithExplicitMetadataBuilder<'a> {
184 offer: OfferContents,
185 metadata_strategy: core::marker::PhantomData<ExplicitMetadata>,
186 secp_ctx: Option<&'a Secp256k1<secp256k1::All>>,
187}
188
189#[cfg(c_bindings)]
195#[derive(Clone)]
196pub struct OfferWithDerivedMetadataBuilder<'a> {
197 offer: OfferContents,
198 metadata_strategy: core::marker::PhantomData<DerivedMetadata>,
199 secp_ctx: Option<&'a Secp256k1<secp256k1::All>>,
200}
201
202pub trait MetadataStrategy {}
206
207pub struct ExplicitMetadata {}
211
212pub struct DerivedMetadata {}
216
217impl MetadataStrategy for ExplicitMetadata {}
218
219impl MetadataStrategy for DerivedMetadata {}
220
221macro_rules! offer_explicit_metadata_builder_methods {
222 (
223 $self: ident, $self_type: ty, $return_type: ty, $return_value: expr
224) => {
225 pub fn new(signing_pubkey: PublicKey) -> Self {
238 Self {
239 offer: OfferContents {
240 chains: None,
241 metadata: None,
242 amount: None,
243 description: None,
244 features: OfferFeatures::empty(),
245 absolute_expiry: None,
246 issuer: None,
247 paths: None,
248 supported_quantity: Quantity::One,
249 issuer_signing_pubkey: Some(signing_pubkey),
250 #[cfg(test)]
251 experimental_foo: None,
252 },
253 metadata_strategy: core::marker::PhantomData,
254 secp_ctx: None,
255 }
256 }
257
258 pub fn metadata(
262 mut $self: $self_type, metadata: Vec<u8>,
263 ) -> Result<$return_type, Bolt12SemanticError> {
264 $self.offer.metadata = Some(Metadata::Bytes(metadata));
265 Ok($return_value)
266 }
267 };
268}
269
270macro_rules! offer_derived_metadata_builder_methods {
271 ($secp_context: ty) => {
272 pub fn deriving_signing_pubkey(
287 node_id: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce,
288 secp_ctx: &'a Secp256k1<$secp_context>,
289 ) -> Self {
290 let derivation_material = MetadataMaterial::new(nonce, expanded_key, None);
291 let metadata = Metadata::DerivedSigningPubkey(derivation_material);
292 Self {
293 offer: OfferContents {
294 chains: None,
295 metadata: Some(metadata),
296 amount: None,
297 description: None,
298 features: OfferFeatures::empty(),
299 absolute_expiry: None,
300 issuer: None,
301 paths: None,
302 supported_quantity: Quantity::One,
303 issuer_signing_pubkey: Some(node_id),
304 #[cfg(test)]
305 experimental_foo: None,
306 },
307 metadata_strategy: core::marker::PhantomData,
308 secp_ctx: Some(secp_ctx),
309 }
310 }
311 };
312}
313
314macro_rules! offer_builder_methods { (
315 $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
316) => {
317 pub fn chain($self: $self_type, network: Network) -> $return_type {
324 $self.chain_hash(ChainHash::using_genesis_block(network))
325 }
326
327 pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type {
334 let chains = $self.offer.chains.get_or_insert_with(Vec::new);
335 if !chains.contains(&chain) {
336 chains.push(chain);
337 }
338
339 $return_value
340 }
341
342 pub fn amount_msats($self: $self_type, amount_msats: u64) -> $return_type {
346 $self.amount(Amount::Bitcoin { amount_msats })
347 }
348
349 pub(super) fn amount($($self_mut)* $self: $self_type, amount: Amount) -> $return_type {
353 $self.offer.amount = Some(amount);
354 $return_value
355 }
356
357 #[cfg_attr(feature = "std", doc = "Any expiry that has already passed is valid and can be checked for using [`Offer::is_expired`].")]
359 pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type {
362 $self.offer.absolute_expiry = Some(absolute_expiry);
363 $return_value
364 }
365
366 pub fn description($($self_mut)* $self: $self_type, description: String) -> $return_type {
370 $self.offer.description = Some(description);
371 $return_value
372 }
373
374 pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type {
378 $self.offer.issuer = Some(issuer);
379 $return_value
380 }
381
382 pub fn path($($self_mut)* $self: $self_type, path: BlindedMessagePath) -> $return_type {
388 $self.offer.paths.get_or_insert_with(Vec::new).push(path);
389 $return_value
390 }
391
392 pub fn supported_quantity($($self_mut)* $self: $self_type, quantity: Quantity) -> $return_type {
397 $self.offer.supported_quantity = quantity;
398 $return_value
399 }
400
401 pub fn build($($self_mut)* $self: $self_type) -> Result<Offer, Bolt12SemanticError> {
403 match $self.offer.amount {
404 Some(Amount::Bitcoin { amount_msats }) => {
405 if amount_msats > MAX_VALUE_MSAT {
406 return Err(Bolt12SemanticError::InvalidAmount);
407 }
408 },
409 Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
410 None => {},
411 }
412
413 if $self.offer.amount.is_some() && $self.offer.description.is_none() {
414 $self.offer.description = Some(String::new());
415 }
416
417 if let Some(chains) = &$self.offer.chains {
418 if chains.len() == 1 && chains[0] == $self.offer.implied_chain() {
419 $self.offer.chains = None;
420 }
421 }
422
423 Ok($self.build_without_checks())
424 }
425
426 fn build_without_checks($($self_mut)* $self: $self_type) -> Offer {
427 if let Some(mut metadata) = $self.offer.metadata.take() {
428 if metadata.has_derivation_material() {
430
431 let iv_bytes = if $self.offer.paths.is_none() {
434 metadata = metadata.without_keys();
435 IV_BYTES_WITH_METADATA
436 } else {
437 IV_BYTES_WITHOUT_METADATA
438 };
439
440 let mut tlv_stream = $self.offer.as_tlv_stream();
441 debug_assert_eq!(tlv_stream.0.metadata, None);
442 tlv_stream.0.metadata = None;
443 if metadata.derives_recipient_keys() {
444 tlv_stream.0.issuer_id = None;
445 }
446
447 let (derived_metadata, keys) =
451 metadata.derive_from(iv_bytes, tlv_stream, $self.secp_ctx);
452 match keys {
453 Some(keys) => $self.offer.issuer_signing_pubkey = Some(keys.public_key()),
454 None => $self.offer.metadata = Some(derived_metadata),
455 }
456 } else {
457 $self.offer.metadata = Some(metadata);
458 }
459 }
460
461 const OFFER_ALLOCATION_SIZE: usize = 512;
462 let mut bytes = Vec::with_capacity(OFFER_ALLOCATION_SIZE);
463 $self.offer.write(&mut bytes).unwrap();
464
465 let id = OfferId::from_valid_offer_tlv_stream(&bytes);
466
467 Offer {
468 bytes,
469 #[cfg(not(c_bindings))]
470 contents: $self.offer,
471 #[cfg(c_bindings)]
472 contents: $self.offer.clone(),
473 id,
474 }
475 }
476} }
477
478#[cfg(test)]
479macro_rules! offer_builder_test_methods { (
480 $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)?
481) => {
482 #[cfg_attr(c_bindings, allow(dead_code))]
483 fn features_unchecked($($self_mut)* $self: $self_type, features: OfferFeatures) -> $return_type {
484 $self.offer.features = features;
485 $return_value
486 }
487
488 #[cfg_attr(c_bindings, allow(dead_code))]
489 pub(crate) fn clear_chains($($self_mut)* $self: $self_type) -> $return_type {
490 $self.offer.chains = None;
491 $return_value
492 }
493
494 #[cfg_attr(c_bindings, allow(dead_code))]
495 pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type {
496 $self.offer.paths = None;
497 $return_value
498 }
499
500 #[cfg_attr(c_bindings, allow(dead_code))]
501 pub(crate) fn clear_issuer_signing_pubkey($($self_mut)* $self: $self_type) -> $return_type {
502 $self.offer.issuer_signing_pubkey = None;
503 $return_value
504 }
505
506 #[cfg_attr(c_bindings, allow(dead_code))]
507 pub(super) fn experimental_foo($($self_mut)* $self: $self_type, experimental_foo: u64) -> $return_type {
508 $self.offer.experimental_foo = Some(experimental_foo);
509 $return_value
510 }
511
512 #[cfg_attr(c_bindings, allow(dead_code))]
513 pub(super) fn build_unchecked($self: $self_type) -> Offer {
514 $self.build_without_checks()
515 }
516} }
517
518impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
519 offer_builder_methods!(self, Self, Self, self, mut);
520
521 #[cfg(test)]
522 offer_builder_test_methods!(self, Self, Self, self, mut);
523}
524
525impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
526 offer_explicit_metadata_builder_methods!(self, Self, Self, self);
527}
528
529impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
530 offer_derived_metadata_builder_methods!(T);
531}
532
533#[cfg(all(c_bindings, not(test)))]
534impl<'a> OfferWithExplicitMetadataBuilder<'a> {
535 offer_explicit_metadata_builder_methods!(self, &mut Self, (), ());
536 offer_builder_methods!(self, &mut Self, (), ());
537}
538
539#[cfg(all(c_bindings, test))]
540impl<'a> OfferWithExplicitMetadataBuilder<'a> {
541 offer_explicit_metadata_builder_methods!(self, &mut Self, &mut Self, self);
542 offer_builder_methods!(self, &mut Self, &mut Self, self);
543 offer_builder_test_methods!(self, &mut Self, &mut Self, self);
544}
545
546#[cfg(all(c_bindings, not(test)))]
547impl<'a> OfferWithDerivedMetadataBuilder<'a> {
548 offer_derived_metadata_builder_methods!(secp256k1::All);
549 offer_builder_methods!(self, &mut Self, (), ());
550}
551
552#[cfg(all(c_bindings, test))]
553impl<'a> OfferWithDerivedMetadataBuilder<'a> {
554 offer_derived_metadata_builder_methods!(secp256k1::All);
555 offer_builder_methods!(self, &mut Self, &mut Self, self);
556 offer_builder_test_methods!(self, &mut Self, &mut Self, self);
557}
558
559#[cfg(c_bindings)]
560impl<'a> From<OfferBuilder<'a, DerivedMetadata, secp256k1::All>>
561 for OfferWithDerivedMetadataBuilder<'a>
562{
563 fn from(builder: OfferBuilder<'a, DerivedMetadata, secp256k1::All>) -> Self {
564 let OfferBuilder { offer, metadata_strategy, secp_ctx } = builder;
565
566 Self { offer, metadata_strategy, secp_ctx }
567 }
568}
569
570#[cfg(c_bindings)]
571impl<'a> From<OfferWithDerivedMetadataBuilder<'a>>
572 for OfferBuilder<'a, DerivedMetadata, secp256k1::All>
573{
574 fn from(builder: OfferWithDerivedMetadataBuilder<'a>) -> Self {
575 let OfferWithDerivedMetadataBuilder { offer, metadata_strategy, secp_ctx } = builder;
576
577 Self { offer, metadata_strategy, secp_ctx }
578 }
579}
580
581pub struct OfferFromHrn {
583 pub offer: Offer,
591 pub hrn: HumanReadableName,
593}
594
595#[derive(Clone, Debug)]
609pub struct Offer {
610 pub(super) bytes: Vec<u8>,
613 pub(super) contents: OfferContents,
614 id: OfferId,
615}
616
617#[derive(Clone, Debug)]
623#[cfg_attr(test, derive(PartialEq))]
624pub(super) struct OfferContents {
625 chains: Option<Vec<ChainHash>>,
626 metadata: Option<Metadata>,
627 amount: Option<Amount>,
628 description: Option<String>,
629 features: OfferFeatures,
630 absolute_expiry: Option<Duration>,
631 issuer: Option<String>,
632 paths: Option<Vec<BlindedMessagePath>>,
633 supported_quantity: Quantity,
634 issuer_signing_pubkey: Option<PublicKey>,
635 #[cfg(test)]
636 experimental_foo: Option<u64>,
637}
638
639macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
640 pub fn chains(&$self) -> Vec<bitcoin::constants::ChainHash> {
647 $contents.chains()
648 }
649
650 pub fn metadata(&$self) -> Option<&Vec<u8>> {
654 $contents.metadata()
655 }
656
657 pub fn amount(&$self) -> Option<$crate::offers::offer::Amount> {
659 $contents.amount()
660 }
661
662 pub fn description(&$self) -> Option<$crate::types::string::PrintableString<'_>> {
665 $contents.description()
666 }
667
668 pub fn offer_features(&$self) -> &$crate::types::features::OfferFeatures {
670 &$contents.features()
671 }
672
673 pub fn absolute_expiry(&$self) -> Option<core::time::Duration> {
677 $contents.absolute_expiry()
678 }
679
680 pub fn issuer(&$self) -> Option<$crate::types::string::PrintableString<'_>> {
683 $contents.issuer()
684 }
685
686 pub fn paths(&$self) -> &[$crate::blinded_path::message::BlindedMessagePath] {
689 $contents.paths()
690 }
691
692 pub fn supported_quantity(&$self) -> $crate::offers::offer::Quantity {
694 $contents.supported_quantity()
695 }
696
697 pub fn issuer_signing_pubkey(&$self) -> Option<bitcoin::secp256k1::PublicKey> {
709 $contents.issuer_signing_pubkey()
710 }
711} }
712
713impl Offer {
714 offer_accessors!(self, self.contents);
715
716 pub fn id(&self) -> OfferId {
718 self.id
719 }
720
721 pub(super) fn implied_chain(&self) -> ChainHash {
722 self.contents.implied_chain()
723 }
724
725 pub fn supports_chain(&self, chain: ChainHash) -> bool {
727 self.contents.supports_chain(chain)
728 }
729
730 #[cfg(feature = "std")]
732 pub fn is_expired(&self) -> bool {
733 self.contents.is_expired()
734 }
735
736 pub fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
738 self.contents.is_expired_no_std(duration_since_epoch)
739 }
740
741 pub fn is_valid_quantity(&self, quantity: u64) -> bool {
743 self.contents.is_valid_quantity(quantity)
744 }
745
746 pub fn expects_quantity(&self) -> bool {
750 self.contents.expects_quantity()
751 }
752
753 pub(super) fn tlv_stream_iter<'a>(
754 bytes: &'a [u8],
755 ) -> impl core::iter::Iterator<Item = TlvRecord<'a>> {
756 TlvStream::new(bytes)
757 .range(OFFER_TYPES)
758 .chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES))
759 }
760
761 pub(super) fn verify<T: secp256k1::Signing>(
762 &self, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>,
763 ) -> Result<(OfferId, Option<Keypair>), ()> {
764 self.contents.verify_using_recipient_data(&self.bytes, nonce, key, secp_ctx)
765 }
766}
767
768macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $offer: expr, $builder: ty, $hrn: expr) => {
769 pub fn request_invoice<
786 'a, 'b,
787 #[cfg(not(c_bindings))]
788 T: secp256k1::Signing
789 >(
790 &'a $self, expanded_key: &ExpandedKey, nonce: Nonce,
791 #[cfg(not(c_bindings))]
792 secp_ctx: &'b Secp256k1<T>,
793 #[cfg(c_bindings)]
794 secp_ctx: &'b Secp256k1<secp256k1::All>,
795 payment_id: PaymentId
796 ) -> Result<$builder, Bolt12SemanticError> {
797 if $offer.offer_features().requires_unknown_bits() {
798 return Err(Bolt12SemanticError::UnknownRequiredFeatures);
799 }
800
801 let mut builder = <$builder>::deriving_signing_pubkey(&$offer, expanded_key, nonce, secp_ctx, payment_id);
802 if let Some(hrn) = $hrn {
803 #[cfg(c_bindings)]
804 {
805 builder.sourced_from_human_readable_name(hrn);
806 }
807 #[cfg(not(c_bindings))]
808 {
809 builder = builder.sourced_from_human_readable_name(hrn);
810 }
811 }
812 Ok(builder)
813 }
814} }
815
816#[cfg(not(c_bindings))]
817impl Offer {
818 request_invoice_derived_signing_pubkey!(self, self, InvoiceRequestBuilder<'a, 'b, T>, None);
819}
820
821#[cfg(not(c_bindings))]
822impl OfferFromHrn {
823 request_invoice_derived_signing_pubkey!(
824 self,
825 self.offer,
826 InvoiceRequestBuilder<'a, 'b, T>,
827 Some(self.hrn)
828 );
829}
830
831#[cfg(c_bindings)]
832impl Offer {
833 request_invoice_derived_signing_pubkey!(
834 self,
835 self,
836 InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>,
837 None
838 );
839}
840
841#[cfg(c_bindings)]
842impl OfferFromHrn {
843 request_invoice_derived_signing_pubkey!(
844 self,
845 self.offer,
846 InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>,
847 Some(self.hrn)
848 );
849}
850
851#[cfg(test)]
852impl Offer {
853 pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef<'_> {
854 self.contents.as_tlv_stream()
855 }
856}
857
858impl AsRef<[u8]> for Offer {
859 fn as_ref(&self) -> &[u8] {
860 &self.bytes
861 }
862}
863
864impl PartialEq for Offer {
865 fn eq(&self, other: &Self) -> bool {
866 self.bytes.eq(&other.bytes)
867 }
868}
869
870impl Eq for Offer {}
871
872impl Hash for Offer {
873 fn hash<H: Hasher>(&self, state: &mut H) {
874 self.bytes.hash(state);
875 }
876}
877
878impl OfferContents {
879 pub fn chains(&self) -> Vec<ChainHash> {
880 self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
881 }
882
883 pub fn implied_chain(&self) -> ChainHash {
884 ChainHash::using_genesis_block(Network::Bitcoin)
885 }
886
887 pub fn supports_chain(&self, chain: ChainHash) -> bool {
888 self.chains().contains(&chain)
889 }
890
891 pub fn metadata(&self) -> Option<&Vec<u8>> {
892 self.metadata.as_ref().and_then(|metadata| metadata.as_bytes())
893 }
894
895 pub fn amount(&self) -> Option<Amount> {
896 self.amount
897 }
898
899 pub fn description(&self) -> Option<PrintableString<'_>> {
900 self.description.as_ref().map(|description| PrintableString(description))
901 }
902
903 pub fn features(&self) -> &OfferFeatures {
904 &self.features
905 }
906
907 pub fn absolute_expiry(&self) -> Option<Duration> {
908 self.absolute_expiry
909 }
910
911 #[cfg(feature = "std")]
912 pub(super) fn is_expired(&self) -> bool {
913 SystemTime::UNIX_EPOCH
914 .elapsed()
915 .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
916 .unwrap_or(false)
917 }
918
919 pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
920 self.absolute_expiry
921 .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
922 .unwrap_or(false)
923 }
924
925 pub fn issuer(&self) -> Option<PrintableString<'_>> {
926 self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
927 }
928
929 pub fn paths(&self) -> &[BlindedMessagePath] {
930 self.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
931 }
932
933 pub(super) fn check_amount_msats_for_quantity(
934 &self, amount_msats: Option<u64>, quantity: Option<u64>,
935 ) -> Result<(), Bolt12SemanticError> {
936 let offer_amount_msats = match self.amount {
937 None => 0,
938 Some(Amount::Bitcoin { amount_msats }) => amount_msats,
939 Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
940 };
941
942 if !self.expects_quantity() || quantity.is_some() {
943 let expected_amount_msats = offer_amount_msats
944 .checked_mul(quantity.unwrap_or(1))
945 .ok_or(Bolt12SemanticError::InvalidAmount)?;
946 let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
947
948 if amount_msats < expected_amount_msats {
949 return Err(Bolt12SemanticError::InsufficientAmount);
950 }
951
952 if amount_msats > MAX_VALUE_MSAT {
953 return Err(Bolt12SemanticError::InvalidAmount);
954 }
955 }
956
957 Ok(())
958 }
959
960 pub fn supported_quantity(&self) -> Quantity {
961 self.supported_quantity
962 }
963
964 pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), Bolt12SemanticError> {
965 let expects_quantity = self.expects_quantity();
966 match quantity {
967 None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
968 Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
969 Some(quantity) if !self.is_valid_quantity(quantity) => {
970 Err(Bolt12SemanticError::InvalidQuantity)
971 },
972 _ => Ok(()),
973 }
974 }
975
976 fn is_valid_quantity(&self, quantity: u64) -> bool {
977 match self.supported_quantity {
978 Quantity::Bounded(n) => quantity <= n.get(),
979 Quantity::Unbounded => quantity > 0,
980 Quantity::One => quantity == 1,
981 }
982 }
983
984 fn expects_quantity(&self) -> bool {
985 match self.supported_quantity {
986 Quantity::Bounded(_) => true,
987 Quantity::Unbounded => true,
988 Quantity::One => false,
989 }
990 }
991
992 pub(super) fn issuer_signing_pubkey(&self) -> Option<PublicKey> {
993 self.issuer_signing_pubkey
994 }
995
996 pub(super) fn verify_using_metadata<T: secp256k1::Signing>(
997 &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>,
998 ) -> Result<(OfferId, Option<Keypair>), ()> {
999 self.verify(bytes, self.metadata.as_ref(), key, IV_BYTES_WITH_METADATA, secp_ctx)
1000 }
1001
1002 pub(super) fn verify_using_recipient_data<T: secp256k1::Signing>(
1003 &self, bytes: &[u8], nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>,
1004 ) -> Result<(OfferId, Option<Keypair>), ()> {
1005 let metadata = Metadata::RecipientData(nonce);
1006 self.verify(bytes, Some(&metadata), key, IV_BYTES_WITHOUT_METADATA, secp_ctx)
1007 }
1008
1009 fn verify<T: secp256k1::Signing>(
1011 &self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey,
1012 iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1<T>,
1013 ) -> Result<(OfferId, Option<Keypair>), ()> {
1014 match metadata {
1015 Some(metadata) => {
1016 let tlv_stream = TlvStream::new(bytes)
1017 .range(OFFER_TYPES)
1018 .filter(|record| match record.r#type {
1019 OFFER_METADATA_TYPE => false,
1020 OFFER_ISSUER_ID_TYPE => !metadata.derives_recipient_keys(),
1021 _ => true,
1022 })
1023 .chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES));
1024
1025 let signing_pubkey = match self.issuer_signing_pubkey() {
1026 Some(signing_pubkey) => signing_pubkey,
1027 None => return Err(()),
1028 };
1029 let keys = signer::verify_recipient_metadata(
1030 metadata.as_ref(),
1031 key,
1032 iv_bytes,
1033 signing_pubkey,
1034 tlv_stream,
1035 secp_ctx,
1036 )?;
1037
1038 let offer_id = OfferId::from_valid_bolt12_tlv_stream(bytes);
1039
1040 Ok((offer_id, keys))
1041 },
1042 None => Err(()),
1043 }
1044 }
1045
1046 pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef<'_> {
1047 let (currency, amount) = match &self.amount {
1048 None => (None, None),
1049 Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),
1050 Some(Amount::Currency { iso4217_code, amount }) => {
1051 (Some(iso4217_code.as_bytes()), Some(*amount))
1052 },
1053 };
1054
1055 let features = {
1056 if self.features == OfferFeatures::empty() {
1057 None
1058 } else {
1059 Some(&self.features)
1060 }
1061 };
1062
1063 let offer = OfferTlvStreamRef {
1064 chains: self.chains.as_ref(),
1065 metadata: self.metadata(),
1066 currency,
1067 amount,
1068 description: self.description.as_ref(),
1069 features,
1070 absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
1071 paths: self.paths.as_ref(),
1072 issuer: self.issuer.as_ref(),
1073 quantity_max: self.supported_quantity.to_tlv_record(),
1074 issuer_id: self.issuer_signing_pubkey.as_ref(),
1075 };
1076
1077 let experimental_offer = ExperimentalOfferTlvStreamRef {
1078 #[cfg(test)]
1079 experimental_foo: self.experimental_foo,
1080 };
1081
1082 (offer, experimental_offer)
1083 }
1084}
1085
1086impl LengthReadable for Offer {
1087 fn read_from_fixed_length_buffer<R: LengthLimitedRead>(
1088 reader: &mut R,
1089 ) -> Result<Self, DecodeError> {
1090 let bytes: WithoutLength<Vec<u8>> = LengthReadable::read_from_fixed_length_buffer(reader)?;
1091 Self::try_from(bytes.0).map_err(|e| match e {
1092 Bolt12ParseError::Decode(e) => e,
1093 _ => DecodeError::InvalidValue,
1094 })
1095 }
1096}
1097
1098impl Writeable for Offer {
1099 fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
1100 WithoutLength(&self.bytes).write(writer)
1101 }
1102}
1103
1104impl Writeable for OfferContents {
1105 fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
1106 self.as_tlv_stream().write(writer)
1107 }
1108}
1109
1110#[derive(Clone, Copy, Debug, PartialEq)]
1113pub enum Amount {
1114 Bitcoin {
1116 amount_msats: u64,
1118 },
1119 Currency {
1121 iso4217_code: CurrencyCode,
1123 amount: u64,
1125 },
1126}
1127
1128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1132pub struct CurrencyCode([u8; 3]);
1133
1134impl CurrencyCode {
1135 pub fn new(code: [u8; 3]) -> Result<Self, CurrencyCodeError> {
1139 if !code.iter().all(|c| c.is_ascii_uppercase()) {
1140 return Err(CurrencyCodeError);
1141 }
1142
1143 Ok(Self(code))
1144 }
1145
1146 pub fn as_bytes(&self) -> &[u8; 3] {
1148 &self.0
1149 }
1150
1151 pub fn as_str(&self) -> &str {
1153 core::str::from_utf8(&self.0).expect("currency code is always valid UTF-8")
1154 }
1155}
1156
1157impl FromStr for CurrencyCode {
1158 type Err = CurrencyCodeError;
1159
1160 fn from_str(s: &str) -> Result<Self, Self::Err> {
1161 if s.len() != 3 {
1162 return Err(CurrencyCodeError);
1163 }
1164
1165 let mut code = [0u8; 3];
1166 code.copy_from_slice(s.as_bytes());
1167 Self::new(code)
1168 }
1169}
1170
1171impl AsRef<[u8]> for CurrencyCode {
1172 fn as_ref(&self) -> &[u8] {
1173 &self.0
1174 }
1175}
1176
1177impl core::fmt::Display for CurrencyCode {
1178 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1179 f.write_str(self.as_str())
1180 }
1181}
1182
1183#[derive(Clone, Copy, Debug, PartialEq)]
1185pub enum Quantity {
1186 Bounded(NonZeroU64),
1192 Unbounded,
1194 One,
1196}
1197
1198impl Quantity {
1199 fn to_tlv_record(self) -> Option<u64> {
1200 match self {
1201 Quantity::Bounded(n) => Some(n.get()),
1202 Quantity::Unbounded => Some(0),
1203 Quantity::One => None,
1204 }
1205 }
1206}
1207
1208pub(super) const OFFER_TYPES: core::ops::Range<u64> = 1..80;
1210
1211const OFFER_METADATA_TYPE: u64 = 4;
1213
1214const OFFER_ISSUER_ID_TYPE: u64 = 22;
1216
1217tlv_stream!(OfferTlvStream, OfferTlvStreamRef<'a>, OFFER_TYPES, {
1218 (2, chains: (Vec<ChainHash>, WithoutLength)),
1219 (OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
1220 (6, currency: [u8; 3]),
1221 (8, amount: (u64, HighZeroBytesDroppedBigSize)),
1222 (10, description: (String, WithoutLength)),
1223 (12, features: (OfferFeatures, WithoutLength)),
1224 (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
1225 (16, paths: (Vec<BlindedMessagePath>, WithoutLength)),
1226 (18, issuer: (String, WithoutLength)),
1227 (20, quantity_max: (u64, HighZeroBytesDroppedBigSize)),
1228 (OFFER_ISSUER_ID_TYPE, issuer_id: PublicKey),
1229});
1230
1231pub(super) const EXPERIMENTAL_OFFER_TYPES: core::ops::Range<u64> = 1_000_000_000..2_000_000_000;
1233
1234#[cfg(not(test))]
1235tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, {
1236});
1237
1238#[cfg(test)]
1239tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, {
1240 (1_999_999_999, experimental_foo: (u64, HighZeroBytesDroppedBigSize)),
1241});
1242
1243type FullOfferTlvStream = (OfferTlvStream, ExperimentalOfferTlvStream);
1244
1245type FullOfferTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef);
1246
1247impl CursorReadable for FullOfferTlvStream {
1248 fn read<R: AsRef<[u8]>>(r: &mut io::Cursor<R>) -> Result<Self, DecodeError> {
1249 let offer = CursorReadable::read(r)?;
1250 let experimental_offer = CursorReadable::read(r)?;
1251
1252 Ok((offer, experimental_offer))
1253 }
1254}
1255
1256impl Bech32Encode for Offer {
1257 const BECH32_HRP: &'static str = "lno";
1258}
1259
1260impl FromStr for Offer {
1261 type Err = Bolt12ParseError;
1262
1263 fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
1264 Self::from_bech32_str(s)
1265 }
1266}
1267
1268impl TryFrom<Vec<u8>> for Offer {
1269 type Error = Bolt12ParseError;
1270
1271 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
1272 let offer = ParsedMessage::<FullOfferTlvStream>::try_from(bytes)?;
1273 let ParsedMessage { bytes, tlv_stream } = offer;
1274 let contents = OfferContents::try_from(tlv_stream)?;
1275 let id = OfferId::from_valid_offer_tlv_stream(&bytes);
1276
1277 Ok(Offer { bytes, contents, id })
1278 }
1279}
1280
1281impl TryFrom<FullOfferTlvStream> for OfferContents {
1282 type Error = Bolt12SemanticError;
1283
1284 fn try_from(tlv_stream: FullOfferTlvStream) -> Result<Self, Self::Error> {
1285 let (
1286 OfferTlvStream {
1287 chains,
1288 metadata,
1289 currency,
1290 amount,
1291 description,
1292 features,
1293 absolute_expiry,
1294 paths,
1295 issuer,
1296 quantity_max,
1297 issuer_id,
1298 },
1299 ExperimentalOfferTlvStream {
1300 #[cfg(test)]
1301 experimental_foo,
1302 },
1303 ) = tlv_stream;
1304
1305 let metadata = metadata.map(|metadata| Metadata::Bytes(metadata));
1306
1307 let amount = match (currency, amount) {
1308 (None, None) => None,
1309 (None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
1310 return Err(Bolt12SemanticError::InvalidAmount);
1311 },
1312 (None, Some(amount_msats)) => Some(Amount::Bitcoin { amount_msats }),
1313 (Some(_), None) => return Err(Bolt12SemanticError::MissingAmount),
1314 (Some(currency_bytes), Some(amount)) => {
1315 let iso4217_code = CurrencyCode::new(currency_bytes)
1316 .map_err(|_| Bolt12SemanticError::InvalidCurrencyCode)?;
1317 Some(Amount::Currency { iso4217_code, amount })
1318 },
1319 };
1320
1321 if amount.is_some() && description.is_none() {
1322 return Err(Bolt12SemanticError::MissingDescription);
1323 }
1324
1325 let features = features.unwrap_or_else(OfferFeatures::empty);
1326
1327 let absolute_expiry =
1328 absolute_expiry.map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
1329
1330 let supported_quantity = match quantity_max {
1331 None => Quantity::One,
1332 Some(0) => Quantity::Unbounded,
1333 Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
1334 };
1335
1336 let (issuer_signing_pubkey, paths) = match (issuer_id, paths) {
1337 (None, None) => return Err(Bolt12SemanticError::MissingIssuerSigningPubkey),
1338 (None, Some(paths)) if paths.is_empty() => {
1339 return Err(Bolt12SemanticError::MissingPaths)
1340 },
1341 (issuer_id, paths) => (issuer_id, paths),
1342 };
1343
1344 Ok(OfferContents {
1345 chains,
1346 metadata,
1347 amount,
1348 description,
1349 features,
1350 absolute_expiry,
1351 issuer,
1352 paths,
1353 supported_quantity,
1354 issuer_signing_pubkey,
1355 #[cfg(test)]
1356 experimental_foo,
1357 })
1358 }
1359}
1360
1361impl core::fmt::Display for Offer {
1362 fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
1363 self.fmt_bech32_str(f)
1364 }
1365}
1366
1367#[derive(Clone, Debug, PartialEq, Eq)]
1373pub struct CurrencyCodeError;
1374
1375impl core::fmt::Display for CurrencyCodeError {
1376 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1377 write!(f, "invalid currency code: must be 3 uppercase ASCII letters (ISO 4217)")
1378 }
1379}
1380
1381#[cfg(test)]
1382mod tests {
1383 #[cfg(not(c_bindings))]
1384 use super::OfferBuilder;
1385 #[cfg(c_bindings)]
1386 use super::OfferWithExplicitMetadataBuilder as OfferBuilder;
1387 use super::{
1388 Amount, ExperimentalOfferTlvStreamRef, Offer, OfferTlvStreamRef, Quantity,
1389 EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES,
1390 };
1391
1392 use crate::blinded_path::message::BlindedMessagePath;
1393 use crate::blinded_path::BlindedHop;
1394 use crate::ln::channelmanager::PaymentId;
1395 use crate::ln::inbound_payment::ExpandedKey;
1396 use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
1397 use crate::offers::nonce::Nonce;
1398 use crate::offers::offer::CurrencyCode;
1399 use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
1400 use crate::offers::test_utils::*;
1401 use crate::types::features::OfferFeatures;
1402 use crate::types::string::PrintableString;
1403 use crate::util::ser::{BigSize, Writeable};
1404 use bitcoin::constants::ChainHash;
1405 use bitcoin::network::Network;
1406 use bitcoin::secp256k1::Secp256k1;
1407 use core::num::NonZeroU64;
1408 use core::time::Duration;
1409
1410 #[test]
1411 fn builds_offer_with_defaults() {
1412 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
1413
1414 let mut buffer = Vec::new();
1415 offer.write(&mut buffer).unwrap();
1416
1417 assert_eq!(offer.bytes, buffer.as_slice());
1418 assert_eq!(offer.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
1419 assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
1420 assert_eq!(offer.metadata(), None);
1421 assert_eq!(offer.amount(), None);
1422 assert_eq!(offer.description(), None);
1423 assert_eq!(offer.offer_features(), &OfferFeatures::empty());
1424 assert_eq!(offer.absolute_expiry(), None);
1425 #[cfg(feature = "std")]
1426 assert!(!offer.is_expired());
1427 assert_eq!(offer.paths(), &[]);
1428 assert_eq!(offer.issuer(), None);
1429 assert_eq!(offer.supported_quantity(), Quantity::One);
1430 assert!(!offer.expects_quantity());
1431 assert_eq!(offer.issuer_signing_pubkey(), Some(pubkey(42)));
1432
1433 assert_eq!(
1434 offer.as_tlv_stream(),
1435 (
1436 OfferTlvStreamRef {
1437 chains: None,
1438 metadata: None,
1439 currency: None,
1440 amount: None,
1441 description: None,
1442 features: None,
1443 absolute_expiry: None,
1444 paths: None,
1445 issuer: None,
1446 quantity_max: None,
1447 issuer_id: Some(&pubkey(42)),
1448 },
1449 ExperimentalOfferTlvStreamRef { experimental_foo: None },
1450 ),
1451 );
1452
1453 if let Err(e) = Offer::try_from(buffer) {
1454 panic!("error parsing offer: {:?}", e);
1455 }
1456 }
1457
1458 #[test]
1459 fn builds_offer_with_chains() {
1460 let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
1461 let testnet = ChainHash::using_genesis_block(Network::Testnet);
1462
1463 let offer = OfferBuilder::new(pubkey(42)).chain(Network::Bitcoin).build().unwrap();
1464 assert!(offer.supports_chain(mainnet));
1465 assert_eq!(offer.chains(), vec![mainnet]);
1466 assert_eq!(offer.as_tlv_stream().0.chains, None);
1467
1468 let offer = OfferBuilder::new(pubkey(42)).chain(Network::Testnet).build().unwrap();
1469 assert!(offer.supports_chain(testnet));
1470 assert_eq!(offer.chains(), vec![testnet]);
1471 assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet]));
1472
1473 let offer = OfferBuilder::new(pubkey(42))
1474 .chain(Network::Testnet)
1475 .chain(Network::Testnet)
1476 .build()
1477 .unwrap();
1478 assert!(offer.supports_chain(testnet));
1479 assert_eq!(offer.chains(), vec![testnet]);
1480 assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet]));
1481
1482 let offer = OfferBuilder::new(pubkey(42))
1483 .chain(Network::Bitcoin)
1484 .chain(Network::Testnet)
1485 .build()
1486 .unwrap();
1487 assert!(offer.supports_chain(mainnet));
1488 assert!(offer.supports_chain(testnet));
1489 assert_eq!(offer.chains(), vec![mainnet, testnet]);
1490 assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![mainnet, testnet]));
1491 }
1492
1493 #[test]
1494 fn builds_offer_with_metadata() {
1495 let offer = OfferBuilder::new(pubkey(42)).metadata(vec![42; 32]).unwrap().build().unwrap();
1496 assert_eq!(offer.metadata(), Some(&vec![42; 32]));
1497 assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![42; 32]));
1498
1499 let offer = OfferBuilder::new(pubkey(42))
1500 .metadata(vec![42; 32])
1501 .unwrap()
1502 .metadata(vec![43; 32])
1503 .unwrap()
1504 .build()
1505 .unwrap();
1506 assert_eq!(offer.metadata(), Some(&vec![43; 32]));
1507 assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![43; 32]));
1508 }
1509
1510 #[test]
1511 fn builds_offer_with_metadata_derived() {
1512 let node_id = recipient_pubkey();
1513 let expanded_key = ExpandedKey::new([42; 32]);
1514 let entropy = FixedEntropy {};
1515 let nonce = Nonce::from_entropy_source(&entropy);
1516 let secp_ctx = Secp256k1::new();
1517 let payment_id = PaymentId([1; 32]);
1518
1519 #[cfg(c_bindings)]
1520 use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
1521 let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
1522 .amount_msats(1000)
1523 .experimental_foo(42)
1524 .build()
1525 .unwrap();
1526 assert!(offer.metadata().is_some());
1527 assert_eq!(offer.issuer_signing_pubkey(), Some(node_id));
1528
1529 let invoice_request = offer
1530 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1531 .unwrap()
1532 .build_and_sign()
1533 .unwrap();
1534 match invoice_request.verify_using_metadata(&expanded_key, &secp_ctx) {
1535 Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
1536 Err(_) => panic!("unexpected error"),
1537 }
1538
1539 let invoice_request = offer
1541 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1542 .unwrap()
1543 .build_and_sign()
1544 .unwrap();
1545 assert!(invoice_request
1546 .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx)
1547 .is_err());
1548
1549 let mut tlv_stream = offer.as_tlv_stream();
1551 tlv_stream.0.amount = Some(100);
1552
1553 let mut encoded_offer = Vec::new();
1554 tlv_stream.write(&mut encoded_offer).unwrap();
1555
1556 let invoice_request = Offer::try_from(encoded_offer)
1557 .unwrap()
1558 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1559 .unwrap()
1560 .build_and_sign()
1561 .unwrap();
1562 assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
1563
1564 let mut tlv_stream = offer.as_tlv_stream();
1566 let metadata = tlv_stream.0.metadata.unwrap().iter().copied().rev().collect();
1567 tlv_stream.0.metadata = Some(&metadata);
1568
1569 let mut encoded_offer = Vec::new();
1570 tlv_stream.write(&mut encoded_offer).unwrap();
1571
1572 let invoice_request = Offer::try_from(encoded_offer)
1573 .unwrap()
1574 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1575 .unwrap()
1576 .build_and_sign()
1577 .unwrap();
1578 assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
1579 }
1580
1581 #[test]
1582 fn builds_offer_with_derived_signing_pubkey() {
1583 let node_id = recipient_pubkey();
1584 let expanded_key = ExpandedKey::new([42; 32]);
1585 let entropy = FixedEntropy {};
1586 let nonce = Nonce::from_entropy_source(&entropy);
1587 let secp_ctx = Secp256k1::new();
1588 let payment_id = PaymentId([1; 32]);
1589
1590 let blinded_path = BlindedMessagePath::from_blinded_path(
1591 pubkey(40),
1592 pubkey(41),
1593 vec![
1594 BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
1595 BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
1596 ],
1597 );
1598
1599 #[cfg(c_bindings)]
1600 use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
1601 let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
1602 .amount_msats(1000)
1603 .path(blinded_path)
1604 .experimental_foo(42)
1605 .build()
1606 .unwrap();
1607 assert!(offer.metadata().is_none());
1608 assert_ne!(offer.issuer_signing_pubkey(), Some(node_id));
1609
1610 let invoice_request = offer
1611 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1612 .unwrap()
1613 .build_and_sign()
1614 .unwrap();
1615 match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) {
1616 Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
1617 Err(_) => panic!("unexpected error"),
1618 }
1619
1620 let invoice_request = offer
1622 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1623 .unwrap()
1624 .build_and_sign()
1625 .unwrap();
1626 assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
1627
1628 let mut tlv_stream = offer.as_tlv_stream();
1630 tlv_stream.0.amount = Some(100);
1631
1632 let mut encoded_offer = Vec::new();
1633 tlv_stream.write(&mut encoded_offer).unwrap();
1634
1635 let invoice_request = Offer::try_from(encoded_offer)
1636 .unwrap()
1637 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1638 .unwrap()
1639 .build_and_sign()
1640 .unwrap();
1641 assert!(invoice_request
1642 .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx)
1643 .is_err());
1644
1645 let mut tlv_stream = offer.as_tlv_stream();
1647 let issuer_id = pubkey(1);
1648 tlv_stream.0.issuer_id = Some(&issuer_id);
1649
1650 let mut encoded_offer = Vec::new();
1651 tlv_stream.write(&mut encoded_offer).unwrap();
1652
1653 let invoice_request = Offer::try_from(encoded_offer)
1654 .unwrap()
1655 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1656 .unwrap()
1657 .build_and_sign()
1658 .unwrap();
1659 assert!(invoice_request
1660 .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx)
1661 .is_err());
1662 }
1663
1664 #[test]
1665 fn builds_offer_with_amount() {
1666 let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };
1667 let currency_amount =
1668 Amount::Currency { iso4217_code: CurrencyCode::new(*b"USD").unwrap(), amount: 10 };
1669
1670 let offer = OfferBuilder::new(pubkey(42)).amount_msats(1000).build().unwrap();
1671 let tlv_stream = offer.as_tlv_stream();
1672 assert_eq!(offer.amount(), Some(bitcoin_amount));
1673 assert_eq!(tlv_stream.0.amount, Some(1000));
1674 assert_eq!(tlv_stream.0.currency, None);
1675
1676 #[cfg(not(c_bindings))]
1677 let builder = OfferBuilder::new(pubkey(42)).amount(currency_amount.clone());
1678 #[cfg(c_bindings)]
1679 let mut builder = OfferBuilder::new(pubkey(42));
1680 #[cfg(c_bindings)]
1681 builder.amount(currency_amount.clone());
1682 let tlv_stream = builder.offer.as_tlv_stream();
1683 assert_eq!(builder.offer.amount, Some(currency_amount.clone()));
1684 assert_eq!(tlv_stream.0.amount, Some(10));
1685 assert_eq!(tlv_stream.0.currency, Some(b"USD"));
1686 match builder.build() {
1687 Ok(_) => panic!("expected error"),
1688 Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
1689 }
1690
1691 let offer = OfferBuilder::new(pubkey(42))
1692 .amount(currency_amount.clone())
1693 .amount(bitcoin_amount.clone())
1694 .build()
1695 .unwrap();
1696 let tlv_stream = offer.as_tlv_stream();
1697 assert_eq!(tlv_stream.0.amount, Some(1000));
1698 assert_eq!(tlv_stream.0.currency, None);
1699
1700 let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
1701 match OfferBuilder::new(pubkey(42)).amount(invalid_amount).build() {
1702 Ok(_) => panic!("expected error"),
1703 Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
1704 }
1705 }
1706
1707 #[test]
1708 fn builds_offer_with_description() {
1709 let offer = OfferBuilder::new(pubkey(42)).description("foo".into()).build().unwrap();
1710 assert_eq!(offer.description(), Some(PrintableString("foo")));
1711 assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("foo")));
1712
1713 let offer = OfferBuilder::new(pubkey(42))
1714 .description("foo".into())
1715 .description("bar".into())
1716 .build()
1717 .unwrap();
1718 assert_eq!(offer.description(), Some(PrintableString("bar")));
1719 assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("bar")));
1720
1721 let offer = OfferBuilder::new(pubkey(42)).amount_msats(1000).build().unwrap();
1722 assert_eq!(offer.description(), Some(PrintableString("")));
1723 assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("")));
1724 }
1725
1726 #[test]
1727 fn builds_offer_with_features() {
1728 let offer = OfferBuilder::new(pubkey(42))
1729 .features_unchecked(OfferFeatures::unknown())
1730 .build()
1731 .unwrap();
1732 assert_eq!(offer.offer_features(), &OfferFeatures::unknown());
1733 assert_eq!(offer.as_tlv_stream().0.features, Some(&OfferFeatures::unknown()));
1734
1735 let offer = OfferBuilder::new(pubkey(42))
1736 .features_unchecked(OfferFeatures::unknown())
1737 .features_unchecked(OfferFeatures::empty())
1738 .build()
1739 .unwrap();
1740 assert_eq!(offer.offer_features(), &OfferFeatures::empty());
1741 assert_eq!(offer.as_tlv_stream().0.features, None);
1742 }
1743
1744 #[test]
1745 fn builds_offer_with_absolute_expiry() {
1746 let future_expiry = Duration::from_secs(u64::max_value());
1747 let past_expiry = Duration::from_secs(0);
1748 let now = future_expiry - Duration::from_secs(1_000);
1749
1750 let offer = OfferBuilder::new(pubkey(42)).absolute_expiry(future_expiry).build().unwrap();
1751 #[cfg(feature = "std")]
1752 assert!(!offer.is_expired());
1753 assert!(!offer.is_expired_no_std(now));
1754 assert_eq!(offer.absolute_expiry(), Some(future_expiry));
1755 assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(future_expiry.as_secs()));
1756
1757 let offer = OfferBuilder::new(pubkey(42))
1758 .absolute_expiry(future_expiry)
1759 .absolute_expiry(past_expiry)
1760 .build()
1761 .unwrap();
1762 #[cfg(feature = "std")]
1763 assert!(offer.is_expired());
1764 assert!(offer.is_expired_no_std(now));
1765 assert_eq!(offer.absolute_expiry(), Some(past_expiry));
1766 assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(past_expiry.as_secs()));
1767 }
1768
1769 #[test]
1770 fn builds_offer_with_paths() {
1771 let paths = vec![
1772 BlindedMessagePath::from_blinded_path(
1773 pubkey(40),
1774 pubkey(41),
1775 vec![
1776 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
1777 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
1778 ],
1779 ),
1780 BlindedMessagePath::from_blinded_path(
1781 pubkey(40),
1782 pubkey(41),
1783 vec![
1784 BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
1785 BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
1786 ],
1787 ),
1788 ];
1789
1790 let offer = OfferBuilder::new(pubkey(42))
1791 .path(paths[0].clone())
1792 .path(paths[1].clone())
1793 .build()
1794 .unwrap();
1795 let tlv_stream = offer.as_tlv_stream();
1796 assert_eq!(offer.paths(), paths.as_slice());
1797 assert_eq!(offer.issuer_signing_pubkey(), Some(pubkey(42)));
1798 assert_ne!(pubkey(42), pubkey(44));
1799 assert_eq!(tlv_stream.0.paths, Some(&paths));
1800 assert_eq!(tlv_stream.0.issuer_id, Some(&pubkey(42)));
1801 }
1802
1803 #[test]
1804 fn builds_offer_with_issuer() {
1805 let offer = OfferBuilder::new(pubkey(42)).issuer("foo".into()).build().unwrap();
1806 assert_eq!(offer.issuer(), Some(PrintableString("foo")));
1807 assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("foo")));
1808
1809 let offer = OfferBuilder::new(pubkey(42))
1810 .issuer("foo".into())
1811 .issuer("bar".into())
1812 .build()
1813 .unwrap();
1814 assert_eq!(offer.issuer(), Some(PrintableString("bar")));
1815 assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("bar")));
1816 }
1817
1818 #[test]
1819 fn builds_offer_with_supported_quantity() {
1820 let one = NonZeroU64::new(1).unwrap();
1821 let ten = NonZeroU64::new(10).unwrap();
1822
1823 let offer =
1824 OfferBuilder::new(pubkey(42)).supported_quantity(Quantity::One).build().unwrap();
1825 let tlv_stream = offer.as_tlv_stream();
1826 assert!(!offer.expects_quantity());
1827 assert_eq!(offer.supported_quantity(), Quantity::One);
1828 assert_eq!(tlv_stream.0.quantity_max, None);
1829
1830 let offer =
1831 OfferBuilder::new(pubkey(42)).supported_quantity(Quantity::Unbounded).build().unwrap();
1832 let tlv_stream = offer.as_tlv_stream();
1833 assert!(offer.expects_quantity());
1834 assert_eq!(offer.supported_quantity(), Quantity::Unbounded);
1835 assert_eq!(tlv_stream.0.quantity_max, Some(0));
1836
1837 let offer = OfferBuilder::new(pubkey(42))
1838 .supported_quantity(Quantity::Bounded(ten))
1839 .build()
1840 .unwrap();
1841 let tlv_stream = offer.as_tlv_stream();
1842 assert!(offer.expects_quantity());
1843 assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
1844 assert_eq!(tlv_stream.0.quantity_max, Some(10));
1845
1846 let offer = OfferBuilder::new(pubkey(42))
1847 .supported_quantity(Quantity::Bounded(one))
1848 .build()
1849 .unwrap();
1850 let tlv_stream = offer.as_tlv_stream();
1851 assert!(offer.expects_quantity());
1852 assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
1853 assert_eq!(tlv_stream.0.quantity_max, Some(1));
1854
1855 let offer = OfferBuilder::new(pubkey(42))
1856 .supported_quantity(Quantity::Bounded(ten))
1857 .supported_quantity(Quantity::One)
1858 .build()
1859 .unwrap();
1860 let tlv_stream = offer.as_tlv_stream();
1861 assert!(!offer.expects_quantity());
1862 assert_eq!(offer.supported_quantity(), Quantity::One);
1863 assert_eq!(tlv_stream.0.quantity_max, None);
1864 }
1865
1866 #[test]
1867 fn fails_requesting_invoice_with_unknown_required_features() {
1868 let expanded_key = ExpandedKey::new([42; 32]);
1869 let entropy = FixedEntropy {};
1870 let nonce = Nonce::from_entropy_source(&entropy);
1871 let secp_ctx = Secp256k1::new();
1872 let payment_id = PaymentId([1; 32]);
1873
1874 match OfferBuilder::new(pubkey(42))
1875 .features_unchecked(OfferFeatures::unknown())
1876 .build()
1877 .unwrap()
1878 .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
1879 {
1880 Ok(_) => panic!("expected error"),
1881 Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
1882 }
1883 }
1884
1885 #[test]
1886 fn parses_offer_with_chains() {
1887 let offer = OfferBuilder::new(pubkey(42))
1888 .chain(Network::Bitcoin)
1889 .chain(Network::Testnet)
1890 .build()
1891 .unwrap();
1892 if let Err(e) = offer.to_string().parse::<Offer>() {
1893 panic!("error parsing offer: {:?}", e);
1894 }
1895 }
1896
1897 #[test]
1898 fn parses_offer_with_amount() {
1899 let offer = OfferBuilder::new(pubkey(42))
1900 .amount(Amount::Bitcoin { amount_msats: 1000 })
1901 .build()
1902 .unwrap();
1903 if let Err(e) = offer.to_string().parse::<Offer>() {
1904 panic!("error parsing offer: {:?}", e);
1905 }
1906
1907 let mut tlv_stream = offer.as_tlv_stream();
1908 tlv_stream.0.amount = Some(1000);
1909 tlv_stream.0.currency = Some(b"USD");
1910
1911 let mut encoded_offer = Vec::new();
1912 tlv_stream.write(&mut encoded_offer).unwrap();
1913
1914 if let Err(e) = Offer::try_from(encoded_offer) {
1915 panic!("error parsing offer: {:?}", e);
1916 }
1917
1918 let mut tlv_stream = offer.as_tlv_stream();
1919 tlv_stream.0.amount = None;
1920 tlv_stream.0.currency = Some(b"USD");
1921
1922 let mut encoded_offer = Vec::new();
1923 tlv_stream.write(&mut encoded_offer).unwrap();
1924
1925 match Offer::try_from(encoded_offer) {
1926 Ok(_) => panic!("expected error"),
1927 Err(e) => assert_eq!(
1928 e,
1929 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)
1930 ),
1931 }
1932
1933 let mut tlv_stream = offer.as_tlv_stream();
1934 tlv_stream.0.amount = Some(MAX_VALUE_MSAT + 1);
1935 tlv_stream.0.currency = None;
1936
1937 let mut encoded_offer = Vec::new();
1938 tlv_stream.write(&mut encoded_offer).unwrap();
1939
1940 match Offer::try_from(encoded_offer) {
1941 Ok(_) => panic!("expected error"),
1942 Err(e) => assert_eq!(
1943 e,
1944 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)
1945 ),
1946 }
1947
1948 let mut tlv_stream = offer.as_tlv_stream();
1949 tlv_stream.0.amount = Some(1000);
1950 tlv_stream.0.currency = Some(b"\xFF\xFE\xFD"); let mut encoded_offer = Vec::new();
1953 tlv_stream.write(&mut encoded_offer).unwrap();
1954
1955 match Offer::try_from(encoded_offer) {
1956 Ok(_) => panic!("expected error"),
1957 Err(e) => assert_eq!(
1958 e,
1959 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidCurrencyCode)
1960 ),
1961 }
1962
1963 let mut tlv_stream = offer.as_tlv_stream();
1964 tlv_stream.0.amount = Some(1000);
1965 tlv_stream.0.currency = Some(b"usd"); let mut encoded_offer = Vec::new();
1968 tlv_stream.write(&mut encoded_offer).unwrap();
1969
1970 match Offer::try_from(encoded_offer) {
1971 Ok(_) => panic!("expected error"),
1972 Err(e) => assert_eq!(
1973 e,
1974 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidCurrencyCode)
1975 ),
1976 }
1977 }
1978
1979 #[test]
1980 fn parses_offer_with_description() {
1981 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
1982 if let Err(e) = offer.to_string().parse::<Offer>() {
1983 panic!("error parsing offer: {:?}", e);
1984 }
1985
1986 let offer = OfferBuilder::new(pubkey(42))
1987 .description("foo".to_string())
1988 .amount_msats(1000)
1989 .build()
1990 .unwrap();
1991 if let Err(e) = offer.to_string().parse::<Offer>() {
1992 panic!("error parsing offer: {:?}", e);
1993 }
1994
1995 let mut tlv_stream = offer.as_tlv_stream();
1996 tlv_stream.0.description = None;
1997
1998 let mut encoded_offer = Vec::new();
1999 tlv_stream.write(&mut encoded_offer).unwrap();
2000
2001 match Offer::try_from(encoded_offer) {
2002 Ok(_) => panic!("expected error"),
2003 Err(e) => {
2004 assert_eq!(
2005 e,
2006 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)
2007 );
2008 },
2009 }
2010 }
2011
2012 #[test]
2013 fn parses_offer_with_paths() {
2014 let offer = OfferBuilder::new(pubkey(42))
2015 .path(BlindedMessagePath::from_blinded_path(
2016 pubkey(40),
2017 pubkey(41),
2018 vec![
2019 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
2020 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
2021 ],
2022 ))
2023 .path(BlindedMessagePath::from_blinded_path(
2024 pubkey(40),
2025 pubkey(41),
2026 vec![
2027 BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
2028 BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
2029 ],
2030 ))
2031 .build()
2032 .unwrap();
2033 if let Err(e) = offer.to_string().parse::<Offer>() {
2034 panic!("error parsing offer: {:?}", e);
2035 }
2036
2037 let offer = OfferBuilder::new(pubkey(42))
2038 .path(BlindedMessagePath::from_blinded_path(
2039 pubkey(40),
2040 pubkey(41),
2041 vec![
2042 BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
2043 BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
2044 ],
2045 ))
2046 .clear_issuer_signing_pubkey()
2047 .build()
2048 .unwrap();
2049 if let Err(e) = offer.to_string().parse::<Offer>() {
2050 panic!("error parsing offer: {:?}", e);
2051 }
2052
2053 let mut builder = OfferBuilder::new(pubkey(42));
2054 builder.offer.issuer_signing_pubkey = None;
2055 builder.offer.paths = Some(vec![]);
2056
2057 let offer = builder.build().unwrap();
2058 match offer.to_string().parse::<Offer>() {
2059 Ok(_) => panic!("expected error"),
2060 Err(e) => {
2061 assert_eq!(
2062 e,
2063 Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)
2064 );
2065 },
2066 }
2067 }
2068
2069 #[test]
2070 fn parses_offer_with_quantity() {
2071 let offer =
2072 OfferBuilder::new(pubkey(42)).supported_quantity(Quantity::One).build().unwrap();
2073 if let Err(e) = offer.to_string().parse::<Offer>() {
2074 panic!("error parsing offer: {:?}", e);
2075 }
2076
2077 let offer =
2078 OfferBuilder::new(pubkey(42)).supported_quantity(Quantity::Unbounded).build().unwrap();
2079 if let Err(e) = offer.to_string().parse::<Offer>() {
2080 panic!("error parsing offer: {:?}", e);
2081 }
2082
2083 let offer = OfferBuilder::new(pubkey(42))
2084 .supported_quantity(Quantity::Bounded(NonZeroU64::new(10).unwrap()))
2085 .build()
2086 .unwrap();
2087 if let Err(e) = offer.to_string().parse::<Offer>() {
2088 panic!("error parsing offer: {:?}", e);
2089 }
2090
2091 let offer = OfferBuilder::new(pubkey(42))
2092 .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
2093 .build()
2094 .unwrap();
2095 if let Err(e) = offer.to_string().parse::<Offer>() {
2096 panic!("error parsing offer: {:?}", e);
2097 }
2098 }
2099
2100 #[test]
2101 fn parses_offer_with_issuer_id() {
2102 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2103 if let Err(e) = offer.to_string().parse::<Offer>() {
2104 panic!("error parsing offer: {:?}", e);
2105 }
2106
2107 let mut tlv_stream = offer.as_tlv_stream();
2108 tlv_stream.0.issuer_id = None;
2109
2110 let mut encoded_offer = Vec::new();
2111 tlv_stream.write(&mut encoded_offer).unwrap();
2112
2113 match Offer::try_from(encoded_offer) {
2114 Ok(_) => panic!("expected error"),
2115 Err(e) => {
2116 assert_eq!(
2117 e,
2118 Bolt12ParseError::InvalidSemantics(
2119 Bolt12SemanticError::MissingIssuerSigningPubkey
2120 )
2121 );
2122 },
2123 }
2124 }
2125
2126 #[test]
2127 fn parses_offer_with_unknown_tlv_records() {
2128 const UNKNOWN_ODD_TYPE: u64 = OFFER_TYPES.end - 1;
2129 assert!(UNKNOWN_ODD_TYPE % 2 == 1);
2130
2131 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2132
2133 let mut encoded_offer = Vec::new();
2134 offer.write(&mut encoded_offer).unwrap();
2135 BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_offer).unwrap();
2136 BigSize(32).write(&mut encoded_offer).unwrap();
2137 [42u8; 32].write(&mut encoded_offer).unwrap();
2138
2139 match Offer::try_from(encoded_offer.clone()) {
2140 Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
2141 Err(e) => panic!("error parsing offer: {:?}", e),
2142 }
2143
2144 const UNKNOWN_EVEN_TYPE: u64 = OFFER_TYPES.end - 2;
2145 assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
2146
2147 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2148
2149 let mut encoded_offer = Vec::new();
2150 offer.write(&mut encoded_offer).unwrap();
2151 BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_offer).unwrap();
2152 BigSize(32).write(&mut encoded_offer).unwrap();
2153 [42u8; 32].write(&mut encoded_offer).unwrap();
2154
2155 match Offer::try_from(encoded_offer) {
2156 Ok(_) => panic!("expected error"),
2157 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2158 }
2159 }
2160
2161 #[test]
2162 fn parses_offer_with_experimental_tlv_records() {
2163 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2164
2165 let mut encoded_offer = Vec::new();
2166 offer.write(&mut encoded_offer).unwrap();
2167 BigSize(EXPERIMENTAL_OFFER_TYPES.start + 1).write(&mut encoded_offer).unwrap();
2168 BigSize(32).write(&mut encoded_offer).unwrap();
2169 [42u8; 32].write(&mut encoded_offer).unwrap();
2170
2171 match Offer::try_from(encoded_offer.clone()) {
2172 Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
2173 Err(e) => panic!("error parsing offer: {:?}", e),
2174 }
2175
2176 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2177
2178 let mut encoded_offer = Vec::new();
2179 offer.write(&mut encoded_offer).unwrap();
2180 BigSize(EXPERIMENTAL_OFFER_TYPES.start).write(&mut encoded_offer).unwrap();
2181 BigSize(32).write(&mut encoded_offer).unwrap();
2182 [42u8; 32].write(&mut encoded_offer).unwrap();
2183
2184 match Offer::try_from(encoded_offer) {
2185 Ok(_) => panic!("expected error"),
2186 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2187 }
2188 }
2189
2190 #[test]
2191 fn fails_parsing_offer_with_out_of_range_tlv_records() {
2192 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2193
2194 let mut encoded_offer = Vec::new();
2195 offer.write(&mut encoded_offer).unwrap();
2196 BigSize(OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
2197 BigSize(32).write(&mut encoded_offer).unwrap();
2198 [42u8; 32].write(&mut encoded_offer).unwrap();
2199
2200 match Offer::try_from(encoded_offer) {
2201 Ok(_) => panic!("expected error"),
2202 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2203 }
2204
2205 let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
2206
2207 let mut encoded_offer = Vec::new();
2208 offer.write(&mut encoded_offer).unwrap();
2209 BigSize(EXPERIMENTAL_OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
2210 BigSize(32).write(&mut encoded_offer).unwrap();
2211 [42u8; 32].write(&mut encoded_offer).unwrap();
2212
2213 match Offer::try_from(encoded_offer) {
2214 Ok(_) => panic!("expected error"),
2215 Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2216 }
2217 }
2218}
2219
2220#[cfg(test)]
2221mod currency_code_tests {
2222 use super::CurrencyCode;
2223
2224 #[test]
2225 fn creates_valid_currency_codes() {
2226 let usd = CurrencyCode::new(*b"USD").unwrap();
2227 assert_eq!(usd.as_str(), "USD");
2228 assert_eq!(usd.as_bytes(), b"USD");
2229
2230 let eur = CurrencyCode::new(*b"EUR").unwrap();
2231 assert_eq!(eur.as_str(), "EUR");
2232 assert_eq!(eur.as_bytes(), b"EUR");
2233 }
2234
2235 #[test]
2236 fn rejects_invalid_utf8() {
2237 let invalid_utf8 = [0xFF, 0xFE, 0xFD];
2238 assert!(CurrencyCode::new(invalid_utf8).is_err());
2239 }
2240
2241 #[test]
2242 fn rejects_lowercase_letters() {
2243 assert!(CurrencyCode::new(*b"usd").is_err());
2244 assert!(CurrencyCode::new(*b"Eur").is_err());
2245 }
2246
2247 #[test]
2248 fn rejects_non_letters() {
2249 assert!(CurrencyCode::new(*b"US1").is_err());
2250 assert!(CurrencyCode::new(*b"U$D").is_err());
2251 }
2252
2253 #[test]
2254 fn from_str_validates_length() {
2255 assert!("US".parse::<CurrencyCode>().is_err());
2256 assert!("USDA".parse::<CurrencyCode>().is_err());
2257
2258 assert!("USD".parse::<CurrencyCode>().is_ok());
2259 }
2260
2261 #[test]
2262 fn works_with_real_currency_codes() {
2263 let codes = ["USD", "EUR", "GBP", "JPY", "CNY"];
2264
2265 for code_str in &codes {
2266 let code1 = CurrencyCode::new(code_str.as_bytes().try_into().unwrap()).unwrap();
2267 let code2 = code_str.parse::<CurrencyCode>().unwrap();
2268
2269 assert_eq!(code1, code2);
2270 assert_eq!(code1.as_str(), *code_str);
2271 }
2272 }
2273}
2274
2275#[cfg(test)]
2276mod bolt12_tests {
2277 use super::{Bolt12ParseError, Bolt12SemanticError, Offer};
2278 use crate::ln::msgs::DecodeError;
2279
2280 #[test]
2281 fn parses_bech32_encoded_offers() {
2282 let offers = [
2283 "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
2285 "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
2287
2288 "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
2290
2291 "lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
2293
2294 "lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
2296
2297 "lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
2299
2300 "lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
2302
2303 "lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
2305
2306 "lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
2308
2309 "lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
2311
2312 "lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
2314
2315 "lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry",
2317
2318 "lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
2320
2321 "lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
2323
2324 "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
2326
2327 "lno1pgx9getnwss8vetrw3hhyucs3yqqqqqqqqqqqqp2qgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqyqqqqqqqqqqqqqqqqqqqqqqqqqqqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqgzyg3zyg3zyg3z93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
2329
2330 "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zygs",
2332
2333 "lno1pgx9getnwss8vetrw3hhyucsl5qj5qeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygpqqqqzqqqqgqqxqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqgqqqqqqqqqqqqqqqqqqqqqqqqqqqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqqsg3zyg3zyg3zygtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry",
2335
2336 "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs",
2338
2339 "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvx078wdv5gg2dpjkcmr0wahhymry",
2341 ];
2342 for encoded_offer in &offers {
2343 if let Err(e) = encoded_offer.parse::<Offer>() {
2344 panic!("Invalid offer ({:?}): {}", e, encoded_offer);
2345 }
2346 }
2347 }
2348
2349 #[test]
2350 fn fails_parsing_bech32_encoded_offers() {
2351 assert_eq!(
2353 "lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs"
2354 .parse::<Offer>(),
2355 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2356 );
2357
2358 assert_eq!(
2360 "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
2361 Err(Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2362 );
2363
2364 assert_eq!(
2366 "lno1".parse::<Offer>(),
2367 Err(Bolt12ParseError::InvalidSemantics(
2368 Bolt12SemanticError::MissingIssuerSigningPubkey
2369 )),
2370 );
2371
2372 assert_eq!(
2374 "lno1pg".parse::<Offer>(),
2375 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2376 );
2377
2378 assert_eq!(
2380 "lno1pt7s".parse::<Offer>(),
2381 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2382 );
2383
2384 assert_eq!(
2386 "lno1pgpq".parse::<Offer>(),
2387 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2388 );
2389
2390 assert_eq!(
2392 "lno1pgpyz".parse::<Offer>(),
2393 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2394 );
2395
2396 assert_eq!(
2398 "lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz"
2399 .parse::<Offer>(),
2400 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2401 );
2402
2403 assert_eq!(
2405 "lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz"
2406 .parse::<Offer>(),
2407 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2408 );
2409
2410 assert_eq!(
2412 "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg"
2413 .parse::<Offer>(),
2414 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2415 );
2416
2417 assert_eq!(
2419 "lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy".parse::<Offer>(),
2420 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2421 );
2422
2423 assert_eq!(
2425 "lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
2426 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2427 );
2428
2429 assert_eq!(
2431 "lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz"
2432 .parse::<Offer>(),
2433 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2434 );
2435
2436 assert_eq!(
2438 "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
2439 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2440 );
2441
2442 assert_eq!(
2444 "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
2445 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2446 );
2447
2448 assert_eq!(
2450 "lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
2451 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2452 );
2453
2454 assert_eq!(
2456 "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
2457 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2458 );
2459
2460 assert_eq!(
2462 "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
2463 Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
2464 );
2465
2466 assert_eq!(
2468 "lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz"
2469 .parse::<Offer>(),
2470 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2471 );
2472
2473 assert_eq!(
2475 "lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg"
2476 .parse::<Offer>(),
2477 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2478 );
2479
2480 assert_eq!(
2482 "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps"
2483 .parse::<Offer>(),
2484 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2485 );
2486
2487 assert_eq!(
2489 "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
2490 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2491 );
2492
2493 assert_eq!(
2495 "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06ae4jsq9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
2496 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2497 );
2498
2499 assert_eq!(
2501 "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06wu6egp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
2502 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2503 );
2504
2505 assert!(
2508 "lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>().is_ok()
2509 );
2510
2511 assert_eq!(
2513 "lno1pqpq86qkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>(),
2515 Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
2516 );
2517
2518 assert_eq!(
2520 "lno1pgx9getnwss8vetrw3hhyuc".parse::<Offer>(),
2521 Err(Bolt12ParseError::InvalidSemantics(
2522 Bolt12SemanticError::MissingIssuerSigningPubkey
2523 )),
2524 );
2525
2526 assert_eq!(
2528 "lno1pgx9getnwss8vetrw3hhyucsespjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zygszqqqqyqqqqsqqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsq".parse::<Offer>(),
2529 Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
2530 );
2531 }
2532}