mod key;
pub mod states;
pub use key::{Key, KeyRef, GenericKey};
use core::fmt;
use wolf_crypto_sys::{
ChaCha,
wc_Chacha_SetKey, wc_Chacha_SetIV,
wc_Chacha_Process
};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::addr_of_mut;
use crate::buf::{GenericIv, U12};
use crate::{can_cast_u32, const_can_cast_u32, lte, Unspecified};
use states::{State, CanProcess, Init, NeedsIv, Ready, Streaming};
macro_rules! impl_fmt {
($(#[$meta:meta])* $trait:ident for $state:ident) => {
impl fmt::$trait for ChaCha20<$state> {
$(#[$meta])*
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(concat!("ChaCha20<", stringify!($state), "> { ... }"))
}
}
};
($state:ident) => {
impl_fmt! { Debug for $state }
impl_fmt! {
#[inline]
Display for $state
}
};
}
#[repr(transparent)]
pub struct ChaCha20<S: State = Init> {
inner: ChaCha,
_state: PhantomData<S>
}
impl ChaCha20<Init> {
pub fn new<K: GenericKey>(key: K) -> ChaCha20<NeedsIv> {
let mut inner = MaybeUninit::<ChaCha>::uninit();
unsafe {
let _res = wc_Chacha_SetKey(
inner.as_mut_ptr(),
key.slice().as_ptr(),
key.size()
);
debug_assert_eq!(_res, 0);
Self::new_with(inner.assume_init())
}
}
}
impl<S: State> ChaCha20<S> {
#[inline]
#[must_use]
const fn new_with<NS: State>(inner: ChaCha) -> ChaCha20<NS> {
ChaCha20::<NS> {
inner,
_state: PhantomData
}
}
}
impl ChaCha20<NeedsIv> {
pub fn set_iv_with_ctr<IV>(mut self, iv: IV, counter: u32) -> ChaCha20<Ready>
where IV: GenericIv<Size = U12>
{
unsafe {
let _res = wc_Chacha_SetIV(
addr_of_mut!(self.inner),
iv.as_slice().as_ptr(),
counter
);
debug_assert_eq!(_res, 0);
}
Self::new_with(self.inner)
}
#[inline]
pub fn set_iv<IV: GenericIv<Size = U12>>(self, iv: IV) -> ChaCha20<Ready> {
self.set_iv_with_ctr(iv, 0)
}
}
impl_fmt! { NeedsIv }
impl<S: CanProcess> ChaCha20<S> {
#[inline]
#[must_use]
const fn predicate(input_len: usize, output_len: usize) -> bool {
input_len <= output_len && can_cast_u32(input_len)
}
#[inline]
#[must_use]
const fn const_predicate<const I: usize, const O: usize>() -> bool {
I <= O && const_can_cast_u32::<I>()
}
#[inline]
unsafe fn process_unchecked(&mut self, input: &[u8], output: &mut [u8]) {
debug_assert!(
Self::predicate(input.len(), output.len()),
"Process unchecked precondition violated (debug assertion). The size of the input must \
be less than or equal to the size of the output. The size of the input must also be \
representable as a `u32` without overflowing."
);
let _res = wc_Chacha_Process(
addr_of_mut!(self.inner),
output.as_mut_ptr(),
input.as_ptr(),
input.len() as u32
);
debug_assert_eq!(_res, 0);
}
#[inline]
unsafe fn process_in_place_unchecked<'io>(&mut self, in_out: &'io mut [u8]) -> &'io [u8] {
debug_assert!(
can_cast_u32(in_out.len()),
"Process unchecked precondition violated (debug assertion). The size of the input must \
be less than or equal to the size of the output. The size of the input must also be \
representable as a `u32` without overflowing."
);
let _res = wc_Chacha_Process(
addr_of_mut!(self.inner),
in_out.as_ptr().cast_mut(),
in_out.as_ptr(),
in_out.len() as u32
);
debug_assert_eq!(_res, 0);
in_out
}
#[inline]
fn process(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), Unspecified> {
if !Self::predicate(input.len(), output.len()) { return Err(Unspecified) }
unsafe { self.process_unchecked(input, output) };
Ok(())
}
#[inline]
fn process_in_place<'io>(&mut self, in_out: &'io mut [u8]) -> Result<&'io [u8], Unspecified> {
if can_cast_u32(in_out.len()) {
Ok(unsafe { self.process_in_place_unchecked(in_out) })
} else {
Err(Unspecified)
}
}
#[inline]
fn process_exact<const C: usize>(
&mut self,
input: &[u8; C],
output: &mut [u8; C]
) -> Result<(), Unspecified> {
if !const_can_cast_u32::<C>() { return Err(Unspecified); }
unsafe { self.process_unchecked(input, output) };
Ok(())
}
#[inline]
fn process_sized<const I: usize, const O: usize>(
&mut self,
input: &[u8; I],
output: &mut [u8; O]
) -> Result<(), Unspecified> {
if !Self::const_predicate::<I, O>() { return Err(Unspecified) }
unsafe { self.process_unchecked(input, output) };
Ok(())
}
#[inline]
fn process_in_place_sized<'io, const C: usize>(&mut self, in_out: &'io mut [u8; C]) -> Result<&'io [u8; C], Unspecified> {
if const_can_cast_u32::<C>() {
unsafe { self.process_in_place_unchecked(in_out) };
Ok(in_out)
} else {
Err(Unspecified)
}
}
#[inline]
fn process_sized_out<const O: usize>(
&mut self,
input: &[u8],
output: &mut [u8; O]
) -> Result<(), Unspecified> {
if !(lte::<O>(input.len()) && can_cast_u32(input.len())) { return Err(Unspecified) }
unsafe { self.process_unchecked(input, output) };
Ok(())
}
}
impl ChaCha20<Ready> {
#[inline]
pub fn encrypt_into(
mut self,
plain: &[u8],
cipher: &mut [u8]
) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process(plain, cipher).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
#[inline]
pub fn encrypt_in_place(mut self, in_out: &mut [u8]) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process_in_place(in_out).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
#[inline]
pub fn encrypt_into_sized<const P: usize, const C: usize>(
mut self,
plain: &[u8; P],
cipher: &mut [u8; C]
) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process_sized(plain, cipher).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
#[inline]
pub fn encrypt_in_place_sized<const C: usize>(mut self, in_out: &mut [u8; C]) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process_in_place_sized(in_out).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
#[inline]
pub fn encrypt_into_sized_out<const C: usize>(
mut self,
plain: &[u8],
cipher: &mut [u8; C]
) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process_sized_out(plain, cipher).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
#[inline]
pub fn encrypt_into_exact<const C: usize>(
mut self,
plain: &[u8; C],
cipher: &mut [u8; C]
) -> Result<ChaCha20<NeedsIv>, Self> {
if self.process_exact(plain, cipher).is_ok() {
Ok(Self::new_with(self.inner))
} else {
Err(self)
}
}
alloc! {
pub fn encrypt(
self,
plain: &[u8]
) -> Result<(alloc::vec::Vec<u8>, ChaCha20<NeedsIv>), Self> {
let mut output = alloc::vec![0u8; plain.len()];
self.encrypt_into(plain, output.as_mut_slice()).map(move |ni| (output, ni))
}
}
#[inline]
pub fn encrypt_exact<const I: usize>(
self,
plain: &[u8; I]
) -> Result<([u8; I], ChaCha20<NeedsIv>), Self> {
let mut output = [0u8; I];
self.encrypt_into_exact(plain, &mut output).map(move |ni| (output, ni))
}
pub const fn stream(self) -> ChaCha20<Streaming> {
Self::new_with(self.inner)
}
}
impl_fmt! { Ready }
impl<S: CanProcess> ChaCha20<S> {
#[inline]
pub fn decrypt_into(&mut self, cipher: &[u8], plain: &mut [u8]) -> Result<(), Unspecified> {
self.process(cipher, plain)
}
#[inline]
pub fn decrypt_in_place<'io>(&mut self, in_out: &'io mut [u8]) -> Result<&'io [u8], Unspecified> {
self.process_in_place(in_out)
}
#[inline]
pub fn decrypt_into_sized<const I: usize, const O: usize>(
&mut self,
cipher: &[u8; I],
plain: &mut [u8; O]
) -> Result<(), Unspecified> {
self.process_sized(cipher, plain)
}
#[inline]
pub fn decrypt_in_place_sized<'io, const C: usize>(
&mut self,
in_out: &'io mut [u8; C]
) -> Result<&'io [u8; C], Unspecified> {
self.process_in_place_sized(in_out)
}
#[inline]
pub fn decrypt_into_exact<const C: usize>(&mut self, cipher: &[u8; C], plain: &mut [u8; C]) -> Result<(), Unspecified> {
self.process_exact(cipher, plain)
}
alloc! {
#[inline]
pub fn decrypt(&mut self, cipher: &[u8]) -> Result<alloc::vec::Vec<u8>, Unspecified> {
let mut output = alloc::vec![0u8; cipher.len()];
self.decrypt_into(cipher, output.as_mut_slice()).map(move |()| output)
}
}
#[inline]
pub fn decrypt_exact<const O: usize>(&mut self, cipher: &[u8; O]) -> Result<[u8; O], Unspecified> {
let mut output = [0u8; O];
self.decrypt_into_exact(cipher, &mut output).map(move |()| output)
}
}
impl ChaCha20<Streaming> {
#[inline]
pub fn encrypt_into(&mut self, plain: &[u8], cipher: &mut [u8]) -> Result<(), Unspecified> {
self.process(plain, cipher)
}
#[inline]
pub fn encrypt_into_sized<const I: usize, const O: usize>(
&mut self,
plain: &[u8; I],
cipher: &mut [u8; O]
) -> Result<(), Unspecified> {
self.process_sized(plain, cipher)
}
#[inline]
pub fn encrypt_into_exact<const C: usize>(
&mut self,
input: &[u8; C],
output: &mut [u8; C]
) -> Result<(), Unspecified> {
self.process_exact(input, output)
}
#[inline]
pub const fn finish(self) -> ChaCha20<NeedsIv> {
Self::new_with(self.inner)
}
std! {
pub const fn writer<W: io::Write, const CHUNK: usize>(self, writer: W) -> Result<Writer<W, CHUNK>, W> {
Writer::new(self, writer)
}
pub const fn default_writer<W: io::Write>(self, writer: W) -> Writer<W, 128> {
unsafe { Writer::<W, 128>::new_unchecked(self, writer) }
}
}
}
impl_fmt! { Streaming }
std! {
use std::io;
use core::ops;
pub struct Writer<W, const CHUNK: usize> {
chacha: ChaCha20<Streaming>,
writer: W
}
impl<W, const CHUNK: usize> Writer<W, CHUNK> {
pub const fn new(chacha: ChaCha20<Streaming>, writer: W) -> Result<Self, W> {
if const_can_cast_u32::<CHUNK>() {
Ok(Self {
chacha,
writer
})
} else {
Err(writer)
}
}
const unsafe fn new_unchecked(chacha: ChaCha20<Streaming>, writer: W) -> Self {
Self {
chacha,
writer
}
}
#[inline]
pub fn finish(self) -> ChaCha20<NeedsIv> {
self.chacha.finish()
}
}
impl<W, const CHUNK: usize> ops::Deref for Writer<W, CHUNK> {
type Target = ChaCha20<Streaming>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.chacha
}
}
impl<W, const CHUNK: usize> ops::DerefMut for Writer<W, CHUNK> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.chacha
}
}
impl<W: io::Write, const CHUNK: usize> io::Write for Writer<W, CHUNK> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut out = [0u8; CHUNK];
let to_write = core::cmp::min(CHUNK, buf.len());
unsafe { self.process_unchecked(&buf[..to_write], &mut out[..to_write]) };
self.writer.write(&out[..to_write])
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
let mut out = [0u8; CHUNK];
let mut pos = 0usize;
let len = buf.len();
while pos + CHUNK <= len {
unsafe {
self.process_unchecked(&buf[pos..pos + CHUNK], &mut out);
pos += CHUNK;
self.writer.write_all(&out)?;
}
}
let last = &buf[pos..];
debug_assert!(last.len() <= CHUNK);
unsafe { self.process_unchecked(last, &mut out) }
self.writer.write_all(&out[..last.len()])
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke() {
let (encrypted, chacha) = ChaCha20::new(&[0u8; 16])
.set_iv([3u8; 12])
.encrypt_exact(b"hello world!")
.unwrap();
let plain = chacha
.set_iv([3u8; 12])
.decrypt_exact(&encrypted)
.unwrap();
assert_eq!(plain, *b"hello world!");
}
}
#[cfg(test)]
mod property_tests {
use crate::aes::test_utils::{BoundList, AnyList};
use proptest::prelude::*;
use crate::chacha::{ChaCha20, Key};
use crate::buf::Nonce;
proptest! {
#![proptest_config(ProptestConfig::with_cases(10_000))]
#[test]
fn in_place_bijectivity(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>()
) {
let mut in_out = input;
ChaCha20::new(key.as_ref())
.set_iv(iv.copy())
.encrypt_in_place(in_out.as_mut_slice())
.unwrap();
if in_out.len() >= 3 {
prop_assert_ne!(in_out, input);
}
ChaCha20::new(key)
.set_iv(iv)
.decrypt_in_place(in_out.as_mut_slice())
.unwrap();
prop_assert_eq!(in_out, input);
}
#[test]
fn enc_into_dec_in_place(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>()
) {
let mut enc = input.create_self();
ChaCha20::new(key.as_ref()).set_iv(iv.copy())
.encrypt_into(input.as_slice(), enc.as_mut_slice())
.unwrap();
if enc.len() >= 3 {
prop_assert_ne!(enc.as_slice(), input.as_slice());
}
ChaCha20::new(key.as_ref()).set_iv(iv)
.decrypt_in_place(enc.as_mut_slice())
.unwrap();
prop_assert_eq!(enc, input);
}
#[test]
fn enc_in_place_dec_into(
input in any::<BoundList<1024>>(),
key in any::<Key>(),
iv in any::<Nonce>()
) {
let mut enc = input;
ChaCha20::new(key.as_ref()).set_iv(iv.copy())
.encrypt_in_place(enc.as_mut_slice())
.unwrap();
if enc.len() >= 3 {
prop_assert_ne!(enc.as_slice(), input.as_slice());
}
let mut dec = input.create_self();
ChaCha20::new(key.as_ref()).set_iv(iv)
.decrypt_into(enc.as_slice(), dec.as_mut_slice())
.unwrap();
prop_assert_eq!(dec, input);
}
#[test]
fn bijective_arb_updates(
inputs in any::<AnyList<32, BoundList<512>>>(),
key in any::<Key>(),
iv in any::<[u8; 12]>()
) {
let mut outputs = inputs.create_self();
let io_iter = inputs.as_slice().iter().zip(outputs.as_mut_slice());
let mut chacha = ChaCha20::new(key.as_ref()).set_iv(&iv).stream();
for (i, o) in io_iter {
chacha.encrypt_into(i, o).unwrap();
if i.len() >= 3 { prop_assert_ne!(i.as_slice(), o.as_slice()); }
}
let mut in_out = outputs.join();
let expected = inputs.join();
ChaCha20::new(key).set_iv(iv)
.decrypt_in_place(in_out.as_mut_slice())
.unwrap();
prop_assert_eq!(in_out, expected);
}
}
}