Stateless verification of Invoice for Offer
[rust-lightning] / lightning / src / offers / refund.rs
index cc0388c0241b5f77a125ab0dabe4da8427504e8f..4628c334fbefa54d27386a127732c624e2976643 100644 (file)
@@ -86,6 +86,7 @@ use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvS
 use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
+use crate::offers::signer::Metadata;
 use crate::onion_message::BlindedPath;
 use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
@@ -117,6 +118,7 @@ impl RefundBuilder {
                        return Err(SemanticError::InvalidAmount);
                }
 
+               let metadata = Metadata::Bytes(metadata);
                let refund = RefundContents {
                        payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None,
                        paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
@@ -216,7 +218,8 @@ impl RefundBuilder {
 ///
 /// [`Invoice`]: crate::offers::invoice::Invoice
 /// [`Offer`]: crate::offers::offer::Offer
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 pub struct Refund {
        pub(super) bytes: Vec<u8>,
        pub(super) contents: RefundContents,
@@ -225,7 +228,8 @@ pub struct Refund {
 /// The contents of a [`Refund`], which may be shared with an [`Invoice`].
 ///
 /// [`Invoice`]: crate::offers::invoice::Invoice
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 pub(super) struct RefundContents {
        payer: PayerContents,
        // offer fields
@@ -279,7 +283,7 @@ impl Refund {
        ///
        /// [`payer_id`]: Self::payer_id
        pub fn metadata(&self) -> &[u8] {
-               &self.contents.payer.0
+               &self.contents.payer.0.as_bytes().unwrap()[..]
        }
 
        /// A chain that the refund is valid for.
@@ -317,13 +321,12 @@ impl Refund {
                self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
        }
 
-       /// Creates an [`Invoice`] for the refund with the given required fields and using the
+       /// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the
        /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time.
        ///
        /// See [`Refund::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(
@@ -337,7 +340,7 @@ impl Refund {
                self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at)
        }
 
-       /// Creates an [`Invoice`] for the refund with the given required fields.
+       /// Creates an [`InvoiceBuilder`] for the refund 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
@@ -356,7 +359,6 @@ impl Refund {
        ///
        /// 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,
@@ -403,7 +405,7 @@ impl RefundContents {
 
        pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
                let payer = PayerTlvStreamRef {
-                       metadata: Some(&self.payer.0),
+                       metadata: self.payer.0.as_bytes(),
                };
 
                let offer = OfferTlvStreamRef {
@@ -507,7 +509,7 @@ impl TryFrom<RefundTlvStream> for RefundContents {
 
                let payer = match payer_metadata {
                        None => return Err(SemanticError::MissingPayerMetadata),
-                       Some(metadata) => PayerContents(metadata),
+                       Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
                };
 
                if metadata.is_some() {
@@ -575,7 +577,7 @@ mod tests {
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
-       use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
+       use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
        use core::convert::TryFrom;
        use core::time::Duration;
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
@@ -584,24 +586,11 @@ mod tests {
        use crate::offers::offer::OfferTlvStreamRef;
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
+       use crate::offers::test_utils::*;
        use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
-       fn payer_pubkey() -> PublicKey {
-               let secp_ctx = Secp256k1::new();
-               KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()).public_key()
-       }
-
-       fn pubkey(byte: u8) -> PublicKey {
-               let secp_ctx = Secp256k1::new();
-               PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
-       }
-
-       fn privkey(byte: u8) -> SecretKey {
-               SecretKey::from_slice(&[byte; 32]).unwrap()
-       }
-
        trait ToBytes {
                fn to_bytes(&self) -> Vec<u8>;
        }
@@ -822,6 +811,18 @@ mod tests {
                assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
        }
 
+       #[test]
+       fn fails_responding_with_unknown_required_features() {
+               match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .features_unchecked(InvoiceRequestFeatures::unknown())
+                       .build().unwrap()
+                       .respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
+               {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+               }
+       }
+
        #[test]
        fn parses_refund_with_metadata() {
                let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()