Skip to content

Commit a6cfb56

Browse files
chacha20poly1305: enable simultaneous writing+encryption
In the upcoming onion messages PR, this will allow us to avoid encoding onion message encrypted data into an intermediate Vec before encrypting it. Instead we encode and encrypt at the same time using this new ChaChaPolyWriteAdapter object.
1 parent 362ddab commit a6cfb56

File tree

1 file changed

+90
-3
lines changed

1 file changed

+90
-3
lines changed

lightning/src/util/chacha20poly1305rfc.rs

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
// This is a port of Andrew Moons poly1305-donna
1111
// https://github.com/floodyberry/poly1305-donna
1212

13+
use util::ser::{Writeable, Writer};
14+
use io::{self, Write};
15+
1316
#[cfg(not(fuzzing))]
1417
mod real_chachapoly {
1518
use util::chacha20::ChaCha20;
@@ -58,11 +61,32 @@ mod real_chachapoly {
5861
}
5962

6063
pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
61-
assert!(input.len() == output.len());
62-
assert!(self.finished == false);
6364
self.cipher.process(input, output);
65+
self.encrypt_inner(input, Some(output), Some(out_tag));
66+
}
67+
68+
pub fn encrypt_in_place(&mut self, input_output: &mut [u8], out_tag: Option<&mut [u8]>) {
69+
self.cipher.process_in_place(input_output);
70+
self.encrypt_inner(input_output, None, out_tag);
71+
}
72+
73+
// Encrypt in place, and fill in the tag if it's provided. If the tag is not provided, then
74+
// `finish_and_get_tag` may be called to check it later.
75+
fn encrypt_inner(&mut self, input: &[u8], output: Option<&mut [u8]>, out_tag: Option<&mut [u8]>) {
76+
assert!(self.finished == false);
77+
if let Some(output) = output {
78+
assert!(input.len() == output.len());
79+
self.mac.input(output);
80+
} else {
81+
self.mac.input(input);
82+
}
6483
self.data_len += input.len();
65-
self.mac.input(output);
84+
if let Some(tag) = out_tag {
85+
self.finish_and_get_tag(tag);
86+
}
87+
}
88+
89+
pub fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
6690
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
6791
self.finished = true;
6892
self.mac.input(&self.aad_len.to_le_bytes());
@@ -97,6 +121,56 @@ mod real_chachapoly {
97121
#[cfg(not(fuzzing))]
98122
pub use self::real_chachapoly::ChaCha20Poly1305RFC;
99123

124+
/// Enables simultaneously writing and encrypting a byte stream into a Writer.
125+
pub(crate) struct ChaChaPolyWriter<'a, W: Writer> {
126+
pub chacha: &'a mut ChaCha20Poly1305RFC,
127+
pub write: &'a mut W,
128+
}
129+
130+
impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
131+
// Encrypt then write bytes from `src` into Self::write.
132+
// `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
133+
// complete.
134+
fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
135+
let num_writes = (src.len() + (8192 - 1)) / 8192;
136+
for i in 0..num_writes {
137+
let mut write_buffer = [0; 8192];
138+
let bytes_written = (&mut write_buffer[..]).write(&src[i * 8192..])?;
139+
self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written], None);
140+
self.write.write_all(&write_buffer[..bytes_written])?;
141+
}
142+
Ok(())
143+
}
144+
}
145+
146+
/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
147+
/// serialized. This allows us to avoid an intermediate Vec allocation.
148+
pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> {
149+
pub rho: [u8; 32],
150+
pub writeable: &'a W,
151+
}
152+
153+
impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
154+
#[allow(unused)] // This will be used for onion messages soon
155+
pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> {
156+
Self { rho, writeable }
157+
}
158+
}
159+
160+
impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
161+
// Simultaneously write and encrypt Self::writeable.
162+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
163+
let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
164+
let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
165+
self.writeable.write(&mut chacha_stream)?;
166+
let mut tag = [0 as u8; 16];
167+
chacha.finish_and_get_tag(&mut tag);
168+
tag.write(w)?;
169+
170+
Ok(())
171+
}
172+
}
173+
100174
#[cfg(fuzzing)]
101175
mod fuzzy_chachapoly {
102176
#[derive(Clone, Copy)]
@@ -130,6 +204,19 @@ mod fuzzy_chachapoly {
130204
self.finished = true;
131205
}
132206

207+
pub fn encrypt_in_place(&mut self, _input_output: &mut [u8], out_tag: Option<&mut [u8]>) {
208+
assert!(self.finished == false);
209+
if let Some(tag) = out_tag {
210+
tag.copy_from_slice(&self.tag);
211+
self.finished = true;
212+
}
213+
}
214+
215+
pub fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
216+
out_tag.copy_from_slice(&self.tag);
217+
self.finished = true;
218+
}
219+
133220
pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
134221
assert!(input.len() == output.len());
135222
assert!(self.finished == false);

0 commit comments

Comments
 (0)