···11+# WHARRGARBL
22+33+A WIP Strobe+AKE protocol with a whimsical name. Based off the [reference rust implementation here](https://github.com/rozbb/strobe-rs), though with diverging API choices for experimentation.
44+55+## License
66+77+TBA when ready
···11+//! ## Herding KATs
22+//!
33+//! This module contains a bigger test suite to evaluate the implementation of `StrobeState`
44+//! fully.
+130
src/keccakf.rs
···11+const SIZE: usize = core::mem::size_of::<u64>();
22+33+/// keccak block size in 64-bit words. This is the N parameter in the STROBE spec
44+pub(crate) const KECCAK_BLOCK_SIZE: usize = 25;
55+66+/// keccak buffer size in bytes. N * 8 (byte size of u64)
77+pub(crate) const KECCAK_BUFFER_SIZE: usize = SIZE * KECCAK_BLOCK_SIZE;
88+99+/// This is a wrapper around 200-byte buffer that's always 8-byte aligned to make pointers to it
1010+/// safely convertible to a pointer to `[u64; 25]` (since u64 words must be 8-byte aligned)
1111+#[derive(Clone)]
1212+#[repr(align(8))]
1313+pub(crate) struct KeccakF1600(pub(crate) [u8; KECCAK_BUFFER_SIZE]);
1414+1515+impl KeccakF1600 {
1616+ fn copy_into(&self, dst: &mut [u64; KECCAK_BLOCK_SIZE]) {
1717+ assert_eq!(self.0.len(), dst.len() * SIZE);
1818+1919+ self.0
2020+ .chunks_exact(SIZE)
2121+ .flat_map(TryInto::<[u8; SIZE]>::try_into)
2222+ .map(u64::from_le_bytes)
2323+ .zip(dst.iter_mut())
2424+ .for_each(|(src, dst)| *dst = src);
2525+ }
2626+2727+ fn copy_from(&mut self, src: &[u64; KECCAK_BLOCK_SIZE]) {
2828+ assert_eq!(self.0.len(), src.len() * SIZE);
2929+3030+ src.iter()
3131+ .zip(self.0.chunks_exact_mut(SIZE))
3232+ .for_each(|(src, dst)| dst.copy_from_slice(&src.to_le_bytes()));
3333+ }
3434+3535+ /// Performs the keccakf\[1600\] permutation on an aligned byte buffer
3636+ pub(crate) fn permute_f1600(&mut self) {
3737+ let mut keccak_block = [0u64; KECCAK_BLOCK_SIZE];
3838+3939+ // Make a little-endian copy, do the operation, then copy the bytes back.
4040+ // This all optimises away so the block is written to directly, confirmed with godbolt.
4141+ self.copy_into(&mut keccak_block);
4242+ keccak::Keccak::new().with_f1600(|f| f(&mut keccak_block));
4343+ self.copy_from(&keccak_block);
4444+ }
4545+}
4646+4747+#[cfg(test)]
4848+mod tests {
4949+ use super::*;
5050+5151+ #[test]
5252+ fn zeroed_state_keccakf_matches_expected_outputs() {
5353+ let mut state = KeccakF1600([0u8; KECCAK_BUFFER_SIZE]);
5454+5555+ // Do one permutation on the KeccakF state
5656+ state.permute_f1600();
5757+5858+ let mut words = [0u64; KECCAK_BLOCK_SIZE];
5959+6060+ // Byte state should match expected word state
6161+ state.copy_into(&mut words);
6262+6363+ assert_eq!(
6464+ &words,
6565+ &[
6666+ 0xF1258F7940E1DDE7,
6767+ 0x84D5CCF933C0478A,
6868+ 0xD598261EA65AA9EE,
6969+ 0xBD1547306F80494D,
7070+ 0x8B284E056253D057,
7171+ 0xFF97A42D7F8E6FD4,
7272+ 0x90FEE5A0A44647C4,
7373+ 0x8C5BDA0CD6192E76,
7474+ 0xAD30A6F71B19059C,
7575+ 0x30935AB7D08FFC64,
7676+ 0xEB5AA93F2317D635,
7777+ 0xA9A6E6260D712103,
7878+ 0x81A57C16DBCF555F,
7979+ 0x43B831CD0347C826,
8080+ 0x01F22F1A11A5569F,
8181+ 0x05E5635A21D9AE61,
8282+ 0x64BEFEF28CC970F2,
8383+ 0x613670957BC46611,
8484+ 0xB87C5A554FD00ECB,
8585+ 0x8C3EE88A1CCF32C8,
8686+ 0x940C7922AE3A2614,
8787+ 0x1841F924A2C509E4,
8888+ 0x16F53526E70465C2,
8989+ 0x75F644E97F30A13B,
9090+ 0xEAF1FF7B5CECA249,
9191+ ]
9292+ );
9393+9494+ // Do another permutation
9595+ state.permute_f1600();
9696+9797+ state.copy_into(&mut words);
9898+9999+ assert_eq!(
100100+ &words,
101101+ &[
102102+ 0x2D5C954DF96ECB3C,
103103+ 0x6A332CD07057B56D,
104104+ 0x093D8D1270D76B6C,
105105+ 0x8A20D9B25569D094,
106106+ 0x4F9C4F99E5E7F156,
107107+ 0xF957B9A2DA65FB38,
108108+ 0x85773DAE1275AF0D,
109109+ 0xFAF4F247C3D810F7,
110110+ 0x1F1B9EE6F79A8759,
111111+ 0xE4FECC0FEE98B425,
112112+ 0x68CE61B6B9CE68A1,
113113+ 0xDEEA66C4BA8F974F,
114114+ 0x33C43D836EAFB1F5,
115115+ 0xE00654042719DBD9,
116116+ 0x7CF8A9F009831265,
117117+ 0xFD5449A6BF174743,
118118+ 0x97DDAD33D8994B40,
119119+ 0x48EAD5FC5D0BE774,
120120+ 0xE3B8C8EE55B7B03C,
121121+ 0x91A0226E649E42E9,
122122+ 0x900E3129E7BADD7B,
123123+ 0x202A9EC5FAA3CCE8,
124124+ 0x5B3402464E1C3DB6,
125125+ 0x609F4E62A44C1059,
126126+ 0x20D06CD26A8FBF5C,
127127+ ]
128128+ )
129129+ }
130130+}
+21
src/lib.rs
···11+#![no_std]
22+#![forbid(unsafe_code)]
33+44+#[cfg(test)]
55+mod basic_kats;
66+#[cfg(test)]
77+mod herd_of_kats;
88+mod keccakf;
99+pub mod strobe;
1010+1111+/// Version of Strobe that this crate implements.
1212+pub static STROBE_VERSION: &str = "1.0.2";
1313+1414+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1515+pub struct GarbledError;
1616+1717+impl core::fmt::Display for GarbledError {
1818+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1919+ f.write_str("Protocol Failure")
2020+ }
2121+}
+473
src/strobe.rs
···11+use enumflags2::{BitFlag, BitFlags};
22+use subtle::ConstantTimeEq;
33+44+use crate::{
55+ GarbledError, STROBE_VERSION,
66+ keccakf::{KECCAK_BUFFER_SIZE, KeccakF1600},
77+};
88+99+#[enumflags2::bitflags]
1010+#[repr(u8)]
1111+#[derive(Copy, Clone, Debug, PartialEq)]
1212+enum OpFlags {
1313+ /// Is data being moved inbound
1414+ Inbound = 0b000001, // 1<<0
1515+ /// Is data being sent to the application
1616+ App = 0b000010, // 1<<1
1717+ /// Does this operation use cipher output
1818+ Cipher = 0b000100, // 1<<2
1919+ /// Is data being sent for transport
2020+ Transport = 0b001000, // 1<<3
2121+ /// Use exclusively for metadata operations
2222+ Meta = 0b010000, // 1<<4
2323+ /// Reserved and currently unimplemented. Using this will cause a panic.
2424+ KeyTree = 0b100000, // 1<<5
2525+}
2626+2727+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2828+enum Role {
2929+ Sender,
3030+ Receiver,
3131+}
3232+3333+#[derive(Debug, Clone, Copy)]
3434+#[repr(usize)]
3535+pub enum SecurityParameter {
3636+ B128 = 128,
3737+ B256 = 256,
3838+}
3939+4040+#[derive(Clone)]
4141+pub struct StrobeState {
4242+ /// Internal Keccak state
4343+ pub(crate) state: KeccakF1600,
4444+ /// Security parameter (128 or 256 bits)
4545+ sec: SecurityParameter,
4646+ /// This is the `R` parameter in the Strobe spec
4747+ rate: usize,
4848+ /// Index into `state`
4949+ position: usize,
5050+ /// Index into `state`
5151+ start: usize,
5252+ /// Represents whether we're a sender or a receiver or uninitialized
5353+ role: Option<Role>,
5454+ /// The last operation performed. This is to verify that the `more` flag is only used across
5555+ /// identical operations.
5656+ prev_flags: BitFlags<OpFlags>,
5757+}
5858+5959+macro_rules! define_mut_operations {
6060+ { $(#[$doc:meta]
6161+ pub fn $name:ident($flags:expr);
6262+ )+ } => {
6363+ $(
6464+ #[$doc]
6565+ pub fn $name(&mut self, data: &mut [u8]) {
6666+ let flags = $flags;
6767+ self.operate(flags, data, self.prev_flags == flags);
6868+ }
6969+ )*
7070+ };
7171+}
7272+7373+macro_rules! define_non_mut_operations {
7474+ { $(#[$doc:meta]
7575+ pub fn $name:ident($flags:expr);
7676+ )+ } => {
7777+ $(
7878+ #[$doc]
7979+ pub fn $name(&mut self, data: &[u8]) {
8080+ let flags = $flags;
8181+ self.operate_no_mutate(flags, data, self.prev_flags == flags);
8282+ }
8383+ )*
8484+ };
8585+}
8686+8787+impl core::fmt::Display for StrobeState {
8888+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
8989+ f.write_str("Strobe-Keccak-")?;
9090+ match self.sec {
9191+ SecurityParameter::B128 => f.write_str("128")?,
9292+ SecurityParameter::B256 => f.write_str("256")?,
9393+ }
9494+ f.write_str("/1600-v")?;
9595+ f.write_str(STROBE_VERSION)
9696+ }
9797+}
9898+9999+impl core::fmt::Debug for StrobeState {
100100+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101101+ // Do not reveal internal state of StrobeState, other than its security level
102102+ f.debug_struct("StrobeState")
103103+ .field("sec", &self.sec)
104104+ .field("version", &STROBE_VERSION)
105105+ .finish_non_exhaustive()
106106+ }
107107+}
108108+109109+impl StrobeState {
110110+ /// Makes a new `StrobeTransport` object with a given protocol byte string and security parameter.
111111+ pub fn new(protocol: &[u8], sec: SecurityParameter) -> Self {
112112+ let rate = KECCAK_BUFFER_SIZE - (sec as usize) / 4 - 2;
113113+ assert!((1..254).contains(&rate));
114114+115115+ // Initialize state: st = F([0x01, R+2, 0x01, 0x00, 0x01, 0x60] + b"STROBEvX.Y.Z")
116116+ let mut state_buffer = [0u8; KECCAK_BUFFER_SIZE];
117117+ state_buffer[0..6].copy_from_slice(&[0x01, (rate as u8) + 2, 0x01, 0x00, 0x01, 0x60]);
118118+ state_buffer[6..13].copy_from_slice(b"STROBEv");
119119+ state_buffer[13..18].copy_from_slice(STROBE_VERSION.as_bytes());
120120+121121+ let mut state = KeccakF1600(state_buffer);
122122+123123+ state.permute_f1600();
124124+125125+ let mut strobe = Self {
126126+ state,
127127+ sec,
128128+ rate,
129129+ position: 0,
130130+ start: 0,
131131+ role: None,
132132+ prev_flags: OpFlags::empty(),
133133+ };
134134+135135+ // Mix the protocol into the state
136136+ strobe.meta_ad(protocol);
137137+138138+ // Reset operation so meta_ad doesn't stream after initialisation
139139+ strobe.reset_ops();
140140+141141+ strobe
142142+ }
143143+144144+ /// Reset the internal operation state. This is to make sure the next call doesn't
145145+ /// stream and to always begin as a new operation. This doesn't modify or change the
146146+ /// internal Strobe state, only the tracked operation state.
147147+ ///
148148+ /// This is a modification to the Strobe API surface to prevent misuse of op calls,
149149+ /// preventing panics/errors so that the compiler can optimise better.
150150+ pub fn reset_ops(&mut self) {
151151+ // This prevents streaming so to always make the prev_flags == flags
152152+ // comparison always fail
153153+ self.prev_flags = OpFlags::empty();
154154+ }
155155+156156+ // Runs the permutation function on the internal state
157157+ fn permutation_f(&mut self) {
158158+ self.state.0[self.position] ^= self.start as u8;
159159+ self.state.0[self.position + 1] ^= 0x04;
160160+ self.state.0[self.rate + 1] ^= 0x80;
161161+162162+ self.state.permute_f1600();
163163+164164+ self.position = 0;
165165+ self.start = 0;
166166+ }
167167+168168+ fn increment_position(&mut self) {
169169+ self.position += 1;
170170+171171+ if self.position == self.rate {
172172+ self.permutation_f();
173173+ }
174174+ }
175175+176176+ /// XORs the given data into the state. This is a special case of the `duplex` code in the
177177+ /// STROBE paper.
178178+ fn absorb(&mut self, data: &[u8]) {
179179+ data.iter().for_each(|&b| {
180180+ self.state.0[self.position] ^= b;
181181+182182+ self.increment_position();
183183+ });
184184+ }
185185+186186+ /// XORs the given data into the state, then sets the data equal the state. This is a special
187187+ /// case of the `duplex` code in the STROBE paper.
188188+ fn absorb_and_set(&mut self, data: &mut [u8]) {
189189+ data.iter_mut().for_each(|b| {
190190+ let state_byte = &mut self.state.0[self.position];
191191+ *state_byte ^= *b;
192192+ *b = *state_byte;
193193+194194+ self.increment_position();
195195+ });
196196+ }
197197+198198+ /// Copies the internal state into the given buffer. This is a special case of `absorb_and_set`
199199+ /// where `data` is all zeros.
200200+ fn copy_state(&mut self, data: &mut [u8]) {
201201+ data.iter_mut().for_each(|b| {
202202+ *b = self.state.0[self.position];
203203+204204+ self.increment_position();
205205+ });
206206+ }
207207+208208+ /// Overwrites the state with the given data while XORing the given data with the old state.
209209+ /// This is a special case of the `duplex` code in the STROBE paper.
210210+ fn exchange(&mut self, data: &mut [u8]) {
211211+ data.iter_mut().for_each(|b| {
212212+ let state_byte = &mut self.state.0[self.position];
213213+ *b ^= *state_byte;
214214+ *state_byte ^= *b;
215215+216216+ self.increment_position();
217217+ });
218218+ }
219219+220220+ /// Overwrites the state with the given data. This is a special case of `Strobe::exchange`,
221221+ /// where we do not want to mutate the input data.
222222+ fn overwrite(&mut self, data: &[u8]) {
223223+ data.iter().for_each(|&b| {
224224+ self.state.0[self.position] = b;
225225+226226+ self.increment_position();
227227+ });
228228+ }
229229+230230+ /// Copies the state into the given buffer and sets the state to 0. This is a special case of
231231+ /// `Strobe::exchange`, where `data` is assumed to be the all-zeros string. This is precisely
232232+ /// the case when the current operation is PRF.
233233+ fn squeeze(&mut self, data: &mut [u8]) {
234234+ data.iter_mut().for_each(|b| {
235235+ let state_byte = &mut self.state.0[self.position];
236236+ *b = *state_byte;
237237+ *state_byte = 0;
238238+239239+ self.increment_position();
240240+ });
241241+ }
242242+243243+ /// Overwrites the state with a specified number of zeros. This is a special case of
244244+ /// `Strobe::exchange`. More specifically, it's a special case of `Strobe::overwrite` and
245245+ /// `Strobe::squeeze`. It's like `squeeze` in that we assume we've been given all zeros as
246246+ /// input, and like `overwrite` in that we do not mutate (or take) any input.
247247+ fn zero_state(&mut self, mut bytes_to_zero: usize) {
248248+ // Do the zero-writing in chunks
249249+ while bytes_to_zero > 0 {
250250+ let slice_len = core::cmp::min(self.rate - self.position, bytes_to_zero);
251251+ self.state.0[self.position..(self.position + slice_len)].fill(0);
252252+253253+ self.position += slice_len;
254254+ bytes_to_zero -= slice_len;
255255+256256+ if self.position == self.rate {
257257+ self.permutation_f();
258258+ }
259259+ }
260260+ }
261261+262262+ /// Mixes the current state index and flags into the state, accounting for whether we are
263263+ /// sending or receiving
264264+ fn begin_op(&mut self, mut flags: BitFlags<OpFlags>) {
265265+ if flags.contains(OpFlags::Transport) {
266266+ let op_role = if flags.contains(OpFlags::Inbound) {
267267+ Role::Receiver
268268+ } else {
269269+ Role::Sender
270270+ };
271271+272272+ // If uninitialized, take on the direction of the first directional operation we get
273273+ if self.role.is_none() {
274274+ self.role = Some(op_role);
275275+ }
276276+277277+ // So that the sender and receiver agree, toggle the I flag as necessary
278278+ // This is equivalent to flags ^= is_receiver
279279+ flags.set(OpFlags::Inbound, self.role.unwrap() != op_role);
280280+ }
281281+282282+ let old_start = self.start;
283283+ self.start = self.position + 1;
284284+285285+ // Mix in the position and flags
286286+ self.absorb(&[old_start as u8, flags.bits()]);
287287+288288+ let force_permutation = flags.contains(OpFlags::Cipher) || flags.contains(OpFlags::KeyTree);
289289+ if force_permutation && self.position != 0 {
290290+ self.permutation_f();
291291+ }
292292+ }
293293+294294+ /// Performs the state / data transformation that corresponds to the given flags. If `more` is
295295+ /// given, this will treat `data` as a continuation of the data given in the previous
296296+ /// call to `operate`.
297297+ fn operate(&mut self, flags: BitFlags<OpFlags>, data: &mut [u8], more: bool) {
298298+ self.prev_flags = flags;
299299+300300+ // If `more` isn't set, this is a new operation. Do the begin_op sequence
301301+ if !more {
302302+ self.begin_op(flags);
303303+ }
304304+305305+ // Meta-ness is only relevant for `begin_op`. Remove it to simplify the below logic.
306306+ let flags = flags & !OpFlags::Meta;
307307+308308+ // TODO?: Assert that input is empty under some flag conditions
309309+ if flags.contains(OpFlags::Cipher | OpFlags::Transport) && !flags.contains(OpFlags::Inbound)
310310+ {
311311+ // This is equivalent to the `duplex` operation in the Python implementation, with
312312+ // `cafter = True`
313313+ if flags == OpFlags::Cipher | OpFlags::Transport {
314314+ // This is `send_mac`. Pretend the input is all zeros
315315+ self.copy_state(data);
316316+ } else {
317317+ self.absorb_and_set(data);
318318+ }
319319+ } else if flags == OpFlags::Inbound | OpFlags::App | OpFlags::Cipher {
320320+ // Special case of case below. This is PRF. Use `squeeze` instead of `exchange`.
321321+ self.squeeze(data);
322322+ } else if flags.contains(OpFlags::Cipher) {
323323+ // This is equivalent to the `duplex` operation in the Python implementation, with
324324+ // `cbefore = True`
325325+ self.exchange(data);
326326+ } else {
327327+ // This should normally call `absorb`, but `absorb` does not mutate, so the implementor
328328+ // should have used operate_no_mutate instead
329329+ unreachable!("operate should not be called for operations that do not require mutation")
330330+ }
331331+ }
332332+333333+ /// Performs the state transformation that corresponds to the given flags. If `more` is given,
334334+ /// this will treat `data` as a continuation of the data given in the previous call to
335335+ /// `operate`. This uses non-mutating variants of the specializations of the `duplex` function.
336336+ fn operate_no_mutate(&mut self, flags: BitFlags<OpFlags>, data: &[u8], more: bool) {
337337+ self.prev_flags = flags;
338338+339339+ // If `more` isn't set, this is a new operation. Do the begin_op sequence
340340+ if !more {
341341+ self.begin_op(flags);
342342+ }
343343+344344+ // There are no non-mutating variants of things with flags & (C | T | I) == C | T
345345+ if flags.contains(OpFlags::Cipher | OpFlags::Transport) && !flags.contains(OpFlags::Inbound)
346346+ {
347347+ unreachable!("operate_no_mutate called on something that requires mutation")
348348+ } else if flags.contains(OpFlags::Cipher) {
349349+ // This is equivalent to a non-mutating form of the `duplex` operation in the Python
350350+ // implementation, with `cbefore = True`
351351+ self.overwrite(data);
352352+ } else {
353353+ // This is equivalent to the `duplex` operation in the Python implementation, with
354354+ // `cbefore = cafter = False`
355355+ self.absorb(data);
356356+ }
357357+ }
358358+359359+ fn recv_mac_inner(
360360+ &mut self,
361361+ mac_copy: &mut [u8],
362362+ flags: BitFlags<OpFlags>,
363363+ ) -> Result<(), GarbledError> {
364364+ // recv_mac can never be streamed
365365+ self.operate(flags, mac_copy, false);
366366+367367+ // Constant-time MAC check. This accumulates the truth values of byte == 0
368368+ let all_zero: bool = mac_copy
369369+ .iter()
370370+ .fold(subtle::Choice::from(1u8), |all_zero, b| {
371371+ all_zero & 0u8.ct_eq(b)
372372+ })
373373+ .into();
374374+375375+ if all_zero { Ok(()) } else { Err(GarbledError) }
376376+ }
377377+378378+ pub fn recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> {
379379+ let mut mac_copy = *mac;
380380+381381+ self.recv_mac_inner(
382382+ &mut mac_copy,
383383+ OpFlags::Inbound | OpFlags::Cipher | OpFlags::Transport,
384384+ )
385385+ }
386386+387387+ pub fn meta_recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> {
388388+ let mut mac_copy = *mac;
389389+390390+ self.recv_mac_inner(
391391+ &mut mac_copy,
392392+ OpFlags::Inbound | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta,
393393+ )
394394+ }
395395+396396+ fn ratchet_inner(&mut self, num_bytes_to_zero: usize, more: bool, flags: BitFlags<OpFlags>) {
397397+ // We don't make an `operate` call, since this is a super special case. That means we have
398398+ // to make the `begin_op` call manually.
399399+ self.prev_flags = flags;
400400+401401+ if !more {
402402+ self.begin_op(flags);
403403+ }
404404+405405+ self.zero_state(num_bytes_to_zero);
406406+ }
407407+408408+ pub fn ratchet(&mut self, num_bytes_to_zero: usize) {
409409+ let flags = OpFlags::Cipher.into();
410410+ self.ratchet_inner(num_bytes_to_zero, self.prev_flags == flags, flags);
411411+ }
412412+413413+ pub fn meta_ratchet(&mut self, num_bytes_to_zero: usize) {
414414+ let flags = OpFlags::Cipher | OpFlags::Meta;
415415+ self.ratchet_inner(num_bytes_to_zero, self.prev_flags == flags, flags);
416416+ }
417417+418418+ define_mut_operations! {
419419+ /// SEND ENC
420420+ pub fn send_enc(OpFlags::App | OpFlags::Cipher | OpFlags::Transport);
421421+ /// META SEND ENC
422422+ pub fn meta_send_enc(OpFlags::App | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta);
423423+ /// RECV ENV
424424+ pub fn recv_enc(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Transport);
425425+ /// META RECV ENC
426426+ pub fn meta_recv_enc(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta);
427427+ /// SEND MAC
428428+ pub fn send_mac(OpFlags::Cipher | OpFlags::Transport);
429429+ /// META SEND MAC
430430+ pub fn meta_send_mac(OpFlags::Cipher | OpFlags::Transport | OpFlags::Meta);
431431+ /// PRF
432432+ pub fn prf(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher);
433433+ /// META PRF
434434+ pub fn meta_prf(OpFlags::Inbound | OpFlags::App | OpFlags::Cipher | OpFlags::Meta);
435435+ }
436436+437437+ define_non_mut_operations! {
438438+ /// AD
439439+ pub fn ad(OpFlags::App.into());
440440+ /// META AD
441441+ pub fn meta_ad(OpFlags::App | OpFlags::Meta);
442442+ /// KEY
443443+ pub fn key(OpFlags::App | OpFlags::Cipher);
444444+ /// META KEY
445445+ pub fn meta_key(OpFlags::App | OpFlags::Cipher | OpFlags::Meta);
446446+ /// SEND CLR
447447+ pub fn send_clr(OpFlags::App | OpFlags::Transport);
448448+ /// META SEND CLR
449449+ pub fn meta_send_clr(OpFlags::App | OpFlags::Transport | OpFlags::Meta);
450450+ /// RECV CLR
451451+ pub fn recv_clr(OpFlags::Inbound | OpFlags::App | OpFlags::Transport);
452452+ /// META RECV CLR
453453+ pub fn meta_recv_clr(OpFlags::Inbound | OpFlags::App | OpFlags::Transport | OpFlags::Meta);
454454+ }
455455+}
456456+457457+#[cfg(test)]
458458+mod tests {
459459+ use super::*;
460460+461461+ extern crate std;
462462+463463+ #[test]
464464+ fn version_formatting() {
465465+ let s = StrobeState::new(b"", SecurityParameter::B128);
466466+467467+ let display = std::format!("{s}");
468468+ let debug = std::format!("{s:?}");
469469+470470+ assert_eq!(&display, "Strobe-Keccak-128/1600-v1.0.2");
471471+ assert_eq!(&debug, "StrobeState { sec: B128, version: \"1.0.2\", .. }");
472472+ }
473473+}