diff --git a/wharrgarbl-neko/SPEC.md b/wharrgarbl-neko/SPEC.md index dbbc11c..ff60d3c 100644 --- a/wharrgarbl-neko/SPEC.md +++ b/wharrgarbl-neko/SPEC.md @@ -2,7 +2,7 @@ NEKO is inspired by STROBE in that it uses Keccak in a duplex construction to perform encryption and message authentication. It has stripped down the amount of operation flags it uses and simplifies the internals, ridding the need to have "streaming" by instead opting for having different operating modes. -In lieu of streaming, NEKO uses op-chaining to reduce the amount of permutations needed. There are two kinds of operations, NON-PERMUTING and PERMUTING. A permutation is when the internals invoke the Keccak permutation function on the NEKO state, resulting in a new, pseudorandomised state. A permutation resets the buffer, and thus allows more data to be ingested into the cipher. +In lieu of streaming, NEKO uses op-chaining to reduce the amount of permutations needed. There are two kinds of operations, NON-PERMUTING and PERMUTING. A permutation is when the internals invoke the Keccak permutation function on the NEKO state, resulting in a new, pseudorandomised state. A permutation resets the buffer & chain, and thus allows more data to be ingested into the cipher. The pattern is a max of four chained ops, with the first being a permuting one or an INIT/CONT op. Any sequence of more than 3 non-permuting ops that don't incur a permutation during data ingestion, and NEKO will throw a panic. The available operations are as follows: @@ -43,7 +43,7 @@ Internally, each operation *operates* on the data with one of 7 different action - **ABSORB**: This XOR's the input directly into the state: `State ^ Input`. This does not mutate the input. - **ABSORB & SET**: This XOR's the input into the state, then sets the input as the resulting state: `State ^ Input, Input = State`. This mutates the input. - **COPY STATE**: This copies out the state directly into the input: `Input = State`. This mutates the input. -- **EXCHANGE**: This XOR's the input to the state, and then XOR's the resulting state to the input: `State ^ Input, Input ^ State`. This mutates the input. +- **EXCHANGE**: This XOR's the state to the input, and then XOR's the resulting input to the state: `Input ^ State, State ^ Input`. This mutates the input. - **OVERWRITE**: This sets the state with the input bytes directly: `State = Input`. This does not mutate the input. - **SQUEEZE**: This sets the input with the state, then zeroes out the state. `Input = State, State = 0`. This mutates the input. - **ZERO STATE**: This op does not take input, but instead zeroes out a specific amount of state blocks according to security level: 128 bit zeros 2 blocks, 256 zeros 4 blocks. `State = 0`. @@ -86,7 +86,7 @@ ALL valid user ops are defined with a combination of two or three flags. No user ## Initialisation -Creating a new Neko State instance is qualified as an INIT operation. This means that it must be encoded as an operation. As such the initial op is INIT and the op counter must be set to one. The buffer position counter is then set to 0 and the Keccak F1600 state buffer is initialised and zeroed: `[0u64; 25]`. The INIT op is then encoded into the first op slot on the state. +Creating a new Neko State instance is qualified as an INIT operation. This means that it must be encoded as an operation. As such the initial op is INIT and the op counter must be set to one. The buffer position counter is then set to 0 and the Keccak P1600 state buffer is initialised and zeroed: `[0u64; 25]`. The INIT op is then encoded into the first op slot on the state. Then, we must encode the preamble & NEKO version onto the state buffer. The preamble is defined as `[0x01, RATE, 0x07, 0x60]`. The `RATE` is calculated as `(200 - SecLevel / 4) / 8 - 1` and encoded as a `u8` value. So for `Neko128`, the `RATE` should resolve to `20`, and `Neko256` should resolve to `16`. These represent the max buffer position that input data can be absorbed into before needing to permute. diff --git a/wharrgarbl-neko/src/kats.rs b/wharrgarbl-neko/src/kats.rs index 23c3da1..7afbba3 100644 --- a/wharrgarbl-neko/src/kats.rs +++ b/wharrgarbl-neko/src/kats.rs @@ -338,7 +338,6 @@ In scelerisque, ex et porta varius, orci risus malesuada nibh, convallis euismod } #[test] -#[should_panic] fn mac_correctly_fails_when_it_doesnt_match() { let key = Array([1u8; 32]); @@ -368,11 +367,11 @@ fn mac_correctly_fails_when_it_doesnt_match() { rx.decrypt(&mut message); let verification = rx.verify_mac(&tag); - assert!(verification.is_ok()); + + assert!(verification.is_err()); } #[test] -#[should_panic] fn mac_correctly_fails_when_message_length_doesnt_match() { let key = Array([1u8; 32]); @@ -401,11 +400,11 @@ fn mac_correctly_fails_when_message_length_doesnt_match() { // Decrypt a message that has had its length extended rx.decrypt(&mut message); let verification = rx.verify_mac(&tag); - assert!(verification.is_ok()); + + assert!(verification.is_err()); } #[test] -#[should_panic] fn omitting_ops_with_empty_data_should_fail() { let key = Array([1u8; 32]); @@ -432,6 +431,8 @@ fn omitting_ops_with_empty_data_should_fail() { assert_eq!(&tag, &expected_tag); // Omit the AD op, even with empty data, and go straight to decrypt rx.decrypt(&mut message); + let verification = rx.verify_mac(&tag); - assert!(verification.is_ok()); + + assert!(verification.is_err()); } diff --git a/wharrgarbl-neko/src/lib.rs b/wharrgarbl-neko/src/lib.rs index 2b27696..5eb4051 100644 --- a/wharrgarbl-neko/src/lib.rs +++ b/wharrgarbl-neko/src/lib.rs @@ -43,7 +43,7 @@ impl IvSizeUser for NekoState { #[derive(Clone)] pub struct NekoState { - pub(crate) state: [u64; keccak::PLEN], + pub(crate) state: keccak::State1600, position: usize, pub(crate) ops_count: usize, sec: PhantomData, @@ -84,7 +84,7 @@ impl NekoState { }; // OPS stack MUST be initialised with the INIT flag as the first op. - let mut state = [0u64; keccak::PLEN]; + let mut state = [0u64; _]; state[Sec::BLOCK_RATE].as_mut_bytes()[MAX_OPS] ^= ops::INIT.bits(); @@ -173,7 +173,7 @@ impl NekoState { #[track_caller] fn begin_op(&mut self, red_flags: OpFlags) { // We can chain upto 4 OPs before needing to permute. - // So we can stack KEY+NONCE+AD ops before the next one + // So we can chain KEY+NONCE+AD ops before the next one // MUST be a permuting op, like ENC/MAC/RATCHET/PRF. // If we go over this, we've hit a failure state and MUST // terminate. @@ -183,7 +183,7 @@ impl NekoState { let op_index = self.ops_count; self.ops_count += 1; - // Encode the opflags to the available stack slot. + // Encode the opflags to the available chain slot. self.state[Sec::BLOCK_RATE].as_mut_bytes()[4..][..MAX_OPS][op_index] ^= red_flags.bits(); } @@ -196,6 +196,7 @@ impl NekoState { let position = self.raw_position(); + // XOR in the padding start to the current position self.state.as_mut_bytes()[position as usize] ^= 0x80u8.to_le(); // First byte is zeroed in case the padding start overlaps, with the second byte being the @@ -355,7 +356,7 @@ impl NekoState { } /// Permutes and zeros a portion of the cipher state in order to provide forward secrecy. - /// The amount of bits zeroed is dependent on the cipher strength (128/256). + /// The amount of bits zeroed is dependent on the cipher strength (128/192/256). /// /// **This is a PERMUTING operation** pub fn ratchet(&mut self) { diff --git a/wharrgarbl-neko/src/operators.rs b/wharrgarbl-neko/src/operators.rs index 8db3692..15af32f 100644 --- a/wharrgarbl-neko/src/operators.rs +++ b/wharrgarbl-neko/src/operators.rs @@ -2,6 +2,9 @@ use zerocopy::IntoBytes; use crate::{NekoSec, NekoState, ops}; +/// A Cursor type that works on a mutable slice of data bytes. Implements +/// operations that modify the incoming data. All operations mutate the +/// Neko state. pub(crate) struct NekoOperateMut<'s, S: NekoSec> { neko: &'s mut NekoState, data: &'s mut [u8], @@ -13,6 +16,8 @@ impl<'s, S: NekoSec> NekoOperateMut<'s, S> { Self { neko, data } } + /// Process the incoming data bytes into the Neko state, and mutate the input with the + /// result from the operation. #[inline(always)] fn operate_mut(&'s mut self, operation: fn((&mut u8, &mut u8))) { const { @@ -26,23 +31,29 @@ impl<'s, S: NekoSec> NekoOperateMut<'s, S> { self.neko.permutation_p12(ops::CONTINUE); } + // Get the next available block to start operating on let block = self.neko.block(); // Trans the neko let transed_bytes = self.neko.state[block..S::BLOCK_RATE].as_mut_bytes(); + // Count the amount of bytes that have been operated on let advanced = transed_bytes .iter_mut() .zip(self.data.iter_mut()) .map(operation) .count(); + // Reslice the remaining data to be operated into the trans neko. self.data = &mut self.data[advanced..]; + // Advance the position state of the neko self.neko.advance_position(advanced); } } + /// This XOR's the input into the state, then sets the input as the resulting state: + /// `State ^ Input, Input = State`. This mutates the input. #[inline] pub(crate) fn absorb_and_set(neko: &'s mut NekoState, data: &'s mut [u8]) { NekoOperateMut::new(neko, data).operate_mut(|(state, byte)| { @@ -51,11 +62,15 @@ impl<'s, S: NekoSec> NekoOperateMut<'s, S> { }); } + /// This copies out the state directly into the input: `Input = State`. + /// This mutates the input. #[inline] pub(crate) fn copy_state(neko: &'s mut NekoState, data: &'s mut [u8]) { NekoOperateMut::new(neko, data).operate_mut(|(state, byte)| *byte = *state); } + /// This XOR's the state to the input, and then XOR's the resulting input to the state: + /// `Input ^ State, State ^ Input`. This mutates the input. #[inline] pub(crate) fn exchange(neko: &'s mut NekoState, data: &'s mut [u8]) { NekoOperateMut::new(neko, data).operate_mut(|(state, byte)| { @@ -64,6 +79,8 @@ impl<'s, S: NekoSec> NekoOperateMut<'s, S> { }); } + /// This sets the input with the state, then zeroes out the state. + /// `Input = State, State = 0`. This mutates the input. #[inline] pub(crate) fn squeeze(neko: &'s mut NekoState, data: &'s mut [u8]) { NekoOperateMut::new(neko, data).operate_mut(|(state, byte)| { @@ -73,6 +90,9 @@ impl<'s, S: NekoSec> NekoOperateMut<'s, S> { } } +/// A Cursor type that works on an immutable slice of data bytes. Implements +/// operations that do not modify the incoming bytes. All operations mutate the +/// Neko state. pub(crate) struct NekoOperate<'s, S: NekoSec> { neko: &'s mut NekoState, data: &'s [u8], @@ -84,6 +104,7 @@ impl<'s, S: NekoSec> NekoOperate<'s, S> { Self { neko, data } } + /// Process the incoming data bytes into the Neko state. #[inline(always)] fn operate(&'s mut self, operation: fn((&mut u8, &u8))) { const { @@ -97,28 +118,36 @@ impl<'s, S: NekoSec> NekoOperate<'s, S> { self.neko.permutation_p12(ops::CONTINUE); } + // Get the next available block to start operating on let block = self.neko.block(); // Trans the neko let transed_bytes = self.neko.state[block..S::BLOCK_RATE].as_mut_bytes(); + // Count the amount of bytes that have been operated on let advanced = transed_bytes .iter_mut() .zip(self.data) .map(operation) .count(); + // Reslice the remaining data to be operated into the trans neko. self.data = &self.data[advanced..]; + // Advance the position state of the neko self.neko.advance_position(advanced); } } + /// This XOR's the input directly into the state: `State ^ Input`. + /// This does not mutate the input. #[inline] pub(crate) fn absorb(neko: &'s mut NekoState, data: &'s [u8]) { NekoOperate::new(neko, data).operate(|(state, byte)| *state ^= *byte); } + /// This sets the state with the input bytes directly: `State = Input`. + /// This does not mutate the input. #[inline] pub(crate) fn overwrite(neko: &'s mut NekoState, data: &'s [u8]) { NekoOperate::new(neko, data).operate(|(state, byte)| *state = *byte);