diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 59612636a9e..d4ffb12d0b4 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -63,7 +63,8 @@ use lightning::routing::router::{ InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters, Router, }; use lightning::sign::{ - EntropySource, InMemorySigner, NodeSigner, PeerStorageKey, Recipient, SignerProvider, + EntropySource, InMemorySigner, NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient, + SignerProvider, }; use lightning::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::util::config::UserConfig; @@ -142,8 +143,8 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _local_node_receive_key: ReceiveAuthKey, + _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } @@ -347,6 +348,10 @@ impl NodeSigner for KeyProvider { PeerStorageKey { inner: [42; 32] } } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + ReceiveAuthKey([41; 32]) + } + fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index f6fa07199fa..1d16b5c4a0f 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -57,7 +57,8 @@ use lightning::routing::router::{ }; use lightning::routing::utxo::UtxoLookup; use lightning::sign::{ - EntropySource, InMemorySigner, NodeSigner, PeerStorageKey, Recipient, SignerProvider, + EntropySource, InMemorySigner, NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient, + SignerProvider, }; use lightning::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::util::config::{ChannelConfig, UserConfig}; @@ -173,8 +174,8 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _local_node_receive_key: ReceiveAuthKey, + _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } @@ -438,6 +439,10 @@ impl NodeSigner for KeyProvider { fn get_peer_storage_key(&self) -> PeerStorageKey { PeerStorageKey { inner: [42; 32] } } + + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + ReceiveAuthKey([41; 32]) + } } impl SignerProvider for KeyProvider { diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 03903abbf6b..85fb5a9f513 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -24,7 +24,9 @@ use lightning::onion_message::messenger::{ }; use lightning::onion_message::offers::{OffersMessage, OffersMessageHandler}; use lightning::onion_message::packet::OnionMessageContents; -use lightning::sign::{EntropySource, NodeSigner, PeerStorageKey, Recipient, SignerProvider}; +use lightning::sign::{ + EntropySource, NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient, SignerProvider, +}; use lightning::types::features::InitFeatures; use lightning::util::logger::Logger; use lightning::util::ser::{LengthReadable, Writeable, Writer}; @@ -104,8 +106,8 @@ impl MessageRouter for TestMessageRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _local_node_receive_key: ReceiveAuthKey, + _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } @@ -278,6 +280,10 @@ impl NodeSigner for KeyProvider { fn get_peer_storage_key(&self) -> PeerStorageKey { unreachable!() } + + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + ReceiveAuthKey([41; 32]) + } } impl SignerProvider for KeyProvider { diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 73dccdadd23..68f25ada2d7 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -174,7 +174,7 @@ mod test { AOnionMessenger, Destination, MessageRouter, OnionMessagePath, OnionMessenger, }; use lightning::routing::router::RouteParametersConfig; - use lightning::sign::{KeysManager, NodeSigner, Recipient}; + use lightning::sign::{KeysManager, NodeSigner, ReceiveAuthKey, Recipient}; use lightning::types::features::InitFeatures; use lightning::types::payment::PaymentHash; use lightning::util::logger::Logger; @@ -230,11 +230,18 @@ mod test { } fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, _peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, _peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let keys = KeysManager::new(&[0; 32], 42, 43); - Ok(vec![BlindedMessagePath::one_hop(recipient, context, &keys, secp_ctx).unwrap()]) + Ok(vec![BlindedMessagePath::one_hop( + recipient, + local_node_receive_key, + context, + &keys, + secp_ctx, + ) + .unwrap()]) } } impl Deref for DirectlyConnectedRouter { @@ -336,8 +343,15 @@ mod test { let (msg, context) = payer.resolver.resolve_name(payment_id, name.clone(), &*payer_keys).unwrap(); let query_context = MessageContext::DNSResolver(context); - let reply_path = - BlindedMessagePath::one_hop(payer_id, query_context, &*payer_keys, &secp_ctx).unwrap(); + let receive_key = payer_keys.get_receive_auth_key(); + let reply_path = BlindedMessagePath::one_hop( + payer_id, + receive_key, + query_context, + &*payer_keys, + &secp_ctx, + ) + .unwrap(); payer.pending_messages.lock().unwrap().push(( DNSResolverMessage::DNSSECQuery(msg), MessageSendInstructions::WithSpecifiedReplyPath { diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index ba99b283ee4..a3d6e8aa9a4 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -26,12 +26,10 @@ use crate::offers::nonce::Nonce; use crate::offers::offer::OfferId; use crate::onion_message::packet::ControlTlvs; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey, Recipient}; use crate::types::payment::PaymentHash; use crate::util::scid_utils; use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, Writeable, Writer}; -use bitcoin::hashes::hmac::Hmac; -use bitcoin::hashes::sha256::Hash as Sha256; use core::mem; use core::ops::Deref; @@ -57,13 +55,13 @@ impl Readable for BlindedMessagePath { impl BlindedMessagePath { /// Create a one-hop blinded path for a message. pub fn one_hop( - recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES, - secp_ctx: &Secp256k1, + recipient_node_id: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, { - Self::new(&[], recipient_node_id, context, entropy_source, secp_ctx) + Self::new(&[], recipient_node_id, local_node_receive_key, context, entropy_source, secp_ctx) } /// Create a path for an onion message, to be forwarded along `node_pks`. The last node @@ -73,7 +71,8 @@ impl BlindedMessagePath { // TODO: make all payloads the same size with padding + add dummy hops pub fn new( intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey, - context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1, + local_node_receive_key: ReceiveAuthKey, context: MessageContext, entropy_source: ES, + secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource, @@ -94,6 +93,7 @@ impl BlindedMessagePath { recipient_node_id, context, &blinding_secret, + local_node_receive_key, ) .map_err(|_| ())?, })) @@ -404,12 +404,6 @@ pub enum OffersContext { /// [`Refund`]: crate::offers::refund::Refund /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest nonce: Nonce, - - /// Authentication code for the [`PaymentId`], which should be checked when the context is - /// used with an [`InvoiceError`]. - /// - /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError - hmac: Option>, }, /// Context used by a [`BlindedMessagePath`] as a reply path for a [`Bolt12Invoice`]. /// @@ -422,19 +416,6 @@ pub enum OffersContext { /// /// [`Bolt12Invoice::payment_hash`]: crate::offers::invoice::Bolt12Invoice::payment_hash payment_hash: PaymentHash, - - /// A nonce used for authenticating that a received [`InvoiceError`] is for a valid - /// sent [`Bolt12Invoice`]. - /// - /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - nonce: Nonce, - - /// Authentication code for the [`PaymentHash`], which should be checked when the context is - /// used to log the received [`InvoiceError`]. - /// - /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError - hmac: Hmac, }, } @@ -542,35 +523,12 @@ pub enum AsyncPaymentsContext { /// /// [`Offer`]: crate::offers::offer::Offer payment_id: PaymentId, - /// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding - /// [`HeldHtlcAvailable`] message. - /// - /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc - /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable - nonce: Nonce, - /// Authentication code for the [`PaymentId`]. - /// - /// Prevents the recipient from being able to deanonymize us by creating a blinded path to us - /// containing the expected [`PaymentId`]. - hmac: Hmac, }, /// Context contained within the [`BlindedMessagePath`]s we put in static invoices, provided back /// to us in corresponding [`HeldHtlcAvailable`] messages. /// /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable InboundPayment { - /// A nonce used for authenticating that a [`HeldHtlcAvailable`] message is valid for a - /// preceding static invoice. - /// - /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable - nonce: Nonce, - /// Authentication code for the [`HeldHtlcAvailable`] message. - /// - /// Prevents nodes from creating their own blinded path to us, sending a [`HeldHtlcAvailable`] - /// message and trivially getting notified whenever we come online. - /// - /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable - hmac: Hmac, /// The time as duration since the Unix epoch at which this path expires and messages sent over /// it should be ignored. Without this, anyone with the path corresponding to this context is /// able to trivially ask if we're online forever. @@ -585,6 +543,14 @@ impl_writeable_tlv_based_enum!(MessageContext, {3, DNSResolver} => (), ); +// NOTE: +// Several TLV fields (`nonce`, `hmac`, etc.) were removed in LDK v0.1.X +// following the introduction of `ReceiveAuthKey`-based authentication for +// inbound `BlindedMessagePath`s. These fields are now commented out and +// their `type` values must not be reused unless support for LDK v0.1.X +// and earlier is fully dropped. +// +// For context-specific removals, see the commented-out fields within each enum variant. impl_writeable_tlv_based_enum!(OffersContext, (0, InvoiceRequest) => { (0, nonce, required), @@ -592,12 +558,12 @@ impl_writeable_tlv_based_enum!(OffersContext, (1, OutboundPayment) => { (0, payment_id, required), (1, nonce, required), - (2, hmac, option), + // Removed: (2, hmac, option) }, (2, InboundPayment) => { (0, payment_hash, required), - (1, nonce, required), - (2, hmac, required) + // Removed: (1, nonce, required), + // Removed: (2, hmac, required) }, (3, StaticInvoiceRequested) => { (0, recipient_id, required), @@ -609,12 +575,12 @@ impl_writeable_tlv_based_enum!(OffersContext, impl_writeable_tlv_based_enum!(AsyncPaymentsContext, (0, OutboundPayment) => { (0, payment_id, required), - (2, nonce, required), - (4, hmac, required), + // Removed: (2, nonce, required), + // Removed: (4, hmac, required), }, (1, InboundPayment) => { - (0, nonce, required), - (2, hmac, required), + // Removed: (0, nonce, required), + // Removed: (2, hmac, required), (4, path_absolute_expiry, required), }, (2, OfferPaths) => { @@ -661,18 +627,19 @@ pub(crate) const MESSAGE_PADDING_ROUND_OFF: usize = 100; pub(super) fn blinded_hops( secp_ctx: &Secp256k1, intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey, + local_node_receive_key: ReceiveAuthKey, ) -> Result, secp256k1::Error> { let pks = intermediate_nodes .iter() - .map(|node| node.node_id) - .chain(core::iter::once(recipient_node_id)); + .map(|node| (node.node_id, None)) + .chain(core::iter::once((recipient_node_id, Some(local_node_receive_key)))); let is_compact = intermediate_nodes.iter().any(|node| node.short_channel_id.is_some()); let tlvs = pks .clone() .skip(1) // The first node's TLVs contains the next node's pubkey .zip(intermediate_nodes.iter().map(|node| node.short_channel_id)) - .map(|(pubkey, scid)| match scid { + .map(|((pubkey, _), scid)| match scid { Some(scid) => NextMessageHop::ShortChannelId(scid), None => NextMessageHop::NodeId(pubkey), }) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 95ad76c3644..96913ef3c62 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -664,8 +664,10 @@ pub(super) fn blinded_hops( secp_ctx: &Secp256k1, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, ) -> Result, secp256k1::Error> { - let pks = - intermediate_nodes.iter().map(|node| node.node_id).chain(core::iter::once(payee_node_id)); + let pks = intermediate_nodes + .iter() + .map(|node| (node.node_id, None)) + .chain(core::iter::once((payee_node_id, None))); let tlvs = intermediate_nodes .iter() .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index b17fa01bbcf..f9792157ded 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -17,10 +17,12 @@ use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use super::message::BlindedMessagePath; use super::{BlindedHop, BlindedPath}; -use crate::crypto::streams::ChaChaPolyWriteAdapter; +use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; +use crate::crypto::streams::chachapoly_encrypt_with_swapped_aad; use crate::io; use crate::ln::onion_utils; use crate::onion_message::messenger::Destination; +use crate::sign::ReceiveAuthKey; use crate::util::ser::{Writeable, Writer}; use core::borrow::Borrow; @@ -105,7 +107,6 @@ macro_rules! build_keys_helper { }; } -#[inline] pub(crate) fn construct_keys_for_onion_message<'a, T, I, F>( secp_ctx: &Secp256k1, unblinded_path: I, destination: Destination, session_priv: &SecretKey, mut callback: F, @@ -113,9 +114,13 @@ pub(crate) fn construct_keys_for_onion_message<'a, T, I, F>( where T: secp256k1::Signing + secp256k1::Verification, I: Iterator, - F: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option, Option>), + F: FnMut(SharedSecret, PublicKey, [u8; 32], Option, Option>), { - build_keys_helper!(session_priv, secp_ctx, callback); + let mut callback_wrapper = + |_, ss, pk, encrypted_payload_rho, unblinded_hop_data, encrypted_payload| { + callback(ss, pk, encrypted_payload_rho, unblinded_hop_data, encrypted_payload); + }; + build_keys_helper!(session_priv, secp_ctx, callback_wrapper); for pk in unblinded_path { build_keys_in_loop!(pk, false, None); @@ -133,8 +138,7 @@ where Ok(()) } -#[inline] -pub(super) fn construct_keys_for_blinded_path<'a, T, I, F, H>( +fn construct_keys_for_blinded_path<'a, T, I, F, H>( secp_ctx: &Secp256k1, unblinded_path: I, session_priv: &SecretKey, mut callback: F, ) -> Result<(), secp256k1::Error> where @@ -145,7 +149,8 @@ where { build_keys_helper!(session_priv, secp_ctx, callback); - for pk in unblinded_path { + let mut iter = unblinded_path.peekable(); + while let Some(pk) = iter.next() { build_keys_in_loop!(pk, false, None); } Ok(()) @@ -153,6 +158,7 @@ where struct PublicKeyWithTlvs { pubkey: PublicKey, + hop_recv_key: Option, tlvs: W, } @@ -167,20 +173,26 @@ pub(crate) fn construct_blinded_hops<'a, T, I, W>( ) -> Result, secp256k1::Error> where T: secp256k1::Signing + secp256k1::Verification, - I: Iterator, + I: Iterator), W)>, W: Writeable, { let mut blinded_hops = Vec::with_capacity(unblinded_path.size_hint().0); construct_keys_for_blinded_path( secp_ctx, - unblinded_path.map(|(pubkey, tlvs)| PublicKeyWithTlvs { pubkey, tlvs }), + unblinded_path.map(|((pubkey, hop_recv_key), tlvs)| PublicKeyWithTlvs { + pubkey, + hop_recv_key, + tlvs, + }), session_priv, |blinded_node_id, _, _, encrypted_payload_rho, unblinded_hop_data, _| { + let hop_data = unblinded_hop_data.unwrap(); blinded_hops.push(BlindedHop { blinded_node_id, encrypted_payload: encrypt_payload( - unblinded_hop_data.unwrap().tlvs, + hop_data.tlvs, encrypted_payload_rho, + hop_data.hop_recv_key, ), }); }, @@ -189,9 +201,19 @@ where } /// Encrypt TLV payload to be used as a [`crate::blinded_path::BlindedHop::encrypted_payload`]. -fn encrypt_payload(payload: P, encrypted_tlvs_rho: [u8; 32]) -> Vec { - let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_rho, &payload); - write_adapter.encode() +fn encrypt_payload( + payload: P, encrypted_tlvs_rho: [u8; 32], hop_recv_key: Option, +) -> Vec { + let mut payload_data = payload.encode(); + if let Some(hop_recv_key) = hop_recv_key { + chachapoly_encrypt_with_swapped_aad(payload_data, encrypted_tlvs_rho, hop_recv_key.0) + } else { + let mut chacha = ChaCha20Poly1305RFC::new(&encrypted_tlvs_rho, &[0; 12], &[]); + let mut tag = [0; 16]; + chacha.encrypt_full_message_in_place(&mut payload_data, &mut tag); + payload_data.extend_from_slice(&tag); + payload_data + } } /// A data structure used exclusively to pad blinded path payloads, ensuring they are of diff --git a/lightning/src/crypto/chacha20poly1305rfc.rs b/lightning/src/crypto/chacha20poly1305rfc.rs index f1c261cb1f1..839fad9ce6c 100644 --- a/lightning/src/crypto/chacha20poly1305rfc.rs +++ b/lightning/src/crypto/chacha20poly1305rfc.rs @@ -10,249 +10,148 @@ // This is a port of Andrew Moons poly1305-donna // https://github.com/floodyberry/poly1305-donna -#[cfg(not(fuzzing))] -mod real_chachapoly { - use super::super::chacha20::ChaCha20; - use super::super::fixed_time_eq; - use super::super::poly1305::Poly1305; - - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - cipher: ChaCha20, - mac: Poly1305, - finished: bool, - data_len: usize, - aad_len: u64, - } - - impl ChaCha20Poly1305RFC { - #[inline] - fn pad_mac_16(mac: &mut Poly1305, len: usize) { - if len % 16 != 0 { - mac.input(&[0; 16][0..16 - (len % 16)]); - } - } - pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - - let mut cipher = ChaCha20::new(key, &nonce[4..]); - let mut mac_key = [0u8; 64]; - let zero_key = [0u8; 64]; - cipher.process(&zero_key, &mut mac_key); - - let mut mac = Poly1305::new(&mac_key[..32]); - mac.input(aad); - ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); - - ChaCha20Poly1305RFC { - cipher, - mac, - finished: false, - data_len: 0, - aad_len: aad.len() as u64, - } - } - - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(!self.finished); - self.cipher.process(input, output); - self.data_len += input.len(); - self.mac.input(output); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - pub fn encrypt_full_message_in_place( - &mut self, input_output: &mut [u8], out_tag: &mut [u8], - ) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } - - // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` - // below. - pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(!self.finished); - self.cipher.process_in_place(input_output); - self.data_len += input_output.len(); - self.mac.input(input_output); - } - - // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish - // encrypting and calculate the tag. - pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - debug_assert!(!self.finished); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.finished = true; - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - self.mac.raw_result(out_tag); - } - - /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents - /// into `output`. Note that, because `output` is not touched until the `tag` is checked, - /// this decryption is *variable time*. - pub fn variable_time_decrypt( - &mut self, input: &[u8], output: &mut [u8], tag: &[u8], - ) -> Result<(), ()> { - assert!(input.len() == output.len()); - assert!(!self.finished); - - self.finished = true; - - self.mac.input(input); - - self.data_len += input.len(); - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); - - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - self.cipher.process(input, output); - Ok(()) - } else { - Err(()) - } - } - - pub fn check_decrypt_in_place( - &mut self, input_output: &mut [u8], tag: &[u8], - ) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { - Ok(()) - } else { - Err(()) - } - } - - /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it - /// later when decryption finishes. - /// - /// Should never be `pub` because the public API should always enforce tag checking. - pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { - debug_assert!(!self.finished); - self.mac.input(input_output); - self.data_len += input_output.len(); - self.cipher.process_in_place(input_output); - } - - /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used - /// to check the tag. Returns whether or not the tag is valid. - pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - debug_assert!(!self.finished); - self.finished = true; - ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); - self.mac.input(&self.aad_len.to_le_bytes()); - self.mac.input(&(self.data_len as u64).to_le_bytes()); +use super::chacha20::ChaCha20; +use super::fixed_time_eq; +use super::poly1305::Poly1305; + +pub struct ChaCha20Poly1305RFC { + cipher: ChaCha20, + mac: Poly1305, + finished: bool, + data_len: usize, + aad_len: u64, +} - let mut calc_tag = [0u8; 16]; - self.mac.raw_result(&mut calc_tag); - if fixed_time_eq(&calc_tag, tag) { - true - } else { - false - } +impl ChaCha20Poly1305RFC { + #[inline] + fn pad_mac_16(mac: &mut Poly1305, len: usize) { + if len % 16 != 0 { + mac.input(&[0; 16][0..16 - (len % 16)]); } } -} -#[cfg(not(fuzzing))] -pub use self::real_chachapoly::ChaCha20Poly1305RFC; - -#[cfg(fuzzing)] -mod fuzzy_chachapoly { - #[derive(Clone, Copy)] - pub struct ChaCha20Poly1305RFC { - tag: [u8; 16], - finished: bool, + pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC { + assert!(key.len() == 16 || key.len() == 32); + assert!(nonce.len() == 12); + + // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant + assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); + + let mut cipher = ChaCha20::new(key, &nonce[4..]); + let mut mac_key = [0u8; 64]; + let zero_key = [0u8; 64]; + cipher.process(&zero_key, &mut mac_key); + + #[cfg(not(fuzzing))] + let mut mac = Poly1305::new(&mac_key[..32]); + #[cfg(fuzzing)] + let mut mac = Poly1305::new(&key); + mac.input(aad); + ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len()); + + ChaCha20Poly1305RFC { cipher, mac, finished: false, data_len: 0, aad_len: aad.len() as u64 } } - impl ChaCha20Poly1305RFC { - pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC { - assert!(key.len() == 16 || key.len() == 32); - assert!(nonce.len() == 12); - - // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant - assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0); - let mut tag = [0; 16]; - tag.copy_from_slice(&key[0..16]); + pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { + assert!(input.len() == output.len()); + assert!(!self.finished); + self.cipher.process(input, output); + self.data_len += input.len(); + self.mac.input(output); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + out_tag.copy_from_slice(&self.mac.result()); + } - ChaCha20Poly1305RFC { tag, finished: false } - } + pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) { + self.encrypt_in_place(input_output); + self.finish_and_get_tag(out_tag); + } - pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) { - assert!(input.len() == output.len()); - assert!(self.finished == false); + // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag` + // below. + pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(!self.finished); + self.cipher.process_in_place(input_output); + self.data_len += input_output.len(); + self.mac.input(input_output); + } - output.copy_from_slice(&input); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } + // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish + // encrypting and calculate the tag. + pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { + debug_assert!(!self.finished); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.finished = true; + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + out_tag.copy_from_slice(&self.mac.result()); + } - pub fn encrypt_full_message_in_place( - &mut self, input_output: &mut [u8], out_tag: &mut [u8], - ) { - self.encrypt_in_place(input_output); - self.finish_and_get_tag(out_tag); - } + /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents + /// into `output`. Note that, because `output` is not touched until the `tag` is checked, + /// this decryption is *variable time*. + pub fn variable_time_decrypt( + &mut self, input: &[u8], output: &mut [u8], tag: &[u8], + ) -> Result<(), ()> { + assert!(input.len() == output.len()); + assert!(!self.finished); - pub(in super::super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) { - assert!(self.finished == false); - } + self.finished = true; - pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) { - assert!(self.finished == false); - out_tag.copy_from_slice(&self.tag); - self.finished = true; - } + self.mac.input(input); - pub fn variable_time_decrypt( - &mut self, input: &[u8], output: &mut [u8], tag: &[u8], - ) -> Result<(), ()> { - assert!(input.len() == output.len()); - assert!(self.finished == false); + self.data_len += input.len(); + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); - if tag[..] != self.tag[..] { - return Err(()); - } - output.copy_from_slice(input); - self.finished = true; + let calc_tag = self.mac.result(); + if fixed_time_eq(&calc_tag, tag) { + self.cipher.process(input, output); Ok(()) + } else { + Err(()) } + } - pub fn check_decrypt_in_place( - &mut self, input_output: &mut [u8], tag: &[u8], - ) -> Result<(), ()> { - self.decrypt_in_place(input_output); - if self.finish_and_check_tag(tag) { - Ok(()) - } else { - Err(()) - } + pub fn check_decrypt_in_place( + &mut self, input_output: &mut [u8], tag: &[u8], + ) -> Result<(), ()> { + self.decrypt_in_place(input_output); + if self.finish_and_check_tag(tag) { + Ok(()) + } else { + Err(()) } + } - pub(in super::super) fn decrypt_in_place(&mut self, _input: &mut [u8]) { - assert!(self.finished == false); - } + /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it + /// later when decryption finishes. + /// + /// Should never be `pub` because the public API should always enforce tag checking. + pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) { + debug_assert!(!self.finished); + self.mac.input(input_output); + self.data_len += input_output.len(); + self.cipher.process_in_place(input_output); + } - pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { - if tag[..] != self.tag[..] { - return false; - } - self.finished = true; + /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used + /// to check the tag. Returns whether or not the tag is valid. + pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool { + debug_assert!(!self.finished); + self.finished = true; + ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len); + self.mac.input(&self.aad_len.to_le_bytes()); + self.mac.input(&(self.data_len as u64).to_le_bytes()); + + let calc_tag = self.mac.result(); + if fixed_time_eq(&calc_tag, tag) { true + } else { + false } } } -#[cfg(fuzzing)] -pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC; diff --git a/lightning/src/crypto/mod.rs b/lightning/src/crypto/mod.rs index 4dc851a3683..478918a49a8 100644 --- a/lightning/src/crypto/mod.rs +++ b/lightning/src/crypto/mod.rs @@ -1,9 +1,14 @@ #[cfg(not(fuzzing))] -use bitcoin::hashes::cmp::fixed_time_eq; +pub(crate) use bitcoin::hashes::cmp::fixed_time_eq; + +#[cfg(fuzzing)] +fn fixed_time_eq(a: &[u8], b: &[u8]) -> bool { + assert_eq!(a.len(), b.len()); + a == b +} pub(crate) mod chacha20; pub(crate) mod chacha20poly1305rfc; -#[cfg(not(fuzzing))] pub(crate) mod poly1305; pub(crate) mod streams; pub(crate) mod utils; diff --git a/lightning/src/crypto/poly1305.rs b/lightning/src/crypto/poly1305.rs index 6ac1c6c9694..c7306863e9e 100644 --- a/lightning/src/crypto/poly1305.rs +++ b/lightning/src/crypto/poly1305.rs @@ -7,385 +7,426 @@ // This is a port of Andrew Moons poly1305-donna // https://github.com/floodyberry/poly1305-donna -use core::cmp::min; - -use crate::prelude::*; - -#[derive(Clone, Copy)] -pub struct Poly1305 { - r: [u32; 5], - h: [u32; 5], - pad: [u32; 4], - leftover: usize, - buffer: [u8; 16], - finalized: bool, -} - -impl Poly1305 { - pub fn new(key: &[u8]) -> Poly1305 { - assert!(key.len() == 32); - let mut poly = Poly1305 { - r: [0u32; 5], - h: [0u32; 5], - pad: [0u32; 4], - leftover: 0, - buffer: [0u8; 16], - finalized: false, - }; - - // r &= 0xffffffc0ffffffc0ffffffc0fffffff - poly.r[0] = (u32::from_le_bytes(key[0..4].try_into().expect("len is 4"))) & 0x3ffffff; - poly.r[1] = (u32::from_le_bytes(key[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffff03; - poly.r[2] = (u32::from_le_bytes(key[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffc0ff; - poly.r[3] = (u32::from_le_bytes(key[9..13].try_into().expect("len is 4")) >> 6) & 0x3f03fff; - poly.r[4] = - (u32::from_le_bytes(key[12..16].try_into().expect("len is 4")) >> 8) & 0x00fffff; - - poly.pad[0] = u32::from_le_bytes(key[16..20].try_into().expect("len is 4")); - poly.pad[1] = u32::from_le_bytes(key[20..24].try_into().expect("len is 4")); - poly.pad[2] = u32::from_le_bytes(key[24..28].try_into().expect("len is 4")); - poly.pad[3] = u32::from_le_bytes(key[28..32].try_into().expect("len is 4")); - - poly +#[cfg(not(fuzzing))] +mod fuzzy_poly1305 { + use core::cmp::min; + + #[derive(Clone, Copy)] + pub struct Poly1305 { + r: [u32; 5], + h: [u32; 5], + pad: [u32; 4], + leftover: usize, + buffer: [u8; 16], + finalized: bool, } - fn block(&mut self, m: &[u8]) { - let hibit: u32 = if self.finalized { 0 } else { 1 << 24 }; - - let r0 = self.r[0]; - let r1 = self.r[1]; - let r2 = self.r[2]; - let r3 = self.r[3]; - let r4 = self.r[4]; - - let s1 = r1 * 5; - let s2 = r2 * 5; - let s3 = r3 * 5; - let s4 = r4 * 5; - - let mut h0 = self.h[0]; - let mut h1 = self.h[1]; - let mut h2 = self.h[2]; - let mut h3 = self.h[3]; - let mut h4 = self.h[4]; - - // h += m - h0 += (u32::from_le_bytes(m[0..4].try_into().expect("len is 4"))) & 0x3ffffff; - h1 += (u32::from_le_bytes(m[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffffff; - h2 += (u32::from_le_bytes(m[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffffff; - h3 += (u32::from_le_bytes(m[9..13].try_into().expect("len is 4")) >> 6) & 0x3ffffff; - h4 += (u32::from_le_bytes(m[12..16].try_into().expect("len is 4")) >> 8) | hibit; - - // h *= r - let d0 = (h0 as u64 * r0 as u64) - + (h1 as u64 * s4 as u64) - + (h2 as u64 * s3 as u64) - + (h3 as u64 * s2 as u64) - + (h4 as u64 * s1 as u64); - let mut d1 = (h0 as u64 * r1 as u64) - + (h1 as u64 * r0 as u64) - + (h2 as u64 * s4 as u64) - + (h3 as u64 * s3 as u64) - + (h4 as u64 * s2 as u64); - let mut d2 = (h0 as u64 * r2 as u64) - + (h1 as u64 * r1 as u64) - + (h2 as u64 * r0 as u64) - + (h3 as u64 * s4 as u64) - + (h4 as u64 * s3 as u64); - let mut d3 = (h0 as u64 * r3 as u64) - + (h1 as u64 * r2 as u64) - + (h2 as u64 * r1 as u64) - + (h3 as u64 * r0 as u64) - + (h4 as u64 * s4 as u64); - let mut d4 = (h0 as u64 * r4 as u64) - + (h1 as u64 * r3 as u64) - + (h2 as u64 * r2 as u64) - + (h3 as u64 * r1 as u64) - + (h4 as u64 * r0 as u64); - - // (partial) h %= p - let mut c: u32; - c = (d0 >> 26) as u32; - h0 = d0 as u32 & 0x3ffffff; - d1 += c as u64; - c = (d1 >> 26) as u32; - h1 = d1 as u32 & 0x3ffffff; - d2 += c as u64; - c = (d2 >> 26) as u32; - h2 = d2 as u32 & 0x3ffffff; - d3 += c as u64; - c = (d3 >> 26) as u32; - h3 = d3 as u32 & 0x3ffffff; - d4 += c as u64; - c = (d4 >> 26) as u32; - h4 = d4 as u32 & 0x3ffffff; - h0 += c * 5; - c = h0 >> 26; - h0 &= 0x3ffffff; - h1 += c; - - self.h[0] = h0; - self.h[1] = h1; - self.h[2] = h2; - self.h[3] = h3; - self.h[4] = h4; - } + impl Poly1305 { + pub fn new(key: &[u8]) -> Poly1305 { + assert!(key.len() == 32); + let mut poly = Poly1305 { + r: [0u32; 5], + h: [0u32; 5], + pad: [0u32; 4], + leftover: 0, + buffer: [0u8; 16], + finalized: false, + }; + + // r &= 0xffffffc0ffffffc0ffffffc0fffffff + poly.r[0] = (u32::from_le_bytes(key[0..4].try_into().expect("len is 4"))) & 0x3ffffff; + poly.r[1] = + (u32::from_le_bytes(key[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffff03; + poly.r[2] = + (u32::from_le_bytes(key[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffc0ff; + poly.r[3] = + (u32::from_le_bytes(key[9..13].try_into().expect("len is 4")) >> 6) & 0x3f03fff; + poly.r[4] = + (u32::from_le_bytes(key[12..16].try_into().expect("len is 4")) >> 8) & 0x00fffff; + + poly.pad[0] = u32::from_le_bytes(key[16..20].try_into().expect("len is 4")); + poly.pad[1] = u32::from_le_bytes(key[20..24].try_into().expect("len is 4")); + poly.pad[2] = u32::from_le_bytes(key[24..28].try_into().expect("len is 4")); + poly.pad[3] = u32::from_le_bytes(key[28..32].try_into().expect("len is 4")); + + poly + } + + fn block(&mut self, m: &[u8]) { + let hibit: u32 = if self.finalized { 0 } else { 1 << 24 }; + + let r0 = self.r[0]; + let r1 = self.r[1]; + let r2 = self.r[2]; + let r3 = self.r[3]; + let r4 = self.r[4]; + + let s1 = r1 * 5; + let s2 = r2 * 5; + let s3 = r3 * 5; + let s4 = r4 * 5; + + let mut h0 = self.h[0]; + let mut h1 = self.h[1]; + let mut h2 = self.h[2]; + let mut h3 = self.h[3]; + let mut h4 = self.h[4]; + + // h += m + h0 += (u32::from_le_bytes(m[0..4].try_into().expect("len is 4"))) & 0x3ffffff; + h1 += (u32::from_le_bytes(m[3..7].try_into().expect("len is 4")) >> 2) & 0x3ffffff; + h2 += (u32::from_le_bytes(m[6..10].try_into().expect("len is 4")) >> 4) & 0x3ffffff; + h3 += (u32::from_le_bytes(m[9..13].try_into().expect("len is 4")) >> 6) & 0x3ffffff; + h4 += (u32::from_le_bytes(m[12..16].try_into().expect("len is 4")) >> 8) | hibit; + + // h *= r + let d0 = (h0 as u64 * r0 as u64) + + (h1 as u64 * s4 as u64) + + (h2 as u64 * s3 as u64) + + (h3 as u64 * s2 as u64) + + (h4 as u64 * s1 as u64); + let mut d1 = (h0 as u64 * r1 as u64) + + (h1 as u64 * r0 as u64) + + (h2 as u64 * s4 as u64) + + (h3 as u64 * s3 as u64) + + (h4 as u64 * s2 as u64); + let mut d2 = (h0 as u64 * r2 as u64) + + (h1 as u64 * r1 as u64) + + (h2 as u64 * r0 as u64) + + (h3 as u64 * s4 as u64) + + (h4 as u64 * s3 as u64); + let mut d3 = (h0 as u64 * r3 as u64) + + (h1 as u64 * r2 as u64) + + (h2 as u64 * r1 as u64) + + (h3 as u64 * r0 as u64) + + (h4 as u64 * s4 as u64); + let mut d4 = (h0 as u64 * r4 as u64) + + (h1 as u64 * r3 as u64) + + (h2 as u64 * r2 as u64) + + (h3 as u64 * r1 as u64) + + (h4 as u64 * r0 as u64); + + // (partial) h %= p + let mut c: u32; + c = (d0 >> 26) as u32; + h0 = d0 as u32 & 0x3ffffff; + d1 += c as u64; + c = (d1 >> 26) as u32; + h1 = d1 as u32 & 0x3ffffff; + d2 += c as u64; + c = (d2 >> 26) as u32; + h2 = d2 as u32 & 0x3ffffff; + d3 += c as u64; + c = (d3 >> 26) as u32; + h3 = d3 as u32 & 0x3ffffff; + d4 += c as u64; + c = (d4 >> 26) as u32; + h4 = d4 as u32 & 0x3ffffff; + h0 += c * 5; + c = h0 >> 26; + h0 &= 0x3ffffff; + h1 += c; + + self.h[0] = h0; + self.h[1] = h1; + self.h[2] = h2; + self.h[3] = h3; + self.h[4] = h4; + } - pub fn finish(&mut self) { - if self.leftover > 0 { - self.buffer[self.leftover] = 1; - for i in self.leftover + 1..16 { - self.buffer[i] = 0; + pub fn finish(&mut self) { + if self.leftover > 0 { + self.buffer[self.leftover] = 1; + for i in self.leftover + 1..16 { + self.buffer[i] = 0; + } + self.finalized = true; + let tmp = self.buffer; + self.block(&tmp); } - self.finalized = true; - let tmp = self.buffer; - self.block(&tmp); + + // fully carry h + let mut h0 = self.h[0]; + let mut h1 = self.h[1]; + let mut h2 = self.h[2]; + let mut h3 = self.h[3]; + let mut h4 = self.h[4]; + + let mut c: u32; + c = h1 >> 26; + h1 &= 0x3ffffff; + h2 += c; + c = h2 >> 26; + h2 &= 0x3ffffff; + h3 += c; + c = h3 >> 26; + h3 &= 0x3ffffff; + h4 += c; + c = h4 >> 26; + h4 &= 0x3ffffff; + h0 += c * 5; + c = h0 >> 26; + h0 &= 0x3ffffff; + h1 += c; + + // compute h + -p + let mut g0 = h0.wrapping_add(5); + c = g0 >> 26; + g0 &= 0x3ffffff; + let mut g1 = h1.wrapping_add(c); + c = g1 >> 26; + g1 &= 0x3ffffff; + let mut g2 = h2.wrapping_add(c); + c = g2 >> 26; + g2 &= 0x3ffffff; + let mut g3 = h3.wrapping_add(c); + c = g3 >> 26; + g3 &= 0x3ffffff; + let mut g4 = h4.wrapping_add(c).wrapping_sub(1 << 26); + + // select h if h < p, or h + -p if h >= p + let mut mask = (g4 >> (32 - 1)).wrapping_sub(1); + g0 &= mask; + g1 &= mask; + g2 &= mask; + g3 &= mask; + g4 &= mask; + mask = !mask; + h0 = (h0 & mask) | g0; + h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; + h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + // h = h % (2^128) + h0 = ((h0) | (h1 << 26)) & 0xffffffff; + h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; + h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; + h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; + + // h = mac = (h + pad) % (2^128) + let mut f: u64; + f = h0 as u64 + self.pad[0] as u64; + h0 = f as u32; + f = h1 as u64 + self.pad[1] as u64 + (f >> 32); + h1 = f as u32; + f = h2 as u64 + self.pad[2] as u64 + (f >> 32); + h2 = f as u32; + f = h3 as u64 + self.pad[3] as u64 + (f >> 32); + h3 = f as u32; + + self.h[0] = h0; + self.h[1] = h1; + self.h[2] = h2; + self.h[3] = h3; } - // fully carry h - let mut h0 = self.h[0]; - let mut h1 = self.h[1]; - let mut h2 = self.h[2]; - let mut h3 = self.h[3]; - let mut h4 = self.h[4]; - - let mut c: u32; - c = h1 >> 26; - h1 &= 0x3ffffff; - h2 += c; - c = h2 >> 26; - h2 &= 0x3ffffff; - h3 += c; - c = h3 >> 26; - h3 &= 0x3ffffff; - h4 += c; - c = h4 >> 26; - h4 &= 0x3ffffff; - h0 += c * 5; - c = h0 >> 26; - h0 &= 0x3ffffff; - h1 += c; - - // compute h + -p - let mut g0 = h0.wrapping_add(5); - c = g0 >> 26; - g0 &= 0x3ffffff; - let mut g1 = h1.wrapping_add(c); - c = g1 >> 26; - g1 &= 0x3ffffff; - let mut g2 = h2.wrapping_add(c); - c = g2 >> 26; - g2 &= 0x3ffffff; - let mut g3 = h3.wrapping_add(c); - c = g3 >> 26; - g3 &= 0x3ffffff; - let mut g4 = h4.wrapping_add(c).wrapping_sub(1 << 26); - - // select h if h < p, or h + -p if h >= p - let mut mask = (g4 >> (32 - 1)).wrapping_sub(1); - g0 &= mask; - g1 &= mask; - g2 &= mask; - g3 &= mask; - g4 &= mask; - mask = !mask; - h0 = (h0 & mask) | g0; - h1 = (h1 & mask) | g1; - h2 = (h2 & mask) | g2; - h3 = (h3 & mask) | g3; - h4 = (h4 & mask) | g4; - - // h = h % (2^128) - h0 = ((h0) | (h1 << 26)) & 0xffffffff; - h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; - h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; - h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; - - // h = mac = (h + pad) % (2^128) - let mut f: u64; - f = h0 as u64 + self.pad[0] as u64; - h0 = f as u32; - f = h1 as u64 + self.pad[1] as u64 + (f >> 32); - h1 = f as u32; - f = h2 as u64 + self.pad[2] as u64 + (f >> 32); - h2 = f as u32; - f = h3 as u64 + self.pad[3] as u64 + (f >> 32); - h3 = f as u32; - - self.h[0] = h0; - self.h[1] = h1; - self.h[2] = h2; - self.h[3] = h3; - } + pub fn input(&mut self, data: &[u8]) { + assert!(!self.finalized); + let mut m = data; - pub fn input(&mut self, data: &[u8]) { - assert!(!self.finalized); - let mut m = data; + if self.leftover > 0 { + let want = min(16 - self.leftover, m.len()); + for i in 0..want { + self.buffer[self.leftover + i] = m[i]; + } + m = &m[want..]; + self.leftover += want; - if self.leftover > 0 { - let want = min(16 - self.leftover, m.len()); - for i in 0..want { - self.buffer[self.leftover + i] = m[i]; - } - m = &m[want..]; - self.leftover += want; + if self.leftover < 16 { + return; + } - if self.leftover < 16 { - return; + // self.block(self.buffer[..]); + let tmp = self.buffer; + self.block(&tmp); + + self.leftover = 0; } - // self.block(self.buffer[..]); - let tmp = self.buffer; - self.block(&tmp); + while m.len() >= 16 { + self.block(&m[0..16]); + m = &m[16..]; + } - self.leftover = 0; + for i in 0..m.len() { + self.buffer[i] = m[i]; + } + self.leftover = m.len(); } - while m.len() >= 16 { - self.block(&m[0..16]); - m = &m[16..]; + pub fn result(&mut self) -> [u8; 16] { + if !self.finalized { + self.finish(); + } + let mut output = [0; 16]; + output[0..4].copy_from_slice(&self.h[0].to_le_bytes()); + output[4..8].copy_from_slice(&self.h[1].to_le_bytes()); + output[8..12].copy_from_slice(&self.h[2].to_le_bytes()); + output[12..16].copy_from_slice(&self.h[3].to_le_bytes()); + output } + } - for i in 0..m.len() { - self.buffer[i] = m[i]; + #[cfg(test)] + mod test { + use core::iter::repeat; + + use super::Poly1305; + + fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8; 16]) { + let mut poly = Poly1305::new(key); + poly.input(msg); + *mac = poly.result(); } - self.leftover = m.len(); - } - pub fn raw_result(&mut self, output: &mut [u8]) { - assert!(output.len() >= 16); - if !self.finalized { - self.finish(); + #[test] + fn test_nacl_vector() { + let key = [ + 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, + 0x3c, 0x25, 0x25, 0x39, 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, 0x2d, 0x65, 0x1f, 0xa4, + 0xc8, 0xcf, 0xf8, 0x80, + ]; + + let msg = [ + 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, + 0x76, 0xce, 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, 0x47, 0x6f, 0xb8, 0xc5, + 0x31, 0xa1, 0x18, 0x6a, 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, 0x4d, 0xa7, + 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, + 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, + 0xcc, 0x8a, 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, 0x90, 0x22, 0x43, 0x68, + 0x51, 0x7a, 0xcf, 0xea, 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, 0x99, 0x83, + 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, + 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, + 0x5a, 0x74, 0xe3, 0x55, 0xa5, + ]; + + let expected = [ + 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, + 0x05, 0xd9, + ]; + + let mut mac = [0u8; 16]; + poly1305(&key, &msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); + + let mut poly = Poly1305::new(&key); + poly.input(&msg[0..32]); + poly.input(&msg[32..96]); + poly.input(&msg[96..112]); + poly.input(&msg[112..120]); + poly.input(&msg[120..124]); + poly.input(&msg[124..126]); + poly.input(&msg[126..127]); + poly.input(&msg[127..128]); + poly.input(&msg[128..129]); + poly.input(&msg[129..130]); + poly.input(&msg[130..131]); + let mac = poly.result(); + assert_eq!(&mac[..], &expected[..]); } - output[0..4].copy_from_slice(&self.h[0].to_le_bytes()); - output[4..8].copy_from_slice(&self.h[1].to_le_bytes()); - output[8..12].copy_from_slice(&self.h[2].to_le_bytes()); - output[12..16].copy_from_slice(&self.h[3].to_le_bytes()); - } -} -#[cfg(test)] -mod test { - use core::iter::repeat; + #[test] + fn donna_self_test() { + let wrap_key = [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; - use super::Poly1305; + let wrap_msg = [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, + ]; - fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8]) { - let mut poly = Poly1305::new(key); - poly.input(msg); - poly.raw_result(mac); - } + let wrap_mac = [ + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; - #[test] - fn test_nacl_vector() { - let key = [ - 0xee, 0xa6, 0xa7, 0x25, 0x1c, 0x1e, 0x72, 0x91, 0x6d, 0x11, 0xc2, 0xcb, 0x21, 0x4d, - 0x3c, 0x25, 0x25, 0x39, 0x12, 0x1d, 0x8e, 0x23, 0x4e, 0x65, 0x2d, 0x65, 0x1f, 0xa4, - 0xc8, 0xcf, 0xf8, 0x80, - ]; - - let msg = [ - 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, - 0x76, 0xce, 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, 0x47, 0x6f, 0xb8, 0xc5, - 0x31, 0xa1, 0x18, 0x6a, 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, 0x4d, 0xa7, - 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, - 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, - 0xcc, 0x8a, 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, 0x90, 0x22, 0x43, 0x68, - 0x51, 0x7a, 0xcf, 0xea, 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, 0x99, 0x83, - 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, - 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, - 0x5a, 0x74, 0xe3, 0x55, 0xa5, - ]; - - let expected = [ - 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, - 0x05, 0xd9, - ]; - - let mut mac = [0u8; 16]; - poly1305(&key, &msg, &mut mac); - assert_eq!(&mac[..], &expected[..]); - - let mut poly = Poly1305::new(&key); - poly.input(&msg[0..32]); - poly.input(&msg[32..96]); - poly.input(&msg[96..112]); - poly.input(&msg[112..120]); - poly.input(&msg[120..124]); - poly.input(&msg[124..126]); - poly.input(&msg[126..127]); - poly.input(&msg[127..128]); - poly.input(&msg[128..129]); - poly.input(&msg[129..130]); - poly.input(&msg[130..131]); - poly.raw_result(&mut mac); - assert_eq!(&mac[..], &expected[..]); - } + let mut mac = [0u8; 16]; + poly1305(&wrap_key, &wrap_msg, &mut mac); + assert_eq!(&mac[..], &wrap_mac[..]); + + let total_key = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + ]; + + let total_mac = [ + 0x64, 0xaf, 0xe2, 0xe8, 0xd6, 0xad, 0x7b, 0xbd, 0xd2, 0x87, 0xf9, 0x7c, 0x44, 0x62, + 0x3d, 0x39, + ]; + + let mut tpoly = Poly1305::new(&total_key); + for i in 0..256 { + let key: Vec = repeat(i as u8).take(32).collect(); + let msg: Vec = repeat(i as u8).take(256).collect(); + let mut mac = [0u8; 16]; + poly1305(&key[..], &msg[0..i], &mut mac); + tpoly.input(&mac); + } + let mac = tpoly.result(); + assert_eq!(&mac[..], &total_mac[..]); + } - #[test] - fn donna_self_test() { - let wrap_key = [ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ]; - - let wrap_msg = [ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, - ]; - - let wrap_mac = [ - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - - let mut mac = [0u8; 16]; - poly1305(&wrap_key, &wrap_msg, &mut mac); - assert_eq!(&mac[..], &wrap_mac[..]); - - let total_key = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, - ]; - - let total_mac = [ - 0x64, 0xaf, 0xe2, 0xe8, 0xd6, 0xad, 0x7b, 0xbd, 0xd2, 0x87, 0xf9, 0x7c, 0x44, 0x62, - 0x3d, 0x39, - ]; - - let mut tpoly = Poly1305::new(&total_key); - for i in 0..256 { - let key: Vec = repeat(i as u8).take(32).collect(); - let msg: Vec = repeat(i as u8).take(256).collect(); + #[test] + fn test_tls_vectors() { + // from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 + let key = b"this is 32-byte key for Poly1305"; + let msg = [0u8; 32]; + let expected = [ + 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, + 0x03, 0x07, + ]; let mut mac = [0u8; 16]; - poly1305(&key[..], &msg[0..i], &mut mac); - tpoly.input(&mac); + poly1305(key, &msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); + + let msg = b"Hello world!"; + let expected = [ + 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, + 0xb2, 0xf0, + ]; + poly1305(key, msg, &mut mac); + assert_eq!(&mac[..], &expected[..]); } - tpoly.raw_result(&mut mac); - assert_eq!(&mac[..], &total_mac[..]); } +} +pub use fuzzy_poly1305::*; + +#[cfg(fuzzing)] +mod fuzzy_poly1305 { + #[derive(Clone, Copy)] + pub struct Poly1305 { + tag: [u8; 16], + finalized: bool, + } + + impl Poly1305 { + pub fn new(key: &[u8]) -> Poly1305 { + assert_eq!(key.len(), 32); + let mut poly = Poly1305 { tag: [0; 16], finalized: false }; + poly.tag.copy_from_slice(&key[..16]); - #[test] - fn test_tls_vectors() { - // from http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04 - let key = b"this is 32-byte key for Poly1305"; - let msg = [0u8; 32]; - let expected = [ - 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, - 0x03, 0x07, - ]; - let mut mac = [0u8; 16]; - poly1305(key, &msg, &mut mac); - assert_eq!(&mac[..], &expected[..]); - - let msg = b"Hello world!"; - let expected = [ - 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, - 0xb2, 0xf0, - ]; - poly1305(key, msg, &mut mac); - assert_eq!(&mac[..], &expected[..]); + poly + } + + pub fn finish(&mut self) { + self.finalized = true; + } + + pub fn input(&mut self, _data: &[u8]) { + assert!(!self.finalized); + } + + pub fn result(&mut self) -> [u8; 16] { + if !self.finalized { + self.finish(); + } + self.tag + } } } +pub use fuzzy_poly1305::*; diff --git a/lightning/src/crypto/streams.rs b/lightning/src/crypto/streams.rs index 82680131f1d..7f30245325b 100644 --- a/lightning/src/crypto/streams.rs +++ b/lightning/src/crypto/streams.rs @@ -1,5 +1,7 @@ use crate::crypto::chacha20::ChaCha20; use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; +use crate::crypto::fixed_time_eq; +use crate::crypto::poly1305::Poly1305; use crate::io::{self, Read, Write}; use crate::ln::msgs::DecodeError; @@ -7,6 +9,8 @@ use crate::util::ser::{ FixedLengthReader, LengthLimitedRead, LengthReadableArgs, Readable, Writeable, Writer, }; +use alloc::vec::Vec; + pub(crate) struct ChaChaReader<'a, R: io::Read> { pub chacha: &'a mut ChaCha20, pub read: R, @@ -49,6 +53,132 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> { } } +/// Encrypts the provided plaintext with the given key using ChaCha20Poly1305 in the modified +/// with-AAD form used in [`ChaChaDualPolyReadAdapter`]. +pub(crate) fn chachapoly_encrypt_with_swapped_aad( + mut plaintext: Vec, key: [u8; 32], aad: [u8; 32], +) -> Vec { + let mut chacha = ChaCha20::new(&key[..], &[0; 12]); + let mut mac_key = [0u8; 64]; + chacha.process_in_place(&mut mac_key); + + let mut mac = Poly1305::new(&mac_key[..32]); + chacha.process_in_place(&mut plaintext[..]); + mac.input(&plaintext[..]); + + if plaintext.len() % 16 != 0 { + mac.input(&[0; 16][0..16 - (plaintext.len() % 16)]); + } + + mac.input(&aad[..]); + // Note that we don't need to pad the AAD since its a multiple of 16 bytes + + mac.input(&(plaintext.len() as u64).to_le_bytes()); + mac.input(&32u64.to_le_bytes()); + + plaintext.extend_from_slice(&mac.result()); + plaintext +} + +/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted +/// and deserialized. This allows us to avoid an intermediate Vec allocation. +/// +/// This variant of [`ChaChaPolyReadAdapter`] calculates Poly1305 tags twice, once using the given +/// key and once with the given 32-byte AAD appended after the encrypted stream, accepting either +/// being correct as sufficient. +/// +/// Note that we do *not* use the provided AAD as the standard ChaCha20Poly1305 AAD as that would +/// require placing it first and prevent us from avoiding redundant Poly1305 rounds. Instead, the +/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the the contents being +/// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving +/// like classic ChaCha20Poly1305 for the non-AAD-containing MAC. +pub(crate) struct ChaChaDualPolyReadAdapter { + pub readable: R, + pub used_aad: bool, +} + +impl LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyReadAdapter { + // Simultaneously read and decrypt an object from a LengthLimitedRead storing it in + // Self::readable. LengthLimitedRead must be used instead of std::io::Read because we need the + // total length to separate out the tag at the end. + fn read( + r: &mut R, params: ([u8; 32], [u8; 32]), + ) -> Result { + if r.remaining_bytes() < 16 { + return Err(DecodeError::InvalidValue); + } + let (key, aad) = params; + + let mut chacha = ChaCha20::new(&key[..], &[0; 12]); + let mut mac_key = [0u8; 64]; + chacha.process_in_place(&mut mac_key); + + #[cfg(not(fuzzing))] + let mut mac = Poly1305::new(&mac_key[..32]); + #[cfg(fuzzing)] + let mut mac = Poly1305::new(&key); + + let decrypted_len = r.remaining_bytes() - 16; + let s = FixedLengthReader::new(r, decrypted_len); + let mut chacha_stream = + ChaChaDualPolyReader { chacha: &mut chacha, poly: &mut mac, read_len: 0, read: s }; + + let readable: T = Readable::read(&mut chacha_stream)?; + chacha_stream.read.eat_remaining()?; + + let read_len = chacha_stream.read_len; + + if read_len % 16 != 0 { + mac.input(&[0; 16][0..16 - (read_len % 16)]); + } + + let mut mac_aad = mac; + + mac_aad.input(&aad[..]); + // Note that we don't need to pad the AAD since its a multiple of 16 bytes + + // For the AAD-containing MAC, swap the AAD and the read data, effectively. + mac_aad.input(&(read_len as u64).to_le_bytes()); + mac_aad.input(&32u64.to_le_bytes()); + + // For the non-AAD-containing MAC, leave the data and AAD where they belong. + mac.input(&0u64.to_le_bytes()); + mac.input(&(read_len as u64).to_le_bytes()); + + let mut tag = [0 as u8; 16]; + r.read_exact(&mut tag)?; + if fixed_time_eq(&mac.result(), &tag) { + Ok(Self { readable, used_aad: false }) + } else if fixed_time_eq(&mac_aad.result(), &tag) { + Ok(Self { readable, used_aad: true }) + } else { + return Err(DecodeError::InvalidValue); + } + } +} + +struct ChaChaDualPolyReader<'a, R: Read> { + chacha: &'a mut ChaCha20, + poly: &'a mut Poly1305, + read_len: usize, + pub read: R, +} + +impl<'a, R: Read> Read for ChaChaDualPolyReader<'a, R> { + // Decrypt bytes from Self::read into `dest`. + // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads + // complete. + fn read(&mut self, dest: &mut [u8]) -> Result { + let res = self.read.read(dest)?; + if res > 0 { + self.poly.input(&dest[0..res]); + self.chacha.process_in_place(&mut dest[0..res]); + self.read_len += res; + } + Ok(res) + } +} + /// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and /// deserialized. This allows us to avoid an intermediate Vec allocation. pub(crate) struct ChaChaPolyReadAdapter { diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index ab9f172ce1b..3c89837f4f2 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -247,6 +247,7 @@ fn create_static_invoice( .message_router .create_blinded_paths( always_online_counterparty.node.get_our_node_id(), + always_online_counterparty.keys_manager.get_receive_auth_key(), MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), Vec::new(), &secp_ctx, @@ -383,6 +384,7 @@ fn static_invoice_unknown_required_features() { .message_router .create_blinded_paths( nodes[1].node.get_our_node_id(), + nodes[1].keys_manager.get_receive_auth_key(), MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), Vec::new(), &secp_ctx, @@ -1023,6 +1025,7 @@ fn invalid_async_receive_with_retry( .message_router .create_blinded_paths( nodes[1].node.get_our_node_id(), + nodes[1].keys_manager.get_receive_auth_key(), MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), Vec::new(), &secp_ctx, diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 4a868e0eb76..7c28f4a8cf2 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -36,7 +36,7 @@ use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::nonce::Nonce; use crate::prelude::*; use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters, TrampolineHop}; -use crate::sign::{NodeSigner, PeerStorageKey, Recipient}; +use crate::sign::{NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient}; use crate::util::config::UserConfig; use crate::util::ser::{WithoutLength, Writeable}; use crate::util::test_utils; @@ -1556,17 +1556,23 @@ fn route_blinding_spec_test_vector() { let blinding_override = PublicKey::from_secret_key(&secp_ctx, &dave_eve_session_priv); assert_eq!(blinding_override, pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")); // Can't use the public API here as the encrypted payloads contain unknown TLVs. - let path = [(dave_node_id, WithoutLength(&dave_unblinded_tlvs)), (eve_node_id, WithoutLength(&eve_unblinded_tlvs))]; + let path = [ + ((dave_node_id, None), WithoutLength(&dave_unblinded_tlvs)), + ((eve_node_id, None), WithoutLength(&eve_unblinded_tlvs)), + ]; let mut dave_eve_blinded_hops = blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &dave_eve_session_priv + &secp_ctx, path.into_iter(), &dave_eve_session_priv, ).unwrap(); // Concatenate an additional Bob -> Carol blinded path to the Eve -> Dave blinded path. let bob_carol_session_priv = secret_from_hex("0202020202020202020202020202020202020202020202020202020202020202"); let bob_blinding_point = PublicKey::from_secret_key(&secp_ctx, &bob_carol_session_priv); - let path = [(bob_node_id, WithoutLength(&bob_unblinded_tlvs)), (carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + let path = [ + ((bob_node_id, None), WithoutLength(&bob_unblinded_tlvs)), + ((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs)), + ]; let bob_carol_blinded_hops = blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &bob_carol_session_priv + &secp_ctx, path.into_iter(), &bob_carol_session_priv, ).unwrap(); let mut blinded_hops = bob_carol_blinded_hops; @@ -1632,6 +1638,7 @@ fn route_blinding_spec_test_vector() { &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } fn get_peer_storage_key(&self) -> PeerStorageKey { unreachable!() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { unreachable!() } fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { unreachable!() } @@ -1942,6 +1949,7 @@ fn test_trampoline_inbound_payment_decoding() { &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } fn get_peer_storage_key(&self) -> PeerStorageKey { unreachable!() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { unreachable!() } fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { unreachable!() } @@ -2026,9 +2034,9 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); let carol_unblinded_tlvs = payee_tlvs.encode(); - let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, ).unwrap() } else { let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs { @@ -2047,9 +2055,9 @@ fn do_test_trampoline_single_hop_receive(success: bool) { }; let carol_unblinded_tlvs = payee_tlvs.encode(); - let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, ).unwrap() }; @@ -2249,11 +2257,11 @@ fn test_trampoline_unblinded_receive() { }; let carol_unblinded_tlvs = payee_tlvs.encode(); - let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))]; let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv); let carol_blinded_hops = blinded_path::utils::construct_blinded_hops( - &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv, ).unwrap(); let route = Route { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8c034b23343..91949fd6fae 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -545,24 +545,6 @@ pub trait Verification { ) -> Result<(), ()>; } -impl Verification for PaymentHash { - /// Constructs an HMAC to include in [`OffersContext::InboundPayment`] for the payment hash - /// along with the given [`Nonce`]. - fn hmac_for_offer_payment( - &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Hmac { - signer::hmac_for_payment_hash(*self, nonce, expanded_key) - } - - /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an - /// [`OffersContext::InboundPayment`]. - fn verify_for_offer_payment( - &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Result<(), ()> { - signer::verify_payment_hash(*self, hmac, nonce, expanded_key) - } -} - impl Verification for UnauthenticatedReceiveTlvs { fn hmac_for_offer_payment( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, @@ -587,42 +569,6 @@ pub struct PaymentId(pub [u8; Self::LENGTH]); impl PaymentId { /// Number of bytes in the id. pub const LENGTH: usize = 32; - - /// Constructs an HMAC to include in [`AsyncPaymentsContext::OutboundPayment`] for the payment id - /// along with the given [`Nonce`]. - #[cfg(async_payments)] - pub fn hmac_for_async_payment( - &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Hmac { - signer::hmac_for_async_payment_id(*self, nonce, expanded_key) - } - - /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an - /// [`AsyncPaymentsContext::OutboundPayment`]. - #[cfg(async_payments)] - pub fn verify_for_async_payment( - &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Result<(), ()> { - signer::verify_async_payment_id(*self, hmac, nonce, expanded_key) - } -} - -impl Verification for PaymentId { - /// Constructs an HMAC to include in [`OffersContext::OutboundPayment`] for the payment id - /// along with the given [`Nonce`]. - fn hmac_for_offer_payment( - &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Hmac { - signer::hmac_for_offer_payment_id(*self, nonce, expanded_key) - } - - /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an - /// [`OffersContext::OutboundPayment`]. - fn verify_for_offer_payment( - &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, - ) -> Result<(), ()> { - signer::verify_offer_payment_id(*self, hmac, nonce, expanded_key) - } } impl PaymentId { @@ -3750,7 +3696,7 @@ where let flow = OffersMessageFlow::new( ChainHash::using_genesis_block(params.network), params.best_block, our_network_pubkey, current_timestamp, expanded_inbound_key, - secp_ctx.clone(), message_router + node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router ); ChannelManager { @@ -5322,10 +5268,7 @@ where }, }; - let entropy = &*self.entropy_source; - let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available( - entropy, invoice, payment_id, self.get_peers_for_blinded_path(), @@ -11317,7 +11260,7 @@ where let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - self.flow.enqueue_invoice(entropy, invoice.clone(), refund, self.get_peers_for_blinded_path())?; + self.flow.enqueue_invoice(invoice.clone(), refund, self.get_peers_for_blinded_path())?; Ok(invoice) }, @@ -13324,8 +13267,6 @@ where fn handle_message( &self, message: OffersMessage, context: Option, responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)> { - let expanded_key = &self.inbound_payment_key; - macro_rules! handle_pay_invoice_res { ($res: expr, $invoice: expr, $logger: expr) => {{ let error = match $res { @@ -13441,12 +13382,7 @@ where #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => { let payment_id = match context { - Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { - if payment_id.verify_for_offer_payment(hmac, nonce, expanded_key).is_err() { - return None - } - payment_id - }, + Some(OffersContext::OutboundPayment { payment_id, .. }) => payment_id, _ => return None }; let res = self.initiate_async_payment(&invoice, payment_id); @@ -13454,12 +13390,7 @@ where }, OffersMessage::InvoiceError(invoice_error) => { let payment_hash = match context { - Some(OffersContext::InboundPayment { payment_hash, nonce, hmac }) => { - match payment_hash.verify_for_offer_payment(hmac, nonce, expanded_key) { - Ok(_) => Some(payment_hash), - Err(_) => None, - } - }, + Some(OffersContext::InboundPayment { payment_hash }) => Some(payment_hash), _ => None, }; @@ -13467,12 +13398,10 @@ where log_trace!(logger, "Received invoice_error: {}", invoice_error); match context { - Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { - if let Ok(()) = payment_id.verify_for_offer_payment(hmac, nonce, expanded_key) { - self.abandon_payment_with_reason( - payment_id, PaymentFailureReason::InvoiceRequestRejected, - ); - } + Some(OffersContext::OutboundPayment { payment_id, .. }) => { + self.abandon_payment_with_reason( + payment_id, PaymentFailureReason::InvoiceRequestRejected, + ); }, _ => {}, } @@ -13621,15 +13550,18 @@ where fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { #[cfg(async_payments)] { - if let Ok(payment_id) = self.flow.verify_outbound_async_payment_context(_context) { - if let Err(e) = self.send_payment_for_static_invoice(payment_id) { - log_trace!( - self.logger, - "Failed to release held HTLC with payment id {}: {:?}", - payment_id, - e - ); - } + let payment_id = match _context { + AsyncPaymentsContext::OutboundPayment { payment_id } => payment_id, + _ => return, + }; + + if let Err(e) = self.send_payment_for_static_invoice(payment_id) { + log_trace!( + self.logger, + "Failed to release held HTLC with payment id {}: {:?}", + payment_id, + e + ); } } } @@ -15754,7 +15686,7 @@ where let flow = OffersMessageFlow::new( chain_hash, best_block, our_network_pubkey, highest_seen_timestamp, expanded_inbound_key, - secp_ctx.clone(), args.message_router + args.node_signer.get_receive_auth_key(), secp_ctx.clone(), args.message_router ).with_async_payments_offers_cache(async_receive_offer_cache); let channel_manager = ChannelManager { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 645724e9af2..a1b83260dfd 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -32,9 +32,7 @@ use crate::prelude::*; use crate::chain::BestBlock; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{ - Verification, {PaymentId, CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY}, -}; +use crate::ln::channelmanager::{PaymentId, CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY}; use crate::ln::inbound_payment; use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache; use crate::offers::invoice::{ @@ -54,7 +52,7 @@ use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendIns use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::OnionMessageContents; use crate::routing::router::Router; -use crate::sign::{EntropySource, NodeSigner}; +use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey}; use crate::sync::{Mutex, RwLock}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::util::ser::Writeable; @@ -64,7 +62,6 @@ use { crate::blinded_path::message::AsyncPaymentsContext, crate::blinded_path::payment::AsyncBolt12OfferContext, crate::offers::offer::Amount, - crate::offers::signer, crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}, crate::onion_message::async_payments::{ HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, @@ -95,6 +92,8 @@ where highest_seen_timestamp: AtomicUsize, inbound_payment_key: inbound_payment::ExpandedKey, + receive_auth_key: ReceiveAuthKey, + secp_ctx: Secp256k1, message_router: MR, @@ -123,7 +122,7 @@ where pub fn new( chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey, current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey, - secp_ctx: Secp256k1, message_router: MR, + receive_auth_key: ReceiveAuthKey, secp_ctx: Secp256k1, message_router: MR, ) -> Self { Self { chain_hash, @@ -133,6 +132,8 @@ where highest_seen_timestamp: AtomicUsize::new(current_timestamp as usize), inbound_payment_key, + receive_auth_key, + secp_ctx, message_router, @@ -188,6 +189,10 @@ where self.our_network_pubkey } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + self.receive_auth_key + } + fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); @@ -326,11 +331,12 @@ where &self, peers: Vec, context: MessageContext, ) -> Result, ()> { let recipient = self.get_our_node_id(); + let receive_key = self.get_receive_auth_key(); let secp_ctx = &self.secp_ctx; let peers = peers.into_iter().map(|node| node.node_id).collect(); self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) + .create_blinded_paths(recipient, receive_key, context, peers, secp_ctx) .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } @@ -342,11 +348,13 @@ where &self, peers: Vec, context: OffersContext, ) -> Result, ()> { let recipient = self.get_our_node_id(); + let receive_key = self.get_receive_auth_key(); let secp_ctx = &self.secp_ctx; self.message_router .create_compact_blinded_paths( recipient, + receive_key, MessageContext::Offers(context), peers, secp_ctx, @@ -548,9 +556,7 @@ where &self, context: AsyncPaymentsContext, ) -> Result<(), ()> { match context { - AsyncPaymentsContext::InboundPayment { nonce, hmac, path_absolute_expiry } => { - signer::verify_held_htlc_available_context(nonce, hmac, &self.inbound_payment_key)?; - + AsyncPaymentsContext::InboundPayment { path_absolute_expiry } => { if self.duration_since_epoch() > path_absolute_expiry { return Err(()); } @@ -560,30 +566,6 @@ where } } - /// Verifies the provided [`AsyncPaymentsContext`] for an inbound [`ReleaseHeldHtlc`] message. - /// - /// The context is verified using the `nonce` and `hmac` values, and if valid, - /// returns the associated [`PaymentId`]. - /// - /// # Errors - /// - /// Returns `Err(())` if: - /// - The HMAC verification fails for outbound context. - /// - /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc - #[cfg(async_payments)] - pub fn verify_outbound_async_payment_context( - &self, context: AsyncPaymentsContext, - ) -> Result { - match context { - AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } => { - payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key)?; - Ok(payment_id) - }, - _ => Err(()), - } - } - /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the /// [`OffersMessageFlow`], and any corresponding [`InvoiceRequest`] can be verified using /// [`Self::verify_invoice_request`]. The offer will expire at `absolute_expiry` if `Some`, @@ -723,7 +705,7 @@ where let secp_ctx = &self.secp_ctx; let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; + let context = OffersContext::OutboundPayment { payment_id, nonce }; let path = self .create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry), peers) @@ -803,16 +785,12 @@ where ) .map_err(|()| Bolt12SemanticError::MissingPaths)?; - let nonce = Nonce::from_entropy_source(entropy); - let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key); let path_absolute_expiry = Duration::from_secs(inbound_payment::calculate_absolute_expiry( created_at.as_secs(), relative_expiry_secs, )); let context = MessageContext::AsyncPayments(AsyncPaymentsContext::InboundPayment { - nonce, - hmac, path_absolute_expiry, }); @@ -916,7 +894,6 @@ where R::Target: Router, { let entropy = &*entropy_source; - let expanded_key = &self.inbound_payment_key; let secp_ctx = &self.secp_ctx; let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; @@ -978,13 +955,8 @@ where match response { Ok(invoice) => { - let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers(OffersContext::InboundPayment { - payment_hash, - nonce, - hmac, - }); + let context = + MessageContext::Offers(OffersContext::InboundPayment { payment_hash }); (OffersMessage::Invoice(invoice), Some(context)) }, @@ -1021,14 +993,7 @@ where &self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce, peers: Vec, ) -> Result<(), Bolt12SemanticError> { - let expanded_key = &self.inbound_payment_key; - - let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers(OffersContext::OutboundPayment { - payment_id, - nonce, - hmac: Some(hmac), - }); + let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, nonce }); let reply_paths = self .create_blinded_paths(peers, context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -1069,23 +1034,12 @@ where /// to create blinded reply paths /// /// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages - pub fn enqueue_invoice( - &self, entropy_source: ES, invoice: Bolt12Invoice, refund: &Refund, - peers: Vec, - ) -> Result<(), Bolt12SemanticError> - where - ES::Target: EntropySource, - { - let expanded_key = &self.inbound_payment_key; - let entropy = &*entropy_source; - + pub fn enqueue_invoice( + &self, invoice: Bolt12Invoice, refund: &Refund, peers: Vec, + ) -> Result<(), Bolt12SemanticError> { let payment_hash = invoice.payment_hash(); - let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - - let context = - MessageContext::Offers(OffersContext::InboundPayment { payment_hash, nonce, hmac }); + let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash }); let reply_paths = self .create_blinded_paths(peers, context) @@ -1146,23 +1100,11 @@ where /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc /// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages #[cfg(async_payments)] - pub fn enqueue_held_htlc_available( - &self, entropy_source: ES, invoice: &StaticInvoice, payment_id: PaymentId, - peers: Vec, - ) -> Result<(), Bolt12SemanticError> - where - ES::Target: EntropySource, - { - let expanded_key = &self.inbound_payment_key; - let entropy = &*entropy_source; - - let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_id.hmac_for_async_payment(nonce, expanded_key); - let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { - payment_id, - nonce, - hmac, - }); + pub fn enqueue_held_htlc_available( + &self, invoice: &StaticInvoice, payment_id: PaymentId, peers: Vec, + ) -> Result<(), Bolt12SemanticError> { + let context = + MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id }); let reply_paths = self .create_blinded_paths(peers, context) diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index 329b90d2076..6c0fddba6d3 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -21,7 +21,6 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1, SecretKey}; use core::fmt; -use types::payment::PaymentHash; use crate::prelude::*; @@ -38,23 +37,25 @@ const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16]; const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16]; const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; -// HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`. -const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16]; -// HMAC input for a `PaymentId`. The HMAC is used in `AsyncPaymentsContext::OutboundPayment`. -#[cfg(async_payments)] -const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16]; - -// HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`. -const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; +// NOTE: +// The following `HMAC_INPUT` constants were previously used for authenticating fields +// in `OffersContext` and `AsyncPaymentsContext`, but were removed in LDK v0.1.X with +// the introduction of `ReceiveAuthKey`-based authentication. +// Their corresponding values (`[5; 16]`, `[6; 16]`, `[7; 16]`, and `[9; 16]`) are now +// reserved and must not be reused to preserve backward compatibility. +// +// Reserved HMAC_INPUT values — do not reuse: +// +// const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16]; +// #[cfg(async_payments)] +// const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16]; +// const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; +// #[cfg(async_payments)] +// const ASYNC_PAYMENTS_HELD_HTLC_HMAC_INPUT: &[u8; 16] = &[9; 16]; // HMAC input for `ReceiveTlvs`. The HMAC is used in `blinded_path::payment::PaymentContext`. const PAYMENT_TLVS_HMAC_INPUT: &[u8; 16] = &[8; 16]; -// HMAC input used in `AsyncPaymentsContext::InboundPayment` to authenticate inbound -// held_htlc_available onion messages. -#[cfg(async_payments)] -const ASYNC_PAYMENTS_HELD_HTLC_HMAC_INPUT: &[u8; 16] = &[9; 16]; - /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. #[derive(Clone)] @@ -453,76 +454,6 @@ fn hmac_for_message<'a>( Ok(hmac) } -pub(crate) fn hmac_for_offer_payment_id( - payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Hmac { - hmac_for_payment_id(payment_id, nonce, OFFER_PAYMENT_ID_HMAC_INPUT, expanded_key) -} - -pub(crate) fn verify_offer_payment_id( - payment_id: PaymentId, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Result<(), ()> { - if hmac_for_offer_payment_id(payment_id, nonce, expanded_key) == hmac { - Ok(()) - } else { - Err(()) - } -} - -pub(crate) fn hmac_for_payment_hash( - payment_hash: PaymentHash, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Hmac { - const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment Hash"; - let mut hmac = expanded_key.hmac_for_offer(); - hmac.input(IV_BYTES); - hmac.input(&nonce.0); - hmac.input(PAYMENT_HASH_HMAC_INPUT); - hmac.input(&payment_hash.0); - - Hmac::from_engine(hmac) -} - -pub(crate) fn verify_payment_hash( - payment_hash: PaymentHash, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Result<(), ()> { - if hmac_for_payment_hash(payment_hash, nonce, expanded_key) == hmac { - Ok(()) - } else { - Err(()) - } -} - -#[cfg(async_payments)] -pub(crate) fn hmac_for_async_payment_id( - payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Hmac { - hmac_for_payment_id(payment_id, nonce, ASYNC_PAYMENT_ID_HMAC_INPUT, expanded_key) -} - -#[cfg(async_payments)] -pub(crate) fn verify_async_payment_id( - payment_id: PaymentId, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, -) -> Result<(), ()> { - if hmac_for_async_payment_id(payment_id, nonce, expanded_key) == hmac { - Ok(()) - } else { - Err(()) - } -} - -fn hmac_for_payment_id( - payment_id: PaymentId, nonce: Nonce, hmac_input: &[u8; 16], expanded_key: &ExpandedKey, -) -> Hmac { - const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~"; - let mut hmac = expanded_key.hmac_for_offer(); - hmac.input(IV_BYTES); - hmac.input(&nonce.0); - hmac.input(hmac_input); - hmac.input(&payment_id.0); - - Hmac::from_engine(hmac) -} - pub(crate) fn hmac_for_payment_tlvs( receive_tlvs: &UnauthenticatedReceiveTlvs, nonce: Nonce, expanded_key: &ExpandedKey, ) -> Hmac { @@ -546,27 +477,3 @@ pub(crate) fn verify_payment_tlvs( Err(()) } } - -#[cfg(async_payments)] -pub(crate) fn hmac_for_held_htlc_available_context( - nonce: Nonce, expanded_key: &ExpandedKey, -) -> Hmac { - const IV_BYTES: &[u8; IV_LEN] = b"LDK Held HTLC OM"; - let mut hmac = expanded_key.hmac_for_offer(); - hmac.input(IV_BYTES); - hmac.input(&nonce.0); - hmac.input(ASYNC_PAYMENTS_HELD_HTLC_HMAC_INPUT); - - Hmac::from_engine(hmac) -} - -#[cfg(async_payments)] -pub(crate) fn verify_held_htlc_available_context( - nonce: Nonce, hmac: Hmac, expanded_key: &ExpandedKey, -) -> Result<(), ()> { - if hmac_for_held_htlc_available_context(nonce, expanded_key) == hmac { - Ok(()) - } else { - Err(()) - } -} diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 98fd98d2086..3cbb618dc0b 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -432,8 +432,10 @@ fn one_blinded_hop() { let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; + let receive_key = nodes[1].messenger.node_signer.get_receive_auth_key(); let blinded_path = - BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, &secp_ctx).unwrap(); + BlindedMessagePath::new(&[], nodes[1].node_id, receive_key, context, entropy, &secp_ctx) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -451,9 +453,16 @@ fn two_unblinded_two_blinded() { [MessageForwardNode { node_id: nodes[3].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[4].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[4].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[4].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[4].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let path = OnionMessagePath { intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id], destination: Destination::BlindedPath(blinded_path), @@ -477,9 +486,16 @@ fn three_blinded_hops() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[3].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[3].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[3].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -504,8 +520,10 @@ fn async_response_over_one_blinded_hop() { let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; + let receive_key = nodes[1].messenger.node_signer.get_receive_auth_key(); let reply_path = - BlindedMessagePath::new(&[], nodes[1].node_id, context, entropy, &secp_ctx).unwrap(); + BlindedMessagePath::new(&[], nodes[1].node_id, receive_key, context, entropy, &secp_ctx) + .unwrap(); // 4. Create a responder using the reply path for Alice. let responder = Some(Responder::new(reply_path)); @@ -543,8 +561,10 @@ fn async_response_with_reply_path_succeeds() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); + let entropy = &*bob.entropy_source; + let receive_key = bob.messenger.node_signer.get_receive_auth_key(); let reply_path = - BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx) + BlindedMessagePath::new(&[], bob.node_id, receive_key, context, entropy, &secp_ctx) .unwrap(); // Alice asynchronously responds to Bob, expecting a response back from him. @@ -584,8 +604,10 @@ fn async_response_with_reply_path_fails() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); + let entropy = &*bob.entropy_source; + let receive_key = bob.messenger.node_signer.get_receive_auth_key(); let reply_path = - BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx) + BlindedMessagePath::new(&[], bob.node_id, receive_key, context, entropy, &secp_ctx) .unwrap(); // Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced and @@ -634,11 +656,14 @@ fn test_blinded_path_padding_for_full_length_path() { // Update the context to create a larger final receive TLVs, ensuring that // the hop sizes vary before padding. let context = MessageContext::Custom(vec![0u8; 42]); + let entropy = &*nodes[3].entropy_source; + let receive_key = nodes[3].messenger.node_signer.get_receive_auth_key(); let blinded_path = BlindedMessagePath::new( &intermediate_nodes, nodes[3].node_id, + receive_key, context, - &*nodes[3].entropy_source, + entropy, &secp_ctx, ) .unwrap(); @@ -667,11 +692,14 @@ fn test_blinded_path_no_padding_for_compact_path() { // Update the context to create a larger final receive TLVs, ensuring that // the hop sizes vary before padding. let context = MessageContext::Custom(vec![0u8; 42]); + let entropy = &*nodes[3].entropy_source; + let receive_key = nodes[3].messenger.node_signer.get_receive_auth_key(); let blinded_path = BlindedMessagePath::new( &intermediate_nodes, nodes[3].node_id, + receive_key, context, - &*nodes[3].entropy_source, + entropy, &secp_ctx, ) .unwrap(); @@ -693,9 +721,16 @@ fn we_are_intro_node() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[2].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -708,9 +743,16 @@ fn we_are_intro_node() { [MessageForwardNode { node_id: nodes[0].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[1].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[1].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[1].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[1].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -731,9 +773,16 @@ fn invalid_blinded_path_error() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let mut blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[2].messenger.node_signer.get_receive_auth_key(); + let mut blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); blinded_path.clear_blinded_hops(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -760,9 +809,16 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let reply_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[0].messenger.node_signer.get_receive_auth_key(); + let reply_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[0].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); nodes[0] .messenger .send_onion_message_using_path(path, test_msg.clone(), Some(reply_path)) @@ -781,9 +837,16 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[3].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[3].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[3].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let intermediate_nodes = [ MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, @@ -791,9 +854,16 @@ fn reply_path() { ]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let reply_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[0].messenger.node_signer.get_receive_auth_key(); + let reply_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[0].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let instructions = MessageSendInstructions::WithSpecifiedReplyPath { destination, reply_path }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -889,9 +959,16 @@ fn requests_peer_connection_for_buffered_messages() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[0].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -931,9 +1008,16 @@ fn drops_buffered_messages_waiting_for_peer_connection() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[0].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[0].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -989,9 +1073,16 @@ fn intercept_offline_peer_oms() { [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let entropy = &*nodes[2].entropy_source; - let blinded_path = - BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, entropy, &secp_ctx) - .unwrap(); + let receive_key = nodes[2].messenger.node_signer.get_receive_auth_key(); + let blinded_path = BlindedMessagePath::new( + &intermediate_nodes, + nodes[2].node_id, + receive_key, + context, + entropy, + &secp_ctx, + ) + .unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 31067fad2ed..96021815d24 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -40,7 +40,7 @@ use crate::ln::msgs::{ }; use crate::ln::onion_utils; use crate::routing::gossip::{NetworkGraph, NodeId, ReadOnlyNetworkGraph}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey, Recipient}; use crate::types::features::{InitFeatures, NodeFeatures}; use crate::util::async_poll::{MultiResultFuturePoller, ResultFuture}; use crate::util::logger::{Logger, WithContext}; @@ -195,6 +195,7 @@ where /// # use lightning::ln::peer_handler::IgnoringMessageHandler; /// # use lightning::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions, OnionMessagePath, OnionMessenger}; /// # use lightning::onion_message::packet::OnionMessageContents; +/// # use lightning::sign::{NodeSigner, ReceiveAuthKey}; /// # use lightning::util::logger::{Logger, Record}; /// # use lightning::util::ser::{Writeable, Writer}; /// # use lightning::io; @@ -217,7 +218,8 @@ where /// # }) /// # } /// # fn create_blinded_paths( -/// # &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1 +/// # &self, _recipient: PublicKey, _local_node_receive_key: ReceiveAuthKey, +/// # _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1 /// # ) -> Result, ()> { /// # unreachable!() /// # } @@ -273,7 +275,8 @@ where /// MessageForwardNode { node_id: hop_node_id4, short_channel_id: None }, /// ]; /// let context = MessageContext::Custom(Vec::new()); -/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, context, &keys_manager, &secp_ctx).unwrap(); +/// let receive_key = keys_manager.get_receive_auth_key(); +/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, receive_key, context, &keys_manager, &secp_ctx).unwrap(); /// /// // Send a custom onion message to a blinded path. /// let destination = Destination::BlindedPath(blinded_path); @@ -306,6 +309,9 @@ pub struct OnionMessenger< CMH::Target: CustomOnionMessageHandler, { entropy_source: ES, + #[cfg(test)] + pub(super) node_signer: NS, + #[cfg(not(test))] node_signer: NS, logger: L, message_recipients: Mutex>, @@ -501,8 +507,8 @@ pub trait MessageRouter { /// Creates [`BlindedMessagePath`]s to the `recipient` node. The nodes in `peers` are assumed to /// be direct peers with the `recipient`. fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()>; /// Creates compact [`BlindedMessagePath`]s to the `recipient` node. The nodes in `peers` are @@ -519,14 +525,14 @@ pub trait MessageRouter { /// The provided implementation simply delegates to [`MessageRouter::create_blinded_paths`], /// ignoring the short channel ids. fn create_compact_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let peers = peers .into_iter() .map(|MessageForwardNode { node_id, short_channel_id: _ }| node_id) .collect(); - self.create_blinded_paths(recipient, context, peers, secp_ctx) + self.create_blinded_paths(recipient, local_node_receive_key, context, peers, secp_ctx) } } @@ -561,8 +567,9 @@ where I: ExactSizeIterator, T: secp256k1::Signing + secp256k1::Verification, >( - network_graph: &G, recipient: PublicKey, context: MessageContext, peers: I, - entropy_source: &ES, secp_ctx: &Secp256k1, compact_paths: bool, + network_graph: &G, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: I, entropy_source: &ES, secp_ctx: &Secp256k1, + compact_paths: bool, ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PATHS: usize = 3; @@ -601,7 +608,14 @@ where let paths = peer_info .into_iter() .map(|(peer, _, _)| { - BlindedMessagePath::new(&[peer], recipient, context.clone(), entropy, secp_ctx) + BlindedMessagePath::new( + &[peer], + recipient, + local_node_receive_key, + context.clone(), + entropy, + secp_ctx, + ) }) .take(MAX_PATHS) .collect::, _>>(); @@ -610,8 +624,15 @@ where Ok(paths) if !paths.is_empty() => Ok(paths), _ => { if is_recipient_announced { - BlindedMessagePath::new(&[], recipient, context, &**entropy_source, secp_ctx) - .map(|path| vec![path]) + BlindedMessagePath::new( + &[], + recipient, + local_node_receive_key, + context, + &**entropy_source, + secp_ctx, + ) + .map(|path| vec![path]) } else { Err(()) } @@ -669,14 +690,16 @@ where } pub(crate) fn create_blinded_paths( - network_graph: &G, recipient: PublicKey, context: MessageContext, peers: Vec, - entropy_source: &ES, secp_ctx: &Secp256k1, + network_graph: &G, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, entropy_source: &ES, + secp_ctx: &Secp256k1, ) -> Result, ()> { let peers = peers.into_iter().map(|node_id| MessageForwardNode { node_id, short_channel_id: None }); Self::create_blinded_paths_from_iter( network_graph, recipient, + local_node_receive_key, context, peers.into_iter(), entropy_source, @@ -686,12 +709,14 @@ where } pub(crate) fn create_compact_blinded_paths( - network_graph: &G, recipient: PublicKey, context: MessageContext, - peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, + network_graph: &G, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, entropy_source: &ES, + secp_ctx: &Secp256k1, ) -> Result, ()> { Self::create_blinded_paths_from_iter( network_graph, recipient, + local_node_receive_key, context, peers.into_iter(), entropy_source, @@ -714,12 +739,13 @@ where } fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { Self::create_blinded_paths( &self.network_graph, recipient, + local_node_receive_key, context, peers, &self.entropy_source, @@ -728,12 +754,13 @@ where } fn create_compact_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { Self::create_compact_blinded_paths( &self.network_graph, recipient, + local_node_receive_key, context, peers, &self.entropy_source, @@ -1074,11 +1101,12 @@ where }, } }; + let receiving_context_auth_key = node_signer.get_receive_auth_key(); let next_hop = onion_utils::decode_next_untagged_hop( onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, - (control_tlvs_ss, custom_handler.deref(), logger.deref()), + (control_tlvs_ss, custom_handler.deref(), receiving_context_auth_key, logger.deref()), ); match next_hop { Ok(( @@ -1086,6 +1114,7 @@ where message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { context }), reply_path, + control_tlvs_authenticated, }, None, )) => match (message, context) { @@ -1114,6 +1143,8 @@ where Ok(PeeledOnion::DNSResolver(msg, None, reply_path)) }, _ => { + // Hide the "`control_tlvs_authenticated` is unused warning". We'll use it here soon + let _ = control_tlvs_authenticated; log_trace!( logger, "Received message was sent on a blinded path with wrong or missing context." @@ -1436,7 +1467,13 @@ where }; self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) + .create_blinded_paths( + recipient, + self.node_signer.get_receive_auth_key(), + context, + peers, + secp_ctx, + ) .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| SendError::PathNotFound) } @@ -2283,12 +2320,7 @@ fn packet_payloads_and_keys< unblinded_path.into_iter(), destination, session_priv, - |_, - onion_packet_ss, - ephemeral_pubkey, - control_tlvs_ss, - unblinded_pk_opt, - enc_payload_opt| { + |onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| { if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops { if let Some(ss) = prev_control_tlvs_ss.take() { payloads.push(( @@ -2339,7 +2371,12 @@ fn packet_payloads_and_keys< if let Some(control_tlvs) = final_control_tlvs { payloads.push(( - Payload::Receive { control_tlvs, reply_path: reply_path.take(), message }, + Payload::Receive { + control_tlvs, + reply_path: reply_path.take(), + message, + control_tlvs_authenticated: false, + }, prev_control_tlvs_ss.unwrap(), )); } else { @@ -2348,6 +2385,7 @@ fn packet_payloads_and_keys< control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { context: None }), reply_path: reply_path.take(), message, + control_tlvs_authenticated: false, }, prev_control_tlvs_ss.unwrap(), )); diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 632cbc9c8a3..301473fba6a 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -18,9 +18,10 @@ use super::dns_resolution::DNSResolverMessage; use super::messenger::CustomOnionMessageHandler; use super::offers::OffersMessage; use crate::blinded_path::message::{BlindedMessagePath, ForwardTlvs, NextMessageHop, ReceiveTlvs}; -use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; +use crate::crypto::streams::{ChaChaDualPolyReadAdapter, ChaChaPolyWriteAdapter}; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; +use crate::sign::ReceiveAuthKey; use crate::util::logger::Logger; use crate::util::ser::{ BigSize, FixedLengthReader, LengthLimitedRead, LengthReadable, LengthReadableArgs, Readable, @@ -112,7 +113,14 @@ pub(super) enum Payload { /// This payload is for an intermediate hop. Forward(ForwardControlTlvs), /// This payload is for the final hop. - Receive { control_tlvs: ReceiveControlTlvs, reply_path: Option, message: T }, + Receive { + /// The [`ReceiveControlTlvs`] were authenticated with the additional key which was + /// provided to [`ReadableArgs::read`]. + control_tlvs_authenticated: bool, + control_tlvs: ReceiveControlTlvs, + reply_path: Option, + message: T, + }, } /// The contents of an [`OnionMessage`] as read from the wire. @@ -223,6 +231,7 @@ impl Writeable for (Payload, [u8; 32]) { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes), reply_path, message, + control_tlvs_authenticated: _, } => { _encode_varint_length_prefixed_tlv!(w, { (2, reply_path, option), @@ -238,6 +247,7 @@ impl Writeable for (Payload, [u8; 32]) { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs), reply_path, message, + control_tlvs_authenticated: _, } => { let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs); _encode_varint_length_prefixed_tlv!(w, { @@ -252,22 +262,25 @@ impl Writeable for (Payload, [u8; 32]) { } // Uses the provided secret to simultaneously decode and decrypt the control TLVs and data TLV. -impl ReadableArgs<(SharedSecret, &H, &L)> +impl + ReadableArgs<(SharedSecret, &H, ReceiveAuthKey, &L)> for Payload::CustomMessage>> { - fn read(r: &mut R, args: (SharedSecret, &H, &L)) -> Result { - let (encrypted_tlvs_ss, handler, logger) = args; + fn read( + r: &mut R, args: (SharedSecret, &H, ReceiveAuthKey, &L), + ) -> Result { + let (encrypted_tlvs_ss, handler, receive_tlvs_key, logger) = args; let v: BigSize = Readable::read(r)?; let mut rd = FixedLengthReader::new(r, v.0); let mut reply_path: Option = None; - let mut read_adapter: Option> = None; + let mut read_adapter: Option> = None; let rho = onion_utils::gen_rho_from_shared_secret(&encrypted_tlvs_ss.secret_bytes()); let mut message_type: Option = None; let mut message = None; decode_tlv_stream_with_custom_tlv_decode!(&mut rd, { (2, reply_path, option), - (4, read_adapter, (option: LengthReadableArgs, rho)), + (4, read_adapter, (option: LengthReadableArgs, (rho, receive_tlvs_key.0))), }, |msg_type, msg_reader| { if msg_type < 64 { return Ok(false) } // Don't allow reading more than one data TLV from an onion message. @@ -304,17 +317,18 @@ impl ReadableArgs<(Sh match read_adapter { None => return Err(DecodeError::InvalidValue), - Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(tlvs) }) => { - if message_type.is_some() { + Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Forward(tlvs), used_aad }) => { + if used_aad || message_type.is_some() { return Err(DecodeError::InvalidValue); } Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs))) }, - Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs) }) => { + Some(ChaChaDualPolyReadAdapter { readable: ControlTlvs::Receive(tlvs), used_aad }) => { Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs), reply_path, message: message.ok_or(DecodeError::InvalidValue)?, + control_tlvs_authenticated: used_aad, }) }, } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 2610131d96f..a1ba428ded6 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -804,6 +804,15 @@ pub struct PeerStorageKey { pub inner: [u8; 32], } +/// A secret key used to authenticate message contexts in received [`BlindedMessagePath`]s. +/// +/// This key ensures that a node only accepts incoming messages delivered through +/// blinded paths that it constructed itself. +/// +/// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ReceiveAuthKey(pub [u8; 32]); + /// Specifies the recipient of an invoice. /// /// This indicates to [`NodeSigner::sign_invoice`] what node secret key should be used to sign @@ -851,6 +860,19 @@ pub trait NodeSigner { /// can be re-derived from data which would be available after state loss (eg the wallet seed). fn get_peer_storage_key(&self) -> PeerStorageKey; + /// Returns the [`ReceiveAuthKey`] used to authenticate incoming [`BlindedMessagePath`] contexts. + /// + /// This key is used as additional associated data (AAD) during MAC verification of the + /// [`MessageContext`] at the final hop of a blinded path. It ensures that only paths + /// constructed by this node will be accepted, preventing unauthorized parties from forging + /// valid-looking messages. + /// + /// Implementers must ensure that this key remains secret and consistent across invocations. + /// + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`MessageContext`]: crate::blinded_path::message::MessageContext + fn get_receive_auth_key(&self) -> ReceiveAuthKey; + /// Get node id based on the provided [`Recipient`]. /// /// This method must return the same value each time it is called with a given [`Recipient`] @@ -1827,6 +1849,7 @@ pub struct KeysManager { channel_master_key: Xpriv, channel_child_index: AtomicUsize, peer_storage_key: PeerStorageKey, + receive_auth_key: ReceiveAuthKey, #[cfg(test)] pub(crate) entropy_source: RandomBytes, @@ -1864,6 +1887,7 @@ impl KeysManager { const CHANNEL_MASTER_KEY_INDEX: ChildNumber = ChildNumber::Hardened { index: 3 }; const INBOUND_PAYMENT_KEY_INDEX: ChildNumber = ChildNumber::Hardened { index: 5 }; const PEER_STORAGE_KEY_INDEX: ChildNumber = ChildNumber::Hardened { index: 6 }; + const RECEIVE_AUTH_KEY_INDEX: ChildNumber = ChildNumber::Hardened { index: 7 }; let secp_ctx = Secp256k1::new(); // Note that when we aren't serializing the key, network doesn't matter @@ -1906,6 +1930,11 @@ impl KeysManager { .expect("Your RNG is busted") .private_key; + let receive_auth_key = master_key + .derive_priv(&secp_ctx, &RECEIVE_AUTH_KEY_INDEX) + .expect("Your RNG is busted") + .private_key; + let mut rand_bytes_engine = Sha256::engine(); rand_bytes_engine.input(&starting_time_secs.to_be_bytes()); rand_bytes_engine.input(&starting_time_nanos.to_be_bytes()); @@ -1921,6 +1950,7 @@ impl KeysManager { inbound_payment_key: ExpandedKey::new(inbound_pmt_key_bytes), peer_storage_key: PeerStorageKey { inner: peer_storage_key.secret_bytes() }, + receive_auth_key: ReceiveAuthKey(receive_auth_key.secret_bytes()), destination_script, shutdown_pubkey, @@ -2151,6 +2181,10 @@ impl NodeSigner for KeysManager { self.peer_storage_key.clone() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + self.receive_auth_key.clone() + } + fn sign_invoice( &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { @@ -2316,6 +2350,10 @@ impl NodeSigner for PhantomKeysManager { self.inner.peer_storage_key.clone() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + self.inner.receive_auth_key.clone() + } + fn sign_invoice( &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { diff --git a/lightning/src/util/dyn_signer.rs b/lightning/src/util/dyn_signer.rs index 7e9844e2ac0..ea062de5c76 100644 --- a/lightning/src/util/dyn_signer.rs +++ b/lightning/src/util/dyn_signer.rs @@ -14,8 +14,8 @@ use crate::ln::script::ShutdownScript; use crate::sign::ecdsa::EcdsaChannelSigner; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; -use crate::sign::ChannelSigner; use crate::sign::InMemorySigner; +use crate::sign::{ChannelSigner, ReceiveAuthKey}; use crate::sign::{EntropySource, HTLCDescriptor, OutputSpender, PhantomKeysManager}; use crate::sign::{ NodeSigner, PeerStorageKey, Recipient, SignerProvider, SpendableOutputDescriptor, @@ -217,7 +217,8 @@ inner, invoice: &crate::offers::invoice::UnsignedBolt12Invoice ) -> Result, fn get_inbound_payment_key(,) -> ExpandedKey, - fn get_peer_storage_key(,) -> PeerStorageKey + fn get_peer_storage_key(,) -> PeerStorageKey, + fn get_receive_auth_key(,) -> ReceiveAuthKey ); delegate!(DynKeysInterface, SignerProvider, @@ -282,7 +283,8 @@ delegate!(DynPhantomKeysInterface, NodeSigner, fn sign_bolt12_invoice(, invoice: &crate::offers::invoice::UnsignedBolt12Invoice ) -> Result, fn get_inbound_payment_key(,) -> ExpandedKey, - fn get_peer_storage_key(,) -> PeerStorageKey + fn get_peer_storage_key(,) -> PeerStorageKey, + fn get_receive_auth_key(,) -> ReceiveAuthKey ); impl SignerProvider for DynPhantomKeysInterface { diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index e0869bf4364..4165afea767 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -46,7 +46,7 @@ use crate::routing::router::{ }; use crate::routing::scoring::{ChannelUsage, ScoreLookUp, ScoreUpdate}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; -use crate::sign; +use crate::sign::{self, ReceiveAuthKey}; use crate::sign::{ChannelSigner, PeerStorageKey}; use crate::sync::RwLock; use crate::types::features::{ChannelFeatures, InitFeatures, NodeFeatures}; @@ -346,8 +346,8 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { } fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let mut peers = peers; { @@ -356,12 +356,12 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { peers = peers_override.clone(); } } - self.inner.create_blinded_paths(recipient, context, peers, secp_ctx) + self.inner.create_blinded_paths(recipient, local_node_receive_key, context, peers, secp_ctx) } fn create_compact_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, - secp_ctx: &Secp256k1, + &self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey, + context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let mut peers = peers; { @@ -374,7 +374,13 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { .collect(); } } - self.inner.create_compact_blinded_paths(recipient, context, peers, secp_ctx) + self.inner.create_compact_blinded_paths( + recipient, + local_node_receive_key, + context, + peers, + secp_ctx, + ) } } @@ -1524,6 +1530,10 @@ impl NodeSigner for TestNodeSigner { unreachable!() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + ReceiveAuthKey(self.node_secret.secret_bytes()) + } + fn get_node_id(&self, recipient: Recipient) -> Result { let node_secret = match recipient { Recipient::Node => Ok(&self.node_secret), @@ -1608,6 +1618,10 @@ impl NodeSigner for TestKeysInterface { self.backing.get_peer_storage_key() } + fn get_receive_auth_key(&self) -> ReceiveAuthKey { + self.backing.get_receive_auth_key() + } + fn sign_bolt12_invoice( &self, invoice: &UnsignedBolt12Invoice, ) -> Result {