X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Foffer.rs;h=db910b5e1bba361d977448bd6bd53a706cd92cab;hb=c13b6d814bfb5dd95f9e40f8ff07dd45c47a387e;hp=8e93a93f8736f0163038c25977cbec303977b0c7;hpb=2b14cc40a6acf226aafaddd0efe67264c0df706f;p=rust-lightning diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 8e93a93f..db910b5e 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -24,7 +24,7 @@ //! use core::num::NonZeroU64; //! use core::time::Duration; //! -//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; +//! use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; //! use lightning::offers::offer::{Offer, OfferBuilder, Quantity}; //! use lightning::offers::parse::Bolt12ParseError; //! use lightning::util::ser::{Readable, Writeable}; @@ -39,11 +39,12 @@ //! # #[cfg(feature = "std")] //! # fn build() -> Result<(), Bolt12ParseError> { //! let secp_ctx = Secp256k1::new(); -//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); +//! let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); //! let pubkey = PublicKey::from(keys); //! //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60); -//! let offer = OfferBuilder::new("coffee, large".to_string(), pubkey) +//! let offer = OfferBuilder::new(pubkey) +//! .description("coffee, large".to_string()) //! .amount_msats(20_000) //! .supported_quantity(Quantity::Unbounded) //! .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap()) @@ -77,8 +78,8 @@ //! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder use bitcoin::blockdata::constants::ChainHash; -use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; +use bitcoin::network::Network; +use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, self}; use core::hash::{Hash, Hasher}; use core::num::NonZeroU64; use core::ops::Deref; @@ -162,10 +163,9 @@ pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> { /// /// See [module-level documentation] for usage. /// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -/// /// [module-level documentation]: self #[cfg(c_bindings)] +#[derive(Clone)] pub struct OfferWithExplicitMetadataBuilder<'a> { offer: OfferContents, metadata_strategy: core::marker::PhantomData, @@ -176,10 +176,9 @@ pub struct OfferWithExplicitMetadataBuilder<'a> { /// /// See [module-level documentation] for usage. /// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -/// /// [module-level documentation]: self #[cfg(c_bindings)] +#[derive(Clone)] pub struct OfferWithDerivedMetadataBuilder<'a> { offer: OfferContents, metadata_strategy: core::marker::PhantomData, @@ -208,9 +207,8 @@ impl MetadataStrategy for DerivedMetadata {} macro_rules! offer_explicit_metadata_builder_methods { ( $self: ident, $self_type: ty, $return_type: ty, $return_value: expr ) => { - /// Creates a new builder for an offer setting the [`Offer::description`] and using the - /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered - /// while the offer is valid. + /// Creates a new builder for an offer using the [`Offer::signing_pubkey`] for signing invoices. + /// The associated secret key must be remembered while the offer is valid. /// /// Use a different pubkey per offer to avoid correlating offers. /// @@ -221,10 +219,10 @@ macro_rules! offer_explicit_metadata_builder_methods { ( /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder - pub fn new(description: String, signing_pubkey: PublicKey) -> Self { + pub fn new(signing_pubkey: PublicKey) -> Self { Self { offer: OfferContents { - chains: None, metadata: None, amount: None, description, + chains: None, metadata: None, amount: None, description: None, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, supported_quantity: Quantity::One, signing_pubkey: Some(signing_pubkey), }, @@ -255,7 +253,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { /// [`InvoiceRequest::verify`]: crate::offers::invoice_request::InvoiceRequest::verify /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_signing_pubkey( - description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, + node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'a Secp256k1<$secp_context> ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); @@ -263,7 +261,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { let metadata = Metadata::DerivedSigningPubkey(derivation_material); Self { offer: OfferContents { - chains: None, metadata: Some(metadata), amount: None, description, + chains: None, metadata: Some(metadata), amount: None, description: None, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, supported_quantity: Quantity::One, signing_pubkey: Some(node_id), }, @@ -325,6 +323,14 @@ macro_rules! offer_builder_methods { ( $return_value } + /// Sets the [`Offer::description`]. + /// + /// Successive calls to this method will override the previous setting. + pub fn description($($self_mut)* $self: $self_type, description: String) -> $return_type { + $self.offer.description = Some(description); + $return_value + } + /// Sets the [`Offer::issuer`]. /// /// Successive calls to this method will override the previous setting. @@ -364,6 +370,10 @@ macro_rules! offer_builder_methods { ( None => {}, } + if $self.offer.amount.is_some() && $self.offer.description.is_none() { + $self.offer.description = Some(String::new()); + } + if let Some(chains) = &$self.offer.chains { if chains.len() == 1 && chains[0] == $self.offer.implied_chain() { $self.offer.chains = None; @@ -542,7 +552,7 @@ pub(super) struct OfferContents { chains: Option>, metadata: Option, amount: Option, - description: String, + description: Option, features: OfferFeatures, absolute_expiry: Option, issuer: Option, @@ -570,13 +580,13 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => { } /// The minimum amount required for a successful payment of a single item. - pub fn amount(&$self) -> Option<&$crate::offers::offer::Amount> { + pub fn amount(&$self) -> Option<$crate::offers::offer::Amount> { $contents.amount() } /// A complete description of the purpose of the payment. Intended to be displayed to the user /// but with the caveat that it has not been verified in any way. - pub fn description(&$self) -> $crate::util::string::PrintableString { + pub fn description(&$self) -> Option<$crate::util::string::PrintableString> { $contents.description() } @@ -796,12 +806,12 @@ impl OfferContents { self.metadata.as_ref().and_then(|metadata| metadata.as_bytes()) } - pub fn amount(&self) -> Option<&Amount> { - self.amount.as_ref() + pub fn amount(&self) -> Option { + self.amount } - pub fn description(&self) -> PrintableString { - PrintableString(&self.description) + pub fn description(&self) -> Option { + self.description.as_ref().map(|description| PrintableString(description)) } pub fn features(&self) -> &OfferFeatures { @@ -899,7 +909,7 @@ impl OfferContents { /// Verifies that the offer metadata was produced from the offer in the TLV stream. pub(super) fn verify( &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> Result<(OfferId, Option), ()> { + ) -> Result<(OfferId, Option), ()> { match self.metadata() { Some(metadata) => { let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| { @@ -945,7 +955,7 @@ impl OfferContents { metadata: self.metadata(), currency, amount, - description: Some(&self.description), + description: self.description.as_ref(), features, absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()), paths: self.paths.as_ref(), @@ -956,6 +966,13 @@ impl OfferContents { } } +impl Readable for Offer { + fn read(reader: &mut R) -> Result { + let bytes: WithoutLength> = Readable::read(reader)?; + Self::try_from(bytes.0).map_err(|_| DecodeError::InvalidValue) + } +} + impl Writeable for Offer { fn write(&self, writer: &mut W) -> Result<(), io::Error> { WithoutLength(&self.bytes).write(writer) @@ -970,7 +987,7 @@ impl Writeable for OfferContents { /// The minimum amount required for an item in an [`Offer`], denominated in either bitcoin or /// another currency. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Amount { /// An amount of bitcoin. Bitcoin { @@ -1083,10 +1100,9 @@ impl TryFrom for OfferContents { (Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }), }; - let description = match description { - None => return Err(Bolt12SemanticError::MissingDescription), - Some(description) => description, - }; + if amount.is_some() && description.is_none() { + return Err(Bolt12SemanticError::MissingDescription); + } let features = features.unwrap_or_else(OfferFeatures::empty); @@ -1131,7 +1147,7 @@ mod tests { }; use bitcoin::blockdata::constants::ChainHash; - use bitcoin::network::constants::Network; + use bitcoin::network::Network; use bitcoin::secp256k1::Secp256k1; use core::num::NonZeroU64; use core::time::Duration; @@ -1147,7 +1163,7 @@ mod tests { #[test] fn builds_offer_with_defaults() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap(); + let offer = OfferBuilder::new(pubkey(42)).build().unwrap(); let mut buffer = Vec::new(); offer.write(&mut buffer).unwrap(); @@ -1157,7 +1173,7 @@ mod tests { assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin))); assert_eq!(offer.metadata(), None); assert_eq!(offer.amount(), None); - assert_eq!(offer.description(), PrintableString("foo")); + assert_eq!(offer.description(), None); assert_eq!(offer.offer_features(), &OfferFeatures::empty()); assert_eq!(offer.absolute_expiry(), None); #[cfg(feature = "std")] @@ -1165,6 +1181,7 @@ mod tests { assert_eq!(offer.paths(), &[]); assert_eq!(offer.issuer(), None); assert_eq!(offer.supported_quantity(), Quantity::One); + assert!(!offer.expects_quantity()); assert_eq!(offer.signing_pubkey(), Some(pubkey(42))); assert_eq!( @@ -1174,7 +1191,7 @@ mod tests { metadata: None, currency: None, amount: None, - description: Some(&String::from("foo")), + description: None, features: None, absolute_expiry: None, paths: None, @@ -1194,7 +1211,7 @@ mod tests { let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); let testnet = ChainHash::using_genesis_block(Network::Testnet); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Bitcoin) .build() .unwrap(); @@ -1202,7 +1219,7 @@ mod tests { assert_eq!(offer.chains(), vec![mainnet]); assert_eq!(offer.as_tlv_stream().chains, None); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Testnet) .build() .unwrap(); @@ -1210,7 +1227,7 @@ mod tests { assert_eq!(offer.chains(), vec![testnet]); assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet])); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Testnet) .chain(Network::Testnet) .build() @@ -1219,7 +1236,7 @@ mod tests { assert_eq!(offer.chains(), vec![testnet]); assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet])); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Bitcoin) .chain(Network::Testnet) .build() @@ -1232,14 +1249,14 @@ mod tests { #[test] fn builds_offer_with_metadata() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .metadata(vec![42; 32]).unwrap() .build() .unwrap(); assert_eq!(offer.metadata(), Some(&vec![42; 32])); assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32])); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .metadata(vec![42; 32]).unwrap() .metadata(vec![43; 32]).unwrap() .build() @@ -1250,7 +1267,6 @@ mod tests { #[test] fn builds_offer_with_metadata_derived() { - let desc = "foo".to_string(); let node_id = recipient_pubkey(); let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; @@ -1259,7 +1275,7 @@ mod tests { #[cfg(c_bindings)] use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder - ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + ::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) .build().unwrap(); assert_eq!(offer.signing_pubkey(), Some(node_id)); @@ -1302,7 +1318,6 @@ mod tests { #[test] fn builds_offer_with_derived_signing_pubkey() { - let desc = "foo".to_string(); let node_id = recipient_pubkey(); let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; @@ -1320,7 +1335,7 @@ mod tests { #[cfg(c_bindings)] use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder - ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + ::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) .path(blinded_path) .build().unwrap(); @@ -1367,20 +1382,20 @@ mod tests { let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 }; let currency_amount = Amount::Currency { iso4217_code: *b"USD", amount: 10 }; - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .amount_msats(1000) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); - assert_eq!(offer.amount(), Some(&bitcoin_amount)); + assert_eq!(offer.amount(), Some(bitcoin_amount)); assert_eq!(tlv_stream.amount, Some(1000)); assert_eq!(tlv_stream.currency, None); #[cfg(not(c_bindings))] - let builder = OfferBuilder::new("foo".into(), pubkey(42)) + let builder = OfferBuilder::new(pubkey(42)) .amount(currency_amount.clone()); #[cfg(c_bindings)] - let mut builder = OfferBuilder::new("foo".into(), pubkey(42)); + let mut builder = OfferBuilder::new(pubkey(42)); #[cfg(c_bindings)] builder.amount(currency_amount.clone()); let tlv_stream = builder.offer.as_tlv_stream(); @@ -1392,7 +1407,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency), } - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .amount(currency_amount.clone()) .amount(bitcoin_amount.clone()) .build() @@ -1402,22 +1417,47 @@ mod tests { assert_eq!(tlv_stream.currency, None); let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 }; - match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() { + match OfferBuilder::new(pubkey(42)).amount(invalid_amount).build() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } } + #[test] + fn builds_offer_with_description() { + let offer = OfferBuilder::new(pubkey(42)) + .description("foo".into()) + .build() + .unwrap(); + assert_eq!(offer.description(), Some(PrintableString("foo"))); + assert_eq!(offer.as_tlv_stream().description, Some(&String::from("foo"))); + + let offer = OfferBuilder::new(pubkey(42)) + .description("foo".into()) + .description("bar".into()) + .build() + .unwrap(); + assert_eq!(offer.description(), Some(PrintableString("bar"))); + assert_eq!(offer.as_tlv_stream().description, Some(&String::from("bar"))); + + let offer = OfferBuilder::new(pubkey(42)) + .amount_msats(1000) + .build() + .unwrap(); + assert_eq!(offer.description(), Some(PrintableString(""))); + assert_eq!(offer.as_tlv_stream().description, Some(&String::from(""))); + } + #[test] fn builds_offer_with_features() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .features_unchecked(OfferFeatures::unknown()) .build() .unwrap(); assert_eq!(offer.offer_features(), &OfferFeatures::unknown()); assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown())); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .features_unchecked(OfferFeatures::unknown()) .features_unchecked(OfferFeatures::empty()) .build() @@ -1432,7 +1472,7 @@ mod tests { let past_expiry = Duration::from_secs(0); let now = future_expiry - Duration::from_secs(1_000); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .absolute_expiry(future_expiry) .build() .unwrap(); @@ -1442,7 +1482,7 @@ mod tests { assert_eq!(offer.absolute_expiry(), Some(future_expiry)); assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs())); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .absolute_expiry(future_expiry) .absolute_expiry(past_expiry) .build() @@ -1475,7 +1515,7 @@ mod tests { }, ]; - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .path(paths[0].clone()) .path(paths[1].clone()) .build() @@ -1490,20 +1530,20 @@ mod tests { #[test] fn builds_offer_with_issuer() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) - .issuer("bar".into()) + let offer = OfferBuilder::new(pubkey(42)) + .issuer("foo".into()) .build() .unwrap(); - assert_eq!(offer.issuer(), Some(PrintableString("bar"))); - assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar"))); + assert_eq!(offer.issuer(), Some(PrintableString("foo"))); + assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("foo"))); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) + .issuer("foo".into()) .issuer("bar".into()) - .issuer("baz".into()) .build() .unwrap(); - assert_eq!(offer.issuer(), Some(PrintableString("baz"))); - assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("baz"))); + assert_eq!(offer.issuer(), Some(PrintableString("bar"))); + assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar"))); } #[test] @@ -1511,51 +1551,56 @@ mod tests { let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::One) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); + assert!(!offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::One); assert_eq!(tlv_stream.quantity_max, None); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Unbounded) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); + assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Unbounded); assert_eq!(tlv_stream.quantity_max, Some(0)); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(ten)) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); + assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten)); assert_eq!(tlv_stream.quantity_max, Some(10)); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(one)) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); + assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Bounded(one)); assert_eq!(tlv_stream.quantity_max, Some(1)); - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(ten)) .supported_quantity(Quantity::One) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); + assert!(!offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::One); assert_eq!(tlv_stream.quantity_max, None); } #[test] fn fails_requesting_invoice_with_unknown_required_features() { - match OfferBuilder::new("foo".into(), pubkey(42)) + match OfferBuilder::new(pubkey(42)) .features_unchecked(OfferFeatures::unknown()) .build().unwrap() .request_invoice(vec![1; 32], pubkey(43)) @@ -1567,7 +1612,7 @@ mod tests { #[test] fn parses_offer_with_chains() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Bitcoin) .chain(Network::Testnet) .build() @@ -1579,7 +1624,7 @@ mod tests { #[test] fn parses_offer_with_amount() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .amount(Amount::Bitcoin { amount_msats: 1000 }) .build() .unwrap(); @@ -1625,7 +1670,15 @@ mod tests { #[test] fn parses_offer_with_description() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap(); + let offer = OfferBuilder::new(pubkey(42)).build().unwrap(); + if let Err(e) = offer.to_string().parse::() { + panic!("error parsing offer: {:?}", e); + } + + let offer = OfferBuilder::new(pubkey(42)) + .description("foo".to_string()) + .amount_msats(1000) + .build().unwrap(); if let Err(e) = offer.to_string().parse::() { panic!("error parsing offer: {:?}", e); } @@ -1646,7 +1699,7 @@ mod tests { #[test] fn parses_offer_with_paths() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .path(BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), @@ -1669,7 +1722,7 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .path(BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), @@ -1685,7 +1738,7 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let mut builder = OfferBuilder::new("foo".into(), pubkey(42)); + let mut builder = OfferBuilder::new(pubkey(42)); builder.offer.paths = Some(vec![]); let offer = builder.build().unwrap(); @@ -1699,7 +1752,7 @@ mod tests { #[test] fn parses_offer_with_quantity() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::One) .build() .unwrap(); @@ -1707,7 +1760,7 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Unbounded) .build() .unwrap(); @@ -1715,7 +1768,7 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(NonZeroU64::new(10).unwrap())) .build() .unwrap(); @@ -1723,7 +1776,7 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let offer = OfferBuilder::new("foo".into(), pubkey(42)) + let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap())) .build() .unwrap(); @@ -1734,7 +1787,7 @@ mod tests { #[test] fn parses_offer_with_node_id() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap(); + let offer = OfferBuilder::new(pubkey(42)).build().unwrap(); if let Err(e) = offer.to_string().parse::() { panic!("error parsing offer: {:?}", e); } @@ -1755,7 +1808,7 @@ mod tests { #[test] fn fails_parsing_offer_with_extra_tlv_records() { - let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap(); + let offer = OfferBuilder::new(pubkey(42)).build().unwrap(); let mut encoded_offer = Vec::new(); offer.write(&mut encoded_offer).unwrap(); @@ -1820,6 +1873,9 @@ mod bolt12_tests { // with blinded path via Bob (0x424242...), blinding 020202... "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs", + // ... and with sciddir introduction node + "lno1pgx9getnwss8vetrw3hhyucs3yqqqqqqqqqqqqp2qgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqyqqqqqqqqqqqqqqqqqqqqqqqqqqqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqgzyg3zyg3zyg3z93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj", + // ... and with second blinded path via Carol (0x434343...), blinding 020202... "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese", @@ -1850,7 +1906,7 @@ mod bolt12_tests { // Malformed: empty assert_eq!( "lno1".parse::(), - Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)), + Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)), ); // Malformed: truncated at type @@ -1975,7 +2031,8 @@ mod bolt12_tests { // Missing offer_description assert_eq!( - "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese".parse::(), + // TODO: Match the spec once it is updated. + "lno1pqpq86qkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::(), Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)), );