diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 417c66374a9..7dc899256f9 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -466,15 +466,13 @@ pub enum OffersContext { OutboundPaymentForRefund { /// Payment ID used when creating a [`Refund`]. /// - /// [`Refund`]: crate::offers::refund::Refund - payment_id: PaymentId, - - /// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid [`Refund`] and - /// for deriving its signing keys. + /// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path + /// created for this payment, rather than one an attacker could use to learn our identity by + /// observing which payment we make. The invoice itself is verified using its payer metadata. /// - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`Refund`]: crate::offers::refund::Refund - nonce: Nonce, + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + payment_id: PaymentId, }, /// Context used by a [`BlindedMessagePath`] as a reply path for an [`InvoiceRequest`]. /// @@ -487,15 +485,13 @@ pub enum OffersContext { OutboundPaymentForOffer { /// Payment ID used when creating an [`InvoiceRequest`]. /// - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - payment_id: PaymentId, - - /// A nonce used for authenticating that a [`Bolt12Invoice`] is for a valid - /// [`InvoiceRequest`] and for deriving its signing keys. + /// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path + /// created for this payment, rather than one an attacker could use to learn our identity by + /// observing which payment we make. The invoice itself is verified using its payer metadata. /// - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - nonce: Nonce, + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + payment_id: PaymentId, }, /// Context used by a [`BlindedMessagePath`] as a reply path for a [`Bolt12Invoice`]. /// @@ -678,7 +674,6 @@ impl_ser_tlv_based_enum!(OffersContext, }, (1, OutboundPaymentForRefund) => { (0, payment_id, required), - (1, nonce, required), }, (2, InboundPayment) => { (0, payment_hash, required), @@ -690,7 +685,6 @@ impl_ser_tlv_based_enum!(OffersContext, }, (4, OutboundPaymentForOffer) => { (0, payment_id, required), - (1, nonce, required), }, ); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6398613a762..ff4c0f87411 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -15075,13 +15075,13 @@ impl< let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); self.flow.enqueue_invoice_request( - invoice_request.clone(), payment_id, nonce, + invoice_request.clone(), payment_id, self.get_peers_for_blinded_path() )?; let retryable_invoice_request = RetryableInvoiceRequest { invoice_request: invoice_request.clone(), - nonce, + nonce: Some(nonce), needs_retry: true, }; @@ -17231,11 +17231,11 @@ impl< for (payment_id, retryable_invoice_request) in self.pending_outbound_payments.release_invoice_requests_awaiting_invoice() { - let RetryableInvoiceRequest { invoice_request, nonce, .. } = retryable_invoice_request; + let RetryableInvoiceRequest { invoice_request, .. } = retryable_invoice_request; let peers = self.get_peers_for_blinded_path(); let enqueue_invreq_res = - self.flow.enqueue_invoice_request(invoice_request, payment_id, nonce, peers); + self.flow.enqueue_invoice_request(invoice_request, payment_id, peers); if enqueue_invreq_res.is_err() { log_warn!( self.logger, diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 04e80038cc9..22fdc4722d2 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -173,14 +173,18 @@ pub(crate) enum PendingOutboundPayment { #[derive(Clone)] pub(crate) struct RetryableInvoiceRequest { pub(crate) invoice_request: InvoiceRequest, - pub(crate) nonce: Nonce, + // No longer used, but written so that the payment can be retried after downgrading to a + // version that verifies invoices using the nonce instead of the payer metadata. Set when + // creating an invoice request and otherwise retains the value read from disk, which may have + // been written by such a version. + pub(crate) nonce: Option, pub(super) needs_retry: bool, } impl_ser_tlv_based!(RetryableInvoiceRequest, { (0, invoice_request, required), (1, needs_retry, (default_value, true)), - (2, nonce, required), + (2, nonce, option), }); impl PendingOutboundPayment { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index bdc3475b554..ade684e5be1 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -484,14 +484,14 @@ impl OffersMessageFlow { Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) } - /// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer - /// metadata, returning the corresponding [`PaymentId`] if successful. + /// Verifies a [`Bolt12Invoice`] using the invoice's payer metadata, returning the + /// corresponding [`PaymentId`] if successful. /// /// - If an [`OffersContext::OutboundPaymentForOffer`] or - /// [`OffersContext::OutboundPaymentForRefund`] with a `nonce` is provided, verification is - /// performed using this to form the payer metadata. - /// - If no context is provided and the invoice corresponds to a [`Refund`] without blinded paths, - /// verification is performed using the [`Bolt12Invoice::payer_metadata`]. + /// [`OffersContext::OutboundPaymentForRefund`] is provided, the extracted [`PaymentId`] must + /// also match the context's `payment_id`. + /// - If no context is provided, the invoice must correspond to a [`Refund`] without blinded + /// paths. /// - If neither condition is met, verification fails. pub fn verify_bolt12_invoice( &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, @@ -503,16 +503,20 @@ impl OffersMessageFlow { None if invoice.is_for_refund_without_paths() => { invoice.verify_using_metadata(expanded_key, secp_ctx) }, - Some(&OffersContext::OutboundPaymentForOffer { payment_id, nonce, .. }) => { + Some(&OffersContext::OutboundPaymentForOffer { payment_id }) => { if invoice.is_for_offer() { - invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) + invoice.verify_using_metadata(expanded_key, secp_ctx).and_then(|extracted| { + (extracted == payment_id).then(|| payment_id).ok_or(()) + }) } else { Err(()) } }, - Some(&OffersContext::OutboundPaymentForRefund { payment_id, nonce, .. }) => { + Some(&OffersContext::OutboundPaymentForRefund { payment_id }) => { if invoice.is_for_refund() { - invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) + invoice.verify_using_metadata(expanded_key, secp_ctx).and_then(|extracted| { + (extracted == payment_id).then(|| payment_id).ok_or(()) + }) } else { Err(()) } @@ -689,7 +693,7 @@ impl OffersMessageFlow { let nonce = Nonce::from_entropy_source(entropy); let context = - MessageContext::Offers(OffersContext::OutboundPaymentForRefund { payment_id, nonce }); + MessageContext::Offers(OffersContext::OutboundPaymentForRefund { payment_id }); // Create the base builder with common properties let mut builder = RefundBuilder::deriving_signing_pubkey( @@ -1085,13 +1089,6 @@ impl OffersMessageFlow { /// over those blinded paths, which can be verified against the intended outbound payment, /// ensuring the invoice corresponds to a payment we actually want to make. /// - /// # Nonce - /// The nonce is used to create a unique [`MessageContext`] for the reply paths. - /// These will be used to verify the corresponding [`Bolt12Invoice`] when it is received. - /// - /// Note: The provided [`Nonce`] MUST be the same as the [`Nonce`] used for creating the - /// [`InvoiceRequest`] to ensure correct verification of the corresponding [`Bolt12Invoice`]. - /// /// See [`OffersMessageFlow::create_invoice_request_builder`] for more details. /// /// # Peers @@ -1103,11 +1100,10 @@ impl OffersMessageFlow { /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError /// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages pub fn enqueue_invoice_request( - &self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce, + &self, invoice_request: InvoiceRequest, payment_id: PaymentId, peers: Vec, ) -> Result<(), Bolt12SemanticError> { - let context = - MessageContext::Offers(OffersContext::OutboundPaymentForOffer { payment_id, nonce }); + let context = MessageContext::Offers(OffersContext::OutboundPaymentForOffer { payment_id }); let reply_paths = self .create_blinded_paths(peers, context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index fd77595ca7d..2a42d0f4e96 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -133,7 +133,6 @@ use crate::offers::invoice_request::{ use crate::offers::merkle::{ self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, }; -use crate::offers::nonce::Nonce; use crate::offers::offer::{ Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OfferId, OfferTlvStream, OfferTlvStreamRef, Quantity, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, @@ -1008,30 +1007,17 @@ impl Bolt12Invoice { (&invoice_request.inner.payer.0, INVOICE_REQUEST_IV_BYTES) }, InvoiceContents::ForRefund { refund, .. } => { - (&refund.payer.0, REFUND_IV_BYTES_WITH_METADATA) + let iv_bytes = if refund.paths().is_empty() { + REFUND_IV_BYTES_WITH_METADATA + } else { + REFUND_IV_BYTES_WITHOUT_METADATA + }; + (&refund.payer.0, iv_bytes) }, }; self.contents.verify(&self.bytes, metadata, key, iv_bytes, secp_ctx) } - /// Verifies that the invoice was for a request or refund created using the given key by - /// checking a payment id and nonce included with the [`BlindedMessagePath`] for which the invoice was - /// sent through. - pub fn verify_using_payer_data( - &self, payment_id: PaymentId, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1, - ) -> Result { - let metadata = Metadata::payer_data(payment_id, nonce, key); - let iv_bytes = match &self.contents { - InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES, - InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA, - }; - self.contents.verify(&self.bytes, &metadata, key, iv_bytes, secp_ctx).and_then( - |extracted_payment_id| { - (payment_id == extracted_payment_id).then(|| payment_id).ok_or(()) - }, - ) - } - pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef<'_> { let ( payer_tlv_stream, @@ -1892,6 +1878,8 @@ mod tests { let secp_ctx = Secp256k1::new(); let payment_id = PaymentId([1; 32]); let encrypted_payment_id = expanded_key.crypt_for_offer(payment_id.0, nonce); + let mut payer_metadata = encrypted_payment_id.to_vec(); + payer_metadata.extend_from_slice(nonce.as_slice()); let payment_paths = payment_paths(); let payment_hash = payment_hash(); @@ -1913,7 +1901,7 @@ mod tests { unsigned_invoice.write(&mut buffer).unwrap(); assert_eq!(unsigned_invoice.bytes, buffer.as_slice()); - assert_eq!(unsigned_invoice.payer_metadata(), &encrypted_payment_id); + assert_eq!(unsigned_invoice.payer_metadata(), payer_metadata.as_slice()); assert_eq!( unsigned_invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]) @@ -1957,7 +1945,7 @@ mod tests { invoice.write(&mut buffer).unwrap(); assert_eq!(invoice.bytes, buffer.as_slice()); - assert_eq!(invoice.payer_metadata(), &encrypted_payment_id); + assert_eq!(invoice.payer_metadata(), payer_metadata.as_slice()); assert_eq!( invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]) @@ -1975,10 +1963,7 @@ mod tests { assert_eq!(invoice.amount_msats(), 1000); assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(invoice.quantity(), None); - assert_eq!( - invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx), - Ok(payment_id), - ); + assert_eq!(invoice.verify_using_metadata(&expanded_key, &secp_ctx), Ok(payment_id)); assert_eq!(invoice.payer_note(), None); assert_eq!(invoice.payment_paths(), payment_paths.as_slice()); assert_eq!(invoice.created_at(), now); @@ -2001,7 +1986,7 @@ mod tests { assert_eq!( invoice.as_tlv_stream(), ( - PayerTlvStreamRef { metadata: Some(&encrypted_payment_id.to_vec()) }, + PayerTlvStreamRef { metadata: Some(&payer_metadata) }, OfferTlvStreamRef { chains: None, metadata: None, diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 2b4379e76e7..07bd15160b7 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1588,6 +1588,8 @@ mod tests { let secp_ctx = Secp256k1::new(); let payment_id = PaymentId([1; 32]); let encrypted_payment_id = expanded_key.crypt_for_offer(payment_id.0, nonce); + let mut payer_metadata = encrypted_payment_id.to_vec(); + payer_metadata.extend_from_slice(nonce.as_slice()); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) @@ -1602,7 +1604,7 @@ mod tests { invoice_request.write(&mut buffer).unwrap(); assert_eq!(invoice_request.bytes, buffer.as_slice()); - assert_eq!(invoice_request.payer_metadata(), &encrypted_payment_id); + assert_eq!(invoice_request.payer_metadata(), payer_metadata.as_slice()); assert_eq!( invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)] @@ -1634,7 +1636,7 @@ mod tests { assert_eq!( invoice_request.as_tlv_stream(), ( - PayerTlvStreamRef { metadata: Some(&encrypted_payment_id.to_vec()) }, + PayerTlvStreamRef { metadata: Some(&payer_metadata) }, OfferTlvStreamRef { chains: None, metadata: None, @@ -1735,10 +1737,10 @@ mod tests { .unwrap() .sign(recipient_sign) .unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_ok()); + match invoice.verify_using_metadata(&expanded_key, &secp_ctx) { + Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), + Err(()) => panic!("verification failed"), + } // Fails verification with altered fields let ( @@ -1774,9 +1776,7 @@ mod tests { .unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_err()); + assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered payer id let ( @@ -1812,9 +1812,7 @@ mod tests { .unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_err()); + assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); } #[test] diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index c0fd9dfdd3e..85ea3b61435 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -210,15 +210,12 @@ macro_rules! refund_builder_methods { ( /// /// Also, sets the metadata when [`RefundBuilder::build`] is called such that it can be used by /// [`Bolt12Invoice::verify_using_metadata`] to determine if the invoice was produced for the - /// refund given an [`ExpandedKey`]. However, if [`RefundBuilder::path`] is called, then the - /// metadata must be included in each [`BlindedMessagePath`] instead. In this case, use - /// [`Bolt12Invoice::verify_using_payer_data`]. + /// refund given an [`ExpandedKey`]. /// /// The `payment_id` is encrypted in the metadata and should be unique. This ensures that only /// one invoice will be paid for the refund and that payments can be uniquely identified. /// /// [`Bolt12Invoice::verify_using_metadata`]: crate::offers::invoice::Bolt12Invoice::verify_using_metadata - /// [`Bolt12Invoice::verify_using_payer_data`]: crate::offers::invoice::Bolt12Invoice::verify_using_payer_data /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_signing_pubkey( node_id: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce, @@ -329,6 +326,8 @@ macro_rules! refund_builder_methods { ( if $self.refund.payer.0.has_derivation_material() { let mut metadata = core::mem::take(&mut $self.refund.payer.0); + // Don't derive keys if no blinded paths were given since this means the payer id must + // be a public node id. let iv_bytes = if $self.refund.paths.is_none() { metadata = metadata.without_keys(); IV_BYTES_WITH_METADATA @@ -1167,9 +1166,6 @@ mod tests { Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), Err(()) => panic!("verification failed"), } - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_err()); let mut tlv_stream = refund.as_tlv_stream(); tlv_stream.2.amount = Some(2000); @@ -1248,10 +1244,10 @@ mod tests { .unwrap() .sign(recipient_sign) .unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_ok()); + match invoice.verify_using_metadata(&expanded_key, &secp_ctx) { + Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), + Err(()) => panic!("verification failed"), + } // Fails verification with altered fields let mut tlv_stream = refund.as_tlv_stream(); @@ -1268,9 +1264,7 @@ mod tests { .unwrap() .sign(recipient_sign) .unwrap(); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_err()); + assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered payer_id let mut tlv_stream = refund.as_tlv_stream(); @@ -1288,9 +1282,7 @@ mod tests { .unwrap() .sign(recipient_sign) .unwrap(); - assert!(invoice - .verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx) - .is_err()); + assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); } #[test] diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index e51a120b6d7..43d1370238a 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -63,11 +63,6 @@ pub(super) enum Metadata { /// This variant should only be used at verification time, never when building. RecipientData(Nonce), - /// Metadata for deriving keys included as payer data in a blinded path. - /// - /// This variant should only be used at verification time, never when building. - PayerData([u8; PaymentId::LENGTH + Nonce::LENGTH]), - /// Metadata to be derived from message contents and given material. /// /// This variant should only be used at building time. @@ -80,16 +75,6 @@ pub(super) enum Metadata { } impl Metadata { - pub fn payer_data(payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey) -> Self { - let encrypted_payment_id = expanded_key.crypt_for_offer(payment_id.0, nonce); - - let mut bytes = [0u8; PaymentId::LENGTH + Nonce::LENGTH]; - bytes[..PaymentId::LENGTH].copy_from_slice(encrypted_payment_id.as_slice()); - bytes[PaymentId::LENGTH..].copy_from_slice(nonce.as_slice()); - - Metadata::PayerData(bytes) - } - pub fn as_bytes(&self) -> Option<&Vec> { match self { Metadata::Bytes(bytes) => Some(bytes), @@ -107,10 +92,6 @@ impl Metadata { debug_assert!(false); false }, - Metadata::PayerData(_) => { - debug_assert!(false); - false - }, Metadata::Derived(_) => true, Metadata::DerivedSigningPubkey(_) => true, } @@ -125,7 +106,6 @@ impl Metadata { // Nonce::LENGTH had been set explicitly. Metadata::Bytes(bytes) => bytes.len() == PaymentId::LENGTH + Nonce::LENGTH, Metadata::RecipientData(_) => false, - Metadata::PayerData(_) => true, Metadata::Derived(_) => false, Metadata::DerivedSigningPubkey(_) => true, } @@ -140,7 +120,6 @@ impl Metadata { // been set explicitly. Metadata::Bytes(bytes) => bytes.len() == Nonce::LENGTH, Metadata::RecipientData(_) => true, - Metadata::PayerData(_) => false, Metadata::Derived(_) => false, Metadata::DerivedSigningPubkey(_) => true, } @@ -158,10 +137,6 @@ impl Metadata { debug_assert!(false); self }, - Metadata::PayerData(_) => { - debug_assert!(false); - self - }, Metadata::Derived(_) => self, Metadata::DerivedSigningPubkey(material) => Metadata::Derived(material), } @@ -176,10 +151,6 @@ impl Metadata { debug_assert!(false); (self, None) }, - Metadata::PayerData(_) => { - debug_assert!(false); - (self, None) - }, Metadata::Derived(metadata_material) => { (Metadata::Bytes(metadata_material.derive_metadata(iv_bytes, tlv_stream)), None) }, @@ -204,7 +175,6 @@ impl AsRef<[u8]> for Metadata { match self { Metadata::Bytes(bytes) => &bytes, Metadata::RecipientData(nonce) => &nonce.0, - Metadata::PayerData(bytes) => bytes.as_slice(), Metadata::Derived(_) => { debug_assert!(false); &[] @@ -222,7 +192,6 @@ impl fmt::Debug for Metadata { match self { Metadata::Bytes(bytes) => bytes.fmt(f), Metadata::RecipientData(Nonce(bytes)) => bytes.fmt(f), - Metadata::PayerData(bytes) => bytes.fmt(f), Metadata::Derived(_) => f.write_str("Derived"), Metadata::DerivedSigningPubkey(_) => f.write_str("DerivedSigningPubkey"), } @@ -241,7 +210,6 @@ impl PartialEq for Metadata { } }, Metadata::RecipientData(_) => false, - Metadata::PayerData(_) => false, Metadata::Derived(_) => false, Metadata::DerivedSigningPubkey(_) => false, } @@ -290,7 +258,8 @@ impl MetadataMaterial { self.hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT); self.maybe_include_encrypted_payment_id(); - let bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or_default(); + let mut bytes = self.encrypted_payment_id.map(|id| id.to_vec()).unwrap_or_default(); + bytes.extend_from_slice(self.nonce.as_slice()); let hmac = Hmac::from_engine(self.hmac); let privkey = SecretKey::from_slice(hmac.as_byte_array()).unwrap();