X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=a1a0520c62259409b4cd516ed9607eb17bad3296;hb=ca9ca75f082dec8dfc70f3e263a7c3789e17a054;hp=dc7d590b7ebd91eb3c23f20ab9707465ad616fb7;hpb=13ba7cc5233bf7ed692adebefee0b352d4800ab3;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index dc7d590b..a1a0520c 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -9,15 +9,17 @@ //! Data structures and encoding for `invoice_request` messages. //! -//! An [`InvoiceRequest`] can be either built from a parsed [`Offer`] as an "offer to be paid" or -//! built directly as an "offer for money" (e.g., refund, ATM withdrawal). In the former case, it is +//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is //! typically constructed by a customer and sent to the merchant who had published the corresponding -//! offer. In the latter case, an offer doesn't exist as a precursor to the request. Rather the -//! merchant would typically construct the invoice request and present it to the customer. +//! offer. The recipient of the request responds with an [`Invoice`]. //! -//! The recipient of the request responds with an `Invoice`. +//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a +//! precursor, see [`Refund`]. //! -//! ```ignore +//! [`Invoice`]: crate::offers::invoice::Invoice +//! [`Refund`]: crate::offers::refund::Refund +//! +//! ``` //! extern crate bitcoin; //! extern crate lightning; //! @@ -34,7 +36,6 @@ //! let pubkey = PublicKey::from(keys); //! let mut buffer = Vec::new(); //! -//! // "offer to be paid" flow //! "lno1qcp4256ypq" //! .parse::()? //! .request_invoice(vec![42; 64], pubkey)? @@ -57,12 +58,15 @@ use bitcoin::secp256k1::{Message, PublicKey}; use bitcoin::secp256k1::schnorr::Signature; use core::convert::TryFrom; use crate::io; +use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::msgs::DecodeError; +use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; +use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -171,6 +175,35 @@ impl<'a> InvoiceRequestBuilder<'a> { } } +#[cfg(test)] +impl<'a> InvoiceRequestBuilder<'a> { + fn chain_unchecked(mut self, network: Network) -> Self { + let chain = ChainHash::using_genesis_block(network); + self.invoice_request.chain = Some(chain); + self + } + + fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self { + self.invoice_request.amount_msats = Some(amount_msats); + self + } + + fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { + self.invoice_request.features = features; + self + } + + fn quantity_unchecked(mut self, quantity: u64) -> Self { + self.invoice_request.quantity = Some(quantity); + self + } + + pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> { + let InvoiceRequestBuilder { offer, invoice_request } = self; + UnsignedInvoiceRequest { offer, invoice_request } + } +} + /// A semantically valid [`InvoiceRequest`] that hasn't been signed. pub struct UnsignedInvoiceRequest<'a> { offer: &'a Offer, @@ -194,11 +227,11 @@ impl<'a> UnsignedInvoiceRequest<'a> { unsigned_tlv_stream.write(&mut bytes).unwrap(); let pubkey = self.invoice_request.payer_id; - let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?); + let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?; // Append the signature TLV record to the bytes. let signature_tlv_stream = SignatureTlvStreamRef { - signature: signature.as_ref(), + signature: Some(&signature), }; signature_tlv_stream.write(&mut bytes).unwrap(); @@ -210,24 +243,27 @@ impl<'a> UnsignedInvoiceRequest<'a> { } } -/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`]. +/// An `InvoiceRequest` is a request for an [`Invoice`] formulated from an [`Offer`]. /// /// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request /// specifies these such that its recipient can send an invoice for payment. /// +/// [`Invoice`]: crate::offers::invoice::Invoice /// [`Offer`]: crate::offers::offer::Offer -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct InvoiceRequest { - bytes: Vec, - contents: InvoiceRequestContents, - signature: Option, + pub(super) bytes: Vec, + pub(super) contents: InvoiceRequestContents, + signature: Signature, } -/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`. -#[derive(Clone, Debug)] +/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`]. +/// +/// [`Invoice`]: crate::offers::invoice::Invoice +#[derive(Clone, Debug, PartialEq)] pub(super) struct InvoiceRequestContents { payer: PayerContents, - offer: OfferContents, + pub(super) offer: OfferContents, chain: Option, amount_msats: Option, features: InvoiceRequestFeatures, @@ -258,7 +294,7 @@ impl InvoiceRequest { self.contents.amount_msats } - /// Features for paying the invoice. + /// Features pertaining to requesting an invoice. pub fn features(&self) -> &InvoiceRequestFeatures { &self.contents.features } @@ -282,13 +318,71 @@ impl InvoiceRequest { /// Signature of the invoice request using [`payer_id`]. /// /// [`payer_id`]: Self::payer_id - pub fn signature(&self) -> Option { + pub fn signature(&self) -> Signature { self.signature } + + /// Creates an [`Invoice`] for the request with the given required fields and using the + /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. + /// + /// See [`InvoiceRequest::respond_with_no_std`] for further details where the aforementioned + /// creation time is used for the `created_at` parameter. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + /// [`Duration`]: core::time::Duration + #[cfg(feature = "std")] + pub fn respond_with( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash + ) -> Result { + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); + + self.respond_with_no_std(payment_paths, payment_hash, created_at) + } + + /// Creates an [`Invoice`] for the request with the given required fields. + /// + /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after + /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where + /// [`std::time::SystemTime`] is not available. + /// + /// The caller is expected to remember the preimage of `payment_hash` in order to claim a payment + /// for the invoice. + /// + /// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It + /// must contain one or more elements ordered from most-preferred to least-preferred, if there's + /// a preference. Note, however, that any privacy is lost if a public node id was used for + /// [`Offer::signing_pubkey`]. + /// + /// Errors if the request contains unknown required features. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at + pub fn respond_with_no_std( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + created_at: core::time::Duration + ) -> Result { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) + } + + #[cfg(test)] + fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { + let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = + self.contents.as_tlv_stream(); + let signature_tlv_stream = SignatureTlvStreamRef { + signature: Some(&self.signature), + }; + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) + } } impl InvoiceRequestContents { - fn chain(&self) -> ChainHash { + pub(super) fn chain(&self) -> ChainHash { self.chain.unwrap_or_else(|| self.offer.implied_chain()) } @@ -332,7 +426,7 @@ impl Writeable for InvoiceRequestContents { tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, { (80, chain: ChainHash), (82, amount: (u64, HighZeroBytesDroppedBigSize)), - (84, features: InvoiceRequestFeatures), + (84, features: (InvoiceRequestFeatures, WithoutLength)), (86, quantity: (u64, HighZeroBytesDroppedBigSize)), (88, payer_id: PublicKey), (89, payer_note: (String, WithoutLength)), @@ -341,6 +435,14 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, { type FullInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream); +#[cfg(test)] +type FullInvoiceRequestTlvStreamRef<'a> = ( + PayerTlvStreamRef<'a>, + OfferTlvStreamRef<'a>, + InvoiceRequestTlvStreamRef<'a>, + SignatureTlvStreamRef<'a>, +); + impl SeekReadable for FullInvoiceRequestTlvStream { fn read(r: &mut R) -> Result { let payer = SeekReadable::read(r)?; @@ -374,9 +476,11 @@ impl TryFrom> for InvoiceRequest { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; - if let Some(signature) = &signature { - merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; - } + let signature = match signature { + None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)), + Some(signature) => signature, + }; + merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) } @@ -424,14 +528,856 @@ impl TryFrom for InvoiceRequestContents { #[cfg(test)] mod tests { - use super::InvoiceRequest; + use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG}; - use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey}; + use bitcoin::blockdata::constants::ChainHash; + use bitcoin::network::constants::Network; + use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, self}; + use bitcoin::secp256k1::schnorr::Signature; use core::convert::{Infallible, TryFrom}; - use crate::ln::msgs::DecodeError; - use crate::offers::offer::OfferBuilder; - use crate::offers::parse::ParseError; + use core::num::NonZeroU64; + #[cfg(feature = "std")] + use core::time::Duration; + use crate::ln::features::InvoiceRequestFeatures; + use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; + use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self}; + use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::parse::{ParseError, SemanticError}; + use crate::offers::payer::PayerTlvStreamRef; use crate::util::ser::{BigSize, Writeable}; + use crate::util::string::PrintableString; + + fn payer_keys() -> KeyPair { + let secp_ctx = Secp256k1::new(); + KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) + } + + fn payer_sign(digest: &Message) -> Result { + let secp_ctx = Secp256k1::new(); + let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)) + } + + fn payer_pubkey() -> PublicKey { + payer_keys().public_key() + } + + fn recipient_sign(digest: &Message) -> Result { + let secp_ctx = Secp256k1::new(); + let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()); + Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)) + } + + fn recipient_pubkey() -> PublicKey { + let secp_ctx = Secp256k1::new(); + KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()).public_key() + } + + #[test] + fn builds_invoice_request_with_defaults() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + assert_eq!(invoice_request.bytes, buffer.as_slice()); + assert_eq!(invoice_request.metadata(), &[1; 32]); + assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); + assert_eq!(invoice_request.amount_msats(), None); + assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty()); + assert_eq!(invoice_request.quantity(), None); + assert_eq!(invoice_request.payer_id(), payer_pubkey()); + assert_eq!(invoice_request.payer_note(), None); + assert!( + merkle::verify_signature( + &invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey() + ).is_ok() + ); + + assert_eq!( + invoice_request.as_tlv_stream(), + ( + PayerTlvStreamRef { metadata: Some(&vec![1; 32]) }, + OfferTlvStreamRef { + chains: None, + metadata: None, + currency: None, + amount: Some(1000), + description: Some(&String::from("foo")), + features: None, + absolute_expiry: None, + paths: None, + issuer: None, + quantity_max: None, + node_id: Some(&recipient_pubkey()), + }, + InvoiceRequestTlvStreamRef { + chain: None, + amount: None, + features: None, + quantity: None, + payer_id: Some(&payer_pubkey()), + payer_note: None, + }, + SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, + ), + ); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice request: {:?}", e); + } + } + + #[cfg(feature = "std")] + #[test] + fn builds_invoice_request_from_offer_with_expiration() { + let future_expiry = Duration::from_secs(u64::max_value()); + let past_expiry = Duration::from_secs(0); + + if let Err(e) = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .absolute_expiry(future_expiry) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + panic!("error building invoice_request: {:?}", e); + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .absolute_expiry(past_expiry) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::AlreadyExpired), + } + } + + #[test] + fn builds_invoice_request_with_chain() { + let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); + let testnet = ChainHash::using_genesis_block(Network::Testnet); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Bitcoin).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.chain(), mainnet); + assert_eq!(tlv_stream.chain, None); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .chain(Network::Testnet) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Testnet).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.chain(), testnet); + assert_eq!(tlv_stream.chain, Some(&testnet)); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .chain(Network::Bitcoin) + .chain(Network::Testnet) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Bitcoin).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.chain(), mainnet); + assert_eq!(tlv_stream.chain, None); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .chain(Network::Bitcoin) + .chain(Network::Testnet) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Bitcoin).unwrap() + .chain(Network::Testnet).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.chain(), testnet); + assert_eq!(tlv_stream.chain, Some(&testnet)); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .chain(Network::Testnet) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Bitcoin) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::UnsupportedChain), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .chain(Network::Testnet) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::UnsupportedChain), + } + } + + #[test] + fn builds_invoice_request_with_amount() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(1000).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(1000)); + assert_eq!(tlv_stream.amount, Some(1000)); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(1001).unwrap() + .amount_msats(1000).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(1000)); + assert_eq!(tlv_stream.amount, Some(1000)); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(1001).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(1001)); + assert_eq!(tlv_stream.amount, Some(1001)); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(999) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .quantity(2).unwrap() + .amount_msats(1000) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(MAX_VALUE_MSAT + 1) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidAmount), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(1000).unwrap() + .quantity(2).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::MissingAmount), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .quantity(u64::max_value()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidAmount), + } + } + + #[test] + fn builds_invoice_request_with_features() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .features_unchecked(InvoiceRequestFeatures::unknown()) + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::unknown()); + assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown())); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .features_unchecked(InvoiceRequestFeatures::unknown()) + .features_unchecked(InvoiceRequestFeatures::empty()) + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty()); + assert_eq!(tlv_stream.features, None); + } + + #[test] + fn builds_invoice_request_with_quantity() { + let one = NonZeroU64::new(1).unwrap(); + let ten = NonZeroU64::new(10).unwrap(); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::One) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.quantity(), None); + assert_eq!(tlv_stream.quantity, None); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::One) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(2_000).unwrap() + .quantity(2) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::UnexpectedQuantity), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(ten)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(10_000).unwrap() + .quantity(10).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(10_000)); + assert_eq!(tlv_stream.amount, Some(10_000)); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(ten)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(11_000).unwrap() + .quantity(11) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidQuantity), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(2_000).unwrap() + .quantity(2).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(2_000)); + assert_eq!(tlv_stream.amount, Some(2_000)); + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::MissingQuantity), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(one)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build() + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::MissingQuantity), + } + } + + #[test] + fn builds_invoice_request_with_payer_note() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .payer_note("bar".into()) + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.payer_note(), Some(PrintableString("bar"))); + assert_eq!(tlv_stream.payer_note, Some(&String::from("bar"))); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .payer_note("bar".into()) + .payer_note("baz".into()) + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.payer_note(), Some(PrintableString("baz"))); + assert_eq!(tlv_stream.payer_note, Some(&String::from("baz"))); + } + + #[test] + fn fails_signing_invoice_request() { + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(|_| Err(())) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SignError::Signing(())), + } + + match OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(recipient_sign) + { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::InvalidSignature)), + } + } + + #[test] + fn parses_invoice_request_with_metadata() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![42; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + } + + #[test] + fn parses_invoice_request_with_chain() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Bitcoin).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain_unchecked(Network::Testnet) + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)), + } + } + + #[test] + fn parses_invoice_request_with_amount() { + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(1000).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats_unchecked(999) + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 }) + .build_unchecked() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency)); + }, + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .quantity(u64::max_value()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)), + } + } + + #[test] + fn parses_invoice_request_with_quantity() { + let one = NonZeroU64::new(1).unwrap(); + let ten = NonZeroU64::new(10).unwrap(); + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::One) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::One) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(2_000).unwrap() + .quantity_unchecked(2) + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity)); + }, + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(ten)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(10_000).unwrap() + .quantity(10).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(ten)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(11_000).unwrap() + .quantity_unchecked(11) + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(2_000).unwrap() + .quantity(2).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)), + } + + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .supported_quantity(Quantity::Bounded(one)) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build_unchecked() + .sign(payer_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)), + } + } + + #[test] + fn fails_parsing_invoice_request_without_metadata() { + let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap(); + let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap(); + let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream(); + tlv_stream.0.metadata = None; + + let mut buffer = Vec::new(); + tlv_stream.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata)); + }, + } + } + + #[test] + fn fails_parsing_invoice_request_without_payer_id() { + let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap(); + let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap(); + let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream(); + tlv_stream.2.payer_id = None; + + let mut buffer = Vec::new(); + tlv_stream.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)), + } + } + + #[test] + fn fails_parsing_invoice_request_without_node_id() { + let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap(); + let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap(); + let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream(); + tlv_stream.1.node_id = None; + + let mut buffer = Vec::new(); + tlv_stream.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey)); + }, + } + } + + #[test] + fn fails_parsing_invoice_request_without_signature() { + let mut buffer = Vec::new(); + OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .invoice_request + .write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)), + } + } + + #[test] + fn fails_parsing_invoice_request_with_invalid_signature() { + let mut invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let last_signature_byte = invoice_request.bytes.last_mut().unwrap(); + *last_signature_byte = last_signature_byte.wrapping_add(1); + + let mut buffer = Vec::new(); + invoice_request.write(&mut buffer).unwrap(); + + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature)); + }, + } + } #[test] fn fails_parsing_invoice_request_with_extra_tlv_records() {