InvoiceRequest metadata and payer id derivation
[rust-lightning] / lightning / src / offers / invoice.rs
index 49c03a443475cb0c0c4f6ed556f5a242cda7d40b..9d83ce899b153d836288facf83b82e7515b1dc90 100644 (file)
@@ -146,7 +146,7 @@ impl<'a> InvoiceBuilder<'a> {
        ) -> Result<Self, SemanticError> {
                let amount_msats = match invoice_request.amount_msats() {
                        Some(amount_msats) => amount_msats,
-                       None => match invoice_request.contents.offer.amount() {
+                       None => match invoice_request.contents.inner.offer.amount() {
                                Some(Amount::Bitcoin { amount_msats }) => {
                                        amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
                                                .ok_or(SemanticError::InvalidAmount)?
@@ -161,7 +161,7 @@ impl<'a> InvoiceBuilder<'a> {
                        fields: InvoiceFields {
                                payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
                                fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
-                               signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
+                               signing_pubkey: invoice_request.contents.inner.offer.signing_pubkey(),
                        },
                };
 
@@ -313,7 +313,8 @@ impl<'a> UnsignedInvoice<'a> {
 /// [`Offer`]: crate::offers::offer::Offer
 /// [`Refund`]: crate::offers::refund::Refund
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 pub struct Invoice {
        bytes: Vec<u8>,
        contents: InvoiceContents,
@@ -324,7 +325,8 @@ pub struct Invoice {
 ///
 /// [`Offer`]: crate::offers::offer::Offer
 /// [`Refund`]: crate::offers::refund::Refund
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 enum InvoiceContents {
        /// Contents for an [`Invoice`] corresponding to an [`Offer`].
        ///
@@ -469,6 +471,11 @@ impl Invoice {
                self.signature
        }
 
+       /// Hash that was used for signing the invoice.
+       pub fn signable_hash(&self) -> [u8; 32] {
+               merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
+       }
+
        #[cfg(test)]
        fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
                let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
@@ -486,7 +493,8 @@ impl InvoiceContents {
        #[cfg(feature = "std")]
        fn is_offer_or_refund_expired(&self) -> bool {
                match self {
-                       InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.offer.is_expired(),
+                       InvoiceContents::ForOffer { invoice_request, .. } =>
+                               invoice_request.inner.offer.is_expired(),
                        InvoiceContents::ForRefund { refund, .. } => refund.is_expired(),
                }
        }
@@ -772,68 +780,27 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
 
 #[cfg(test)]
 mod tests {
-       use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
+       use super::{DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
 
        use bitcoin::blockdata::script::Script;
        use bitcoin::hashes::Hash;
        use bitcoin::network::constants::Network;
-       use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, self};
-       use bitcoin::secp256k1::schnorr::Signature;
+       use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self};
        use bitcoin::util::address::{Address, Payload, WitnessVersion};
        use bitcoin::util::schnorr::TweakedPublicKey;
-       use core::convert::{Infallible, TryFrom};
+       use core::convert::TryFrom;
        use core::time::Duration;
-       use crate::ln::PaymentHash;
        use crate::ln::msgs::DecodeError;
-       use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+       use crate::ln::features::Bolt12InvoiceFeatures;
        use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
        use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
        use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
-       use crate::onion_message::{BlindedHop, BlindedPath};
+       use crate::offers::test_utils::*;
        use crate::util::ser::{BigSize, Iterable, Writeable};
 
-       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<Signature, Infallible> {
-               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_keys() -> KeyPair {
-               let secp_ctx = Secp256k1::new();
-               KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
-       }
-
-       fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
-               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 {
-               recipient_keys().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>;
        }
@@ -850,58 +817,6 @@ mod tests {
                }
        }
 
-       fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
-               let paths = vec![
-                       BlindedPath {
-                               introduction_node_id: pubkey(40),
-                               blinding_point: pubkey(41),
-                               blinded_hops: vec![
-                                       BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
-                                       BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
-                               ],
-                       },
-                       BlindedPath {
-                               introduction_node_id: pubkey(40),
-                               blinding_point: pubkey(41),
-                               blinded_hops: vec![
-                                       BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
-                                       BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
-                               ],
-                       },
-               ];
-
-               let payinfo = vec![
-                       BlindedPayInfo {
-                               fee_base_msat: 1,
-                               fee_proportional_millionths: 1_000,
-                               cltv_expiry_delta: 42,
-                               htlc_minimum_msat: 100,
-                               htlc_maximum_msat: 1_000_000_000_000,
-                               features: BlindedHopFeatures::empty(),
-                       },
-                       BlindedPayInfo {
-                               fee_base_msat: 1,
-                               fee_proportional_millionths: 1_000,
-                               cltv_expiry_delta: 42,
-                               htlc_minimum_msat: 100,
-                               htlc_maximum_msat: 1_000_000_000_000,
-                               features: BlindedHopFeatures::empty(),
-                       },
-               ];
-
-               paths.into_iter().zip(payinfo.into_iter()).collect()
-       }
-
-       fn payment_hash() -> PaymentHash {
-               PaymentHash([42; 32])
-       }
-
-       fn now() -> Duration {
-               std::time::SystemTime::now()
-                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
-                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH")
-       }
-
        #[test]
        fn builds_invoice_for_offer_with_defaults() {
                let payment_paths = payment_paths();
@@ -937,6 +852,11 @@ mod tests {
                        ).is_ok()
                );
 
+               let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
+               let pubkey = recipient_pubkey().into();
+               let secp_ctx = Secp256k1::verification_only();
+               assert!(secp_ctx.verify_schnorr(&invoice.signature, &digest, &pubkey).is_ok());
+
                assert_eq!(
                        invoice.as_tlv_stream(),
                        (