X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=5b15704ca5127f413a0a26f3958ada9899f6c3fc;hb=3554678e9c778c8893fc091a6566240a313998f4;hp=690bc8d0bdb72d03fc0914b628ed1842a0ef9701;hpb=1a437f41502e2f5c768ac30e3976fba7d8c99571;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 690bc8d0..5b15704c 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -11,11 +11,12 @@ //! //! 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. The recipient of the request responds with an `Invoice`. +//! offer. 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`]. //! +//! [`Invoice`]: crate::offers::invoice::Invoice //! [`Refund`]: crate::offers::refund::Refund //! //! ```ignore @@ -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; @@ -239,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)] pub struct InvoiceRequest { pub(super) bytes: Vec, - contents: InvoiceRequestContents, + pub(super) contents: InvoiceRequestContents, signature: Signature, } -/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`. +/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`]. +/// +/// [`Invoice`]: crate::offers::invoice::Invoice #[derive(Clone, Debug)] pub(super) struct InvoiceRequestContents { payer: PayerContents, - offer: OfferContents, + pub(super) offer: OfferContents, chain: Option, amount_msats: Option, features: InvoiceRequestFeatures, @@ -315,6 +322,43 @@ impl InvoiceRequest { self.signature } + /// Creates an [`Invoice`] for the request with the given required fields. + /// + /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after + /// calling this method in `std` builds. For `no-std` builds, a final [`Duration`] parameter + /// must be given, which is used to set [`Invoice::created_at`] since [`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. + /// + /// [`Duration`]: core::time::Duration + /// [`Invoice`]: crate::offers::invoice::Invoice + /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at + pub fn respond_with( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + #[cfg(any(test, not(feature = "std")))] + created_at: core::time::Duration + ) -> Result { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + #[cfg(all(not(test), feature = "std"))] + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); + + 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) = @@ -327,7 +371,7 @@ impl InvoiceRequest { } impl InvoiceRequestContents { - fn chain(&self) -> ChainHash { + pub(super) fn chain(&self) -> ChainHash { self.chain.unwrap_or_else(|| self.offer.implied_chain()) } @@ -371,7 +415,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)), @@ -803,11 +847,12 @@ mod tests { #[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()) + .supported_quantity(Quantity::One) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() @@ -818,7 +863,7 @@ mod tests { match OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) - .supported_quantity(Quantity::one()) + .supported_quantity(Quantity::One) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .amount_msats(2_000).unwrap() @@ -876,6 +921,17 @@ mod tests { 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] @@ -1060,11 +1116,12 @@ mod tests { #[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()) + .supported_quantity(Quantity::One) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() @@ -1079,7 +1136,7 @@ mod tests { let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) - .supported_quantity(Quantity::one()) + .supported_quantity(Quantity::One) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .amount_msats(2_000).unwrap() @@ -1164,6 +1221,22 @@ mod tests { 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]