├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── benchmark.rs └── src ├── error.rs ├── lib.rs ├── test.rs ├── xoodoo ├── impl_portable.rs ├── impl_x86_64.rs └── mod.rs └── xoodyak ├── any.rs ├── hash.rs ├── keyed.rs ├── mod.rs └── tag.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /target 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xoodyak" 3 | version = "0.8.4" 4 | authors = ["Frank Denis "] 5 | edition = "2018" 6 | description = "Xoodyak / Xoodoo - A versatile cryptographic scheme that can be used for hashing, encryption, MAC computation and authenticated encryption." 7 | readme = "README.md" 8 | keywords = ["crypto", "xoodyak", "xoodoo", "cyclist"] 9 | license = "MIT" 10 | homepage = "https://github.com/jedisct1/rust-xoodyak" 11 | repository = "https://github.com/jedisct1/rust-xoodyak" 12 | categories = ["no-std", "cryptography", "wasm"] 13 | 14 | [features] 15 | default = ["std"] 16 | std = [] 17 | 18 | [dependencies] 19 | zeroize = "1.8" 20 | 21 | [dev-dependencies] 22 | benchmark-simple = "0.1.10" 23 | 24 | [profile.release] 25 | codegen-units = 1 26 | incremental = false 27 | panic = "abort" 28 | lto = "fat" 29 | 30 | [profile.bench] 31 | codegen-units = 1 32 | 33 | [[bench]] 34 | name = "benchmark" 35 | harness = false 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Frank Denis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub CI](https://github.com/jedisct1/rust-xoodyak/workflows/Rust/badge.svg) 2 | 3 | # Xoodyak for Rust 4 | 5 | This is a Rust implementation of [Xoodyak](https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/xoodyak-spec-final.pdf), a cryptographic primitive that can be used for hashing, encryption, MAC computation and authenticated encryption. 6 | 7 | * `no_std`-friendly 8 | * Lightweight 9 | * Can be compiled to WebAssembly/WASI 10 | * Session support 11 | * Safe Rust interface 12 | * AEAD with attached and detached tags 13 | * In-place encryption 14 | * Ratcheting 15 | * Variable-length output hashing, authentication 16 | * `squeeze_more()`, `absorb_more()` for streaming. 17 | 18 | # [API documentation](https://docs.rs/xoodyak) 19 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use benchmark_simple::*; 2 | use xoodyak::*; 3 | 4 | fn main() { 5 | let bench = Bench::new(); 6 | let options = &Options { 7 | iterations: 250_000, 8 | warmup_iterations: 25_000, 9 | min_samples: 5, 10 | max_samples: 10, 11 | max_rsd: 1.0, 12 | ..Default::default() 13 | }; 14 | 15 | { 16 | let mut out = [0u8; 48]; 17 | let mut st = Xoodoo::default(); 18 | let res = bench.run(options, || { 19 | st.permute(); 20 | st.bytes(&mut out); 21 | out 22 | }); 23 | println!("Xoodoo permutation: {}", res.throughput(out.len() as _)); 24 | } 25 | 26 | { 27 | let mut out = [0u8; 64]; 28 | let mut st = XoodyakHash::new(); 29 | let res = bench.run(options, || { 30 | st.absorb(b"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. "); 31 | st.squeeze(&mut out); 32 | out 33 | }); 34 | println!("Xoodyak hash : {}", res.throughput(out.len() as _)); 35 | } 36 | 37 | { 38 | let mut out = [0u8; 64]; 39 | let mut st = XoodyakKeyed::new(b"key", None, None, None).unwrap(); 40 | let res = bench.run(options, || { 41 | st.absorb(b"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. "); 42 | st.squeeze(&mut out); 43 | out 44 | }); 45 | println!("Xoodyak keyed : {}", res.throughput(out.len() as _)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 4 | pub enum Error { 5 | InvalidBufferLength, 6 | InvalidParameterLength, 7 | KeyRequired, 8 | TagMismatch, 9 | } 10 | 11 | impl std::error::Error for Error {} 12 | 13 | impl Display for Error { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Error::InvalidBufferLength => write!(f, "Invalid buffer length"), 17 | Error::InvalidParameterLength => write!(f, "Key too long"), 18 | Error::KeyRequired => write!(f, "A key is required"), 19 | Error::TagMismatch => write!(f, "Tag mismatch"), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(not(feature = "std"), no_std)] 3 | 4 | mod error; 5 | mod xoodoo; 6 | mod xoodyak; 7 | 8 | pub use crate::error::Error as XoodyakError; 9 | pub use crate::xoodoo::Xoodoo; 10 | pub use crate::xoodyak::{ 11 | Tag as XoodyakTag, XoodyakAny, XoodyakCommon, XoodyakHash, XoodyakKeyed, 12 | AUTH_TAG_BYTES as XOODYAK_AUTH_TAG_BYTES, 13 | }; 14 | 15 | #[cfg(test)] 16 | mod test; 17 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[test] 4 | fn test_keyed_empty() { 5 | let mut st = XoodyakKeyed::new(b"key", None, None, None).unwrap(); 6 | let mut out = [0u8; 32]; 7 | st.squeeze(&mut out); 8 | assert_eq!( 9 | out, 10 | [ 11 | 106, 247, 180, 176, 207, 217, 130, 200, 237, 113, 163, 185, 224, 53, 120, 137, 251, 12 | 126, 216, 3, 87, 45, 239, 214, 41, 201, 246, 56, 83, 55, 18, 108 13 | ] 14 | ); 15 | } 16 | 17 | #[test] 18 | fn test_unkeyed_empty() { 19 | let mut st = XoodyakHash::new(); 20 | let mut out = [0u8; 32]; 21 | st.squeeze(&mut out); 22 | assert_eq!( 23 | out, 24 | [ 25 | 141, 216, 213, 137, 191, 252, 99, 169, 25, 45, 35, 27, 20, 160, 165, 255, 204, 246, 41, 26 | 214, 87, 39, 76, 114, 39, 130, 131, 52, 124, 189, 128, 53 27 | ] 28 | ); 29 | 30 | let mut st = XoodyakHash::new(); 31 | let mut out = [0u8; 32]; 32 | st.absorb(&[]); 33 | st.squeeze(&mut out); 34 | assert_eq!( 35 | out, 36 | [ 37 | 234, 21, 47, 43, 71, 188, 226, 78, 251, 102, 196, 121, 212, 173, 241, 123, 211, 36, 38 | 216, 6, 232, 95, 247, 94, 227, 105, 238, 80, 220, 143, 139, 209 39 | ] 40 | ); 41 | } 42 | 43 | #[test] 44 | fn test_encrypt() { 45 | let mut st = XoodyakKeyed::new(b"key", None, None, None).unwrap(); 46 | let st0 = st.clone(); 47 | let m = b"message"; 48 | let mut c = st.encrypt_to_vec(m).unwrap(); 49 | 50 | let mut st = st0.clone(); 51 | let m2 = st.decrypt_to_vec(&c).unwrap(); 52 | assert_eq!(&m[..], m2.as_slice()); 53 | 54 | let mut st = st0.clone(); 55 | st.ratchet(); 56 | let m2 = st.decrypt_to_vec(&c).unwrap(); 57 | assert_ne!(&m[..], m2.as_slice()); 58 | 59 | let c0 = c.clone(); 60 | let mut st = st0.clone(); 61 | st.decrypt_in_place(&mut c); 62 | assert_eq!(&m[..], &c[..]); 63 | 64 | let mut st = st0; 65 | st.encrypt_in_place(&mut c); 66 | assert_eq!(c0, c); 67 | 68 | let tag = st.squeeze_to_vec(32); 69 | assert_eq!( 70 | tag, 71 | [ 72 | 10, 175, 140, 82, 142, 109, 23, 111, 201, 232, 32, 52, 122, 46, 254, 206, 236, 54, 97, 73 | 165, 40, 85, 166, 91, 124, 88, 26, 144, 100, 250, 243, 157 74 | ] 75 | ); 76 | } 77 | 78 | #[test] 79 | fn test_unkeyed_hash() { 80 | let mut st = XoodyakHash::new(); 81 | let m = b"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."; 82 | st.absorb(&m[..]); 83 | let hash = st.squeeze_to_vec(32); 84 | assert_eq!( 85 | hash, 86 | [ 87 | 144, 82, 141, 27, 59, 215, 34, 104, 197, 106, 251, 142, 112, 235, 111, 168, 19, 6, 112, 88 | 222, 160, 168, 230, 38, 27, 229, 248, 179, 94, 227, 247, 25 89 | ] 90 | ); 91 | st.absorb(&m[..]); 92 | let hash = st.squeeze_to_vec(32); 93 | assert_eq!( 94 | hash, 95 | [ 96 | 102, 50, 250, 132, 79, 91, 248, 161, 121, 248, 225, 33, 105, 159, 111, 230, 135, 252, 97 | 43, 228, 152, 41, 58, 242, 211, 252, 29, 234, 181, 0, 196, 220 98 | ] 99 | ); 100 | } 101 | 102 | #[test] 103 | fn test_aead() { 104 | let nonce = [0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 105 | let mut st = XoodyakKeyed::new(b"key", Some(&nonce), None, None).unwrap(); 106 | let st0 = st.clone(); 107 | let m = b"message"; 108 | let ad = b"ad"; 109 | st.absorb(ad); 110 | let c = st.aead_encrypt_to_vec(Some(m)).unwrap(); 111 | 112 | let mut st = st0.clone(); 113 | st.absorb(ad); 114 | let m2 = st.aead_decrypt_to_vec(&c).unwrap(); 115 | assert_eq!(&m[..], &m2[..]); 116 | 117 | let mut st = st0; 118 | let xm2 = st.aead_decrypt_to_vec(&m[..]); 119 | assert!(xm2.is_err()); 120 | 121 | let mut st = XoodyakKeyed::new(b"Another key", Some(&nonce), None, None).unwrap(); 122 | let xm2 = st.aead_decrypt_to_vec(&m[..]); 123 | assert!(xm2.is_err()); 124 | } 125 | 126 | #[test] 127 | fn test_aead_in_place() { 128 | let nonce = [0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 129 | let mut st = XoodyakKeyed::new(b"key", Some(&nonce), None, None).unwrap(); 130 | let st0 = st.clone(); 131 | 132 | let m = b"message"; 133 | st.absorb(b"ad"); 134 | let c = st.aead_encrypt_in_place_to_vec(m.to_vec()); 135 | 136 | let mut st = st0.clone(); 137 | let xm2 = st.aead_decrypt_in_place_to_vec(c.clone()); 138 | assert!(xm2.is_err()); 139 | 140 | let mut st = st0; 141 | st.absorb(b"ad"); 142 | let m2 = st.aead_decrypt_in_place_to_vec(c).unwrap(); 143 | assert_eq!(&m[..], &m2[..]); 144 | } 145 | 146 | #[test] 147 | fn test_aead_detached() { 148 | let nonce = [0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 149 | let mut st = XoodyakKeyed::new(b"key", Some(&nonce), None, None).unwrap(); 150 | let st0 = st.clone(); 151 | let m = b"message"; 152 | st.absorb(b"ad"); 153 | let (c, auth_tag) = st.aead_encrypt_to_vec_detached(Some(m)).unwrap(); 154 | 155 | let mut st = st0; 156 | let expected_tag = [ 157 | 12, 91, 0, 120, 191, 214, 119, 66, 122, 225, 184, 239, 213, 214, 247, 57, 158 | ]; 159 | assert_eq!(auth_tag.as_ref(), expected_tag); 160 | st.absorb(b"ad"); 161 | let m2 = st 162 | .aead_decrypt_to_vec_detached(expected_tag.into(), Some(&c)) 163 | .unwrap(); 164 | assert_eq!(m2, m); 165 | } 166 | -------------------------------------------------------------------------------- /src/xoodoo/impl_portable.rs: -------------------------------------------------------------------------------- 1 | use super::{Xoodoo, ROUND_KEYS}; 2 | 3 | impl Xoodoo { 4 | #[inline(always)] 5 | fn round(st_words: &mut [u32; 12], round_key: u32) { 6 | let p = [ 7 | st_words[0] ^ st_words[4] ^ st_words[8], 8 | st_words[1] ^ st_words[5] ^ st_words[9], 9 | st_words[2] ^ st_words[6] ^ st_words[10], 10 | st_words[3] ^ st_words[7] ^ st_words[11], 11 | ]; 12 | 13 | let e = [ 14 | p[3].rotate_left(5) ^ p[3].rotate_left(14), 15 | p[0].rotate_left(5) ^ p[0].rotate_left(14), 16 | p[1].rotate_left(5) ^ p[1].rotate_left(14), 17 | p[2].rotate_left(5) ^ p[2].rotate_left(14), 18 | ]; 19 | 20 | let mut tmp = [0u32; 12]; 21 | 22 | tmp[0] = e[0] ^ st_words[0] ^ round_key; 23 | tmp[1] = e[1] ^ st_words[1]; 24 | tmp[2] = e[2] ^ st_words[2]; 25 | tmp[3] = e[3] ^ st_words[3]; 26 | 27 | tmp[4] = e[3] ^ st_words[7]; 28 | tmp[5] = e[0] ^ st_words[4]; 29 | tmp[6] = e[1] ^ st_words[5]; 30 | tmp[7] = e[2] ^ st_words[6]; 31 | 32 | tmp[8] = (e[0] ^ st_words[8]).rotate_left(11); 33 | tmp[9] = (e[1] ^ st_words[9]).rotate_left(11); 34 | tmp[10] = (e[2] ^ st_words[10]).rotate_left(11); 35 | tmp[11] = (e[3] ^ st_words[11]).rotate_left(11); 36 | 37 | st_words[0] = (!tmp[4] & tmp[8]) ^ tmp[0]; 38 | st_words[1] = (!tmp[5] & tmp[9]) ^ tmp[1]; 39 | st_words[2] = (!tmp[6] & tmp[10]) ^ tmp[2]; 40 | st_words[3] = (!tmp[7] & tmp[11]) ^ tmp[3]; 41 | 42 | st_words[4] = ((!tmp[8] & tmp[0]) ^ tmp[4]).rotate_left(1); 43 | st_words[5] = ((!tmp[9] & tmp[1]) ^ tmp[5]).rotate_left(1); 44 | st_words[6] = ((!tmp[10] & tmp[2]) ^ tmp[6]).rotate_left(1); 45 | st_words[7] = ((!tmp[11] & tmp[3]) ^ tmp[7]).rotate_left(1); 46 | 47 | st_words[8] = ((!tmp[2] & tmp[6]) ^ tmp[10]).rotate_left(8); 48 | st_words[9] = ((!tmp[3] & tmp[7]) ^ tmp[11]).rotate_left(8); 49 | st_words[10] = ((!tmp[0] & tmp[4]) ^ tmp[8]).rotate_left(8); 50 | st_words[11] = ((!tmp[1] & tmp[5]) ^ tmp[9]).rotate_left(8); 51 | } 52 | 53 | pub fn permute(&mut self) { 54 | let mut st_words = self.to_words(); 55 | for &round_key in &ROUND_KEYS { 56 | Self::round(&mut st_words, round_key) 57 | } 58 | self.init_from_words(st_words); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/xoodoo/impl_x86_64.rs: -------------------------------------------------------------------------------- 1 | use core::arch::x86_64::*; 2 | 3 | use super::{Xoodoo, ROUND_KEYS}; 4 | 5 | impl Xoodoo { 6 | #[allow( 7 | non_upper_case_globals, 8 | clippy::many_single_char_names, 9 | clippy::cast_ptr_alignment 10 | )] 11 | pub fn permute(&mut self) { 12 | let st = &mut self.st; 13 | unsafe { 14 | let rho_east_2 = _mm_set_epi32(0x0605_0407, 0x0201_0003, 0x0e0d_0c0f, 0x0a09_080b); 15 | let mut a = _mm_loadu_si128(st.as_ptr().add(0 * 4) as *const _); 16 | let mut b = _mm_loadu_si128(st.as_ptr().add(4 * 4) as *const _); 17 | let mut c = _mm_loadu_si128(st.as_ptr().add(8 * 4) as *const _); 18 | for &round_key in &ROUND_KEYS { 19 | let mut p = _mm_shuffle_epi32(_mm_xor_si128(_mm_xor_si128(a, b), c), 0x93); 20 | let mut e = _mm_or_si128(_mm_slli_epi32(p, 5), _mm_srli_epi32(p, 32 - 5)); 21 | p = _mm_or_si128(_mm_slli_epi32(p, 14), _mm_srli_epi32(p, 32 - 14)); 22 | e = _mm_xor_si128(e, p); 23 | a = _mm_xor_si128(a, e); 24 | b = _mm_xor_si128(b, e); 25 | c = _mm_xor_si128(c, e); 26 | b = _mm_shuffle_epi32(b, 0x93); 27 | c = _mm_or_si128(_mm_slli_epi32(c, 11), _mm_srli_epi32(c, 32 - 11)); 28 | a = _mm_xor_si128(a, _mm_set_epi32(0, 0, 0, round_key as _)); 29 | a = _mm_xor_si128(a, _mm_andnot_si128(b, c)); 30 | b = _mm_xor_si128(b, _mm_andnot_si128(c, a)); 31 | c = _mm_xor_si128(c, _mm_andnot_si128(a, b)); 32 | b = _mm_or_si128(_mm_slli_epi32(b, 1), _mm_srli_epi32(b, 32 - 1)); 33 | c = _mm_shuffle_epi8(c, rho_east_2); 34 | } 35 | _mm_storeu_si128(st.as_mut_ptr().add(0 * 4) as *mut _, a); 36 | _mm_storeu_si128(st.as_mut_ptr().add(4 * 4) as *mut _, b); 37 | _mm_storeu_si128(st.as_mut_ptr().add(8 * 4) as *mut _, c); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/xoodoo/mod.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use zeroize::Zeroize; 3 | 4 | #[cfg(not(target_arch = "x86_64"))] 5 | mod impl_portable; 6 | #[cfg(target_arch = "x86_64")] 7 | mod impl_x86_64; 8 | 9 | const ROUND_KEYS: [u32; 12] = [ 10 | 0x058, 0x038, 0x3c0, 0x0d0, 0x120, 0x014, 0x060, 0x02c, 0x380, 0x0f0, 0x1a0, 0x012, 11 | ]; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct Xoodoo { 15 | st: [u8; 48], 16 | } 17 | 18 | impl Default for Xoodoo { 19 | fn default() -> Self { 20 | Self { st: [0u8; 48] } 21 | } 22 | } 23 | 24 | impl Xoodoo { 25 | #[inline(always)] 26 | fn bytes_view(&self) -> &[u8] { 27 | &self.st 28 | } 29 | 30 | #[inline(always)] 31 | fn bytes_view_mut(&mut self) -> &mut [u8] { 32 | &mut self.st 33 | } 34 | 35 | #[inline(always)] 36 | #[allow(dead_code)] 37 | fn to_words(&self) -> [u32; 12] { 38 | let mut st_words = [0u32; 12]; 39 | for (st_word, bytes) in st_words.iter_mut().zip(self.st.chunks_exact(4)) { 40 | *st_word = u32::from_le_bytes(bytes.try_into().unwrap()); 41 | } 42 | st_words 43 | } 44 | 45 | #[inline(always)] 46 | #[allow(dead_code)] 47 | fn init_from_words(&mut self, st_words: [u32; 12]) { 48 | for (bytes, st_word) in self.st.chunks_exact_mut(4).zip(st_words.iter()) { 49 | bytes.copy_from_slice(&st_word.to_le_bytes()); 50 | } 51 | } 52 | 53 | #[cfg(not(target_endian = "little"))] 54 | #[inline(always)] 55 | fn endian_swap(&mut self) { 56 | let mut st_words = self.to_words(); 57 | for st_word in &mut st_words { 58 | *st_word = (*st_word).to_le() 59 | } 60 | self.from_words(&st_words); 61 | } 62 | 63 | #[cfg(target_endian = "little")] 64 | #[inline(always)] 65 | fn endian_swap(&mut self) { 66 | _ = self 67 | } 68 | 69 | #[inline] 70 | pub fn from_bytes(bytes: [u8; 48]) -> Self { 71 | let mut st = Xoodoo::default(); 72 | let st_bytes = st.bytes_view_mut(); 73 | st_bytes.copy_from_slice(&bytes); 74 | st 75 | } 76 | 77 | #[inline(always)] 78 | pub fn bytes(&self, out: &mut [u8; 48]) { 79 | let st_bytes = self.bytes_view(); 80 | out.copy_from_slice(st_bytes); 81 | } 82 | 83 | #[inline(always)] 84 | pub fn add_byte(&mut self, byte: u8, offset: usize) { 85 | self.endian_swap(); 86 | let st_bytes = self.bytes_view_mut(); 87 | st_bytes[offset] ^= byte; 88 | self.endian_swap(); 89 | } 90 | 91 | #[inline(always)] 92 | pub fn add_bytes(&mut self, bytes: &[u8]) { 93 | self.endian_swap(); 94 | let st_bytes = self.bytes_view_mut(); 95 | for (st_byte, byte) in st_bytes.iter_mut().zip(bytes) { 96 | *st_byte ^= byte; 97 | } 98 | self.endian_swap(); 99 | } 100 | 101 | #[inline(always)] 102 | pub fn extract_bytes(&mut self, out: &mut [u8]) { 103 | self.endian_swap(); 104 | let st_bytes = self.bytes_view(); 105 | out.copy_from_slice(&st_bytes[..out.len()]); 106 | self.endian_swap(); 107 | } 108 | } 109 | 110 | impl Drop for Xoodoo { 111 | fn drop(&mut self) { 112 | self.st.zeroize() 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/xoodyak/any.rs: -------------------------------------------------------------------------------- 1 | use super::internal::{Mode, Phase}; 2 | use super::*; 3 | 4 | #[derive(Clone, Debug)] 5 | pub enum XoodyakAny { 6 | Hash(XoodyakHash), 7 | Keyed(XoodyakKeyed), 8 | } 9 | 10 | impl internal::XoodyakCommon for XoodyakAny { 11 | fn state(&mut self) -> &mut Xoodoo { 12 | match self { 13 | XoodyakAny::Hash(x) => x.state(), 14 | XoodyakAny::Keyed(x) => x.state(), 15 | } 16 | } 17 | 18 | fn mode(&self) -> Mode { 19 | match self { 20 | XoodyakAny::Hash(x) => x.mode(), 21 | XoodyakAny::Keyed(x) => x.mode(), 22 | } 23 | } 24 | 25 | fn phase(&self) -> Phase { 26 | match self { 27 | XoodyakAny::Hash(x) => x.phase(), 28 | XoodyakAny::Keyed(x) => x.phase(), 29 | } 30 | } 31 | 32 | fn set_phase(&mut self, phase: Phase) { 33 | match self { 34 | XoodyakAny::Hash(x) => x.set_phase(phase), 35 | XoodyakAny::Keyed(x) => x.set_phase(phase), 36 | } 37 | } 38 | 39 | fn absorb_rate(&self) -> usize { 40 | match self { 41 | XoodyakAny::Hash(x) => x.absorb_rate(), 42 | XoodyakAny::Keyed(x) => x.absorb_rate(), 43 | } 44 | } 45 | 46 | fn squeeze_rate(&self) -> usize { 47 | match self { 48 | XoodyakAny::Hash(x) => x.squeeze_rate(), 49 | XoodyakAny::Keyed(x) => x.squeeze_rate(), 50 | } 51 | } 52 | } 53 | 54 | impl XoodyakAny { 55 | #[inline] 56 | fn keyed(&mut self) -> Result<&mut XoodyakKeyed, Error> { 57 | match self { 58 | XoodyakAny::Hash(_) => Err(Error::KeyRequired), 59 | XoodyakAny::Keyed(ref mut x) => Ok(x), 60 | } 61 | } 62 | 63 | #[inline] 64 | pub fn absorb_key_and_nonce( 65 | &mut self, 66 | key: &[u8], 67 | key_id: Option<&[u8]>, 68 | nonce: Option<&[u8]>, 69 | counter: Option<&[u8]>, 70 | ) -> Result<(), Error> { 71 | self.keyed()? 72 | .absorb_key_and_nonce(key, key_id, nonce, counter) 73 | } 74 | 75 | #[inline] 76 | pub fn ratchet(&mut self) -> Result<(), Error> { 77 | Ok(self.keyed()?.ratchet()) 78 | } 79 | 80 | #[inline] 81 | pub fn encrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 82 | self.keyed()?.encrypt(out, bin) 83 | } 84 | 85 | #[inline] 86 | pub fn decrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 87 | self.keyed()?.decrypt(out, bin) 88 | } 89 | 90 | #[inline] 91 | pub fn encrypt_in_place(&mut self, in_out: &mut [u8]) -> Result<(), Error> { 92 | Ok(self.keyed()?.encrypt_in_place(in_out)) 93 | } 94 | 95 | #[inline] 96 | pub fn decrypt_in_place(&mut self, in_out: &mut [u8]) -> Result<(), Error> { 97 | Ok(self.keyed()?.decrypt_in_place(in_out)) 98 | } 99 | 100 | #[inline] 101 | pub fn aead_encrypt_detached( 102 | &mut self, 103 | out: &mut [u8], 104 | bin: Option<&[u8]>, 105 | ) -> Result { 106 | self.keyed()?.aead_encrypt_detached(out, bin) 107 | } 108 | 109 | #[inline] 110 | pub fn aead_encrypt(&mut self, out: &mut [u8], bin: Option<&[u8]>) -> Result<(), Error> { 111 | self.keyed()?.aead_encrypt(out, bin) 112 | } 113 | 114 | #[inline] 115 | pub fn aead_decrypt_detached( 116 | &mut self, 117 | out: &mut [u8], 118 | auth_tag: &Tag, 119 | bin: Option<&[u8]>, 120 | ) -> Result<(), Error> { 121 | self.keyed()?.aead_decrypt_detached(out, auth_tag, bin) 122 | } 123 | 124 | #[inline] 125 | pub fn aead_decrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 126 | self.keyed()?.aead_decrypt(out, bin) 127 | } 128 | 129 | #[inline] 130 | pub fn aead_encrypt_in_place_detached(&mut self, in_out: &mut [u8]) -> Result { 131 | Ok(self.keyed()?.aead_encrypt_in_place_detached(in_out)) 132 | } 133 | 134 | #[inline] 135 | pub fn aead_encrypt_in_place(&mut self, in_out: &mut [u8]) -> Result<(), Error> { 136 | self.keyed()?.aead_encrypt_in_place(in_out) 137 | } 138 | 139 | #[inline] 140 | pub fn aead_decrypt_in_place_detached( 141 | &mut self, 142 | in_out: &mut [u8], 143 | auth_tag: &Tag, 144 | ) -> Result<(), Error> { 145 | self.keyed()? 146 | .aead_decrypt_in_place_detached(in_out, auth_tag) 147 | } 148 | 149 | #[inline] 150 | pub fn aead_decrypt_in_place<'t>( 151 | &mut self, 152 | in_out: &'t mut [u8], 153 | ) -> Result<&'t mut [u8], Error> { 154 | self.keyed()?.aead_decrypt_in_place(in_out) 155 | } 156 | 157 | #[cfg(feature = "std")] 158 | #[inline] 159 | pub fn encrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 160 | self.keyed()?.encrypt_to_vec(bin) 161 | } 162 | 163 | #[cfg(feature = "std")] 164 | #[inline] 165 | pub fn decrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 166 | self.keyed()?.decrypt_to_vec(bin) 167 | } 168 | 169 | #[cfg(feature = "std")] 170 | #[inline] 171 | pub fn aead_encrypt_to_vec_detached( 172 | &mut self, 173 | 174 | bin: Option<&[u8]>, 175 | ) -> Result<(Vec, Tag), Error> { 176 | self.keyed()?.aead_encrypt_to_vec_detached(bin) 177 | } 178 | 179 | #[cfg(feature = "std")] 180 | #[inline] 181 | pub fn aead_encrypt_to_vec(&mut self, bin: Option<&[u8]>) -> Result, Error> { 182 | self.keyed()?.aead_encrypt_to_vec(bin) 183 | } 184 | 185 | #[cfg(feature = "std")] 186 | #[inline] 187 | pub fn aead_encrypt_in_place_to_vec(&mut self, in_out: Vec) -> Result, Error> { 188 | Ok(self.keyed()?.aead_encrypt_in_place_to_vec(in_out)) 189 | } 190 | 191 | #[cfg(feature = "std")] 192 | #[inline] 193 | pub fn aead_decrypt_to_vec_detached( 194 | &mut self, 195 | auth_tag: Tag, 196 | 197 | bin: Option<&[u8]>, 198 | ) -> Result, Error> { 199 | self.keyed()?.aead_decrypt_to_vec_detached(auth_tag, bin) 200 | } 201 | 202 | #[cfg(feature = "std")] 203 | #[inline] 204 | pub fn aead_decrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 205 | self.keyed()?.aead_decrypt_to_vec(bin) 206 | } 207 | 208 | #[cfg(feature = "std")] 209 | #[inline] 210 | pub fn aead_decrypt_in_place_to_vec(&mut self, in_out: Vec) -> Result, Error> { 211 | self.keyed()?.aead_decrypt_in_place_to_vec(in_out) 212 | } 213 | } 214 | 215 | impl XoodyakCommon for XoodyakAny {} 216 | -------------------------------------------------------------------------------- /src/xoodyak/hash.rs: -------------------------------------------------------------------------------- 1 | use super::internal::{Mode, Phase}; 2 | use super::*; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct XoodyakHash { 6 | state: Xoodoo, 7 | phase: Phase, 8 | } 9 | 10 | impl XoodyakHash { 11 | pub fn new() -> Self { 12 | XoodyakHash { 13 | state: Xoodoo::default(), 14 | phase: Phase::Up, 15 | } 16 | } 17 | } 18 | 19 | impl Default for XoodyakHash { 20 | #[inline] 21 | fn default() -> Self { 22 | XoodyakHash::new() 23 | } 24 | } 25 | 26 | impl internal::XoodyakCommon for XoodyakHash { 27 | #[inline(always)] 28 | fn state(&mut self) -> &mut Xoodoo { 29 | &mut self.state 30 | } 31 | 32 | #[inline(always)] 33 | fn mode(&self) -> Mode { 34 | Mode::Hash 35 | } 36 | 37 | #[inline(always)] 38 | fn phase(&self) -> Phase { 39 | self.phase 40 | } 41 | 42 | #[inline(always)] 43 | fn set_phase(&mut self, phase: Phase) { 44 | self.phase = phase 45 | } 46 | 47 | #[inline(always)] 48 | fn absorb_rate(&self) -> usize { 49 | HASH_ABSORB_RATE 50 | } 51 | 52 | #[inline(always)] 53 | fn squeeze_rate(&self) -> usize { 54 | HASH_SQUEEZE_RATE 55 | } 56 | } 57 | 58 | impl XoodyakCommon for XoodyakHash {} 59 | -------------------------------------------------------------------------------- /src/xoodyak/keyed.rs: -------------------------------------------------------------------------------- 1 | use super::internal::XoodyakCommon as _; 2 | use super::internal::{Mode, Phase}; 3 | use super::*; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct XoodyakKeyed { 7 | state: Xoodoo, 8 | mode: Mode, 9 | phase: Phase, 10 | } 11 | 12 | impl internal::XoodyakCommon for XoodyakKeyed { 13 | #[inline(always)] 14 | fn state(&mut self) -> &mut Xoodoo { 15 | &mut self.state 16 | } 17 | 18 | #[inline(always)] 19 | fn mode(&self) -> Mode { 20 | self.mode 21 | } 22 | 23 | #[inline(always)] 24 | fn phase(&self) -> Phase { 25 | self.phase 26 | } 27 | 28 | #[inline(always)] 29 | fn set_phase(&mut self, phase: Phase) { 30 | self.phase = phase 31 | } 32 | 33 | #[inline(always)] 34 | fn absorb_rate(&self) -> usize { 35 | KEYED_ABSORB_RATE 36 | } 37 | 38 | #[inline(always)] 39 | fn squeeze_rate(&self) -> usize { 40 | KEYED_SQUEEZE_RATE 41 | } 42 | } 43 | 44 | impl XoodyakCommon for XoodyakKeyed {} 45 | 46 | impl XoodyakKeyed { 47 | pub fn new( 48 | key: &[u8], 49 | nonce: Option<&[u8]>, 50 | key_id: Option<&[u8]>, 51 | counter: Option<&[u8]>, 52 | ) -> Result { 53 | let mut xoodyak = XoodyakKeyed { 54 | state: Xoodoo::default(), 55 | phase: Phase::Up, 56 | mode: Mode::Keyed, 57 | }; 58 | xoodyak.absorb_key_and_nonce(key, key_id, nonce, counter)?; 59 | Ok(xoodyak) 60 | } 61 | 62 | pub fn absorb_key_and_nonce( 63 | &mut self, 64 | key: &[u8], 65 | key_id: Option<&[u8]>, 66 | nonce: Option<&[u8]>, 67 | counter: Option<&[u8]>, 68 | ) -> Result<(), Error> { 69 | let nonce = nonce.unwrap_or_default(); 70 | 71 | let key_id_len = key_id.unwrap_or_default().len(); 72 | let nonce_len = nonce.len(); 73 | if key.len() + 1 + key_id_len + nonce_len > KEYED_ABSORB_RATE { 74 | return Err(Error::InvalidParameterLength); 75 | } 76 | let mut iv = [0u8; KEYED_ABSORB_RATE]; 77 | let key_len = key.len(); 78 | iv[..key_len].copy_from_slice(key); 79 | let mut iv_len = key_len; 80 | 81 | let t = key_id.unwrap_or(nonce); 82 | let t_len = t.len(); 83 | iv[iv_len..iv_len + t_len].copy_from_slice(t); 84 | iv_len += t_len; 85 | iv[iv_len] = t_len as u8; 86 | iv_len += 1; 87 | self.absorb_any(&iv[..iv_len], KEYED_ABSORB_RATE, 0x02); 88 | 89 | if key_id.is_some() { 90 | self.absorb_any(nonce, KEYED_ABSORB_RATE, 0x00); 91 | } 92 | if let Some(counter) = counter { 93 | self.absorb_any(counter, 1, 0x00) 94 | } 95 | Ok(()) 96 | } 97 | 98 | pub fn ratchet(&mut self) { 99 | debug_assert_eq!(self.mode(), Mode::Keyed); 100 | let mut rolled_key = [0u8; RATCHET_RATE]; 101 | self.squeeze_any(&mut rolled_key, 0x10); 102 | self.absorb_any(&rolled_key, RATCHET_RATE, 0x00); 103 | } 104 | 105 | pub fn encrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 106 | debug_assert_eq!(self.mode(), Mode::Keyed); 107 | if out.len() < bin.len() { 108 | return Err(Error::InvalidBufferLength); 109 | } 110 | let mut cu = 0x80; 111 | for (out_chunk, chunk) in out 112 | .chunks_mut(KEYED_SQUEEZE_RATE) 113 | .zip(bin.chunks(KEYED_SQUEEZE_RATE)) 114 | { 115 | self.up(Some(out_chunk), cu); 116 | cu = 0x00; 117 | self.down(Some(chunk), 0x00); 118 | for (out_chunk_byte, chunk_byte) in out_chunk.iter_mut().zip(chunk) { 119 | *out_chunk_byte ^= *chunk_byte; 120 | } 121 | } 122 | Ok(()) 123 | } 124 | 125 | pub fn decrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 126 | debug_assert_eq!(self.mode(), Mode::Keyed); 127 | if out.len() < bin.len() { 128 | return Err(Error::InvalidBufferLength); 129 | } 130 | let mut cu = 0x80; 131 | for (out_chunk, chunk) in out 132 | .chunks_mut(KEYED_SQUEEZE_RATE) 133 | .zip(bin.chunks(KEYED_SQUEEZE_RATE)) 134 | { 135 | self.up(Some(out_chunk), cu); 136 | cu = 0x00; 137 | for (out_chunk_byte, chunk_byte) in out_chunk.iter_mut().zip(chunk) { 138 | *out_chunk_byte ^= *chunk_byte; 139 | } 140 | self.down(Some(out_chunk), 0x00); 141 | } 142 | Ok(()) 143 | } 144 | 145 | pub fn encrypt_in_place(&mut self, in_out: &mut [u8]) { 146 | debug_assert_eq!(self.mode(), Mode::Keyed); 147 | let mut tmp = [0u8; KEYED_SQUEEZE_RATE]; 148 | let mut cu = 0x80; 149 | for in_out_chunk in in_out.chunks_mut(KEYED_SQUEEZE_RATE) { 150 | self.up(Some(&mut tmp), cu); 151 | cu = 0x00; 152 | self.down(Some(in_out_chunk), 0x00); 153 | for (in_out_chunk_byte, tmp_byte) in in_out_chunk.iter_mut().zip(&tmp) { 154 | *in_out_chunk_byte ^= *tmp_byte; 155 | } 156 | } 157 | } 158 | 159 | pub fn decrypt_in_place(&mut self, in_out: &mut [u8]) { 160 | debug_assert_eq!(self.mode(), Mode::Keyed); 161 | let mut tmp = [0u8; KEYED_SQUEEZE_RATE]; 162 | let mut cu = 0x80; 163 | for in_out_chunk in in_out.chunks_mut(KEYED_SQUEEZE_RATE) { 164 | self.up(Some(&mut tmp), cu); 165 | cu = 0x00; 166 | for (in_out_chunk_byte, tmp_byte) in in_out_chunk.iter_mut().zip(&tmp) { 167 | *in_out_chunk_byte ^= *tmp_byte; 168 | } 169 | self.down(Some(in_out_chunk), 0x00); 170 | } 171 | } 172 | 173 | pub fn aead_encrypt_detached( 174 | &mut self, 175 | out: &mut [u8], 176 | bin: Option<&[u8]>, 177 | ) -> Result { 178 | if out.len() < bin.unwrap_or_default().len() { 179 | return Err(Error::InvalidBufferLength); 180 | } 181 | self.encrypt(out, bin.unwrap_or_default())?; 182 | let mut auth_tag = Tag::default(); 183 | self.squeeze(auth_tag.inner_mut()); 184 | Ok(auth_tag) 185 | } 186 | 187 | pub fn aead_encrypt(&mut self, out: &mut [u8], bin: Option<&[u8]>) -> Result<(), Error> { 188 | let ct_len = bin.unwrap_or_default().len(); 189 | if out.len() < ct_len + AUTH_TAG_BYTES { 190 | return Err(Error::InvalidBufferLength); 191 | } 192 | let auth_tag = self.aead_encrypt_detached(out, bin)?; 193 | out[ct_len..ct_len + AUTH_TAG_BYTES].copy_from_slice(auth_tag.as_ref()); 194 | Ok(()) 195 | } 196 | 197 | pub fn aead_decrypt_detached( 198 | &mut self, 199 | out: &mut [u8], 200 | auth_tag: &Tag, 201 | bin: Option<&[u8]>, 202 | ) -> Result<(), Error> { 203 | if out.len() < bin.unwrap_or_default().len() { 204 | return Err(Error::InvalidBufferLength); 205 | } 206 | self.decrypt(out, bin.unwrap_or_default())?; 207 | let mut computed_tag = Tag::default(); 208 | self.squeeze(computed_tag.inner_mut()); 209 | if computed_tag == *auth_tag { 210 | return Ok(()); 211 | } 212 | out.iter_mut().for_each(|x| *x = 0); 213 | Err(Error::TagMismatch) 214 | } 215 | 216 | pub fn aead_decrypt(&mut self, out: &mut [u8], bin: &[u8]) -> Result<(), Error> { 217 | let ct_len = bin 218 | .len() 219 | .checked_sub(AUTH_TAG_BYTES) 220 | .ok_or(Error::InvalidBufferLength)?; 221 | if bin.len() < ct_len { 222 | return Err(Error::InvalidBufferLength); 223 | } 224 | let mut auth_tag_bin = [0u8; AUTH_TAG_BYTES]; 225 | auth_tag_bin.copy_from_slice(&bin[ct_len..]); 226 | let auth_tag = Tag::from(auth_tag_bin); 227 | let ct = &bin[..ct_len]; 228 | self.aead_decrypt_detached(out, &auth_tag, Some(ct))?; 229 | Ok(()) 230 | } 231 | 232 | pub fn aead_encrypt_in_place_detached(&mut self, in_out: &mut [u8]) -> Tag { 233 | self.encrypt_in_place(in_out); 234 | let mut auth_tag = Tag::default(); 235 | self.squeeze(auth_tag.inner_mut()); 236 | auth_tag 237 | } 238 | 239 | pub fn aead_encrypt_in_place(&mut self, in_out: &mut [u8]) -> Result<(), Error> { 240 | let ct_len = in_out 241 | .len() 242 | .checked_sub(AUTH_TAG_BYTES) 243 | .ok_or(Error::InvalidBufferLength)?; 244 | let auth_tag = self.aead_encrypt_in_place_detached(&mut in_out[..ct_len]); 245 | in_out[ct_len..].copy_from_slice(auth_tag.as_ref()); 246 | Ok(()) 247 | } 248 | 249 | pub fn aead_decrypt_in_place_detached( 250 | &mut self, 251 | in_out: &mut [u8], 252 | auth_tag: &Tag, 253 | ) -> Result<(), Error> { 254 | self.decrypt_in_place(in_out); 255 | let mut computed_tag = Tag::default(); 256 | self.squeeze(computed_tag.inner_mut()); 257 | if computed_tag == *auth_tag { 258 | return Ok(()); 259 | } 260 | in_out.iter_mut().for_each(|x| *x = 0); 261 | Err(Error::TagMismatch) 262 | } 263 | 264 | pub fn aead_decrypt_in_place<'t>( 265 | &mut self, 266 | in_out: &'t mut [u8], 267 | ) -> Result<&'t mut [u8], Error> { 268 | let ct_len = in_out 269 | .len() 270 | .checked_sub(AUTH_TAG_BYTES) 271 | .ok_or(Error::InvalidBufferLength)?; 272 | let mut auth_tag_bin = [0u8; AUTH_TAG_BYTES]; 273 | auth_tag_bin.copy_from_slice(&in_out[ct_len..]); 274 | let ct = &mut in_out[..ct_len]; 275 | let auth_tag = Tag::from(auth_tag_bin); 276 | self.aead_decrypt_in_place_detached(ct, &auth_tag)?; 277 | Ok(ct) 278 | } 279 | 280 | #[cfg(feature = "std")] 281 | pub fn encrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 282 | let mut out = vec![0u8; bin.len()]; 283 | self.encrypt(&mut out, bin)?; 284 | Ok(out) 285 | } 286 | 287 | #[cfg(feature = "std")] 288 | pub fn decrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 289 | let mut out = vec![0u8; bin.len()]; 290 | self.decrypt(&mut out, bin)?; 291 | Ok(out) 292 | } 293 | 294 | #[cfg(feature = "std")] 295 | pub fn aead_encrypt_to_vec_detached( 296 | &mut self, 297 | 298 | bin: Option<&[u8]>, 299 | ) -> Result<(Vec, Tag), Error> { 300 | let mut out = vec![0u8; bin.unwrap_or_default().len()]; 301 | let auth_tag = self.aead_encrypt_detached(&mut out, bin)?; 302 | Ok((out, auth_tag)) 303 | } 304 | 305 | #[cfg(feature = "std")] 306 | pub fn aead_encrypt_to_vec(&mut self, bin: Option<&[u8]>) -> Result, Error> { 307 | let mut out = vec![0u8; bin.unwrap_or_default().len() + AUTH_TAG_BYTES]; 308 | self.aead_encrypt(&mut out, bin)?; 309 | Ok(out) 310 | } 311 | 312 | #[cfg(feature = "std")] 313 | pub fn aead_encrypt_in_place_to_vec(&mut self, mut in_out: Vec) -> Vec { 314 | let ct_len = in_out.len(); 315 | in_out.resize_with(ct_len + AUTH_TAG_BYTES, || 0); 316 | self.aead_encrypt_in_place(&mut in_out).unwrap(); 317 | in_out 318 | } 319 | 320 | #[cfg(feature = "std")] 321 | pub fn aead_decrypt_to_vec_detached( 322 | &mut self, 323 | auth_tag: Tag, 324 | bin: Option<&[u8]>, 325 | ) -> Result, Error> { 326 | let mut out = vec![0u8; bin.unwrap_or_default().len()]; 327 | self.aead_decrypt_detached(&mut out, &auth_tag, bin)?; 328 | Ok(out) 329 | } 330 | 331 | #[cfg(feature = "std")] 332 | pub fn aead_decrypt_to_vec(&mut self, bin: &[u8]) -> Result, Error> { 333 | let ct_len = bin 334 | .len() 335 | .checked_sub(AUTH_TAG_BYTES) 336 | .ok_or(Error::InvalidBufferLength)?; 337 | let mut out = vec![0u8; ct_len]; 338 | self.aead_decrypt(&mut out, bin)?; 339 | Ok(out) 340 | } 341 | 342 | #[cfg(feature = "std")] 343 | pub fn aead_decrypt_in_place_to_vec(&mut self, mut in_out: Vec) -> Result, Error> { 344 | let ct_len = in_out 345 | .len() 346 | .checked_sub(AUTH_TAG_BYTES) 347 | .ok_or(Error::InvalidBufferLength)?; 348 | self.aead_decrypt_in_place(&mut in_out)?; 349 | in_out.truncate(ct_len); 350 | Ok(in_out) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/xoodyak/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::unit_arg)] 2 | mod any; 3 | mod hash; 4 | mod keyed; 5 | mod tag; 6 | 7 | pub use any::*; 8 | pub use hash::*; 9 | pub use keyed::*; 10 | pub use tag::*; 11 | 12 | use crate::error::*; 13 | use crate::xoodoo::*; 14 | 15 | pub(crate) const HASH_ABSORB_RATE: usize = 16; 16 | pub(crate) const HASH_SQUEEZE_RATE: usize = 16; 17 | pub(crate) const KEYED_ABSORB_RATE: usize = 44; 18 | pub(crate) const KEYED_SQUEEZE_RATE: usize = 24; 19 | pub(crate) const RATCHET_RATE: usize = 16; 20 | 21 | mod internal { 22 | use super::*; 23 | 24 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 25 | pub enum Mode { 26 | Hash, 27 | Keyed, 28 | } 29 | 30 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 31 | pub enum Phase { 32 | Up, 33 | Down, 34 | } 35 | 36 | pub trait XoodyakCommon { 37 | fn state(&mut self) -> &mut Xoodoo; 38 | fn mode(&self) -> Mode; 39 | fn phase(&self) -> Phase; 40 | fn set_phase(&mut self, phase: Phase); 41 | fn absorb_rate(&self) -> usize; 42 | fn squeeze_rate(&self) -> usize; 43 | 44 | #[inline(always)] 45 | fn permute(&mut self) { 46 | self.state().permute() 47 | } 48 | 49 | #[inline(always)] 50 | fn add_byte(&mut self, byte: u8, offset: usize) { 51 | self.state().add_byte(byte, offset); 52 | } 53 | 54 | #[inline(always)] 55 | fn add_bytes(&mut self, bytes: &[u8]) { 56 | self.state().add_bytes(bytes); 57 | } 58 | 59 | #[inline(always)] 60 | fn extract_bytes(&mut self, out: &mut [u8]) { 61 | self.state().extract_bytes(out); 62 | } 63 | 64 | #[inline(always)] 65 | fn up(&mut self, out: Option<&mut [u8]>, cu: u8) { 66 | debug_assert!(out.as_ref().map(|x| x.len()).unwrap_or(0) <= self.squeeze_rate()); 67 | self.set_phase(Phase::Up); 68 | if self.mode() != Mode::Hash { 69 | self.add_byte(cu, 47); 70 | } 71 | self.permute(); 72 | if let Some(out) = out { 73 | self.extract_bytes(out); 74 | } 75 | } 76 | 77 | #[inline(always)] 78 | fn down(&mut self, bin: Option<&[u8]>, cd: u8) { 79 | debug_assert!(bin.as_ref().map(|x| x.len()).unwrap_or(0) <= self.absorb_rate()); 80 | self.set_phase(Phase::Down); 81 | if let Some(bin) = bin { 82 | self.add_bytes(bin); 83 | self.add_byte(0x01, bin.len()); 84 | } else { 85 | self.add_byte(0x01, 0); 86 | } 87 | if self.mode() == Mode::Hash { 88 | self.add_byte(cd & 0x01, 47); 89 | } else { 90 | self.add_byte(cd, 47); 91 | } 92 | } 93 | 94 | #[inline] 95 | fn absorb_any(&mut self, bin: &[u8], rate: usize, cd: u8) { 96 | let mut chunks_it = bin.chunks(rate); 97 | if self.phase() != Phase::Up { 98 | self.up(None, 0x00) 99 | } 100 | self.down(chunks_it.next(), cd); 101 | for chunk in chunks_it { 102 | self.up(None, 0x00); 103 | self.down(Some(chunk), 0x00); 104 | } 105 | } 106 | 107 | #[inline] 108 | fn squeeze_any(&mut self, out: &mut [u8], cu: u8) { 109 | let mut chunks_it = out.chunks_mut(self.squeeze_rate()); 110 | self.up(chunks_it.next(), cu); 111 | for chunk in chunks_it { 112 | self.down(None, 0x00); 113 | self.up(Some(chunk), 0x00); 114 | } 115 | } 116 | } 117 | } 118 | 119 | pub trait XoodyakCommon: internal::XoodyakCommon { 120 | #[inline(always)] 121 | fn absorb(&mut self, bin: &[u8]) { 122 | self.absorb_any(bin, self.absorb_rate(), 0x03); 123 | } 124 | 125 | #[inline] 126 | fn absorb_more(&mut self, bin: &[u8], rate: usize) { 127 | for chunk in bin.chunks(rate) { 128 | self.up(None, 0x00); 129 | self.down(Some(chunk), 0x00); 130 | } 131 | } 132 | 133 | #[inline(always)] 134 | fn squeeze(&mut self, out: &mut [u8]) { 135 | self.squeeze_any(out, 0x40); 136 | } 137 | 138 | #[inline(always)] 139 | fn squeeze_key(&mut self, out: &mut [u8]) { 140 | self.squeeze_any(out, 0x20); 141 | } 142 | 143 | #[inline] 144 | fn squeeze_more(&mut self, out: &mut [u8]) { 145 | for chunk in out.chunks_mut(self.squeeze_rate()) { 146 | self.down(None, 0x00); 147 | self.up(Some(chunk), 0x00); 148 | } 149 | } 150 | 151 | #[cfg(feature = "std")] 152 | fn squeeze_to_vec(&mut self, len: usize) -> Vec { 153 | let mut out = vec![0u8; len]; 154 | self.squeeze(&mut out); 155 | out 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/xoodyak/tag.rs: -------------------------------------------------------------------------------- 1 | use zeroize::Zeroize; 2 | 3 | use crate::error::Error; 4 | 5 | pub const AUTH_TAG_BYTES: usize = 16; 6 | 7 | #[derive(Clone, Debug, Default, Eq)] 8 | pub struct Tag([u8; AUTH_TAG_BYTES]); 9 | 10 | impl Tag { 11 | #[inline(always)] 12 | pub(crate) fn inner_mut(&mut self) -> &mut [u8; AUTH_TAG_BYTES] { 13 | &mut self.0 14 | } 15 | 16 | #[inline] 17 | pub fn verify(&self, bin: [u8; AUTH_TAG_BYTES]) -> Result<(), Error> { 18 | if &Tag::from(bin) == self { 19 | Ok(()) 20 | } else { 21 | Err(Error::TagMismatch) 22 | } 23 | } 24 | } 25 | 26 | impl Drop for Tag { 27 | #[inline] 28 | fn drop(&mut self) { 29 | self.0.zeroize(); 30 | } 31 | } 32 | 33 | impl PartialEq for Tag { 34 | fn eq(&self, other: &Tag) -> bool { 35 | other 36 | .0 37 | .iter() 38 | .zip(self.0.iter()) 39 | .fold(0, |c, (a, b)| c | (a ^ b)) 40 | == 0 41 | } 42 | } 43 | 44 | impl AsRef<[u8]> for Tag { 45 | #[inline(always)] 46 | fn as_ref(&self) -> &[u8] { 47 | &self.0 48 | } 49 | } 50 | 51 | impl From for [u8; AUTH_TAG_BYTES] { 52 | #[inline(always)] 53 | fn from(tag: Tag) -> Self { 54 | tag.0 55 | } 56 | } 57 | 58 | impl From<[u8; AUTH_TAG_BYTES]> for Tag { 59 | #[inline(always)] 60 | fn from(bin: [u8; AUTH_TAG_BYTES]) -> Self { 61 | Tag(bin) 62 | } 63 | } 64 | --------------------------------------------------------------------------------