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 (chunks, remainder) = z.as_chunks::<8>();
65            for chunk in chunks {
66                state.absorb_block(chunk);
67                p12(&mut state);
68            }
69            if !remainder.is_empty() {
70                let mut padded = [0u8; 8];
71                padded[..remainder.len()].copy_from_slice(remainder);
72                padded[remainder.len()] = 0x01;
73                state.absorb_block(&padded);
74                p12(&mut state);
75            } else {
76                // Z was a multiple of 8 bytes - add a pad block
77                let mut padded = [0u8; 8];
78                padded[0] = 0x01;
79                state.absorb_block(&padded);
80                p12(&mut state);
81            }
82        } else {
83            // Empty Z: add a pad block [0x01, 0x00, ...]
84            let mut padded = [0u8; 8];
85            padded[0] = 0x01;
86            state.absorb_block(&padded);
87            p12(&mut state);
88        }
89
90        AsconCxof128 {
91            state,
92            buf: [0u8; 8],
93            buf_len: 0,
94            squeezing: false,
95            squeeze_pos: 0,
96            current_block: [0u8; 8],
97        }
98    }
99}
100
101impl Xof for AsconCxof128 {
102    fn absorb(&mut self, mut data: &[u8]) {
103        assert!(!self.squeezing, "absorb cannot be called after squeeze");
104
105        if self.buf_len > 0 {
106            let to_fill = (8 - self.buf_len).min(data.len());
107            self.buf[self.buf_len..self.buf_len + to_fill].copy_from_slice(&data[..to_fill]);
108            self.buf_len += to_fill;
109            data = &data[to_fill..];
110
111            if self.buf_len == 8 {
112                self.state.absorb_block(&self.buf);
113                p12(&mut self.state);
114                self.buf_len = 0;
115            }
116        }
117
118        let (chunks, remainder) = data.as_chunks::<8>();
119        for chunk in chunks {
120            self.state.absorb_block(chunk);
121            p12(&mut self.state);
122        }
123
124        if !remainder.is_empty() {
125            self.buf[..remainder.len()].copy_from_slice(remainder);
126            self.buf_len = remainder.len();
127        }
128    }
129
130    fn squeeze(&mut self, out: &mut [u8]) {
131        if !self.squeezing {
132            let mut padded = [0u8; 8];
133            padded[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
134            padded[self.buf_len] = 0x01;
135            self.state.absorb_block(&padded);
136            p12(&mut self.state);
137            self.squeezing = true;
138        }
139
140        let mut remaining = out;
141        while !remaining.is_empty() {
142            if self.squeeze_pos == 0 {
143                self.current_block = self.state.squeeze_byte();
144            }
145            let n = remaining.len().min(8 - self.squeeze_pos);
146            remaining[..n].copy_from_slice(&self.current_block[self.squeeze_pos..self.squeeze_pos + n]);
147            self.squeeze_pos += n;
148            remaining = &mut remaining[n..];
149
150            if self.squeeze_pos == 8 && !remaining.is_empty() {
151                self.squeeze_pos = 0;
152                p12(&mut self.state);
153            }
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::Xof;
162
163    #[test]
164    fn empty_cxof() {
165        let mut out = [0u8; 64];
166        let mut cxof = AsconCxof128::new();
167        cxof.absorb(b"");
168        cxof.squeeze(&mut out);
169        let expected = hex::decode("4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74").unwrap();
170        assert_eq!(out.as_slice(), expected.as_slice());
171    }
172
173    #[test]
174    fn cxof_with_customization() {
175        let mut out = [0u8; 64];
176        let mut cxof = AsconCxof128::new_with_customization(b"\x10");
177        cxof.absorb(b"");
178        cxof.squeeze(&mut out);
179        let expected = hex::decode("0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6").unwrap();
180        assert_eq!(out.as_slice(), expected.as_slice());
181    }
182
183    #[test]
184    fn cxof_differs_from_xof() {
185        let mut xof_out = [0u8; 32];
186        {
187            let mut xof = crate::ascon::AsconXof128::new();
188            xof.absorb(b"test");
189            xof.squeeze(&mut xof_out);
190        }
191
192        let mut cxof_out = [0u8; 32];
193        let mut cxof = AsconCxof128::new_with_customization(b"test");
194        cxof.absorb(b"");
195        cxof.squeeze(&mut cxof_out);
196
197        assert_ne!(xof_out, cxof_out, "CXOF with non-empty customization should differ from XOF");
198    }
199
200    #[test]
201    #[should_panic(expected = "CXOF customization string must be at most 256 bytes")]
202    fn cxof_customization_too_long() {
203        AsconCxof128::new_with_customization(&[0u8; 257]);
204    }
205
206    #[test]
207    fn kat_vectors() {
208        let data = include_str!("../../testdata/ascon/LWC_CXOF_KAT_128_512.txt");
209        let mut count = 0u64;
210        let mut msg_hex = String::new();
211        let mut z_hex = String::new();
212
213        for line in data.lines() {
214            let line = line.trim();
215            if line.is_empty() {
216                continue;
217            }
218            if line.starts_with("Count = ") {
219                count = line["Count = ".len()..].parse().unwrap();
220                msg_hex.clear();
221                z_hex.clear();
222                continue;
223            }
224            if line.starts_with("Msg = ") {
225                msg_hex = line[6..].to_string();
226                continue;
227            }
228            if line.starts_with("Z = ") {
229                z_hex = line[4..].to_string();
230                continue;
231            }
232            if line.starts_with("MD = ") {
233                let expected_md: &str = &line[5..];
234                let msg = hex::decode(&msg_hex).unwrap();
235                let z = hex::decode(&z_hex).unwrap();
236                let expected = hex::decode(expected_md).unwrap();
237                let mut cxof = AsconCxof128::new_with_customization(&z);
238                cxof.absorb(&msg);
239                let mut out = vec![0u8; expected.len()];
240                cxof.squeeze(&mut out);
241                assert_eq!(out.as_slice(), expected.as_slice(), "KAT CXOF Count={count} mismatch");
242                msg_hex.clear();
243                z_hex.clear();
244                continue;
245            }
246        }
247    }
248}