Skip to content

Modular handshake #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eb6a371
build scaffold for handshake module substitution support
arik-so Feb 12, 2020
b71b7ea
change peer_handler.rs to use modular handshake and encryption handle…
arik-so Feb 12, 2020
92eac9b
make ephemeral private key explicit for handshake (todo: remove it fr…
arik-so Feb 12, 2020
986f25f
remove import of rand
arik-so Feb 12, 2020
ffbf5ec
make linter complain less about docs
arik-so Feb 12, 2020
19b7700
allocate act messages without vector prevarication
arik-so Feb 13, 2020
17fda75
reduce vector allocations for message encryption
arik-so Feb 13, 2020
8169b31
address some of Jeff's comments pertaining to message decryption, con…
arik-so Feb 19, 2020
f1002c5
Use type standin for remaining act lengths when parsing. Use the same…
arik-so Feb 19, 2020
f0fc10b
Improve comments and type aliasing for handshake module.
arik-so Feb 20, 2020
eb297f9
Merge branch 'master' into modular_handshake
arik-so Feb 20, 2020
5492717
Merge branch 'master' into modular_handshake
arik-so Feb 20, 2020
256b6f5
Reflect new modular encryption mechanism in tock ping message creation.
arik-so Feb 20, 2020
b4921e9
Elaborate on lightning codec in conduit's decrypt method.
arik-so Feb 21, 2020
299b6f7
Split up conduit unit tests by tested functionality.
arik-so Feb 21, 2020
944177a
Make handshake store the remote public key instead of passing an opti…
arik-so Feb 21, 2020
0fbd895
Panic when attempting invalid state transitions.
arik-so Feb 27, 2020
6cf5a07
Merge branch 'master' into modular_handshake
arik-so Mar 12, 2020
6f4e76a
Group peer handler's connected state checks instead of repeating them…
arik-so Mar 12, 2020
c2227b6
Fix missing init message send upon connection initiation.
arik-so Mar 13, 2020
6bae489
Merge branch 'master' into modular_handshake
arik-so Apr 9, 2020
2df93ca
fix some unit tests
arik-so Apr 9, 2020
eda13bf
Disconnect peer if act message is too short.
arik-so Apr 9, 2020
4e6b25a
Replace unwrapping public keys with handleable errors in handshake mo…
arik-so Apr 9, 2020
f1940e9
Merge branch 'master' into modular_handshake
arik-so Apr 11, 2020
4deb290
Split conduit into encryptor and decryptor components (to allow for b…
arik-so Apr 30, 2020
5e9c350
Fix conduit constructor bugs and revert indentation for message proce…
arik-so Apr 30, 2020
4b4cb98
Merge remote-tracking branch 'upstream/master' into modular_handshake
arik-so Apr 30, 2020
029bb66
Replace hashing and secp256k1 dependencies with components of the bit…
arik-so Apr 30, 2020
54b7464
Restrict conduit borrow scope for compatibility with Rust 1.22.0.
arik-so Apr 30, 2020
fe705a9
Fix lightning-net-tokio peer handler import.
arik-so Apr 30, 2020
be5e2a5
Apply message handling extraction to relocated peer handler.
arik-so Jun 12, 2020
2e4e659
Merge branch 'master' into modular_handshake
arik-so Jun 12, 2020
a4fff76
Fix unit tests
arik-so Jun 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions fuzz/src/full_stack.rs

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions lightning-net-tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
//! type FeeEstimator = dyn lightning::chain::chaininterface::FeeEstimator;
//! type ChannelMonitor = lightning::ln::channelmonitor::SimpleManyChannelMonitor<lightning::chain::transaction::OutPoint, lightning::chain::keysinterface::InMemoryChannelKeys, Arc<TxBroadcaster>, Arc<FeeEstimator>>;
//! type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager<ChannelMonitor, TxBroadcaster, FeeEstimator>;
//! type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager<lightning_net_tokio::SocketDescriptor, ChannelMonitor, TxBroadcaster, FeeEstimator>;
//! type PeerManager = lightning::ln::peers::handler::SimpleArcPeerManager<lightning_net_tokio::SocketDescriptor, ChannelMonitor, TxBroadcaster, FeeEstimator>;
//!
//! // Connect to node with pubkey their_node_id at addr:
//! async fn connect_to_node(peer_manager: PeerManager, channel_monitor: Arc<ChannelMonitor>, channel_manager: ChannelManager, their_node_id: PublicKey, addr: SocketAddr) {
Expand Down Expand Up @@ -66,8 +66,8 @@ use tokio::{io, time};
use tokio::sync::mpsc;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};

use lightning::ln::peer_handler;
use lightning::ln::peer_handler::SocketDescriptor as LnSocketTrait;
use lightning::ln::peers::handler as peer_handler;
use lightning::ln::peers::handler::SocketDescriptor as LnSocketTrait;
use lightning::ln::msgs::ChannelMessageHandler;

use std::{task, thread};
Expand Down Expand Up @@ -479,7 +479,7 @@ impl Hash for SocketDescriptor {
mod tests {
use lightning::ln::features::*;
use lightning::ln::msgs::*;
use lightning::ln::peer_handler::{MessageHandler, PeerManager};
use lightning::ln::peers::handler::{MessageHandler, PeerManager};
use lightning::util::events::*;
use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey};

Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod channelmanager;
pub mod channelmonitor;
pub mod msgs;
pub mod router;
pub mod peer_handler;
pub mod peers;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this up one so its not in ln? If we're gonna put almost everything in one top-level module, it seems like we should just not have that module :).

pub mod chan_utils;
pub mod features;
pub(crate) mod onchaintx;
Expand Down
40 changes: 40 additions & 0 deletions lightning/src/ln/peers/chacha.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use util::byte_utils;
use util::chacha20poly1305rfc::ChaCha20Poly1305RFC;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in ln::peers? It seems to be pure crypto functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the AEAD-based encryption methods are only used for handshakes and peer message encryption IIRC, and not for the onion construction.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but its also a pure-crypto primitive. I guess my preference is for such things (even if it implements a lightning protocol crypto primitive) to be in some kind of crypto module.

pub const TAG_SIZE: usize = 16;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there's a few places where this can be used throughout the file.


pub fn encrypt(key: &[u8], nonce: u64, associated_data: &[u8], plaintext: &[u8]) -> Vec<u8> {
let mut nonce_bytes = [0; 12];
nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce));

let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data);
let mut ciphertext = vec![0u8; plaintext.len()];
let mut authentication_tag = [0u8; 16];
chacha.encrypt(plaintext, &mut ciphertext, &mut authentication_tag);

let mut tagged_ciphertext = ciphertext;
tagged_ciphertext.extend_from_slice(&authentication_tag);
tagged_ciphertext
}

pub fn decrypt(key: &[u8], nonce: u64, associated_data: &[u8], tagged_ciphertext: &[u8]) -> Result<Vec<u8>, String> {
let mut nonce_bytes = [0; 12];
nonce_bytes[4..].copy_from_slice(&byte_utils::le64_to_array(nonce));

let length = tagged_ciphertext.len();
if length < 16 {
return Err("ciphertext cannot be shorter than tag length of 16 bytes".to_string());
}
let end_index = length - 16;
let ciphertext = &tagged_ciphertext[0..end_index];
let authentication_tag = &tagged_ciphertext[end_index..length];

let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce_bytes, associated_data);
let mut plaintext = vec![0u8; length - 16];
let success = chacha.decrypt(ciphertext, &mut plaintext, authentication_tag);
if success {
Ok(plaintext.to_vec())
} else {
Err("invalid hmac".to_string())
}
}
297 changes: 297 additions & 0 deletions lightning/src/ln/peers/conduit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
//! Handles all over the wire message encryption and decryption upon handshake completion.

use ln::peers::{chacha, hkdf};
use util::byte_utils;

pub(super) type SymmetricKey = [u8; 32];

const MESSAGE_LENGTH_HEADER_SIZE: usize = 2;
const TAGGED_MESSAGE_LENGTH_HEADER_SIZE: usize = MESSAGE_LENGTH_HEADER_SIZE + chacha::TAG_SIZE;

const KEY_ROTATION_INDEX: u32 = 1000;

/// Returned after a successful handshake to encrypt and decrypt communication with peer nodes.
/// It should not normally be manually instantiated.
/// Automatically handles key rotation.
/// For decryption, it is recommended to call `decrypt_message_stream` for automatic buffering.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation is out-of-date.

pub struct Conduit {
Comment on lines +13 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "conduit" is another name for "channel", which I assume you are trying not to overload. That said, nothing about this struct's behaviors fit the definition of those words. That is, the struct is not responsible for transferring (flowing) data from a source to a destination; the caller does that. Rather, it is simply responsible for encrypting and decrypting data that is transferred.

Further, the fields of the struct are essentially divided into analogous "sending" and "receiving" fields used for encrypting and decrypting respectively. And these fields are disjoint. Therefore, I'd recommend dividing this struct into two structs called Encryptor and Decryptor. The common associated functions can be moved to the module-level. Then, each struct has a single responsibility and there is no need to distinguish between types of keys and nonces (and mistakenly using the wrong one).

Then your PeerState enum can become:

enum PeerState {
    Authenticating(PeerHandshake),
    Connected(Encryptor, Decryptor),
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes perfect sense and I would love to do that, though what would you call the module?

Copy link
Contributor

@jkczyz jkczyz Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about simply "encryption.rs"? Or you could break them into "encryptor.rs" and "decryptor.rs" with key rotation utilities living elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. But I'd reserve it for a separate PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to use name Transcoder

pub(super) encryptor: Encryptor,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my rust-lightning node I handle input and output parts of TCP socket in different threads; so it will be nice to have an ability to split encryptor and decryptor as well. So I propose to make those fields public — or add split and join functions (see my proposed changes below)

pub(super) decryptor: Decryptor

}

pub(super) struct Encryptor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(super) struct Encryptor {
pub struct Encryptor {

sending_key: SymmetricKey,
sending_chaining_key: SymmetricKey,
sending_nonce: u32,
}

pub(super) struct Decryptor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(super) struct Decryptor {
pub struct Decryptor {

receiving_key: SymmetricKey,
receiving_chaining_key: SymmetricKey,
receiving_nonce: u32,

pending_message_length: Option<usize>,
read_buffer: Option<Vec<u8>>,
}

impl Iterator for Decryptor {
type Item = Vec<u8>;

fn next(&mut self) -> Option<Self::Item> {
self.decrypt_single_message(None)
}
}

impl Conduit {
/// Instantiate a new Conduit with specified sending and receiving keys
pub fn new(sending_key: SymmetricKey, receiving_key: SymmetricKey, chaining_key: SymmetricKey) -> Self {
Conduit {
encryptor: Encryptor {
sending_key,
sending_chaining_key: chaining_key,
sending_nonce: 0
},
decryptor: Decryptor {
receiving_key,
receiving_chaining_key: chaining_key,
receiving_nonce: 0,
read_buffer: None,
pending_message_length: None
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn join(encryptor: Encryptor, decryptor: Decryptor) -> Self {
Self {
encryptor,
decryptor
}
}
pub fn split(self) -> (Encryptor, Decryptor) {
(self.encryptor, self.decryptor)
}

/// Encrypt data to be sent to peer
pub fn encrypt(&mut self, buffer: &[u8]) -> Vec<u8> {
self.encryptor.encrypt(buffer)
}

pub(super) fn read(&mut self, data: &[u8]) {
self.decryptor.read(data)
}

/// Decrypt a single message. If data containing more than one message has been received,
/// only the first message will be returned, and the rest stored in the internal buffer.
/// If a message pending in the buffer still hasn't been decrypted, that message will be
/// returned in lieu of anything new, even if new data is provided.
pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option<Vec<u8>> {
self.decryptor.decrypt_single_message(new_data)
}

/// Decrypt a message from the beginning of the provided buffer. Returns the consumed number of bytes.
fn decrypt(&mut self, buffer: &[u8]) -> (Option<Vec<u8>>, usize) {
self.decryptor.decrypt(buffer)
}

fn increment_sending_nonce(&mut self) {
self.encryptor.increment_nonce()
}

fn increment_receiving_nonce(&mut self) {
self.decryptor.increment_nonce()
}
Comment on lines +87 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This private functions are not used from anywhere – and I can't imagine where they may be needed

Suggested change
fn increment_sending_nonce(&mut self) {
self.encryptor.increment_nonce()
}
fn increment_receiving_nonce(&mut self) {
self.decryptor.increment_nonce()
}


fn increment_nonce(nonce: &mut u32, chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) {
*nonce += 1;
if *nonce == KEY_ROTATION_INDEX {
Self::rotate_key(chaining_key, key);
*nonce = 0;
}
}

fn rotate_key(chaining_key: &mut SymmetricKey, key: &mut SymmetricKey) {
let (new_chaining_key, new_key) = hkdf::derive(chaining_key, key);
chaining_key.copy_from_slice(&new_chaining_key);
key.copy_from_slice(&new_key);
}
}

impl Encryptor {
pub(super) fn encrypt(&mut self, buffer: &[u8]) -> Vec<u8> {
let length = buffer.len() as u16;
let length_bytes = byte_utils::be16_to_array(length);

let mut ciphertext = vec![0u8; TAGGED_MESSAGE_LENGTH_HEADER_SIZE + length as usize + chacha::TAG_SIZE];

ciphertext[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], &length_bytes));
self.increment_nonce();

ciphertext[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..].copy_from_slice(&chacha::encrypt(&self.sending_key, self.sending_nonce as u64, &[0; 0], buffer));
self.increment_nonce();

ciphertext
}

fn increment_nonce(&mut self) {
Conduit::increment_nonce(&mut self.sending_nonce, &mut self.sending_chaining_key, &mut self.sending_key);
}
}

impl Decryptor {
pub(super) fn read(&mut self, data: &[u8]) {
let read_buffer = self.read_buffer.get_or_insert(Vec::new());
read_buffer.extend_from_slice(data);
}

/// Decrypt a single message. If data containing more than one message has been received,
/// only the first message will be returned, and the rest stored in the internal buffer.
/// If a message pending in the buffer still hasn't been decrypted, that message will be
/// returned in lieu of anything new, even if new data is provided.
pub fn decrypt_single_message(&mut self, new_data: Option<&[u8]>) -> Option<Vec<u8>> {
let mut read_buffer = if let Some(buffer) = self.read_buffer.take() {
buffer
} else {
Vec::new()
};

if let Some(data) = new_data {
read_buffer.extend_from_slice(data);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is kinda painful. Can we avoid copying the new data if we don't need to store it here? Maybe by returning an iterator over new messages that holds a &mut self to to this Conduit we can structure it so that it reads from the existing data first, then from the new data, then when the Iterator is dropped pushes the still-pending data into the buffer.

}

let (current_message, offset) = self.decrypt(&read_buffer[..]);
read_buffer.drain(..offset); // drain the read buffer
self.read_buffer = Some(read_buffer); // assign the new value to the built-in buffer
current_message
}

fn decrypt(&mut self, buffer: &[u8]) -> (Option<Vec<u8>>, usize) {
let message_length = if let Some(length) = self.pending_message_length {
// we have already decrypted the header
length
} else {
if buffer.len() < TAGGED_MESSAGE_LENGTH_HEADER_SIZE {
// A message must be at least 18 bytes (2 for encrypted length, 16 for the tag)
return (None, 0);
}

let encrypted_length = &buffer[0..TAGGED_MESSAGE_LENGTH_HEADER_SIZE];
let mut length_bytes = [0u8; MESSAGE_LENGTH_HEADER_SIZE];
length_bytes.copy_from_slice(&chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_length).unwrap());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found this bug while adding to the fuzz testing today. It should return an error here in the event of a decryption error instead of panicking. This is a bit involved since the Iterator implementation now needs to return Result objects, but it is doable.


self.increment_nonce();

// the message length
byte_utils::slice_to_be16(&length_bytes) as usize
};

let message_end_index = TAGGED_MESSAGE_LENGTH_HEADER_SIZE + message_length + chacha::TAG_SIZE;

if buffer.len() < message_end_index {
self.pending_message_length = Some(message_length);
return (None, 0);
}

self.pending_message_length = None;

let encrypted_message = &buffer[TAGGED_MESSAGE_LENGTH_HEADER_SIZE..message_end_index];

let message = chacha::decrypt(&self.receiving_key, self.receiving_nonce as u64, &[0; 0], encrypted_message).unwrap();

self.increment_nonce();

(Some(message), message_end_index)
}

fn increment_nonce(&mut self) {
Conduit::increment_nonce(&mut self.receiving_nonce, &mut self.receiving_chaining_key, &mut self.receiving_key);
}
}

#[cfg(test)]
mod tests {
use hex;

use ln::peers::conduit::Conduit;

fn setup_peers() -> (Conduit, Conduit) {
let chaining_key_vec = hex::decode("919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01").unwrap();
let mut chaining_key = [0u8; 32];
chaining_key.copy_from_slice(&chaining_key_vec);

let sending_key_vec = hex::decode("969ab31b4d288cedf6218839b27a3e2140827047f2c0f01bf5c04435d43511a9").unwrap();
let mut sending_key = [0u8; 32];
sending_key.copy_from_slice(&sending_key_vec);

let receiving_key_vec = hex::decode("bb9020b8965f4df047e07f955f3c4b88418984aadc5cdb35096b9ea8fa5c3442").unwrap();
let mut receiving_key = [0u8; 32];
receiving_key.copy_from_slice(&receiving_key_vec);

let connected_peer = Conduit::new(sending_key, receiving_key, chaining_key);
let remote_peer = Conduit::new(receiving_key, sending_key, chaining_key);

(connected_peer, remote_peer)
}

#[test]
fn test_empty_message() {
let (mut connected_peer, mut remote_peer) = setup_peers();

let message: Vec<u8> = vec![];
let encrypted_message = connected_peer.encrypt(&message);
assert_eq!(encrypted_message.len(), 2 + 16 + 16);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the constants that you've defined for these?


let decrypted_message = remote_peer.decrypt_single_message(Some(&encrypted_message)).unwrap();
assert_eq!(decrypted_message, vec![]);
}

#[test]
fn test_nonce_chaining() {
let (mut connected_peer, mut remote_peer) = setup_peers();
let message = hex::decode("68656c6c6f").unwrap();

let encrypted_message = connected_peer.encrypt(&message);
assert_eq!(encrypted_message, hex::decode("cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95").unwrap());

// the second time the same message is encrypted, the ciphertext should be different
let encrypted_message = connected_peer.encrypt(&message);
assert_eq!(encrypted_message, hex::decode("72887022101f0b6753e0c7de21657d35a4cb2a1f5cde2650528bbc8f837d0f0d7ad833b1a256a1").unwrap());
}

#[test]
/// Based on RFC test vectors: https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#message-encryption-tests
fn test_key_rotation() {
let (mut connected_peer, mut remote_peer) = setup_peers();

let message = hex::decode("68656c6c6f").unwrap();
let mut encrypted_messages: Vec<Vec<u8>> = Vec::new();

for _ in 0..1002 {
let encrypted_message = connected_peer.encrypt(&message);
encrypted_messages.push(encrypted_message);
}

assert_eq!(encrypted_messages[500], hex::decode("178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8").unwrap());
assert_eq!(encrypted_messages[501], hex::decode("1b186c57d44eb6de4c057c49940d79bb838a145cb528d6e8fd26dbe50a60ca2c104b56b60e45bd").unwrap());
assert_eq!(encrypted_messages[1000], hex::decode("4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09").unwrap());
assert_eq!(encrypted_messages[1001], hex::decode("2ecd8c8a5629d0d02ab457a0fdd0f7b90a192cd46be5ecb6ca570bfc5e268338b1a16cf4ef2d36").unwrap());
}

#[test]
fn test_decryption_buffering() {
let (mut connected_peer, mut remote_peer) = setup_peers();

let message = hex::decode("68656c6c6f").unwrap();
let mut encrypted_messages: Vec<Vec<u8>> = Vec::new();

for _ in 0..1002 {
let encrypted_message = connected_peer.encrypt(&message);
encrypted_messages.push(encrypted_message);
}

for _ in 0..501 {
// read two messages at once, filling buffer
let mut current_encrypted_message = encrypted_messages.remove(0);
let mut next_encrypted_message = encrypted_messages.remove(0);
current_encrypted_message.extend_from_slice(&next_encrypted_message);
let decrypted_message = remote_peer.decrypt_single_message(Some(&current_encrypted_message)).unwrap();
assert_eq!(decrypted_message, message);
}

for _ in 0..501 {
// decrypt messages directly from buffer without adding to it
let decrypted_message = remote_peer.decrypt_single_message(None).unwrap();
assert_eq!(decrypted_message, message);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like some of your files are missing a trailing newline character.

Loading