Skip to main content

crypto/ascon/
ascon_cxof128.rs

1use super::*;
2use crate::Xof;
3
4/// Ascon-CXOF128 initialization vector (NIST SP 800-232).
5const IV: u64 = 0x0000_0800_00cc_0004;
6
7/// Ascon-CXOF128 customizable extensible-output function (NIST SP 800-232 ยง5.3).
8///
9/// Extends Ascon-XOF128 with a customization string `Z` (up to 256 bytes).
10/// The customization is absorbed before the message, so `new_with_customization(b"")`
11/// produces different output than [`AsconXof128`](super::AsconXof128) on the same input.
12///
13/// Implements the [`Xof`] trait.
14///
15/// # Panics
16///
17/// Panics if the customization string exceeds 256 bytes.
18///
19/// # Incremental API
20///
21/// ```ignore
22/// use crypto::{ascon::AsconCxof128, Xof};
23///
24/// let mut cxof = AsconCxof128::new_with_customization(b"my-app-v1");
25/// cxof.absorb(b"hello world");
26/// let mut out = [0u8; 32];
27/// cxof.squeeze(&mut out);
28/// ```
29#[derive(Clone)]
30#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
31pub struct AsconCxof128 {
32    state: State,
33    buf: [u8; 8],
34    buf_len: usize,
35    squeezing: bool,
36    squeeze_pos: usize,
37    current_block: [u8; 8],
38}
39
40impl AsconCxof128 {
41    /// Creates a new Ascon-CXOF128 with an empty customization string.
42    #[inline]
43    pub fn new() -> Self {
44        Self::new_with_customization(&[])
45    }
46
47    /// Creates a new Ascon-CXOF128 with the given customization string.
48    ///
49    /// # Panics
50    ///
51    /// Panics if `z.len() > 256`.
52    pub fn new_with_customization(z: &[u8]) -> Self {
53        assert!(z.len() <= 256, "CXOF customization string must be at most 256 bytes");
54        let mut state = State::init_hash(IV);
55        p12(&mut state);
56
57        // Absorb the bit-length of Z as a 64-bit little-endian integer
58        let z_bits = (z.len() as u64).wrapping_mul(8);
59        state.absorb_block(&z_bits.to_le_bytes());
60        p12(&mut state);
61
62        // Absorb Z itself
63        if !z.is_empty() {
64            let mut chunks = z.chunks_exact(8);
65            for chunk in &mut chunks {
66                state.absorb_block(chunk);
67                p12(&mut state);
68            }
69            let remainder = chunks.remainder();
70            if !remainder.is_empty() {
71                let mut padded = [0u8; 8];
72                padded[..remainder.len()].copy_from_slice(remainder);
73                padded[remainder.len()] = 0x01;
74                state.absorb_block(&padded);
75                p12(&mut state);
76            } else {
77                // Z was a multiple of 8 bytes - add a pad block
78                let mut padded = [0u8; 8];
79                padded[0] = 0x01;
80                state.absorb_block(&padded);
81                p12(&mut state);
82            }
83        } else {
84            // Empty Z: add a pad block [0x01, 0x00, ...]
85            let mut padded = [0u8; 8];
86            padded[0] = 0x01;
87            state.absorb_block(&padded);
88            p12(&mut state);
89        }
90
91        AsconCxof128 {
92            state,
93            buf: [0u8; 8],
94            buf_len: 0,
95            squeezing: false,
96            squeeze_pos: 0,
97            current_block: [0u8; 8],
98        }
99    }
100}
101
102impl Xof for AsconCxof128 {
103    fn absorb(&mut self, mut data: &[u8]) {
104        assert!(!self.squeezing, "absorb cannot be called after squeeze");
105
106        if self.buf_len > 0 {
107            let to_fill = (8 - self.buf_len).min(data.len());
108            self.buf[self.buf_len..self.buf_len + to_fill].copy_from_slice(&data[..to_fill]);
109            self.buf_len += to_fill;
110            data = &data[to_fill..];
111
112            if self.buf_len == 8 {
113                self.state.absorb_block(&self.buf);
114                p12(&mut self.state);
115                self.buf_len = 0;
116            }
117        }
118
119        let mut chunks = data.chunks_exact(8);
120        for chunk in &mut chunks {
121            self.state.absorb_block(chunk);
122            p12(&mut self.state);
123        }
124
125        let remainder = chunks.remainder();
126        if !remainder.is_empty() {
127            self.buf[..remainder.len()].copy_from_slice(remainder);
128            self.buf_len = remainder.len();
129        }
130    }
131
132    fn squeeze(&mut self, out: &mut [u8]) {
133        if !self.squeezing {
134            let mut padded = [0u8; 8];
135            padded[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
136            padded[self.buf_len] = 0x01;
137            self.state.absorb_block(&padded);
138            p12(&mut self.state);
139            self.squeezing = true;
140        }
141
142        let mut remaining = out;
143        while !remaining.is_empty() {
144            if self.squeeze_pos == 0 {
145                self.current_block = self.state.squeeze_byte();
146            }
147            let n = remaining.len().min(8 - self.squeeze_pos);
148            remaining[..n].copy_from_slice(&self.current_block[self.squeeze_pos..self.squeeze_pos + n]);
149            self.squeeze_pos += n;
150            remaining = &mut remaining[n..];
151
152            if self.squeeze_pos == 8 && !remaining.is_empty() {
153                self.squeeze_pos = 0;
154                p12(&mut self.state);
155            }
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::Xof;
164
165    #[test]
166    fn empty_cxof() {
167        let mut out = [0u8; 64];
168        let mut cxof = AsconCxof128::new();
169        cxof.absorb(b"");
170        cxof.squeeze(&mut out);
171        let expected = hex::decode("4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74").unwrap();
172        assert_eq!(out.as_slice(), expected.as_slice());
173    }
174
175    #[test]
176    fn cxof_with_customization() {
177        let mut out = [0u8; 64];
178        let mut cxof = AsconCxof128::new_with_customization(b"\x10");
179        cxof.absorb(b"");
180        cxof.squeeze(&mut out);
181        let expected = hex::decode("0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6").unwrap();
182        assert_eq!(out.as_slice(), expected.as_slice());
183    }
184
185    #[test]
186    fn cxof_differs_from_xof() {
187        let mut xof_out = [0u8; 32];
188        {
189            let mut xof = crate::ascon::AsconXof128::new();
190            xof.absorb(b"test");
191            xof.squeeze(&mut xof_out);
192        }
193
194        let mut cxof_out = [0u8; 32];
195        let mut cxof = AsconCxof128::new_with_customization(b"test");
196        cxof.absorb(b"");
197        cxof.squeeze(&mut cxof_out);
198
199        assert_ne!(xof_out, cxof_out, "CXOF with non-empty customization should differ from XOF");
200    }
201
202    #[test]
203    #[should_panic(expected = "CXOF customization string must be at most 256 bytes")]
204    fn cxof_customization_too_long() {
205        AsconCxof128::new_with_customization(&[0u8; 257]);
206    }
207
208    #[test]
209    fn kat_vectors() {
210        let data = include_str!("../../testdata/ascon/LWC_CXOF_KAT_128_512.txt");
211        let mut count = 0u64;
212        let mut msg_hex = String::new();
213        let mut z_hex = String::new();
214
215        for line in data.lines() {
216            let line = line.trim();
217            if line.is_empty() {
218                continue;
219            }
220            if line.starts_with("Count = ") {
221                count = line["Count = ".len()..].parse().unwrap();
222                msg_hex.clear();
223                z_hex.clear();
224                continue;
225            }
226            if line.starts_with("Msg = ") {
227                msg_hex = line[6..].to_string();
228                continue;
229            }
230            if line.starts_with("Z = ") {
231                z_hex = line[4..].to_string();
232                continue;
233            }
234            if line.starts_with("MD = ") {
235                let expected_md: &str = &line[5..];
236                let msg = hex::decode(&msg_hex).unwrap();
237                let z = hex::decode(&z_hex).unwrap();
238                let expected = hex::decode(expected_md).unwrap();
239                let mut cxof = AsconCxof128::new_with_customization(&z);
240                cxof.absorb(&msg);
241                let mut out = vec![0u8; expected.len()];
242                cxof.squeeze(&mut out);
243                assert_eq!(out.as_slice(), expected.as_slice(), "KAT CXOF Count={count} mismatch");
244                msg_hex.clear();
245                z_hex.clear();
246                continue;
247            }
248        }
249    }
250}