Skip to content

Commit a2cc67e

Browse files
committed
Replace local ChaCha20-Poly1305 with external crate
Migrates ChaCha20-Poly1305 encryption from the local crypto module to rust-bitcoin's `chacha20-poly1305` crate. Integrated the crate across all modules (Router, PeerStorage, Onion Utils, etc.). Add the chacha20_poly1305_fuzz flag to fuzz config to after implementing the fuzz logic upstream.
1 parent 1a26867 commit a2cc67e

13 files changed

Lines changed: 200 additions & 119 deletions

File tree

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ jobs:
225225
- name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }}
226226
run: |
227227
cd fuzz
228-
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --quiet --color always --lib -j8
229-
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --manifest-path fuzz-fake-hashes/Cargo.toml --quiet --color always --bins -j8
230-
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" cargo test --manifest-path fuzz-real-hashes/Cargo.toml --quiet --color always --bins -j8
228+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz --cfg=chacha20_poly1305_fuzz" cargo test --quiet --color always --lib -j8
229+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz --cfg=chacha20_poly1305_fuzz" cargo test --manifest-path fuzz-fake-hashes/Cargo.toml --quiet --color always --bins -j8
230+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=chacha20_poly1305_fuzz" cargo test --manifest-path fuzz-real-hashes/Cargo.toml --quiet --color always --bins -j8
231231
232232
fuzz:
233233
runs-on: self-hosted

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ check-cfg = [
5858
"cfg(fuzzing)",
5959
"cfg(secp256k1_fuzz)",
6060
"cfg(hashes_fuzz)",
61+
"cfg(chacha20_poly1305_fuzz)",
6162
"cfg(test)",
6263
"cfg(debug_assertions)",
6364
"cfg(c_bindings)",

ci/check-compiles.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ cargo check
66
cargo doc
77
cargo doc --document-private-items
88
cd fuzz
9-
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \
9+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz --cfg=chacha20_poly1305_fuzz" \
1010
cargo check --manifest-path fuzz-fake-hashes/Cargo.toml --features=stdin_fuzz
11-
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" \
11+
RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=chacha20_poly1305_fuzz" \
1212
cargo check --manifest-path fuzz-real-hashes/Cargo.toml --features=stdin_fuzz
1313
cd ../lightning && cargo check --no-default-features
1414
cd .. && RUSTC_BOOTSTRAP=1 RUSTFLAGS="--cfg=c_bindings" cargo check -Z avoid-dev-deps

fuzz/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ check-cfg = [
4444
"cfg(secp256k1_fuzz)",
4545
"cfg(hashes_fuzz)",
4646
"cfg(splicing)",
47+
"cfg(chacha20_poly1305_fuzz)"
4748
]

lightning/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ lightning-macros = { version = "0.2", path = "../lightning-macros" }
4040

4141
bech32 = { version = "0.11.0", default-features = false }
4242
bitcoin = { version = "0.32.4", default-features = false, features = ["secp-recovery"] }
43+
chacha20-poly1305 = { version = "0.2.0", default-features = false }
4344

4445
dnssec-prover = { version = "0.6", default-features = false }
4546
hashbrown = { version = "0.13", default-features = false }

lightning/src/crypto/streams.rs

Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::crypto::chacha20::ChaCha20;
2-
use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
31
use crate::crypto::fixed_time_eq;
4-
use crate::crypto::poly1305::Poly1305;
52

63
use crate::io::{self, Read, Write};
74
use crate::ln::msgs::DecodeError;
@@ -10,6 +7,10 @@ use crate::util::ser::{
107
};
118

129
use alloc::vec::Vec;
10+
use chacha20_poly1305::{
11+
chacha20::{ChaCha20, Key, Nonce},
12+
poly1305::Poly1305,
13+
};
1314

1415
pub(crate) struct ChaChaReader<'a, R: io::Read> {
1516
pub chacha: &'a mut ChaCha20,
@@ -19,7 +20,7 @@ impl<'a, R: io::Read> io::Read for ChaChaReader<'a, R> {
1920
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
2021
let res = self.read.read(dest)?;
2122
if res > 0 {
22-
self.chacha.process_in_place(&mut dest[0..res]);
23+
self.chacha.apply_keystream(&mut dest[..res]);
2324
}
2425
Ok(res)
2526
}
@@ -42,11 +43,20 @@ impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
4243
impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
4344
// Simultaneously write and encrypt Self::writeable.
4445
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
45-
let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
46-
let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
46+
let mut chacha = ChaCha20::new(Key::new(self.rho), Nonce::new([0; 12]), 0);
47+
let mut mac_key = [0u8; 64];
48+
chacha.apply_keystream(&mut mac_key);
49+
50+
#[cfg(not(fuzzing))]
51+
let mac = Poly1305::new(mac_key[..32].try_into().unwrap());
52+
#[cfg(fuzzing)]
53+
let mac = Poly1305::new(self.rho);
54+
55+
let mut chacha_stream =
56+
ChaChaPolyWriter { chacha: &mut chacha, poly: mac, write_len: 0, write: w };
4757
self.writeable.write(&mut chacha_stream)?;
48-
let mut tag = [0 as u8; 16];
49-
chacha.finish_and_get_tag(&mut tag);
58+
59+
let tag = chacha_stream.finish_and_get_tag();
5060
tag.write(w)?;
5161

5262
Ok(())
@@ -62,12 +72,15 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
6272
pub(crate) fn chachapoly_encrypt_with_swapped_aad(
6373
mut plaintext: Vec<u8>, key: [u8; 32], aad: [u8; 32],
6474
) -> Vec<u8> {
65-
let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
75+
let mut chacha = ChaCha20::new(Key::new(key), Nonce::new([0; 12]), 0);
6676
let mut mac_key = [0u8; 64];
67-
chacha.process_in_place(&mut mac_key);
77+
chacha.apply_keystream(&mut mac_key);
6878

69-
let mut mac = Poly1305::new(&mac_key[..32]);
70-
chacha.process_in_place(&mut plaintext[..]);
79+
#[cfg(not(fuzzing))]
80+
let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
81+
#[cfg(fuzzing)]
82+
let mut mac = Poly1305::new(key);
83+
chacha.apply_keystream(&mut plaintext[..]);
7184
mac.input(&plaintext[..]);
7285

7386
if plaintext.len() % 16 != 0 {
@@ -80,7 +93,7 @@ pub(crate) fn chachapoly_encrypt_with_swapped_aad(
8093
mac.input(&(plaintext.len() as u64).to_le_bytes());
8194
mac.input(&32u64.to_le_bytes());
8295

83-
plaintext.extend_from_slice(&mac.result());
96+
plaintext.extend_from_slice(&mac.tag());
8497
plaintext
8598
}
8699

@@ -105,7 +118,7 @@ pub(crate) enum TriPolyAADUsed {
105118
///
106119
/// Note that we do *not* use the provided AADs as the standard ChaCha20Poly1305 AAD as that would
107120
/// require placing it first and prevent us from avoiding redundant Poly1305 rounds. Instead, the
108-
/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the the contents being
121+
/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the contents being
109122
/// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving
110123
/// like classic ChaCha20Poly1305 for the non-AAD-containing MAC.
111124
pub(crate) struct ChaChaTriPolyReadAdapter<R: Readable> {
@@ -127,14 +140,14 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
127140
}
128141
let (key, aad_a, aad_b) = params;
129142

130-
let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
143+
let mut chacha = ChaCha20::new(Key::new(key), Nonce::new([0; 12]), 0);
131144
let mut mac_key = [0u8; 64];
132-
chacha.process_in_place(&mut mac_key);
145+
chacha.apply_keystream(&mut mac_key);
133146

134147
#[cfg(not(fuzzing))]
135-
let mut mac = Poly1305::new(&mac_key[..32]);
148+
let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
136149
#[cfg(fuzzing)]
137-
let mut mac = Poly1305::new(&key);
150+
let mut mac = Poly1305::new(key);
138151

139152
let decrypted_len = r.remaining_bytes() - 16;
140153
let s = FixedLengthReader::new(r, decrypted_len);
@@ -145,7 +158,6 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
145158
while chacha_stream.read.bytes_remain() {
146159
let mut buf = [0; 256];
147160
if chacha_stream.read(&mut buf)? == 0 {
148-
// Reached EOF
149161
return Err(DecodeError::ShortRead);
150162
}
151163
}
@@ -173,13 +185,13 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
173185
mac.input(&0u64.to_le_bytes());
174186
mac.input(&(read_len as u64).to_le_bytes());
175187

176-
let mut tag = [0 as u8; 16];
188+
let mut tag = [0u8; 16];
177189
r.read_exact(&mut tag)?;
178-
if fixed_time_eq(&mac.result(), &tag) {
190+
if fixed_time_eq(&mac.tag(), &tag) {
179191
Ok(Self { readable, used_aad: TriPolyAADUsed::None })
180-
} else if fixed_time_eq(&mac_aad_a.result(), &tag) {
192+
} else if fixed_time_eq(&mac_aad_a.tag(), &tag) {
181193
Ok(Self { readable, used_aad: TriPolyAADUsed::First })
182-
} else if fixed_time_eq(&mac_aad_b.result(), &tag) {
194+
} else if fixed_time_eq(&mac_aad_b.tag(), &tag) {
183195
Ok(Self { readable, used_aad: TriPolyAADUsed::Second })
184196
} else {
185197
return Err(DecodeError::InvalidValue);
@@ -197,12 +209,12 @@ struct ChaChaTriPolyReader<'a, R: Read> {
197209
impl<'a, R: Read> Read for ChaChaTriPolyReader<'a, R> {
198210
// Decrypts bytes from Self::read into `dest`.
199211
// After all reads complete, the caller must compare the expected tag with
200-
// the result of `Poly1305::result()`.
212+
// the result of `Poly1305::tag()`
201213
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
202214
let res = self.read.read(dest)?;
203215
if res > 0 {
204-
self.poly.input(&dest[0..res]);
205-
self.chacha.process_in_place(&mut dest[0..res]);
216+
self.poly.input(&dest[..res]);
217+
self.chacha.apply_keystream(&mut dest[..res]);
206218
self.read_len += res;
207219
}
208220
Ok(res)
@@ -224,64 +236,109 @@ impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
224236
return Err(DecodeError::InvalidValue);
225237
}
226238

227-
let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]);
239+
let mut chacha = ChaCha20::new(Key::new(secret), Nonce::new([0; 12]), 0);
240+
let mut mac_key = [0u8; 64];
241+
chacha.apply_keystream(&mut mac_key);
242+
243+
#[cfg(not(fuzzing))]
244+
let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
245+
#[cfg(fuzzing)]
246+
let mut mac = Poly1305::new(secret);
247+
228248
let decrypted_len = r.remaining_bytes() - 16;
229249
let s = FixedLengthReader::new(r, decrypted_len);
230-
let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s };
250+
let mut chacha_stream = ChaChaPolyReader::new(&mut chacha, &mut mac, s);
231251
let readable: T = Readable::read(&mut chacha_stream)?;
232252
while chacha_stream.read.bytes_remain() {
233253
let mut buf = [0; 256];
234-
chacha_stream.read(&mut buf)?;
254+
if chacha_stream.read(&mut buf)? == 0 {
255+
return Err(DecodeError::ShortRead);
256+
}
235257
}
236258

237-
let mut tag = [0 as u8; 16];
259+
let read_len = chacha_stream.read_len();
260+
drop(chacha_stream);
261+
262+
if read_len % 16 != 0 {
263+
mac.input(&[0; 16][0..16 - (read_len % 16)]);
264+
}
265+
mac.input(&0u64.to_le_bytes());
266+
mac.input(&(read_len as u64).to_le_bytes());
267+
268+
let mut tag = [0u8; 16];
238269
r.read_exact(&mut tag)?;
239-
if !chacha.finish_and_check_tag(&tag) {
270+
if !fixed_time_eq(&mac.tag(), &tag) {
240271
return Err(DecodeError::InvalidValue);
241272
}
242273

243274
Ok(Self { readable })
244275
}
245276
}
246277

247-
/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
278+
/// Enables simultaneously reading and decrypting a ChaCha20Poly1305 stream from a std::io::Read.
248279
struct ChaChaPolyReader<'a, R: Read> {
249-
pub chacha: &'a mut ChaCha20Poly1305RFC,
280+
chacha: &'a mut ChaCha20,
281+
poly: &'a mut Poly1305,
282+
read_len: usize,
250283
pub read: R,
251284
}
252285

286+
impl<'a, R: Read> ChaChaPolyReader<'a, R> {
287+
fn new(chacha: &'a mut ChaCha20, poly: &'a mut Poly1305, read: R) -> Self {
288+
Self { chacha, poly, read_len: 0, read }
289+
}
290+
291+
fn read_len(&self) -> usize {
292+
self.read_len
293+
}
294+
}
295+
253296
impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> {
254297
// Decrypt bytes from Self::read into `dest`.
255-
// `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
256-
// complete.
257298
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
258299
let res = self.read.read(dest)?;
259300
if res > 0 {
260-
self.chacha.decrypt_in_place(&mut dest[0..res]);
301+
self.poly.input(&dest[..res]);
302+
self.chacha.apply_keystream(&mut dest[..res]);
303+
self.read_len += res;
261304
}
262305
Ok(res)
263306
}
264307
}
265308

266309
/// Enables simultaneously writing and encrypting a byte stream into a Writer.
267310
struct ChaChaPolyWriter<'a, W: Writer> {
268-
pub chacha: &'a mut ChaCha20Poly1305RFC,
311+
chacha: &'a mut ChaCha20,
312+
poly: Poly1305,
313+
write_len: usize,
269314
pub write: &'a mut W,
270315
}
271316

317+
impl<'a, W: Writer> ChaChaPolyWriter<'a, W> {
318+
/// Finish encrypting and return the 16-byte authentication tag.
319+
fn finish_and_get_tag(mut self) -> [u8; 16] {
320+
if self.write_len % 16 != 0 {
321+
self.poly.input(&[0; 16][0..16 - (self.write_len % 16)]);
322+
}
323+
self.poly.input(&0u64.to_le_bytes());
324+
self.poly.input(&(self.write_len as u64).to_le_bytes());
325+
self.poly.tag()
326+
}
327+
}
328+
272329
impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
273330
// Encrypt then write bytes from `src` into Self::write.
274-
// `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
275-
// complete.
276331
fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
277332
let mut src_idx = 0;
278333
while src_idx < src.len() {
279334
let mut write_buffer = [0; 8192];
280335
let bytes_written = (&mut write_buffer[..])
281336
.write(&src[src_idx..])
282337
.expect("In-memory writes can't fail");
283-
self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
338+
self.chacha.apply_keystream(&mut write_buffer[..bytes_written]);
339+
self.poly.input(&write_buffer[..bytes_written]);
284340
self.write.write_all(&write_buffer[..bytes_written])?;
341+
self.write_len += bytes_written;
285342
src_idx += bytes_written;
286343
}
287344
Ok(())

lightning/src/ln/inbound_payment.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use bitcoin::hashes::cmp::fixed_time_eq;
1313
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
1414
use bitcoin::hashes::sha256::Hash as Sha256;
1515
use bitcoin::hashes::{Hash, HashEngine};
16+
use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
1617

17-
use crate::crypto::chacha20::ChaCha20;
1818
use crate::crypto::utils::hkdf_extract_expand_7x;
1919
use crate::ln::msgs;
2020
use crate::ln::msgs::MAX_VALUE_MSAT;
21-
use crate::offers::nonce::Nonce;
21+
use crate::offers::nonce::Nonce as LocalNonce;
2222
use crate::sign::EntropySource;
2323
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
2424
use crate::util::errors::APIError;
@@ -96,8 +96,13 @@ impl ExpandedKey {
9696

9797
/// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
9898
/// metadata (e.g., payment id).
99-
pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
100-
ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
99+
pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: LocalNonce) -> [u8; 32] {
100+
ChaCha20::new_from_block(
101+
Key::new(self.offers_encryption_key),
102+
Nonce::new(nonce.0[4..].try_into().unwrap()),
103+
u32::from_le_bytes(nonce.0[..4].try_into().unwrap()),
104+
)
105+
.apply_keystream(&mut bytes);
101106
bytes
102107
}
103108
}
@@ -301,12 +306,14 @@ fn construct_payment_secret(
301306
let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN);
302307
iv_slice.copy_from_slice(iv_bytes);
303308

304-
ChaCha20::encrypt_single_block(
305-
metadata_key,
306-
iv_bytes,
307-
encrypted_metadata_slice,
308-
metadata_bytes,
309-
);
309+
encrypted_metadata_slice.copy_from_slice(metadata_bytes);
310+
ChaCha20::new_from_block(
311+
Key::new(*metadata_key),
312+
Nonce::new(iv_bytes[4..].try_into().unwrap()),
313+
u32::from_le_bytes(iv_bytes[..4].try_into().unwrap()),
314+
)
315+
.apply_keystream(encrypted_metadata_slice);
316+
310317
PaymentSecret(payment_secret_bytes)
311318
}
312319

@@ -485,12 +492,13 @@ fn decrypt_metadata(
485492
iv_bytes.copy_from_slice(iv_slice);
486493

487494
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
488-
ChaCha20::encrypt_single_block(
489-
&keys.metadata_key,
490-
&iv_bytes,
491-
&mut metadata_bytes,
492-
encrypted_metadata_bytes,
493-
);
495+
metadata_bytes.copy_from_slice(encrypted_metadata_bytes);
496+
ChaCha20::new_from_block(
497+
Key::new(keys.metadata_key),
498+
Nonce::new(iv_bytes[4..].try_into().unwrap()),
499+
u32::from_le_bytes(iv_bytes[..4].try_into().unwrap()),
500+
)
501+
.apply_keystream(&mut metadata_bytes);
494502

495503
(iv_bytes, metadata_bytes)
496504
}

0 commit comments

Comments
 (0)