Skip to content

Commit 3676a05

Browse files
authored
Merge pull request #1518 from valentinewallace/2022-06-OMs-prefactor
Onion messages v1 pre-refactor
2 parents 10071b5 + ee805e9 commit 3676a05

File tree

5 files changed

+308
-15
lines changed

5 files changed

+308
-15
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use bitcoin::blockdata::transaction::Transaction;
2424
use bitcoin::blockdata::constants::genesis_block;
2525
use bitcoin::network::constants::Network;
2626

27-
use bitcoin::hashes::{Hash, HashEngine};
27+
use bitcoin::hashes::Hash;
2828
use bitcoin::hashes::sha256::Hash as Sha256;
2929
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
3030
use bitcoin::hash_types::{BlockHash, Txid};
@@ -2175,22 +2175,10 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
21752175
}
21762176
},
21772177
onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
2178-
let mut new_pubkey = msg.onion_routing_packet.public_key.unwrap();
2179-
2180-
let blinding_factor = {
2181-
let mut sha = Sha256::engine();
2182-
sha.input(&new_pubkey.serialize()[..]);
2183-
sha.input(&shared_secret);
2184-
Sha256::from_engine(sha).into_inner()
2185-
};
2186-
2187-
let public_key = if let Err(e) = new_pubkey.mul_assign(&self.secp_ctx, &blinding_factor[..]) {
2188-
Err(e)
2189-
} else { Ok(new_pubkey) };
2190-
2178+
let new_pubkey = msg.onion_routing_packet.public_key.unwrap();
21912179
let outgoing_packet = msgs::OnionPacket {
21922180
version: 0,
2193-
public_key,
2181+
public_key: onion_utils::next_hop_packet_pubkey(&self.secp_ctx, new_pubkey, &shared_secret),
21942182
hop_data: new_packet_bytes,
21952183
hmac: next_hop_hmac.clone(),
21962184
};

lightning/src/ln/onion_utils.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
7474
Hmac::from_engine(hmac).into_inner()
7575
}
7676

77+
pub(super) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, mut packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
78+
let blinding_factor = {
79+
let mut sha = Sha256::engine();
80+
sha.input(&packet_pubkey.serialize()[..]);
81+
sha.input(packet_shared_secret);
82+
Sha256::from_engine(sha).into_inner()
83+
};
84+
85+
packet_pubkey.mul_assign(secp_ctx, &blinding_factor[..]).map(|_| packet_pubkey)
86+
}
87+
7788
// can only fail if an intermediary hop has an invalid public key or session_priv is invalid
7889
#[inline]
7990
pub(super) fn construct_onion_keys_callback<T: secp256k1::Signing, FType: FnMut(SharedSecret, [u8; 32], PublicKey, &RouteHop, usize)> (secp_ctx: &Secp256k1<T>, path: &Vec<RouteHop>, session_priv: &SecretKey, mut callback: FType) -> Result<(), secp256k1::Error> {

lightning/src/util/chacha20poly1305rfc.rs

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

13+
use ln::msgs::DecodeError;
14+
use util::ser::{FixedLengthReader, LengthRead, LengthReadableArgs, Readable, Writeable, Writer};
15+
use io::{self, Read, Write};
16+
1317
#[cfg(not(fuzzing))]
1418
mod real_chachapoly {
1519
use util::chacha20::ChaCha20;
@@ -70,6 +74,26 @@ mod real_chachapoly {
7074
self.mac.raw_result(out_tag);
7175
}
7276

77+
// Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
78+
// below.
79+
pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
80+
debug_assert!(self.finished == false);
81+
self.cipher.process_in_place(input_output);
82+
self.data_len += input_output.len();
83+
self.mac.input(input_output);
84+
}
85+
86+
// If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
87+
// encrypting and calculate the tag.
88+
pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
89+
debug_assert!(self.finished == false);
90+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
91+
self.finished = true;
92+
self.mac.input(&self.aad_len.to_le_bytes());
93+
self.mac.input(&(self.data_len as u64).to_le_bytes());
94+
self.mac.raw_result(out_tag);
95+
}
96+
7397
pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
7498
assert!(input.len() == output.len());
7599
assert!(self.finished == false);
@@ -92,11 +116,141 @@ mod real_chachapoly {
92116
false
93117
}
94118
}
119+
120+
// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it
121+
// later when decryption finishes.
122+
//
123+
// Should never be `pub` because the public API should always enforce tag checking.
124+
pub(super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) {
125+
debug_assert!(self.finished == false);
126+
self.mac.input(input_output);
127+
self.data_len += input_output.len();
128+
self.cipher.process_in_place(input_output);
129+
}
130+
131+
// If we were previously decrypting with `decrypt_in_place`, this method must be used to finish
132+
// decrypting and check the tag. Returns whether or not the tag is valid.
133+
pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
134+
debug_assert!(self.finished == false);
135+
self.finished = true;
136+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
137+
self.mac.input(&self.aad_len.to_le_bytes());
138+
self.mac.input(&(self.data_len as u64).to_le_bytes());
139+
140+
let mut calc_tag = [0u8; 16];
141+
self.mac.raw_result(&mut calc_tag);
142+
if fixed_time_eq(&calc_tag, tag) {
143+
true
144+
} else {
145+
false
146+
}
147+
}
95148
}
96149
}
97150
#[cfg(not(fuzzing))]
98151
pub use self::real_chachapoly::ChaCha20Poly1305RFC;
99152

153+
/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
154+
struct ChaChaPolyReader<'a, R: Read> {
155+
pub chacha: &'a mut ChaCha20Poly1305RFC,
156+
pub read: R,
157+
}
158+
159+
impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> {
160+
// Decrypt bytes from Self::read into `dest`.
161+
// `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
162+
// complete.
163+
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
164+
let res = self.read.read(dest)?;
165+
if res > 0 {
166+
self.chacha.decrypt_in_place(&mut dest[0..res]);
167+
}
168+
Ok(res)
169+
}
170+
}
171+
172+
/// Enables simultaneously writing and encrypting a byte stream into a Writer.
173+
struct ChaChaPolyWriter<'a, W: Writer> {
174+
pub chacha: &'a mut ChaCha20Poly1305RFC,
175+
pub write: &'a mut W,
176+
}
177+
178+
impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
179+
// Encrypt then write bytes from `src` into Self::write.
180+
// `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
181+
// complete.
182+
fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
183+
let mut src_idx = 0;
184+
while src_idx < src.len() {
185+
let mut write_buffer = [0; 8192];
186+
let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail");
187+
self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
188+
self.write.write_all(&write_buffer[..bytes_written])?;
189+
src_idx += bytes_written;
190+
}
191+
Ok(())
192+
}
193+
}
194+
195+
/// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
196+
/// serialized. This allows us to avoid an intermediate Vec allocation.
197+
pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> {
198+
pub rho: [u8; 32],
199+
pub writeable: &'a W,
200+
}
201+
202+
impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
203+
#[allow(unused)] // This will be used for onion messages soon
204+
pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> {
205+
Self { rho, writeable }
206+
}
207+
}
208+
209+
impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
210+
// Simultaneously write and encrypt Self::writeable.
211+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
212+
let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
213+
let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
214+
self.writeable.write(&mut chacha_stream)?;
215+
let mut tag = [0 as u8; 16];
216+
chacha.finish_and_get_tag(&mut tag);
217+
tag.write(w)?;
218+
219+
Ok(())
220+
}
221+
}
222+
223+
/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
224+
/// deserialized. This allows us to avoid an intermediate Vec allocation.
225+
pub(crate) struct ChaChaPolyReadAdapter<R: Readable> {
226+
#[allow(unused)] // This will be used soon for onion messages
227+
pub readable: R,
228+
}
229+
230+
impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
231+
// Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable.
232+
// LengthRead must be used instead of std::io::Read because we need the total length to separate
233+
// out the tag at the end.
234+
fn read<R: LengthRead>(mut r: &mut R, secret: [u8; 32]) -> Result<Self, DecodeError> {
235+
if r.total_bytes() < 16 { return Err(DecodeError::InvalidValue) }
236+
237+
let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]);
238+
let decrypted_len = r.total_bytes() - 16;
239+
let s = FixedLengthReader::new(&mut r, decrypted_len);
240+
let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s };
241+
let readable: T = Readable::read(&mut chacha_stream)?;
242+
chacha_stream.read.eat_remaining()?;
243+
244+
let mut tag = [0 as u8; 16];
245+
r.read_exact(&mut tag)?;
246+
if !chacha.finish_and_check_tag(&tag) {
247+
return Err(DecodeError::InvalidValue)
248+
}
249+
250+
Ok(Self { readable })
251+
}
252+
}
253+
100254
#[cfg(fuzzing)]
101255
mod fuzzy_chachapoly {
102256
#[derive(Clone, Copy)]
@@ -130,6 +284,16 @@ mod fuzzy_chachapoly {
130284
self.finished = true;
131285
}
132286

287+
pub(super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) {
288+
assert!(self.finished == false);
289+
self.finished = true;
290+
}
291+
292+
pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
293+
out_tag.copy_from_slice(&self.tag);
294+
self.finished = true;
295+
}
296+
133297
pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
134298
assert!(input.len() == output.len());
135299
assert!(self.finished == false);
@@ -139,7 +303,106 @@ mod fuzzy_chachapoly {
139303
self.finished = true;
140304
true
141305
}
306+
307+
pub(super) fn decrypt_in_place(&mut self, _input: &mut [u8]) {
308+
assert!(self.finished == false);
309+
}
310+
311+
pub(super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
312+
if tag[..] != self.tag[..] { return false; }
313+
self.finished = true;
314+
true
315+
}
142316
}
143317
}
144318
#[cfg(fuzzing)]
145319
pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;
320+
321+
#[cfg(test)]
322+
mod tests {
323+
use ln::msgs::DecodeError;
324+
use super::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
325+
use util::ser::{self, FixedLengthReader, LengthReadableArgs, Writeable};
326+
327+
// Used for for testing various lengths of serialization.
328+
#[derive(Debug, PartialEq)]
329+
struct TestWriteable {
330+
field1: Vec<u8>,
331+
field2: Vec<u8>,
332+
field3: Vec<u8>,
333+
}
334+
impl_writeable_tlv_based!(TestWriteable, {
335+
(1, field1, vec_type),
336+
(2, field2, vec_type),
337+
(3, field3, vec_type),
338+
});
339+
340+
#[test]
341+
fn test_chacha_stream_adapters() {
342+
// Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an
343+
// encrypted object.
344+
macro_rules! check_object_read_write {
345+
($obj: expr) => {
346+
// First, serialize the object, encrypted with ChaCha20Poly1305.
347+
let rho = [42; 32];
348+
let writeable_len = $obj.serialized_length() as u64 + 16;
349+
let write_adapter = ChaChaPolyWriteAdapter::new(rho, &$obj);
350+
let encrypted_writeable_bytes = write_adapter.encode();
351+
let encrypted_writeable = &encrypted_writeable_bytes[..];
352+
353+
// Now deserialize the object back and make sure it matches the original.
354+
let mut rd = FixedLengthReader::new(encrypted_writeable, writeable_len);
355+
let read_adapter = <ChaChaPolyReadAdapter<TestWriteable>>::read(&mut rd, rho).unwrap();
356+
assert_eq!($obj, read_adapter.readable);
357+
};
358+
}
359+
360+
// Try a big object that will require multiple write buffers.
361+
let big_writeable = TestWriteable {
362+
field1: vec![43],
363+
field2: vec![44; 4192],
364+
field3: vec![45; 4192 + 1],
365+
};
366+
check_object_read_write!(big_writeable);
367+
368+
// Try a small object that fits into one write buffer.
369+
let small_writeable = TestWriteable {
370+
field1: vec![43],
371+
field2: vec![44],
372+
field3: vec![45],
373+
};
374+
check_object_read_write!(small_writeable);
375+
}
376+
377+
fn do_chacha_stream_adapters_ser_macros() -> Result<(), DecodeError> {
378+
let writeable = TestWriteable {
379+
field1: vec![43],
380+
field2: vec![44; 4192],
381+
field3: vec![45; 4192 + 1],
382+
};
383+
384+
// First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305.
385+
let rho = [42; 32];
386+
let write_adapter = ChaChaPolyWriteAdapter::new(rho, &writeable);
387+
let mut writer = ser::VecWriter(Vec::new());
388+
encode_tlv_stream!(&mut writer, {
389+
(1, write_adapter, required),
390+
});
391+
392+
// Now deserialize the object back and make sure it matches the original.
393+
let mut read_adapter: Option<ChaChaPolyReadAdapter<TestWriteable>> = None;
394+
decode_tlv_stream!(&writer.0[..], {
395+
(1, read_adapter, (option: LengthReadableArgs, rho)),
396+
});
397+
assert_eq!(writeable, read_adapter.unwrap().readable);
398+
399+
Ok(())
400+
}
401+
402+
#[test]
403+
fn chacha_stream_adapters_ser_macros() {
404+
// Test that our stream adapters work as expected with the TLV macros.
405+
// This also serves to test the `option: $trait` variant of the `decode_tlv` ser macro.
406+
do_chacha_stream_adapters_ser_macros().unwrap()
407+
}
408+
}

lightning/src/util/ser.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ impl<R: Read> Read for FixedLengthReader<R> {
134134
}
135135
}
136136

137+
impl<R: Read> LengthRead for FixedLengthReader<R> {
138+
#[inline]
139+
fn total_bytes(&self) -> u64 {
140+
self.total_bytes
141+
}
142+
}
143+
137144
/// A Read which tracks whether any bytes have been read at all. This allows us to distinguish
138145
/// between "EOF reached before we started" and "EOF reached mid-read".
139146
pub(crate) struct ReadTrackingReader<R: Read> {
@@ -220,6 +227,21 @@ pub trait ReadableArgs<P>
220227
fn read<R: Read>(reader: &mut R, params: P) -> Result<Self, DecodeError>;
221228
}
222229

230+
/// A std::io::Read that also provides the total bytes available to read.
231+
pub(crate) trait LengthRead: Read {
232+
/// The total number of bytes available to read.
233+
fn total_bytes(&self) -> u64;
234+
}
235+
236+
/// A trait that various higher-level rust-lightning types implement allowing them to be read in
237+
/// from a Read given some additional set of arguments which is required to deserialize, requiring
238+
/// the implementer to provide the total length of the read.
239+
pub(crate) trait LengthReadableArgs<P> where Self: Sized
240+
{
241+
/// Reads a Self in from the given LengthRead
242+
fn read<R: LengthRead>(reader: &mut R, params: P) -> Result<Self, DecodeError>;
243+
}
244+
223245
/// A trait that various rust-lightning types implement allowing them to (maybe) be read in from a Read
224246
///
225247
/// (C-not exported) as we only export serialization to/from byte arrays instead

0 commit comments

Comments
 (0)