diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 164cfcfb1ad..86430327b4a 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -92,6 +92,7 @@ impl BlindedMessagePath { recipient_node_id, context, &blinding_secret, + [41; 32], // TODO: Pass this in ) .map_err(|_| ())?, })) @@ -514,6 +515,7 @@ 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: [u8; 32], ) -> Result, secp256k1::Error> { let pks = intermediate_nodes .iter() @@ -536,13 +538,13 @@ pub(super) fn blinded_hops( if is_compact { let path = pks.zip(tlvs); - utils::construct_blinded_hops(secp_ctx, path, session_priv) + utils::construct_blinded_hops(secp_ctx, path, session_priv, Some(local_node_receive_key)) } else { let path = pks.zip(tlvs.map(|tlv| BlindedPathWithPadding { tlvs: tlv, round_off: MESSAGE_PADDING_ROUND_OFF, })); - utils::construct_blinded_hops(secp_ctx, path, session_priv) + utils::construct_blinded_hops(secp_ctx, path, session_priv, Some(local_node_receive_key)) } } diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 95ad76c3644..18c99ab1eaa 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -675,7 +675,7 @@ pub(super) fn blinded_hops( tlvs.map(|tlv| BlindedPathWithPadding { tlvs: tlv, round_off: PAYMENT_PADDING_ROUND_OFF }), ); - utils::construct_blinded_hops(secp_ctx, path, session_priv) + utils::construct_blinded_hops(secp_ctx, path, session_priv, None) } /// `None` if underflow occurs. diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index b17fa01bbcf..c807836d040 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -17,7 +17,8 @@ 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; @@ -38,7 +39,7 @@ macro_rules! build_keys_helper { let mut onion_packet_pubkey = msg_blinding_point.clone(); macro_rules! build_keys { - ($hop: expr, $blinded: expr, $encrypted_payload: expr) => {{ + ($hop: expr, $blinded: expr, $encrypted_payload: expr, $node_recv_key: expr) => {{ let pk = *$hop.borrow(); let encrypted_data_ss = SharedSecret::new(&pk, &msg_blinding_point_priv); @@ -65,6 +66,7 @@ macro_rules! build_keys_helper { onion_packet_pubkey, rho, unblinded_hop_opt, + $node_recv_key, $encrypted_payload, ); (encrypted_data_ss, onion_packet_ss) @@ -72,9 +74,9 @@ macro_rules! build_keys_helper { } macro_rules! build_keys_in_loop { - ($pk: expr, $blinded: expr, $encrypted_payload: expr) => { + ($pk: expr, $blinded: expr, $encrypted_payload: expr, $node_recv_key: expr) => { let (encrypted_data_ss, onion_packet_ss) = - build_keys!($pk, $blinded, $encrypted_payload); + build_keys!($pk, $blinded, $encrypted_payload, $node_recv_key); let msg_blinding_point_blinding_factor = { let mut sha = Sha256::engine(); @@ -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,40 +114,45 @@ 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, _: Option<()>, 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); + build_keys_in_loop!(pk, false, None, None); } match destination { Destination::Node(pk) => { - build_keys!(pk, false, None); + build_keys!(pk, false, None, None); }, Destination::BlindedPath(BlindedMessagePath(BlindedPath { blinded_hops, .. })) => { for hop in blinded_hops { - build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload)); + build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload), None); } }, } Ok(()) } -#[inline] -pub(super) fn construct_keys_for_blinded_path<'a, T, I, F, H>( - secp_ctx: &Secp256k1, unblinded_path: I, session_priv: &SecretKey, mut callback: F, +fn construct_keys_for_blinded_path<'a, T, I, F, H>( + secp_ctx: &Secp256k1, unblinded_path: I, session_priv: &SecretKey, + mut last_hop_receive_key: Option<[u8; 32]>, mut callback: F, ) -> Result<(), secp256k1::Error> where T: secp256k1::Signing + secp256k1::Verification, H: Borrow, I: Iterator, - F: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option, Option>), + F: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option, Option<[u8; 32]>, Option>), { build_keys_helper!(session_priv, secp_ctx, callback); - for pk in unblinded_path { - build_keys_in_loop!(pk, false, None); + let mut iter = unblinded_path.peekable(); + while let Some(pk) = iter.next() { + let receive_key = if iter.peek().is_none() { last_hop_receive_key.take() } else { None }; + build_keys_in_loop!(pk, false, None, receive_key); } Ok(()) } @@ -164,6 +170,7 @@ impl Borrow for PublicKeyWithTlvs { pub(crate) fn construct_blinded_hops<'a, T, I, W>( secp_ctx: &Secp256k1, unblinded_path: I, session_priv: &SecretKey, + last_hop_receive_key: Option<[u8; 32]>, ) -> Result, secp256k1::Error> where T: secp256k1::Signing + secp256k1::Verification, @@ -175,12 +182,14 @@ where secp_ctx, unblinded_path.map(|(pubkey, tlvs)| PublicKeyWithTlvs { pubkey, tlvs }), session_priv, - |blinded_node_id, _, _, encrypted_payload_rho, unblinded_hop_data, _| { + last_hop_receive_key, + |blinded_node_id, _, _, encrypted_payload_rho, unblinded_hop_data, hop_recv_key, _| { blinded_hops.push(BlindedHop { blinded_node_id, encrypted_payload: encrypt_payload( unblinded_hop_data.unwrap().tlvs, encrypted_payload_rho, + hop_recv_key, ), }); }, @@ -189,9 +198,17 @@ 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<[u8; 32]>) -> 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) + } 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..7c7cf245460 100644 --- a/lightning/src/crypto/chacha20poly1305rfc.rs +++ b/lightning/src/crypto/chacha20poly1305rfc.rs @@ -67,7 +67,7 @@ mod real_chachapoly { 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); + out_tag.copy_from_slice(&self.mac.result()); } pub fn encrypt_full_message_in_place( @@ -94,7 +94,7 @@ mod real_chachapoly { 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); + out_tag.copy_from_slice(&self.mac.result()); } /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents @@ -115,8 +115,7 @@ mod real_chachapoly { 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); + let calc_tag = self.mac.result(); if fixed_time_eq(&calc_tag, tag) { self.cipher.process(input, output); Ok(()) @@ -156,8 +155,7 @@ mod real_chachapoly { 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); + let calc_tag = self.mac.result(); if fixed_time_eq(&calc_tag, tag) { true } else { diff --git a/lightning/src/crypto/poly1305.rs b/lightning/src/crypto/poly1305.rs index 6ac1c6c9694..e500c5fa79f 100644 --- a/lightning/src/crypto/poly1305.rs +++ b/lightning/src/crypto/poly1305.rs @@ -252,15 +252,16 @@ impl Poly1305 { self.leftover = m.len(); } - pub fn raw_result(&mut self, output: &mut [u8]) { - assert!(output.len() >= 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 } } @@ -270,10 +271,10 @@ mod test { use super::Poly1305; - fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8]) { + fn poly1305(key: &[u8], msg: &[u8], mac: &mut [u8; 16]) { let mut poly = Poly1305::new(key); poly.input(msg); - poly.raw_result(mac); + *mac = poly.result(); } #[test] @@ -318,7 +319,7 @@ mod test { poly.input(&msg[128..129]); poly.input(&msg[129..130]); poly.input(&msg[130..131]); - poly.raw_result(&mut mac); + let mac = poly.result(); assert_eq!(&mac[..], &expected[..]); } @@ -363,7 +364,7 @@ mod test { poly1305(&key[..], &msg[0..i], &mut mac); tpoly.input(&mac); } - tpoly.raw_result(&mut mac); + let mac = tpoly.result(); assert_eq!(&mac[..], &total_mac[..]); } diff --git a/lightning/src/crypto/streams.rs b/lightning/src/crypto/streams.rs index 82680131f1d..9085e4b5b35 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::poly1305::Poly1305; +use crate::crypto::fixed_time_eq; 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,129 @@ 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); + + let mut mac = Poly1305::new(&mac_key[..32]); + + 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/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index b714f76616f..f08c4f686dd 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1558,7 +1558,7 @@ fn route_blinding_spec_test_vector() { // 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 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, None, ).unwrap(); // Concatenate an additional Bob -> Carol blinded path to the Eve -> Dave blinded path. @@ -1566,7 +1566,7 @@ fn route_blinding_spec_test_vector() { 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 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, None, ).unwrap(); let mut blinded_hops = bob_carol_blinded_hops; @@ -2028,7 +2028,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let path = [(carol_node_id, 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, None, ).unwrap() } else { let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs { @@ -2049,7 +2049,7 @@ 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))]; 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, None, ).unwrap() }; @@ -2253,7 +2253,7 @@ fn test_trampoline_unblinded_receive() { 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, None, ).unwrap(); let route = Route { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index fd98f78350e..1f45c75fe81 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -1068,11 +1068,12 @@ where }, } }; + let receiving_context_auth_key = [41; 32]; // TODO: pass this in 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(( @@ -1080,6 +1081,7 @@ where message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { context }), reply_path, + control_tlvs_authenticated, }, None, )) => match (message, context) { @@ -1108,6 +1110,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." @@ -2243,8 +2247,7 @@ fn packet_payloads_and_keys< unblinded_path.into_iter(), destination, session_priv, - |_, - onion_packet_ss, + |onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, @@ -2299,7 +2302,7 @@ 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 { @@ -2308,6 +2311,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..4e955993488 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -18,7 +18,7 @@ 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::util::logger::Logger; @@ -112,7 +112,11 @@ 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 +227,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 +243,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 +258,22 @@ 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, [u8; 32], &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, [u8; 32], &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))), }, |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 +310,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, }) }, }