From: Jeffrey Czyz Date: Wed, 5 Apr 2023 05:04:41 +0000 (-0500) Subject: Extract keys from Offer::metadata to sign Invoice X-Git-Tag: v0.0.115~12^2~2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=e1a6bc3cadb8c89ba38afb5846fb80f349d99449;p=rust-lightning Extract keys from Offer::metadata to sign Invoice For offers where the signing pubkey is derived, the keys need to be extracted from the Offer::metadata in order to sign an invoice. Parameterize InvoiceBuilder such that a build_and_sign method is available for this situation. --- diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 17908391a..1635d956c 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -97,11 +97,11 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::hash_types::{WPubkeyHash, WScriptHash}; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self}; +use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::util::address::{Address, Payload, WitnessVersion}; use bitcoin::util::schnorr::TweakedPublicKey; -use core::convert::TryFrom; +use core::convert::{Infallible, TryFrom}; use core::time::Duration; use crate::io; use crate::ln::PaymentHash; @@ -136,28 +136,31 @@ pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", " /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`Refund`]: crate::offers::refund::Refund /// [module-level documentation]: self -pub struct InvoiceBuilder<'a> { +pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> { invreq_bytes: &'a Vec, invoice: InvoiceContents, + keys: Option, + signing_pubkey_strategy: core::marker::PhantomData, } -impl<'a> InvoiceBuilder<'a> { +/// Indicates how [`Invoice::signing_pubkey`] was set. +pub trait SigningPubkeyStrategy {} + +/// [`Invoice::signing_pubkey`] was explicitly set. +pub struct ExplicitSigningPubkey {} + +/// [`Invoice::signing_pubkey`] was derived. +pub struct DerivedSigningPubkey {} + +impl SigningPubkeyStrategy for ExplicitSigningPubkey {} +impl SigningPubkeyStrategy for DerivedSigningPubkey {} + +impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { pub(super) fn for_offer( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration, payment_hash: PaymentHash ) -> Result { - let amount_msats = match invoice_request.amount_msats() { - Some(amount_msats) => amount_msats, - 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)? - }, - Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency), - None => return Err(SemanticError::MissingAmount), - }, - }; - + let amount_msats = Self::check_amount_msats(invoice_request)?; let contents = InvoiceContents::ForOffer { invoice_request: invoice_request.contents.clone(), fields: InvoiceFields { @@ -167,7 +170,7 @@ impl<'a> InvoiceBuilder<'a> { }, }; - Self::new(&invoice_request.bytes, contents) + Self::new(&invoice_request.bytes, contents, None) } pub(super) fn for_refund( @@ -183,15 +186,57 @@ impl<'a> InvoiceBuilder<'a> { }, }; - Self::new(&refund.bytes, contents) + Self::new(&refund.bytes, contents, None) } +} - fn new(invreq_bytes: &'a Vec, contents: InvoiceContents) -> Result { +impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + pub(super) fn for_offer_using_keys( + invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, + created_at: Duration, payment_hash: PaymentHash, keys: KeyPair + ) -> Result { + let amount_msats = Self::check_amount_msats(invoice_request)?; + 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(), + }, + }; + + Self::new(&invoice_request.bytes, contents, Some(keys)) + } +} + +impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result { + match invoice_request.amount_msats() { + Some(amount_msats) => Ok(amount_msats), + 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) + }, + Some(Amount::Currency { .. }) => Err(SemanticError::UnsupportedCurrency), + None => Err(SemanticError::MissingAmount), + }, + } + } + + fn new( + invreq_bytes: &'a Vec, contents: InvoiceContents, keys: Option + ) -> Result { if contents.fields().payment_paths.is_empty() { return Err(SemanticError::MissingPaths); } - Ok(Self { invreq_bytes, invoice: contents }) + Ok(Self { + invreq_bytes, + invoice: contents, + keys, + signing_pubkey_strategy: core::marker::PhantomData, + }) } /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry @@ -248,7 +293,9 @@ impl<'a> InvoiceBuilder<'a> { self.invoice.fields_mut().features.set_basic_mpp_optional(); self } +} +impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by /// [`UnsignedInvoice::sign`]. pub fn build(self) -> Result, SemanticError> { @@ -258,11 +305,36 @@ impl<'a> InvoiceBuilder<'a> { } } - let InvoiceBuilder { invreq_bytes, invoice } = self; + let InvoiceBuilder { invreq_bytes, invoice, .. } = self; Ok(UnsignedInvoice { invreq_bytes, invoice }) } } +impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + /// Builds a signed [`Invoice`] after checking for valid semantics. + pub fn build_and_sign( + self, secp_ctx: &Secp256k1 + ) -> Result { + #[cfg(feature = "std")] { + if self.invoice.is_offer_or_refund_expired() { + return Err(SemanticError::AlreadyExpired); + } + } + + let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self; + let keys = match &invoice { + InvoiceContents::ForOffer { .. } => keys.unwrap(), + InvoiceContents::ForRefund { .. } => unreachable!(), + }; + + let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice }; + let invoice = unsigned_invoice + .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))) + .unwrap(); + Ok(invoice) + } +} + /// A semantically valid [`Invoice`] that hasn't been signed. pub struct UnsignedInvoice<'a> { invreq_bytes: &'a Vec, @@ -551,7 +623,10 @@ impl InvoiceContents { }, }; - signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) + match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) { + Ok(_) => true, + Err(()) => false, + } } fn derives_keys(&self) -> bool { @@ -831,8 +906,10 @@ mod tests { use bitcoin::util::schnorr::TweakedPublicKey; use core::convert::TryFrom; use core::time::Duration; - use crate::ln::msgs::DecodeError; + use crate::chain::keysinterface::KeyMaterial; use crate::ln::features::Bolt12InvoiceFeatures; + use crate::ln::inbound_payment::ExpandedKey; + use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self}; use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity}; @@ -840,6 +917,7 @@ 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}; trait ToBytes { @@ -1084,6 +1162,67 @@ mod tests { } } + #[test] + fn builds_invoice_from_offer_using_derived_keys() { + let desc = "foo".to_string(); + let node_id = recipient_pubkey(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + let blinded_path = BlindedPath { + introduction_node_id: pubkey(40), + blinding_point: pubkey(41), + blinded_hops: vec![ + BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, + BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] }, + ], + }; + + let offer = OfferBuilder + ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + .amount_msats(1000) + .path(blinded_path) + .build().unwrap(); + let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + if let Err(e) = invoice_request + .verify_and_respond_using_derived_keys_no_std( + payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx + ) + .unwrap() + .build_and_sign(&secp_ctx) + { + panic!("error building invoice: {:?}", e); + } + + let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32])); + match invoice_request.verify_and_respond_using_derived_keys_no_std( + payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx + ) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidMetadata), + } + + let desc = "foo".to_string(); + let offer = OfferBuilder + ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + .amount_msats(1000) + .build().unwrap(); + let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + + match invoice_request.verify_and_respond_using_derived_keys_no_std( + payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx + ) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, SemanticError::InvalidMetadata), + } + } + #[test] fn builds_invoice_with_relative_expiry() { let now = now(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 6b5c77862..92fabd6fd 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -64,8 +64,8 @@ use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; -use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self}; +use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, 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}; @@ -469,7 +469,7 @@ impl InvoiceRequest { #[cfg(feature = "std")] pub fn respond_with( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash - ) -> Result { + ) -> Result, SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); @@ -497,7 +497,7 @@ impl InvoiceRequest { pub fn respond_with_no_std( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result { + ) -> Result, SemanticError> { if self.features().requires_unknown_bits() { return Err(SemanticError::UnknownRequiredFeatures); } @@ -505,11 +505,60 @@ impl InvoiceRequest { InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) } - /// Verifies that the request was for an offer created using the given key. + /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses + /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the + /// same [`ExpandedKey`] as the one used to create the offer. + /// + /// See [`InvoiceRequest::respond_with`] for further details. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + #[cfg(feature = "std")] + pub fn verify_and_respond_using_derived_keys( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, SemanticError> { + let created_at = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); + + self.verify_and_respond_using_derived_keys_no_std( + payment_paths, payment_hash, created_at, expanded_key, secp_ctx + ) + } + + /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses + /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the + /// same [`ExpandedKey`] as the one used to create the offer. + /// + /// See [`InvoiceRequest::respond_with_no_std`] for further details. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + pub fn verify_and_respond_using_derived_keys_no_std( + &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, SemanticError> { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + let keys = match self.verify(expanded_key, secp_ctx) { + Err(()) => return Err(SemanticError::InvalidMetadata), + Ok(None) => return Err(SemanticError::InvalidMetadata), + Ok(Some(keys)) => keys, + }; + + InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys) + } + + /// Verifies that the request was for an offer created using the given key. Returns the derived + /// keys need to sign an [`Invoice`] for the request if they could be extracted from the + /// metadata. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice pub fn verify( &self, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { - self.contents.inner.offer.verify(TlvStream::new(&self.bytes), key, secp_ctx) + ) -> Result, ()> { + self.contents.inner.offer.verify(&self.bytes, key, secp_ctx) } #[cfg(test)] diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 9f22e9af1..d2918e809 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -68,7 +68,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; +use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use core::convert::TryFrom; use core::num::NonZeroU64; use core::ops::Deref; @@ -92,7 +92,7 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; /// Builds an [`Offer`] for the "offer to be paid" flow. /// @@ -615,11 +615,11 @@ impl OfferContents { /// Verifies that the offer metadata was produced from the offer in the TLV stream. pub(super) fn verify( - &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { + &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, ()> { match self.metadata() { Some(metadata) => { - let tlv_stream = tlv_stream.range(OFFER_TYPES).filter(|record| { + let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| { match record.r#type { OFFER_METADATA_TYPE => false, OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(), @@ -630,7 +630,7 @@ impl OfferContents { metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx ) }, - None => false, + None => Err(()), } } @@ -962,7 +962,7 @@ mod tests { let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok()); // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); @@ -975,7 +975,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered metadata let mut tlv_stream = offer.as_tlv_stream(); @@ -989,7 +989,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); } #[test] @@ -1019,7 +1019,7 @@ mod tests { let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok()); // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); @@ -1032,7 +1032,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered signing pubkey let mut tlv_stream = offer.as_tlv_stream(); @@ -1046,7 +1046,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); } #[test] diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index 6afd4d68f..3f1a9c887 100644 --- a/lightning/src/offers/parse.rs +++ b/lightning/src/offers/parse.rs @@ -171,6 +171,8 @@ pub enum SemanticError { InvalidQuantity, /// A quantity or quantity bounds was provided but was not expected. UnexpectedQuantity, + /// Metadata could not be used to verify the offers message. + InvalidMetadata, /// Metadata was provided but was not expected. UnexpectedMetadata, /// Payer metadata was expected but was missing. diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 582c5b7eb..899586ba2 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -84,7 +84,7 @@ use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; -use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; +use crate::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError}; @@ -392,7 +392,7 @@ impl Refund { pub fn respond_with( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, - ) -> Result { + ) -> Result, SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); @@ -423,7 +423,7 @@ impl Refund { pub fn respond_with_no_std( &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, created_at: Duration - ) -> Result { + ) -> Result, SemanticError> { if self.features().requires_unknown_bits() { return Err(SemanticError::UnknownRequiredFeatures); } diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index f6141e596..7229775aa 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -172,14 +172,40 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>( metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator>, secp_ctx: &Secp256k1 -) -> bool { +) -> Result, ()> { + let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?; + + if metadata.len() == Nonce::LENGTH { + let derived_keys = KeyPair::from_secret_key( + secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap() + ); + if fixed_time_eq(&signing_pubkey.serialize(), &derived_keys.public_key().serialize()) { + Ok(Some(derived_keys)) + } else { + Err(()) + } + } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN { + if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) { + Ok(None) + } else { + Err(()) + } + } else { + Err(()) + } +} + +fn hmac_for_message<'a>( + metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], + tlv_stream: impl core::iter::Iterator> +) -> Result, ()> { if metadata.len() < Nonce::LENGTH { - return false; + return Err(()); } let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) { Ok(nonce) => nonce, - Err(_) => return false, + Err(_) => return Err(()), }; let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes); @@ -189,13 +215,9 @@ pub(super) fn verify_metadata<'a, T: secp256k1::Signing>( if metadata.len() == Nonce::LENGTH { hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT); - let hmac = Hmac::from_engine(hmac); - let derived_pubkey = SecretKey::from_slice(hmac.as_inner()).unwrap().public_key(&secp_ctx); - fixed_time_eq(&signing_pubkey.serialize(), &derived_pubkey.serialize()) - } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN { - hmac.input(DERIVED_METADATA_HMAC_INPUT); - fixed_time_eq(&metadata[Nonce::LENGTH..], &Hmac::from_engine(hmac).into_inner()) } else { - false + hmac.input(DERIVED_METADATA_HMAC_INPUT); } + + Ok(Hmac::from_engine(hmac)) }