Merge pull request #2319 from valentinewallace/2023-05-forward-less-than-onion
[rust-lightning] / lightning / src / offers / invoice.rs
index 0cc6c407c1dc0e39a3082adc11251e8881ac11f8..fb1f78fd64445c406d582830eebd97d4c107c16b 100644 (file)
@@ -29,9 +29,9 @@
 //!
 //! # use lightning::ln::PaymentHash;
 //! # use lightning::offers::invoice::BlindedPayInfo;
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! #
-//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
 //! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
 //! #
 //! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
@@ -104,6 +104,7 @@ use bitcoin::util::schnorr::TweakedPublicKey;
 use core::convert::{Infallible, TryFrom};
 use core::time::Duration;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
 use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
 use crate::ln::inbound_payment::ExpandedKey;
@@ -115,8 +116,8 @@ use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
 use crate::offers::signer;
-use crate::onion_message::BlindedPath;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
+use crate::util::string::PrintableString;
 
 use crate::prelude::*;
 
@@ -133,6 +134,8 @@ pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "
 ///
 /// See [module-level documentation] for usage.
 ///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 /// [`Refund`]: crate::offers::refund::Refund
 /// [module-level documentation]: self
@@ -144,12 +147,18 @@ pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> {
 }
 
 /// Indicates how [`Invoice::signing_pubkey`] was set.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub trait SigningPubkeyStrategy {}
 
 /// [`Invoice::signing_pubkey`] was explicitly set.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub struct ExplicitSigningPubkey {}
 
 /// [`Invoice::signing_pubkey`] was derived.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub struct DerivedSigningPubkey {}
 
 impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
@@ -157,33 +166,31 @@ impl SigningPubkeyStrategy for DerivedSigningPubkey {}
 
 impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        pub(super) fn for_offer(
-               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, SemanticError> {
                let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
-                               fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
-                               signing_pubkey: invoice_request.contents.inner.offer.signing_pubkey(),
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
                Self::new(&invoice_request.bytes, contents, None)
        }
 
        pub(super) fn for_refund(
-               refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, signing_pubkey: PublicKey
        ) -> Result<Self, SemanticError> {
+               let amount_msats = refund.amount_msats();
                let contents = InvoiceContents::ForRefund {
                        refund: refund.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash,
-                               amount_msats: refund.amount_msats(), fallbacks: None,
-                               features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
                Self::new(&refund.bytes, contents, None)
@@ -192,33 +199,32 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
 
 impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
        pub(super) fn for_offer_using_keys(
-               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
        ) -> Result<Self, SemanticError> {
                let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
-                               fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
-                               signing_pubkey: invoice_request.contents.inner.offer.signing_pubkey(),
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
                Self::new(&invoice_request.bytes, contents, Some(keys))
        }
 
        pub(super) fn for_refund_using_keys(
-               refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, keys: KeyPair,
        ) -> Result<Self, SemanticError> {
+               let amount_msats = refund.amount_msats();
+               let signing_pubkey = keys.public_key();
                let contents = InvoiceContents::ForRefund {
                        refund: refund.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash,
-                               amount_msats: refund.amount_msats(), fallbacks: None,
-                               features: Bolt12InvoiceFeatures::empty(), signing_pubkey: keys.public_key(),
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
                Self::new(&refund.bytes, contents, Some(keys))
@@ -240,6 +246,16 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
                }
        }
 
+       fn fields(
+               payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
+               payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey
+       ) -> InvoiceFields {
+               InvoiceFields {
+                       payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
+                       fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
+               }
+       }
+
        fn new(
                invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
        ) -> Result<Self, SemanticError> {
@@ -361,6 +377,8 @@ impl<'a> UnsignedInvoice<'a> {
        }
 
        /// Signs the invoice using the given function.
+       ///
+       /// This is not exported to bindings users as functions aren't currently mapped.
        pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
        where
                F: FnOnce(&Message) -> Result<Signature, E>
@@ -397,6 +415,8 @@ impl<'a> UnsignedInvoice<'a> {
 /// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
 /// directly after scanning a refund. It includes all the information needed to pay a recipient.
 ///
+/// This is not exported to bindings users as its name conflicts with the BOLT 11 Invoice type.
+///
 /// [`Offer`]: crate::offers::offer::Offer
 /// [`Refund`]: crate::offers::refund::Refund
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
@@ -434,7 +454,7 @@ enum InvoiceContents {
 /// Invoice-specific fields for an `invoice` message.
 #[derive(Clone, Debug, PartialEq)]
 struct InvoiceFields {
-       payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+       payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
        created_at: Duration,
        relative_expiry: Option<Duration>,
        payment_hash: PaymentHash,
@@ -445,12 +465,18 @@ struct InvoiceFields {
 }
 
 impl Invoice {
+       /// A complete description of the purpose of the originating offer or refund. Intended to be
+       /// displayed to the user but with the caveat that it has not been verified in any way.
+       pub fn description(&self) -> PrintableString {
+               self.contents.description()
+       }
+
        /// Paths to the recipient originating from publicly reachable nodes, including information
        /// needed for routing payments across them.
        ///
        /// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
        /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
-       pub fn payment_paths(&self) -> &[(BlindedPath, BlindedPayInfo)] {
+       pub fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
                &self.contents.fields().payment_paths[..]
        }
 
@@ -600,6 +626,15 @@ impl InvoiceContents {
                }
        }
 
+       fn description(&self) -> PrintableString {
+               match self {
+                       InvoiceContents::ForOffer { invoice_request, .. } => {
+                               invoice_request.inner.offer.description()
+                       },
+                       InvoiceContents::ForRefund { refund, .. } => refund.description(),
+               }
+       }
+
        fn fields(&self) -> &InvoiceFields {
                match self {
                        InvoiceContents::ForOffer { fields, .. } => fields,
@@ -668,8 +703,8 @@ impl InvoiceFields {
                };
 
                InvoiceTlvStreamRef {
-                       paths: Some(Iterable(self.payment_paths.iter().map(|(path, _)| path))),
-                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                       paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
+                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| payinfo))),
                        created_at: Some(self.created_at.as_secs()),
                        relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
                        payment_hash: Some(&self.payment_hash),
@@ -715,17 +750,17 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
 });
 
 type BlindedPathIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPath,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
 >;
 
 type BlindedPayInfoIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPayInfo,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPayInfo,
 >;
 
 /// Information needed to route a payment across a [`BlindedPath`].
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
 pub struct BlindedPayInfo {
        /// Base fee charged (in millisatoshi) for the entire blinded path.
        pub fee_base_msat: u32,
@@ -843,15 +878,15 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        },
                ) = tlv_stream;
 
-               let payment_paths = match (paths, blindedpay) {
-                       (None, _) => return Err(SemanticError::MissingPaths),
-                       (_, None) => return Err(SemanticError::InvalidPayInfo),
-                       (Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
-                       (Some(paths), Some(blindedpay)) if paths.len() != blindedpay.len() => {
+               let payment_paths = match (blindedpay, paths) {
+                       (_, None) => return Err(SemanticError::MissingPaths),
+                       (None, _) => return Err(SemanticError::InvalidPayInfo),
+                       (_, Some(paths)) if paths.is_empty() => return Err(SemanticError::MissingPaths),
+                       (Some(blindedpay), Some(paths)) if paths.len() != blindedpay.len() => {
                                return Err(SemanticError::InvalidPayInfo);
                        },
-                       (Some(paths), Some(blindedpay)) => {
-                               paths.into_iter().zip(blindedpay.into_iter()).collect::<Vec<_>>()
+                       (Some(blindedpay), Some(paths)) => {
+                               blindedpay.into_iter().zip(paths.into_iter()).collect::<Vec<_>>()
                        },
                };
 
@@ -919,7 +954,8 @@ mod tests {
        use bitcoin::util::schnorr::TweakedPublicKey;
        use core::convert::TryFrom;
        use core::time::Duration;
-       use crate::chain::keysinterface::KeyMaterial;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::sign::KeyMaterial;
        use crate::ln::features::Bolt12InvoiceFeatures;
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::DecodeError;
@@ -930,8 +966,8 @@ mod tests {
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
        use crate::offers::test_utils::*;
-       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Iterable, Writeable};
+       use crate::util::string::PrintableString;
 
        trait ToBytes {
                fn to_bytes(&self) -> Vec<u8>;
@@ -968,6 +1004,7 @@ mod tests {
                invoice.write(&mut buffer).unwrap();
 
                assert_eq!(invoice.bytes, buffer.as_slice());
+               assert_eq!(invoice.description(), PrintableString("foo"));
                assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
                assert_eq!(invoice.created_at(), now);
                assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1015,8 +1052,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -1050,6 +1087,7 @@ mod tests {
                invoice.write(&mut buffer).unwrap();
 
                assert_eq!(invoice.bytes, buffer.as_slice());
+               assert_eq!(invoice.description(), PrintableString("foo"));
                assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
                assert_eq!(invoice.created_at(), now);
                assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1092,8 +1130,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -1478,7 +1516,7 @@ mod tests {
 
                let empty_payment_paths = vec![];
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
+               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(_, path)| path)));
 
                match Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
@@ -1488,7 +1526,7 @@ mod tests {
                let mut payment_paths = payment_paths();
                payment_paths.pop();
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
+               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo)));
 
                match Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),