Replace local ChaCha20-Poly1305 with external crate
What changed, and why it matters
This commit swaps out a home-grown ChaCha20-Poly1305 implementation for a widely reviewed external crate from the rust-bitcoin project. It is a routine refactoring/cryptographic-hardening change, not an obvious security fix. The diff shows careful translation of the old code to the new API, including fuzzing support and nonce handling. There is no direct evidence of a vulnerability being patched, but any crypto migration carries risk of subtle behavioral differences.
Treat as a normal code-review item. Verify that the new crate's API semantics match the old implementation, especially around Poly1305 key derivation, AAD placement, nonce handling, and tag verification. Run the updated fuzz targets and full test suite. No emergency action is warranted absent additional vulnerability evidence.
Security signals we found
Cryptographic implementation migration from local to external audited crate
Manual Poly1305 key derivation and custom AAD ordering preserved from old implementation
New fuzzing cfg flag added for the external crate
Nonce construction changed in several call sites (8-byte to 12-byte nonce with counter split)
No explicit vulnerability disclosure or CVE referenced in commit message or diff
Evidence from the diff
The commit migrates all ChaCha20 and ChaCha20-Poly1305 usage in rust-lightning from internal modules (crypto::chacha20, crypto::poly1305, crypto::chacha20poly1305rfc) to the external chacha20-poly1305 crate (version 0.2.0) from rust-bitcoin. It updates callers across streams.rs, inbound_payment.rs, onion_utils.rs, our_peer_storage.rs, peer_channel_encryptor.rs, router.rs, sign/mod.rs, and scid_utils.rs. Notable changes include: replacing process_in_place/process with apply_keystream, manually deriving Poly1305 keys from ChaCha20 keystreams to preserve the existing custom AAD handling, adding a chacha20_poly1305_fuzz cfg flag for fuzz builds, and adjusting nonce construction (e.g., 8-byte nonces expanded to 12 bytes with counter split). The old local crypto code is removed from use but the diff does not show deletion of the source files themselves.
Changed components
lightning/src/crypto/streams.rslightning/src/ln/inbound_payment.rslightning/src/ln/onion_utils.rslightning/src/ln/our_peer_storage.rslightning/src/ln/peer_channel_encryptor.rslightning/src/routing/router.rslightning/src/sign/mod.rslightning/src/util/scid_utils.rsCargo.toml dependency graphfuzz build configurationInspect captured patch +200 / −119
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2cad565..5862302 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -225,9 +225,9 @@ jobs:
- name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }}
run: |
cd fuzz
- RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --quiet --color always --lib -j8
- RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --manifest-path fuzz-fake-hashes/Cargo.toml --quiet --color always --bins -j8
- RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" cargo test --manifest-path fuzz-real-hashes/Cargo.toml --quiet --color always --bins -j8
+ RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz --cfg=chacha20_poly1305_fuzz" cargo test --quiet --color always --lib -j8
+ 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
+ RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=chacha20_poly1305_fuzz" cargo test --manifest-path fuzz-real-hashes/Cargo.toml --quiet --color always --bins -j8
fuzz:
runs-on: self-hosted
diff --git a/Cargo.toml b/Cargo.toml
index 7978d9d..98bf306 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,6 +58,7 @@ check-cfg = [
"cfg(fuzzing)",
"cfg(secp256k1_fuzz)",
"cfg(hashes_fuzz)",
+ "cfg(chacha20_poly1305_fuzz)",
"cfg(test)",
"cfg(debug_assertions)",
"cfg(c_bindings)",
diff --git a/ci/check-compiles.sh b/ci/check-compiles.sh
index cd1e075..30f7518 100755
--- a/ci/check-compiles.sh
+++ b/ci/check-compiles.sh
@@ -6,9 +6,9 @@ cargo check
cargo doc
cargo doc --document-private-items
cd fuzz
-RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \
+RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz --cfg=chacha20_poly1305_fuzz" \
cargo check --manifest-path fuzz-fake-hashes/Cargo.toml --features=stdin_fuzz
-RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" \
+RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=chacha20_poly1305_fuzz" \
cargo check --manifest-path fuzz-real-hashes/Cargo.toml --features=stdin_fuzz
cd ../lightning && cargo check --no-default-features
cd .. && RUSTC_BOOTSTRAP=1 RUSTFLAGS="--cfg=c_bindings" cargo check -Z avoid-dev-deps
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 8cafdd1..274b19d 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -44,4 +44,5 @@ check-cfg = [
"cfg(secp256k1_fuzz)",
"cfg(hashes_fuzz)",
"cfg(splicing)",
+ "cfg(chacha20_poly1305_fuzz)"
]
diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml
index 2f2f01b..661f885 100644
--- a/lightning/Cargo.toml
+++ b/lightning/Cargo.toml
@@ -40,6 +40,7 @@ lightning-macros = { version = "0.2", path = "../lightning-macros" }
bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.32.4", default-features = false, features = ["secp-recovery"] }
+chacha20-poly1305 = { version = "0.2.0", default-features = false }
dnssec-prover = { version = "0.6", default-features = false }
hashbrown = { version = "0.13", default-features = false }
diff --git a/lightning/src/crypto/streams.rs b/lightning/src/crypto/streams.rs
index 23a2315..8d46a8d 100644
--- a/lightning/src/crypto/streams.rs
+++ b/lightning/src/crypto/streams.rs
@@ -1,7 +1,4 @@
-use crate::crypto::chacha20::ChaCha20;
-use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
use crate::crypto::fixed_time_eq;
-use crate::crypto::poly1305::Poly1305;
use crate::io::{self, Read, Write};
use crate::ln::msgs::DecodeError;
@@ -10,6 +7,10 @@ use crate::util::ser::{
};
use alloc::vec::Vec;
+use chacha20_poly1305::{
+ chacha20::{ChaCha20, Key, Nonce},
+ poly1305::Poly1305,
+};
pub(crate) struct ChaChaReader<'a, R: io::Read> {
pub chacha: &'a mut ChaCha20,
@@ -19,7 +20,7 @@ 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]);
+ self.chacha.apply_keystream(&mut dest[..res]);
}
Ok(res)
}
@@ -42,11 +43,20 @@ impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
// Simultaneously write and encrypt Self::writeable.
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
- let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
+ let mut chacha = ChaCha20::new(Key::new(self.rho), Nonce::new([0; 12]), 0);
+ let mut mac_key = [0u8; 64];
+ chacha.apply_keystream(&mut mac_key);
+
+ #[cfg(not(fuzzing))]
+ let mac = Poly1305::new(mac_key[..32].try_into().unwrap());
+ #[cfg(fuzzing)]
+ let mac = Poly1305::new(self.rho);
+
+ let mut chacha_stream =
+ ChaChaPolyWriter { chacha: &mut chacha, poly: mac, write_len: 0, write: w };
self.writeable.write(&mut chacha_stream)?;
- let mut tag = [0 as u8; 16];
- chacha.finish_and_get_tag(&mut tag);
+
+ let tag = chacha_stream.finish_and_get_tag();
tag.write(w)?;
Ok(())
@@ -62,12 +72,15 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
pub(crate) fn chachapoly_encrypt_with_swapped_aad(
mut plaintext: Vec<u8>, key: [u8; 32], aad: [u8; 32],
) -> Vec<u8> {
- let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
+ let mut chacha = ChaCha20::new(Key::new(key), Nonce::new([0; 12]), 0);
let mut mac_key = [0u8; 64];
- chacha.process_in_place(&mut mac_key);
+ chacha.apply_keystream(&mut mac_key);
- let mut mac = Poly1305::new(&mac_key[..32]);
- chacha.process_in_place(&mut plaintext[..]);
+ #[cfg(not(fuzzing))]
+ let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
+ #[cfg(fuzzing)]
+ let mut mac = Poly1305::new(key);
+ chacha.apply_keystream(&mut plaintext[..]);
mac.input(&plaintext[..]);
if plaintext.len() % 16 != 0 {
@@ -80,7 +93,7 @@ pub(crate) fn chachapoly_encrypt_with_swapped_aad(
mac.input(&(plaintext.len() as u64).to_le_bytes());
mac.input(&32u64.to_le_bytes());
- plaintext.extend_from_slice(&mac.result());
+ plaintext.extend_from_slice(&mac.tag());
plaintext
}
@@ -105,7 +118,7 @@ pub(crate) enum TriPolyAADUsed {
///
/// Note that we do *not* use the provided AADs as the standard ChaCha20Poly1305 AAD as that would
/// require placing it first and prevent us from avoiding redundant Poly1305 rounds. Instead, the
-/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the the contents being
+/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the contents being
/// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving
/// like classic ChaCha20Poly1305 for the non-AAD-containing MAC.
pub(crate) struct ChaChaTriPolyReadAdapter<R: Readable> {
@@ -127,14 +140,14 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
}
let (key, aad_a, aad_b) = params;
- let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
+ let mut chacha = ChaCha20::new(Key::new(key), Nonce::new([0; 12]), 0);
let mut mac_key = [0u8; 64];
- chacha.process_in_place(&mut mac_key);
+ chacha.apply_keystream(&mut mac_key);
#[cfg(not(fuzzing))]
- let mut mac = Poly1305::new(&mac_key[..32]);
+ let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
#[cfg(fuzzing)]
- let mut mac = Poly1305::new(&key);
+ let mut mac = Poly1305::new(key);
let decrypted_len = r.remaining_bytes() - 16;
let s = FixedLengthReader::new(r, decrypted_len);
@@ -145,7 +158,6 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
while chacha_stream.read.bytes_remain() {
let mut buf = [0; 256];
if chacha_stream.read(&mut buf)? == 0 {
- // Reached EOF
return Err(DecodeError::ShortRead);
}
}
@@ -173,13 +185,13 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
mac.input(&0u64.to_le_bytes());
mac.input(&(read_len as u64).to_le_bytes());
- let mut tag = [0 as u8; 16];
+ let mut tag = [0u8; 16];
r.read_exact(&mut tag)?;
- if fixed_time_eq(&mac.result(), &tag) {
+ if fixed_time_eq(&mac.tag(), &tag) {
Ok(Self { readable, used_aad: TriPolyAADUsed::None })
- } else if fixed_time_eq(&mac_aad_a.result(), &tag) {
+ } else if fixed_time_eq(&mac_aad_a.tag(), &tag) {
Ok(Self { readable, used_aad: TriPolyAADUsed::First })
- } else if fixed_time_eq(&mac_aad_b.result(), &tag) {
+ } else if fixed_time_eq(&mac_aad_b.tag(), &tag) {
Ok(Self { readable, used_aad: TriPolyAADUsed::Second })
} else {
return Err(DecodeError::InvalidValue);
@@ -197,12 +209,12 @@ struct ChaChaTriPolyReader<'a, R: Read> {
impl<'a, R: Read> Read for ChaChaTriPolyReader<'a, R> {
// Decrypts bytes from Self::read into `dest`.
// After all reads complete, the caller must compare the expected tag with
- // the result of `Poly1305::result()`.
+ // the result of `Poly1305::tag()`
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
let res = self.read.read(dest)?;
if res > 0 {
- self.poly.input(&dest[0..res]);
- self.chacha.process_in_place(&mut dest[0..res]);
+ self.poly.input(&dest[..res]);
+ self.chacha.apply_keystream(&mut dest[..res]);
self.read_len += res;
}
Ok(res)
@@ -224,19 +236,38 @@ impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
return Err(DecodeError::InvalidValue);
}
- let mut chacha = ChaCha20Poly1305RFC::new(&secret, &[0; 12], &[]);
+ let mut chacha = ChaCha20::new(Key::new(secret), Nonce::new([0; 12]), 0);
+ let mut mac_key = [0u8; 64];
+ chacha.apply_keystream(&mut mac_key);
+
+ #[cfg(not(fuzzing))]
+ let mut mac = Poly1305::new(mac_key[..32].try_into().unwrap());
+ #[cfg(fuzzing)]
+ let mut mac = Poly1305::new(secret);
+
let decrypted_len = r.remaining_bytes() - 16;
let s = FixedLengthReader::new(r, decrypted_len);
- let mut chacha_stream = ChaChaPolyReader { chacha: &mut chacha, read: s };
+ let mut chacha_stream = ChaChaPolyReader::new(&mut chacha, &mut mac, s);
let readable: T = Readable::read(&mut chacha_stream)?;
while chacha_stream.read.bytes_remain() {
let mut buf = [0; 256];
- chacha_stream.read(&mut buf)?;
+ if chacha_stream.read(&mut buf)? == 0 {
+ return Err(DecodeError::ShortRead);
+ }
}
- let mut tag = [0 as u8; 16];
+ let read_len = chacha_stream.read_len();
+ drop(chacha_stream);
+
+ if read_len % 16 != 0 {
+ mac.input(&[0; 16][0..16 - (read_len % 16)]);
+ }
+ mac.input(&0u64.to_le_bytes());
+ mac.input(&(read_len as u64).to_le_bytes());
+
+ let mut tag = [0u8; 16];
r.read_exact(&mut tag)?;
- if !chacha.finish_and_check_tag(&tag) {
+ if !fixed_time_eq(&mac.tag(), &tag) {
return Err(DecodeError::InvalidValue);
}
@@ -244,20 +275,32 @@ impl<T: Readable> LengthReadableArgs<[u8; 32]> for ChaChaPolyReadAdapter<T> {
}
}
-/// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
+/// Enables simultaneously reading and decrypting a ChaCha20Poly1305 stream from a std::io::Read.
struct ChaChaPolyReader<'a, R: Read> {
- pub chacha: &'a mut ChaCha20Poly1305RFC,
+ chacha: &'a mut ChaCha20,
+ poly: &'a mut Poly1305,
+ read_len: usize,
pub read: R,
}
+impl<'a, R: Read> ChaChaPolyReader<'a, R> {
+ fn new(chacha: &'a mut ChaCha20, poly: &'a mut Poly1305, read: R) -> Self {
+ Self { chacha, poly, read_len: 0, read }
+ }
+
+ fn read_len(&self) -> usize {
+ self.read_len
+ }
+}
+
impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> {
// Decrypt bytes from Self::read into `dest`.
- // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
- // complete.
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
let res = self.read.read(dest)?;
if res > 0 {
- self.chacha.decrypt_in_place(&mut dest[0..res]);
+ self.poly.input(&dest[..res]);
+ self.chacha.apply_keystream(&mut dest[..res]);
+ self.read_len += res;
}
Ok(res)
}
@@ -265,14 +308,26 @@ impl<'a, R: Read> Read for ChaChaPolyReader<'a, R> {
/// Enables simultaneously writing and encrypting a byte stream into a Writer.
struct ChaChaPolyWriter<'a, W: Writer> {
- pub chacha: &'a mut ChaCha20Poly1305RFC,
+ chacha: &'a mut ChaCha20,
+ poly: Poly1305,
+ write_len: usize,
pub write: &'a mut W,
}
+impl<'a, W: Writer> ChaChaPolyWriter<'a, W> {
+ /// Finish encrypting and return the 16-byte authentication tag.
+ fn finish_and_get_tag(mut self) -> [u8; 16] {
+ if self.write_len % 16 != 0 {
+ self.poly.input(&[0; 16][0..16 - (self.write_len % 16)]);
+ }
+ self.poly.input(&0u64.to_le_bytes());
+ self.poly.input(&(self.write_len as u64).to_le_bytes());
+ self.poly.tag()
+ }
+}
+
impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
// Encrypt then write bytes from `src` into Self::write.
- // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
- // complete.
fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
let mut src_idx = 0;
while src_idx < src.len() {
@@ -280,8 +335,10 @@ impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
let bytes_written = (&mut write_buffer[..])
.write(&src[src_idx..])
.expect("In-memory writes can't fail");
- self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
+ self.chacha.apply_keystream(&mut write_buffer[..bytes_written]);
+ self.poly.input(&write_buffer[..bytes_written]);
self.write.write_all(&write_buffer[..bytes_written])?;
+ self.write_len += bytes_written;
src_idx += bytes_written;
}
Ok(())
diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs
index d70a20e..a759770 100644
--- a/lightning/src/ln/inbound_payment.rs
+++ b/lightning/src/ln/inbound_payment.rs
@@ -13,12 +13,12 @@ use bitcoin::hashes::cmp::fixed_time_eq;
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine};
+use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
-use crate::crypto::chacha20::ChaCha20;
use crate::crypto::utils::hkdf_extract_expand_7x;
use crate::ln::msgs;
use crate::ln::msgs::MAX_VALUE_MSAT;
-use crate::offers::nonce::Nonce;
+use crate::offers::nonce::Nonce as LocalNonce;
use crate::sign::EntropySource;
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
use crate::util::errors::APIError;
@@ -96,8 +96,13 @@ impl ExpandedKey {
/// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
/// metadata (e.g., payment id).
- pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: Nonce) -> [u8; 32] {
- ChaCha20::encrypt_single_block_in_place(&self.offers_encryption_key, &nonce.0, &mut bytes);
+ pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], nonce: LocalNonce) -> [u8; 32] {
+ ChaCha20::new_from_block(
+ Key::new(self.offers_encryption_key),
+ Nonce::new(nonce.0[4..].try_into().unwrap()),
+ u32::from_le_bytes(nonce.0[..4].try_into().unwrap()),
+ )
+ .apply_keystream(&mut bytes);
bytes
}
}
@@ -301,12 +306,14 @@ fn construct_payment_secret(
let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN);
iv_slice.copy_from_slice(iv_bytes);
- ChaCha20::encrypt_single_block(
- metadata_key,
- iv_bytes,
- encrypted_metadata_slice,
- metadata_bytes,
- );
+ encrypted_metadata_slice.copy_from_slice(metadata_bytes);
+ ChaCha20::new_from_block(
+ Key::new(*metadata_key),
+ Nonce::new(iv_bytes[4..].try_into().unwrap()),
+ u32::from_le_bytes(iv_bytes[..4].try_into().unwrap()),
+ )
+ .apply_keystream(encrypted_metadata_slice);
+
PaymentSecret(payment_secret_bytes)
}
@@ -485,12 +492,13 @@ fn decrypt_metadata(
iv_bytes.copy_from_slice(iv_slice);
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
- ChaCha20::encrypt_single_block(
- &keys.metadata_key,
- &iv_bytes,
- &mut metadata_bytes,
- encrypted_metadata_bytes,
- );
+ metadata_bytes.copy_from_slice(encrypted_metadata_bytes);
+ ChaCha20::new_from_block(
+ Key::new(keys.metadata_key),
+ Nonce::new(iv_bytes[4..].try_into().unwrap()),
+ u32::from_le_bytes(iv_bytes[..4].try_into().unwrap()),
+ )
+ .apply_keystream(&mut metadata_bytes);
(iv_bytes, metadata_bytes)
}
diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs
index 602d731..fe41bc1 100644
--- a/lightning/src/ln/onion_utils.rs
+++ b/lightning/src/ln/onion_utils.rs
@@ -11,7 +11,6 @@
use super::msgs::OnionErrorPacket;
use crate::blinded_path::BlindedHop;
-use crate::crypto::chacha20::ChaCha20;
use crate::crypto::streams::ChaChaReader;
use crate::events::HTLCHandlingFailureReason;
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
@@ -40,6 +39,8 @@ use bitcoin::secp256k1;
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
+use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
+
use crate::io::{Cursor, Read};
#[allow(unused_imports)]
@@ -725,8 +726,8 @@ pub(super) fn construct_onion_packet(
) -> Result<msgs::OnionPacket, ()> {
let mut packet_data = [0; ONION_DATA_LEN];
- let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
- chacha.process(&[0; ONION_DATA_LEN], &mut packet_data);
+ let mut chacha = ChaCha20::new(Key::new(prng_seed), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut packet_data);
debug_assert_eq!(payloads.len(), onion_keys.len(), "Payloads and keys must have equal lengths");
@@ -763,8 +764,8 @@ pub(super) fn construct_trampoline_onion_packet(
}
let mut packet_data = vec![0u8; packet_length];
- let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
- chacha.process_in_place(&mut packet_data);
+ let mut chacha = ChaCha20::new(Key::new(prng_seed), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut packet_data);
construct_onion_packet_with_init_noise::<_, _>(
payloads,
@@ -783,8 +784,8 @@ pub(super) fn construct_onion_packet_with_writable_hopdata<HD: Writeable>(
) -> Result<msgs::OnionPacket, ()> {
let mut packet_data = [0; ONION_DATA_LEN];
- let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
- chacha.process(&[0; ONION_DATA_LEN], &mut packet_data);
+ let mut chacha = ChaCha20::new(Key::new(prng_seed), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut packet_data);
let packet = FixedSizeOnionPacket(packet_data);
construct_onion_packet_with_init_noise::<_, _>(
@@ -822,8 +823,8 @@ pub(crate) fn construct_onion_message_packet<HD: Writeable, P: Packet<Data = Vec
) -> Result<P, ()> {
let mut packet_data = vec![0; packet_data_len];
- let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
- chacha.process_in_place(&mut packet_data);
+ let mut chacha = ChaCha20::new(Key::new(prng_seed), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut packet_data);
construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, packet_data, None)
}
@@ -843,12 +844,9 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
let mut pos = 0;
for (i, (payload, keys)) in payloads.iter().zip(onion_keys.iter()).enumerate() {
- let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
- // TODO: Batch this.
- for _ in 0..(packet_data.len() - pos) {
- let mut dummy = [0; 1];
- chacha.process_in_place(&mut dummy); // We don't have a seek function :(
- }
+ // Seek to the position in the keystream where we want to start encrypting
+ let seek_pos = (packet_data.len() - pos) as u32;
+ let mut chacha = ChaCha20::new(Key::new(keys.rho), Nonce::new([0; 12]), seek_pos);
let mut payload_len = LengthCalculatingWriter(0);
payload.write(&mut payload_len).expect("Failed to calculate length");
@@ -862,7 +860,7 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
}
res.resize(pos, 0u8);
- chacha.process_in_place(&mut res);
+ chacha.apply_keystream(&mut res);
}
res
};
@@ -877,8 +875,8 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
packet_data[0..payload_len.0].copy_from_slice(&payload.encode()[..]);
packet_data[payload_len.0..(payload_len.0 + 32)].copy_from_slice(&hmac_res);
- let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
- chacha.process_in_place(packet_data);
+ let mut chacha = ChaCha20::new(Key::new(keys.rho), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(packet_data);
if i == 0 {
let stop_index = packet_data.len();
@@ -900,8 +898,8 @@ fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
/// Encrypts/decrypts a failure packet.
fn crypt_failure_packet(shared_secret: &[u8], packet: &mut OnionErrorPacket) {
let ammag = gen_ammag_from_shared_secret(&shared_secret);
- let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
- chacha.process_in_place(&mut packet.data);
+ let mut chacha = ChaCha20::new(Key::new(ammag), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut packet.data);
if let Some(ref mut attribution_data) = packet.attribution_data {
attribution_data.crypt(shared_secret);
@@ -2738,7 +2736,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
});
}
- let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
+ let mut chacha = ChaCha20::new(Key::new(rho), Nonce::new([0; 12]), 0);
let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) };
match R::read(&mut chacha_stream, read_args) {
Err(err) => {
@@ -2803,7 +2801,7 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
}
// 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_bytes.as_mut()[read_pos..]);
+ chacha_stream.chacha.apply_keystream(&mut new_packet_bytes.as_mut()[read_pos..]);
return Ok((msg, Some((hmac, new_packet_bytes)))); // This packet needs forwarding
}
},
@@ -2845,9 +2843,9 @@ impl AttributionData {
/// Encrypts or decrypts the attribution data using the provided shared secret.
pub(crate) fn crypt(&mut self, shared_secret: &[u8]) {
let ammagext = gen_ammagext_from_shared_secret(&shared_secret);
- let mut chacha = ChaCha20::new(&ammagext, &[0u8; 8]);
- chacha.process_in_place(&mut self.hold_times);
- chacha.process_in_place(&mut self.hmacs);
+ let mut chacha = ChaCha20::new(Key::new(ammagext), Nonce::new([0; 12]), 0);
+ chacha.apply_keystream(&mut self.hold_times);
+ chacha.apply_keystream(&mut self.hmacs);
}
/// Adds the current node's HMACs for all possible positions to this packet.
diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs
index ab0e978..937e446 100644
--- a/lightning/src/ln/our_peer_storage.rs
+++ b/lightning/src/ln/our_peer_storage.rs
@@ -14,18 +14,18 @@
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine};
use bitcoin::secp256k1::PublicKey;
+use chacha20_poly1305::{ChaCha20Poly1305, Key, Nonce};
use crate::ln::types::ChannelId;
use crate::sign::PeerStorageKey;
-use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
use crate::prelude::*;
/// [`DecryptedOurPeerStorage`] is used to store serialised channel information that allows for the creation of a
/// `peer_storage` backup.
///
/// This structure is designed to serialize channel data for backup and supports encryption
-/// using `ChaCha20Poly1305RFC` for transmission.
+/// using `ChaCha20Poly1305` for transmission.
///
/// # Key Methods
/// - [`DecryptedOurPeerStorage::new`]: Returns [`DecryptedOurPeerStorage`] with the given data.
@@ -66,9 +66,8 @@ impl DecryptedOurPeerStorage {
let plaintext_len = data.len();
let nonce = derive_nonce(key, random_bytes);
- let mut chacha = ChaCha20Poly1305RFC::new(&key.inner, &nonce, b"");
- let mut tag = [0; 16];
- chacha.encrypt_full_message_in_place(&mut data[0..plaintext_len], &mut tag);
+ let chacha = ChaCha20Poly1305::new(Key::new(key.inner), Nonce::new(nonce));
+ let tag = chacha.encrypt(&mut data[0..plaintext_len], None);
data.extend_from_slice(&tag);
@@ -122,9 +121,11 @@ impl EncryptedOurPeerStorage {
let nonce = derive_nonce(key, random_bytes);
- let mut chacha = ChaCha20Poly1305RFC::new(&key.inner, &nonce, b"");
+ let chacha = ChaCha20Poly1305::new(Key::new(key.inner), Nonce::new(nonce));
- if chacha.check_decrypt_in_place(encrypted_data, tag).is_err() {
+ let mut decrypt_tag = [0; 16];
+ decrypt_tag.copy_from_slice(tag);
+ if chacha.decrypt(encrypted_data, decrypt_tag, None).is_err() {
return Err(());
}
diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs
index 5554c5a..d9fc6dd 100644
--- a/lightning/src/ln/peer_channel_encryptor.rs
+++ b/lightning/src/ln/peer_channel_encryptor.rs
@@ -25,8 +25,8 @@ use bitcoin::secp256k1;
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1::{PublicKey, SecretKey};
+use chacha20_poly1305::{ChaCha20Poly1305, Key, Nonce};
-use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
use crate::crypto::utils::hkdf_extract_expand_twice;
use crate::util::ser::VecWriter;
@@ -150,10 +150,11 @@ impl PeerChannelEncryptor {
fn encrypt_with_ad(res: &mut [u8], n: u64, key: &[u8; 32], h: &[u8], plaintext: &[u8]) {
let mut nonce = [0; 12];
nonce[4..].copy_from_slice(&n.to_le_bytes()[..]);
+ res[0..plaintext.len()].copy_from_slice(plaintext);
+
+ let chacha = ChaCha20Poly1305::new(Key::new(*key), Nonce::new(nonce));
+ let tag = chacha.encrypt(&mut res[0..plaintext.len()], Some(h));
- let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h);
- let mut tag = [0; 16];
- chacha.encrypt(plaintext, &mut res[0..plaintext.len()], &mut tag);
res[plaintext.len()..].copy_from_slice(&tag);
}
@@ -166,9 +167,8 @@ impl PeerChannelEncryptor {
let mut nonce = [0; 12];
nonce[4..].copy_from_slice(&n.to_le_bytes()[..]);
- let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h);
- let mut tag = [0; 16];
- chacha.encrypt_full_message_in_place(&mut res[offset..], &mut tag);
+ let chacha = ChaCha20Poly1305::new(Key::new(*key), Nonce::new(nonce));
+ let tag = chacha.encrypt(&mut res[offset..], Some(h));
res.extend_from_slice(&tag);
}
@@ -178,9 +178,11 @@ impl PeerChannelEncryptor {
let mut nonce = [0; 12];
nonce[4..].copy_from_slice(&n.to_le_bytes()[..]);
- let mut chacha = ChaCha20Poly1305RFC::new(key, &nonce, h);
+ let chacha = ChaCha20Poly1305::new(Key::new(*key), Nonce::new(nonce));
let (inout, tag) = inout.split_at_mut(inout.len() - 16);
- if chacha.check_decrypt_in_place(inout, tag).is_err() {
+ let mut decrypt_tag = [0; 16];
+ decrypt_tag.copy_from_slice(tag);
+ if chacha.decrypt(inout, decrypt_tag, Some(h)).is_err() {
return Err(LightningError {
err: "Bad MAC".to_owned(),
action: msgs::ErrorAction::DisconnectPeer { msg: None },
@@ -197,9 +199,13 @@ impl PeerChannelEncryptor {
nonce[4..].copy_from_slice(&n.to_le_bytes()[..]);
let (data, hmac) = cyphertext.split_at(cyphertext.len() - 16);
+ let mut tag = [0; 16];
+ tag.copy_from_slice(hmac);
+ res.copy_from_slice(data);
+
let mac_check =
- ChaCha20Poly1305RFC::new(key, &nonce, h).variable_time_decrypt(&data, res, hmac);
- mac_check.map_err(|()| LightningError {
+ ChaCha20Poly1305::new(Key::new(*key), Nonce::new(nonce)).decrypt(res, tag, Some(h));
+ mac_check.map_err(|_| LightningError {
err: "Bad MAC".to_owned(),
action: msgs::ErrorAction::DisconnectPeer { msg: None },
})
diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs
index 18b78dd..a3e5ad4 100644
--- a/lightning/src/routing/router.rs
+++ b/lightning/src/routing/router.rs
@@ -10,6 +10,7 @@
//! The router finds paths within a [`NetworkGraph`] for a payment.
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
+use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
use lightning_invoice::Bolt11Invoice;
use crate::blinded_path::payment::{
@@ -17,7 +18,6 @@ use crate::blinded_path::payment::{
PaymentRelay, ReceiveTlvs,
};
use crate::blinded_path::{BlindedHop, Direction, IntroductionNode};
-use crate::crypto::chacha20::ChaCha20;
use crate::ln::channel_state::ChannelDetails;
use crate::ln::channelmanager::{PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
@@ -3944,11 +3944,11 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
}
// Init PRNG with the path-dependant nonce, which is static for private paths.
- let mut prng = ChaCha20::new(random_seed_bytes, &path_nonce);
+ let mut prng = ChaCha20::new(Key::new(*random_seed_bytes), Nonce::new(path_nonce), 0);
let mut random_path_bytes = [0u8; ::core::mem::size_of::<usize>()];
// Pick a random path length in [1 .. 3]
- prng.process_in_place(&mut random_path_bytes);
+ prng.apply_keystream(&mut random_path_bytes);
let random_walk_length = usize::from_be_bytes(random_path_bytes).wrapping_rem(3).wrapping_add(1);
for random_hop in 0..random_walk_length {
@@ -3959,7 +3959,7 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
if let Some(cur_node_id) = cur_hop {
if let Some(cur_node) = network_nodes.get(&cur_node_id) {
// Randomly choose the next unvisited hop.
- prng.process_in_place(&mut random_path_bytes);
+ prng.apply_keystream(&mut random_path_bytes);
if let Some(random_channel) = usize::from_be_bytes(random_path_bytes)
.checked_rem(cur_node.channels.len())
.and_then(|index| cur_node.channels.get(index))
@@ -4080,7 +4080,6 @@ mod tests {
use crate::blinded_path::payment::{BlindedPayInfo, BlindedPaymentPath};
use crate::blinded_path::BlindedHop;
use crate::chain::transaction::OutPoint;
- use crate::crypto::chacha20::ChaCha20;
use crate::ln::chan_utils::make_funding_redeemscript;
use crate::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelShutdownState};
use crate::ln::channelmanager;
@@ -4117,6 +4116,8 @@ mod tests {
use bitcoin::secp256k1::Secp256k1;
use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::transaction::TxOut;
+ use chacha20_poly1305::chacha20::ChaCha20;
+ use chacha20_poly1305::{Key, Nonce};
use crate::io::Cursor;
use crate::prelude::*;
@@ -7709,10 +7710,10 @@ mod tests {
for p in route.paths {
// 1. Select random observation point
- let mut prng = ChaCha20::new(&random_seed_bytes, &[0u8; 12]);
+ let mut prng = ChaCha20::new(Key::new(random_seed_bytes), Nonce::new([0; 12]),0);
let mut random_bytes = [0u8; ::core::mem::size_of::<usize>()];
- prng.process_in_place(&mut random_bytes);
+ prng.apply_keystream(&mut random_bytes);
let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.hops.len());
let observation_point = NodeId::from_pubkey(&p.hops.get(random_path_index).unwrap().pubkey);
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 3237149..374ad38 100644
--- a/lightning/src/sign/mod.rs
+++ b/lightning/src/sign/mod.rs
@@ -34,6 +34,7 @@ use bitcoin::secp256k1::schnorr;
use bitcoin::secp256k1::All;
use bitcoin::secp256k1::{Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing};
use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness};
+use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
use lightning_invoice::RawBolt11Invoice;
@@ -60,7 +61,6 @@ use crate::util::native_async::MaybeSend;
use crate::util::ser::{ReadableArgs, Writeable};
use crate::util::transaction_utils;
-use crate::crypto::chacha20::ChaCha20;
use crate::prelude::*;
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::util::atomic_counter::AtomicCounter;
@@ -2703,7 +2703,14 @@ impl EntropySource for RandomBytes {
let index = self.index.next();
let mut nonce = [0u8; 16];
nonce[..8].copy_from_slice(&index.to_be_bytes());
- ChaCha20::get_single_block(&self.seed, &nonce)
+ let mut chacha_bytes = [0; 32];
+ ChaCha20::new_from_block(
+ Key::new(self.seed),
+ Nonce::new(nonce[4..].try_into().unwrap()),
+ u32::from_le_bytes(nonce[..4].try_into().unwrap()),
+ )
+ .apply_keystream(&mut chacha_bytes);
+ chacha_bytes
}
}
diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs
index d57c529..342c062 100644
--- a/lightning/src/util/scid_utils.rs
+++ b/lightning/src/util/scid_utils.rs
@@ -73,12 +73,12 @@ pub fn scid_from_parts(
/// 3) payments intended to be intercepted will route using a fake scid (this is typically used so
/// the forwarding node can open a JIT channel to the next hop)
pub(crate) mod fake_scid {
- use crate::crypto::chacha20::ChaCha20;
use crate::prelude::*;
use crate::sign::EntropySource;
use crate::util::scid_utils;
use bitcoin::constants::ChainHash;
use bitcoin::Network;
+ use chacha20_poly1305::chacha20::{ChaCha20, Key, Nonce};
const TEST_SEGWIT_ACTIVATION_HEIGHT: u32 = 1;
const MAINNET_SEGWIT_ACTIVATION_HEIGHT: u32 = 481_824;
@@ -150,15 +150,15 @@ pub(crate) mod fake_scid {
fn get_encrypted_vout(
&self, block_height: u32, tx_index: u32, fake_scid_rand_bytes: &[u8; 32],
) -> u8 {
- let mut salt = [0 as u8; 8];
+ let mut salt = [0 as u8; 12];
let block_height_bytes = block_height.to_be_bytes();
- salt[0..4].copy_from_slice(&block_height_bytes);
+ salt[4..8].copy_from_slice(&block_height_bytes);
let tx_index_bytes = tx_index.to_be_bytes();
- salt[4..8].copy_from_slice(&tx_index_bytes);
+ salt[8..12].copy_from_slice(&tx_index_bytes);
- let mut chacha = ChaCha20::new(fake_scid_rand_bytes, &salt);
+ let mut chacha = ChaCha20::new(Key::new(*fake_scid_rand_bytes), Nonce::new(salt), 0);
let mut vout_byte = [*self as u8];
- chacha.process_in_place(&mut vout_byte);
+ chacha.apply_keystream(&mut vout_byte);
vout_byte[0] & NAMESPACE_ID_BITMASK
}
}
Why this scored 36/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.