From 293543b7c3f4f7601569ea4362165db08b120810 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 23 Jul 2024 17:54:31 -0500 Subject: [PATCH] Use different iv_bytes for blinded path metadata Best practice is to use different IV bytes for different contexts. Update Offer and Refund metadata computation to use different IV bytes when the metadata is included in a blinded path. For invoice requests, the metatdata will always be in the blinded path, so it remains the same. --- lightning/src/offers/invoice.rs | 29 ++++++++++++++++------------- lightning/src/offers/offer.rs | 22 ++++++++++++++-------- lightning/src/offers/refund.rs | 12 ++++++++---- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index eee71995e..76a4769cb 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -114,7 +114,7 @@ use crate::blinded_path::BlindedPath; use crate::ln::types::PaymentHash; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; -use crate::ln::inbound_payment::ExpandedKey; +use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::ln::msgs::DecodeError; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; @@ -123,7 +123,7 @@ use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; -use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents}; +use crate::offers::refund::{IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, Refund, RefundContents}; use crate::offers::signer::{Metadata, self}; use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, Readable, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -778,11 +778,15 @@ impl Bolt12Invoice { pub fn verify_using_metadata( &self, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result { - let metadata = match &self.contents { - InvoiceContents::ForOffer { invoice_request, .. } => &invoice_request.inner.payer.0, - InvoiceContents::ForRefund { refund, .. } => &refund.payer.0, + let (metadata, iv_bytes) = match &self.contents { + InvoiceContents::ForOffer { invoice_request, .. } => { + (&invoice_request.inner.payer.0, INVOICE_REQUEST_IV_BYTES) + }, + InvoiceContents::ForRefund { refund, .. } => { + (&refund.payer.0, REFUND_IV_BYTES_WITH_METADATA) + }, }; - self.contents.verify(TlvStream::new(&self.bytes), metadata, key, secp_ctx) + self.contents.verify(TlvStream::new(&self.bytes), metadata, key, iv_bytes, secp_ctx) } /// Verifies that the invoice was for a request or refund created using the given key by @@ -792,7 +796,11 @@ impl Bolt12Invoice { &self, payment_id: PaymentId, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result { let metadata = Metadata::payer_data(payment_id, nonce, key); - self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, secp_ctx) + let iv_bytes = match &self.contents { + InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES, + InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA, + }; + self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, iv_bytes, secp_ctx) .and_then(|extracted_payment_id| (payment_id == extracted_payment_id) .then(|| payment_id) .ok_or(()) @@ -1028,7 +1036,7 @@ impl InvoiceContents { fn verify( &self, tlv_stream: TlvStream<'_>, metadata: &Metadata, key: &ExpandedKey, - secp_ctx: &Secp256k1 + iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1 ) -> Result { let offer_records = tlv_stream.clone().range(OFFER_TYPES); let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| { @@ -1041,11 +1049,6 @@ impl InvoiceContents { let tlv_stream = offer_records.chain(invreq_records); let payer_id = self.payer_id(); - let iv_bytes = match self { - InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES, - InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES, - }; - signer::verify_payer_metadata( metadata.as_ref(), key, iv_bytes, payer_id, tlv_stream, secp_ctx, ) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index deacacd15..24f034688 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -112,7 +112,8 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Offer v2~~~~"; /// An identifier for an [`Offer`] built using [`DerivedMetadata`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -391,9 +392,12 @@ macro_rules! offer_builder_methods { ( // Don't derive keys if no blinded paths were given since this means the signing // pubkey must be the node id of an announced node. - if $self.offer.paths.is_none() { + let iv_bytes = if $self.offer.paths.is_none() { metadata = metadata.without_keys(); - } + IV_BYTES_WITH_METADATA + } else { + IV_BYTES_WITHOUT_METADATA + }; let mut tlv_stream = $self.offer.as_tlv_stream(); debug_assert_eq!(tlv_stream.metadata, None); @@ -406,7 +410,7 @@ macro_rules! offer_builder_methods { ( // for verification. In the former case, the blinded paths must include // `OffersContext::InvoiceRequest` instead. let (derived_metadata, keys) = - metadata.derive_from(IV_BYTES, tlv_stream, $self.secp_ctx); + metadata.derive_from(iv_bytes, tlv_stream, $self.secp_ctx); match keys { Some(keys) => $self.offer.signing_pubkey = Some(keys.public_key()), None => $self.offer.metadata = Some(derived_metadata), @@ -919,18 +923,20 @@ impl OfferContents { pub(super) fn verify_using_metadata( &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { - self.verify(bytes, self.metadata.as_ref(), key, secp_ctx) + self.verify(bytes, self.metadata.as_ref(), key, IV_BYTES_WITH_METADATA, secp_ctx) } pub(super) fn verify_using_recipient_data( &self, bytes: &[u8], nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { - self.verify(bytes, Some(&Metadata::RecipientData(nonce)), key, secp_ctx) + let metadata = Metadata::RecipientData(nonce); + self.verify(bytes, Some(&metadata), key, IV_BYTES_WITHOUT_METADATA, secp_ctx) } /// Verifies that the offer metadata was produced from the offer in the TLV stream. fn verify( - &self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, secp_ctx: &Secp256k1 + &self, bytes: &[u8], metadata: Option<&Metadata>, key: &ExpandedKey, + iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1 ) -> Result<(OfferId, Option), ()> { match metadata { Some(metadata) => { @@ -946,7 +952,7 @@ impl OfferContents { None => return Err(()), }; let keys = signer::verify_recipient_metadata( - metadata.as_ref(), key, IV_BYTES, signing_pubkey, tlv_stream, secp_ctx + metadata.as_ref(), key, iv_bytes, signing_pubkey, tlv_stream, secp_ctx )?; let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 76605c77f..242652577 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -122,7 +122,8 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; +pub(super) const IV_BYTES_WITH_METADATA: &[u8; IV_LEN] = b"LDK Refund ~~~~~"; +pub(super) const IV_BYTES_WITHOUT_METADATA: &[u8; IV_LEN] = b"LDK Refund v2~~~"; /// Builds a [`Refund`] for the "offer for money" flow. /// @@ -306,9 +307,12 @@ macro_rules! refund_builder_methods { ( if $self.refund.payer.0.has_derivation_material() { let mut metadata = core::mem::take(&mut $self.refund.payer.0); - if $self.refund.paths.is_none() { + let iv_bytes = if $self.refund.paths.is_none() { metadata = metadata.without_keys(); - } + IV_BYTES_WITH_METADATA + } else { + IV_BYTES_WITHOUT_METADATA + }; let mut tlv_stream = $self.refund.as_tlv_stream(); tlv_stream.0.metadata = None; @@ -317,7 +321,7 @@ macro_rules! refund_builder_methods { ( } let (derived_metadata, keys) = - metadata.derive_from(IV_BYTES, tlv_stream, $self.secp_ctx); + metadata.derive_from(iv_bytes, tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { $self.refund.payer_id = keys.public_key(); -- 2.39.5