Add a ChaChaReader adapter to read an encrypted stream & use it
authorMatt Corallo <git@bluematt.me>
Fri, 27 Dec 2019 22:38:15 +0000 (17:38 -0500)
committerMatt Corallo <git@bluematt.me>
Tue, 11 Feb 2020 18:48:56 +0000 (13:48 -0500)
This prepares for variable-length per-hop-data by wrapping the full
hop_data field in a decrypting stream, with a few minor
optimizations and redundant allocations to boot.

lightning/src/ln/channelmanager.rs
lightning/src/util/chacha20.rs

index 34329aea0009267394bb083ede614baf21c5a51e..fc521b345a641736d0c72a8f700b75e63a11bfd1 100644 (file)
@@ -38,21 +38,19 @@ use chain::keysinterface::{ChannelKeys, KeysInterface, InMemoryChannelKeys};
 use util::config::UserConfig;
 use util::{byte_utils, events};
 use util::ser::{Readable, ReadableArgs, Writeable, Writer};
-use util::chacha20::ChaCha20;
+use util::chacha20::{ChaCha20, ChaChaReader};
 use util::logger::Logger;
 use util::errors::APIError;
 
 use std::{cmp, mem};
 use std::collections::{HashMap, hash_map, HashSet};
-use std::io::Cursor;
+use std::io::{Cursor, Read};
 use std::sync::{Arc, Mutex, MutexGuard, RwLock};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::time::Duration;
 use std::marker::{Sync, Send};
 use std::ops::Deref;
 
-const SIXTY_FIVE_ZEROS: [u8; 65] = [0; 65];
-
 // We hold various information about HTLC relay in the HTLC objects in Channel itself:
 //
 // Upon receipt of an HTLC from a peer, we'll give it a PendingHTLCStatus indicating if it should
@@ -906,12 +904,9 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
                }
 
                let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
+               let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&msg.onion_routing_packet.hop_data[..]) };
                let (next_hop_data, next_hop_hmac) = {
-                       let mut decoded = [0; 65];
-                       chacha.process(&msg.onion_routing_packet.hop_data[0..65], &mut decoded);
-                       let mut hmac = [0; 32];
-                       hmac.copy_from_slice(&decoded[33..]);
-                       match msgs::OnionHopData::read(&mut Cursor::new(&decoded[..33])) {
+                       match msgs::OnionHopData::read(&mut chacha_stream) {
                                Err(err) => {
                                        let error_code = match err {
                                                msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
@@ -919,7 +914,13 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
                                        };
                                        return_err!("Unable to decode our hop data", error_code, &[0;0]);
                                },
-                               Ok(msg) => (msg, hmac)
+                               Ok(msg) => {
+                                       let mut hmac = [0; 32];
+                                       if let Err(_) = chacha_stream.read_exact(&mut hmac[..]) {
+                                               return_err!("Unable to decode hop data", 0x4000 | 1, &[0;0]);
+                                       }
+                                       (msg, hmac)
+                               },
                        }
                };
 
@@ -933,10 +934,11 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
                                        // as-is (and were originally 0s).
                                        // Of course reverse path calculation is still pretty easy given naive routing
                                        // algorithms, but this fixes the most-obvious case.
-                                       let mut new_packet_data = [0; 19*65];
-                                       chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]);
-                                       assert_ne!(new_packet_data[0..65], [0; 65][..]);
-                                       assert_ne!(new_packet_data[..], [0; 19*65][..]);
+                                       let mut next_bytes = [0; 32];
+                                       chacha_stream.read_exact(&mut next_bytes).unwrap();
+                                       assert_ne!(next_bytes[..], [0; 32][..]);
+                                       chacha_stream.read_exact(&mut next_bytes).unwrap();
+                                       assert_ne!(next_bytes[..], [0; 32][..]);
                                }
 
                                // OUR PAYMENT!
@@ -968,8 +970,10 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
                                })
                        } else {
                                let mut new_packet_data = [0; 20*65];
-                               chacha.process(&msg.onion_routing_packet.hop_data[65..], &mut new_packet_data[0..19*65]);
-                               chacha.process(&SIXTY_FIVE_ZEROS[..], &mut new_packet_data[19*65..]);
+                               let read_pos = chacha_stream.read(&mut new_packet_data).unwrap();
+                               // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
+                               // fill the onion hop data we'll forward to our next-hop peer.
+                               chacha_stream.chacha.process_in_place(&mut new_packet_data[read_pos..]);
 
                                let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
 
index c96577da02c4a31ec1a3827b0ac38d9003e1a831..09e9a847598e10d51071ab1e3aa2d805d1f15860 100644 (file)
@@ -9,6 +9,8 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use std::io;
+
 #[cfg(not(feature = "fuzztarget"))]
 mod real_chacha {
        use std::cmp;
@@ -249,6 +251,29 @@ mod real_chacha {
                                self.offset += count;
                        }
                }
+
+               pub fn process_in_place(&mut self, input_output: &mut [u8]) {
+                       let len = input_output.len();
+                       let mut i = 0;
+                       while i < len {
+                               // If there is no keystream available in the output buffer,
+                               // generate the next block.
+                               if self.offset == 64 {
+                                       self.update();
+                               }
+
+                               // Process the min(available keystream, remaining input length).
+                               let count = cmp::min(64 - self.offset, len - i);
+                               // explicitly assert lengths to avoid bounds checks:
+                               assert!(input_output.len() >= i + count);
+                               assert!(self.output.len() >= self.offset + count);
+                               for j in 0..count {
+                                       input_output[i + j] ^= self.output[self.offset + j];
+                               }
+                               i += count;
+                               self.offset += count;
+                       }
+               }
        }
 }
 #[cfg(not(feature = "fuzztarget"))]
@@ -268,11 +293,27 @@ mod fuzzy_chacha {
                pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
                        output.copy_from_slice(input);
                }
+
+               pub fn process_in_place(&mut self, _input_output: &mut [u8]) {}
        }
 }
 #[cfg(feature = "fuzztarget")]
 pub use self::fuzzy_chacha::ChaCha20;
 
+pub(crate) struct ChaChaReader<'a, R: io::Read> {
+       pub chacha: &'a mut ChaCha20,
+       pub read: R,
+}
+impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> {
+       fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
+               let res = self.read.read(dest)?;
+               if res > 0 {
+                       self.chacha.process_in_place(&mut dest[0..res]);
+               }
+               Ok(res)
+       }
+}
+
 #[cfg(test)]
 mod test {
        use std::iter::repeat;