master
1//! Ascon is a 320-bit permutation, selected as new standard for lightweight cryptography
2//! in the NIST Lightweight Cryptography competition (2019–2023).
3//! https://csrc.nist.gov/pubs/sp/800/232/ipd
4//!
5//! The permutation is compact, and optimized for timing and side channel resistance,
6//! making it a good choice for embedded applications.
7//!
8//! It is not meant to be used directly, but as a building block for symmetric cryptography.
9
10const std = @import("std");
11const builtin = @import("builtin");
12const crypto = std.crypto;
13const debug = std.debug;
14const mem = std.mem;
15const testing = std.testing;
16const rotr = std.math.rotr;
17const native_endian = builtin.cpu.arch.endian();
18
19/// An Ascon state.
20///
21/// The state is represented as 5 64-bit words.
22///
23/// The original NIST submission (v1.2) serializes these words as big-endian,
24/// but NIST SP 800-232 switched to a little-endian representation.
25/// Software implementations are free to use native endianness with no security degradation.
26pub fn State(comptime endian: std.builtin.Endian) type {
27 return struct {
28 const Self = @This();
29
30 /// Number of bytes in the state.
31 pub const block_bytes = 40;
32
33 const Block = [5]u64;
34
35 st: Block,
36
37 /// Initialize the state from a slice of bytes.
38 ///
39 /// Parameters:
40 /// - initial_state: A 40-byte array to initialize the state
41 ///
42 /// Returns: A new State initialized with the provided bytes
43 pub fn init(initial_state: [block_bytes]u8) Self {
44 var state = Self{ .st = undefined };
45 @memcpy(state.asBytes(), &initial_state);
46 state.endianSwap();
47 return state;
48 }
49
50 /// Initialize the state from u64 words in native endianness.
51 ///
52 /// Parameters:
53 /// - initial_state: An array of 5 u64 words in native endianness
54 ///
55 /// Returns: A new State with the provided words
56 pub fn initFromWords(initial_state: [5]u64) Self {
57 return .{ .st = initial_state };
58 }
59
60 /// Initialize the state for Ascon XOF.
61 ///
62 /// Returns: A new State initialized with the Ascon XOF initialization vector
63 pub fn initXof() Self {
64 return Self{ .st = Block{
65 0xb57e273b814cd416,
66 0x2b51042562ae2420,
67 0x66a3a7768ddf2218,
68 0x5aad0a7a8153650c,
69 0x4f3e0e32539493b6,
70 } };
71 }
72
73 /// Initialize the state for Ascon XOFa.
74 ///
75 /// Returns: A new State initialized with the Ascon XOFa initialization vector
76 pub fn initXofA() Self {
77 return Self{ .st = Block{
78 0x44906568b77b9832,
79 0xcd8d6cae53455532,
80 0xf7b5212756422129,
81 0x246885e1de0d225b,
82 0xa8cb5ce33449973f,
83 } };
84 }
85
86 /// A representation of the state as bytes. The byte order is architecture-dependent.
87 ///
88 /// Returns: A pointer to the state's internal byte representation
89 pub fn asBytes(self: *Self) *[block_bytes]u8 {
90 return mem.asBytes(&self.st);
91 }
92
93 /// Byte-swap the entire state if the architecture doesn't match the required endianness.
94 ///
95 /// This ensures the state is in the correct endianness for the current platform.
96 pub fn endianSwap(self: *Self) void {
97 for (&self.st) |*w| {
98 w.* = mem.toNative(u64, w.*, endian);
99 }
100 }
101
102 /// Set bytes starting at the beginning of the state.
103 ///
104 /// Parameters:
105 /// - bytes: Slice of bytes to write into the state (up to 40 bytes)
106 ///
107 /// Note: If bytes.len < 40, remaining state words are zero-padded
108 pub fn setBytes(self: *Self, bytes: []const u8) void {
109 var i: usize = 0;
110 while (i + 8 <= bytes.len) : (i += 8) {
111 self.st[i / 8] = mem.readInt(u64, bytes[i..][0..8], endian);
112 }
113 if (i < bytes.len) {
114 var padded: [8]u8 = @splat(0);
115 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
116 self.st[i / 8] = mem.readInt(u64, padded[0..], endian);
117 }
118 }
119
120 /// XOR a byte into the state at a given offset.
121 ///
122 /// Parameters:
123 /// - byte: The byte to XOR into the state
124 /// - offset: The byte offset in the state (0-39)
125 pub fn addByte(self: *Self, byte: u8, offset: usize) void {
126 const z = switch (endian) {
127 .big => 64 - 8 - 8 * @as(u6, @truncate(offset % 8)),
128 .little => 8 * @as(u6, @truncate(offset % 8)),
129 };
130 self.st[offset / 8] ^= @as(u64, byte) << z;
131 }
132
133 /// XOR bytes into the beginning of the state.
134 ///
135 /// Parameters:
136 /// - bytes: Slice of bytes to XOR into the state (up to 40 bytes)
137 ///
138 /// Note: Handles partial blocks with zero-padding
139 pub fn addBytes(self: *Self, bytes: []const u8) void {
140 var i: usize = 0;
141 while (i + 8 <= bytes.len) : (i += 8) {
142 self.st[i / 8] ^= mem.readInt(u64, bytes[i..][0..8], endian);
143 }
144 if (i < bytes.len) {
145 var padded: [8]u8 = @splat(0);
146 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
147 self.st[i / 8] ^= mem.readInt(u64, padded[0..], endian);
148 }
149 }
150
151 /// Extract the first bytes of the state.
152 ///
153 /// Parameters:
154 /// - out: Output buffer to receive the extracted bytes
155 ///
156 /// Note: Extracts up to out.len bytes from the beginning of the state
157 pub fn extractBytes(self: *Self, out: []u8) void {
158 var i: usize = 0;
159 while (i + 8 <= out.len) : (i += 8) {
160 mem.writeInt(u64, out[i..][0..8], self.st[i / 8], endian);
161 }
162 if (i < out.len) {
163 var padded: [8]u8 = @splat(0);
164 mem.writeInt(u64, padded[0..], self.st[i / 8], endian);
165 @memcpy(out[i..], padded[0 .. out.len - i]);
166 }
167 }
168
169 /// XOR the first bytes of the state into a slice of bytes.
170 ///
171 /// Parameters:
172 /// - out: Output buffer for the XORed result
173 /// - in: Input bytes to XOR with the state
174 ///
175 /// Requires: out.len == in.len
176 pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void {
177 debug.assert(out.len == in.len);
178
179 var i: usize = 0;
180 while (i + 8 <= in.len) : (i += 8) {
181 const x = mem.readInt(u64, in[i..][0..8], native_endian) ^ mem.nativeTo(u64, self.st[i / 8], endian);
182 mem.writeInt(u64, out[i..][0..8], x, native_endian);
183 }
184 if (i < in.len) {
185 var padded: [8]u8 = @splat(0);
186 @memcpy(padded[0 .. in.len - i], in[i..]);
187 const x = mem.readInt(u64, &padded, native_endian) ^ mem.nativeTo(u64, self.st[i / 8], endian);
188 mem.writeInt(u64, &padded, x, native_endian);
189 @memcpy(out[i..], padded[0 .. in.len - i]);
190 }
191 }
192
193 /// Set the words storing the bytes of a given range to zero.
194 ///
195 /// Parameters:
196 /// - from: Starting byte offset (inclusive)
197 /// - to: Ending byte offset (inclusive)
198 ///
199 /// Note: Clears complete words that contain the specified byte range
200 pub fn clear(self: *Self, from: usize, to: usize) void {
201 @memset(self.st[from / 8 .. (to + 7) / 8], 0);
202 }
203
204 /// Clear the entire state, disabling compiler optimizations.
205 ///
206 /// Uses secure zeroing to prevent the compiler from optimizing away
207 /// the clearing operation. Use for sensitive data cleanup.
208 pub fn secureZero(self: *Self) void {
209 crypto.secureZero(u64, &self.st);
210 }
211
212 /// Apply a reduced-round permutation to the state.
213 ///
214 /// Parameters:
215 /// - rounds: Number of rounds to apply (1-12)
216 ///
217 /// Note: Uses the last `rounds` round constants from the full set
218 pub fn permuteR(state: *Self, comptime rounds: u4) void {
219 const rks = [16]u64{ 0x3c, 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
220 inline for (rks[rks.len - rounds ..]) |rk| {
221 state.round(rk);
222 }
223 }
224
225 /// Apply a full-round permutation to the state.
226 ///
227 /// Applies the standard 12-round Ascon permutation.
228 pub fn permute(state: *Self) void {
229 state.permuteR(12);
230 }
231
232 /// Apply a permutation to the state and prevent backtracking.
233 ///
234 /// Parameters:
235 /// - rounds: Number of permutation rounds to apply
236 /// - rate: Rate in bytes (must be multiple of 8, < 40)
237 ///
238 /// The capacity portion is XORed before and after permutation to
239 /// provide forward security (ratcheting).
240 pub fn permuteRatchet(state: *Self, comptime rounds: u4, comptime rate: u6) void {
241 const capacity = block_bytes - rate;
242 debug.assert(capacity > 0 and capacity % 8 == 0); // capacity must be a multiple of 64 bits
243 var mask: [capacity / 8]u64 = undefined;
244 inline for (&mask, state.st[state.st.len - mask.len ..]) |*m, x| m.* = x;
245 state.permuteR(rounds);
246 inline for (mask, state.st[state.st.len - mask.len ..]) |m, *x| x.* ^= m;
247 }
248
249 /// Core Ascon permutation round function.
250 ///
251 /// Parameters:
252 /// - rk: Round constant for this round
253 ///
254 /// Implements one round of the Ascon permutation with S-box and linear layer.
255 fn round(state: *Self, rk: u64) void {
256 const x = &state.st;
257 x[2] ^= rk;
258
259 x[0] ^= x[4];
260 x[4] ^= x[3];
261 x[2] ^= x[1];
262 var t: Block = .{
263 x[0] ^ (~x[1] & x[2]),
264 x[1] ^ (~x[2] & x[3]),
265 x[2] ^ (~x[3] & x[4]),
266 x[3] ^ (~x[4] & x[0]),
267 x[4] ^ (~x[0] & x[1]),
268 };
269 t[1] ^= t[0];
270 t[3] ^= t[2];
271 t[0] ^= t[4];
272
273 x[2] = t[2] ^ rotr(u64, t[2], 6 - 1);
274 x[3] = t[3] ^ rotr(u64, t[3], 17 - 10);
275 x[4] = t[4] ^ rotr(u64, t[4], 41 - 7);
276 x[0] = t[0] ^ rotr(u64, t[0], 28 - 19);
277 x[1] = t[1] ^ rotr(u64, t[1], 61 - 39);
278 x[2] = t[2] ^ rotr(u64, x[2], 1);
279 x[3] = t[3] ^ rotr(u64, x[3], 10);
280 x[4] = t[4] ^ rotr(u64, x[4], 7);
281 x[0] = t[0] ^ rotr(u64, x[0], 19);
282 x[1] = t[1] ^ rotr(u64, x[1], 39);
283 x[2] = ~x[2];
284 }
285 };
286}
287
288test "ascon" {
289 const Ascon = State(.big);
290 var bytes: [Ascon.block_bytes]u8 = undefined;
291 @memset(&bytes, 1);
292 var st = Ascon.init(bytes);
293 var out: [Ascon.block_bytes]u8 = undefined;
294 st.permute();
295 st.extractBytes(&out);
296 const expected1 = [_]u8{ 148, 147, 49, 226, 218, 221, 208, 113, 186, 94, 96, 10, 183, 219, 119, 150, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
297 try testing.expectEqualSlices(u8, &expected1, &out);
298 st.clear(0, 10);
299 st.extractBytes(&out);
300 const expected2 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
301 try testing.expectEqualSlices(u8, &expected2, &out);
302 st.addByte(1, 5);
303 st.addByte(2, 5);
304 st.extractBytes(&out);
305 const expected3 = [_]u8{ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 206, 65, 18, 215, 97, 78, 106, 118, 81, 211, 150, 52, 17, 117, 64, 216, 45, 148, 240, 65, 181, 90, 180 };
306 try testing.expectEqualSlices(u8, &expected3, &out);
307 st.addBytes(&bytes);
308 st.extractBytes(&out);
309 const expected4 = [_]u8{ 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 168, 207, 64, 19, 214, 96, 79, 107, 119, 80, 210, 151, 53, 16, 116, 65, 217, 44, 149, 241, 64, 180, 91, 181 };
310 try testing.expectEqualSlices(u8, &expected4, &out);
311}
312
313const AsconState = State(.little);
314const AuthenticationError = crypto.errors.AuthenticationError;
315
316/// Ascon-AEAD128 as specified in NIST SP 800-232 Section 4
317pub const AsconAead128 = struct {
318 pub const tag_length = 16;
319 pub const nonce_length = 16;
320 pub const key_length = 16;
321 pub const block_length = 16;
322
323 const AeadState = struct {
324 st: AsconState,
325 k0: u64,
326 k1: u64,
327
328 /// Initialize AEAD state with key and nonce.
329 ///
330 /// Parameters:
331 /// - key: 16-byte secret key
332 /// - nonce: 16-byte nonce
333 ///
334 /// Returns: Initialized AEAD state ready for processing
335 fn init(key: [16]u8, nonce: [16]u8) AeadState {
336 const k0 = mem.readInt(u64, key[0..8], .little);
337 const k1 = mem.readInt(u64, key[8..16], .little);
338 const n0 = mem.readInt(u64, nonce[0..8], .little);
339 const n1 = mem.readInt(u64, nonce[8..16], .little);
340
341 // IV for Ascon-AEAD128 (Ascon-128a)
342 const iv: u64 = 0x00001000808C0001;
343 const words: [5]u64 = .{ iv, k0, k1, n0, n1 };
344
345 var st = AsconState.initFromWords(words);
346 st.permuteR(12);
347
348 st.st[3] ^= k0;
349 st.st[4] ^= k1;
350
351 return AeadState{ .st = st, .k0 = k0, .k1 = k1 };
352 }
353
354 /// Process associated data for authentication.
355 ///
356 /// Parameters:
357 /// - ad: Associated data to authenticate
358 ///
359 /// Updates the state to include AD in authentication tag computation.
360 fn processAd(self: *AeadState, ad: []const u8) void {
361 if (ad.len == 0) return;
362
363 var i: usize = 0;
364 // Process full 128-bit blocks
365 while (i + 16 <= ad.len) : (i += 16) {
366 self.st.addBytes(ad[i..][0..16]);
367 self.st.permuteR(8);
368 }
369
370 // Process final partial AD block
371 const adrem = ad.len - i;
372 if (adrem > 0) {
373 if (adrem >= 8) {
374 var buf: [8]u8 = @splat(0);
375 @memcpy(buf[0..8], ad[i..][0..8]);
376 self.st.st[0] ^= mem.readInt(u64, &buf, .little);
377
378 buf = @splat(0);
379 @memcpy(buf[0 .. adrem - 8], ad[i + 8 ..]);
380 buf[adrem - 8] = 0x01;
381 self.st.st[1] ^= mem.readInt(u64, &buf, .little);
382 } else {
383 var buf: [8]u8 = @splat(0);
384 @memcpy(buf[0..adrem], ad[i..]);
385 buf[adrem] = 0x01;
386 self.st.st[0] ^= mem.readInt(u64, &buf, .little);
387 }
388 self.st.permuteR(8);
389 }
390 }
391
392 /// Finalize the AEAD operation and prepare tag.
393 ///
394 /// Applies final permutation and XORs key for tag generation.
395 fn finalize(self: *AeadState) void {
396 // XOR key before final permutation
397 self.st.st[2] ^= self.k0;
398 self.st.st[3] ^= self.k1;
399 self.st.permuteR(12);
400
401 // XOR key again for tag generation
402 self.st.st[3] ^= self.k0;
403 self.st.st[4] ^= self.k1;
404 }
405 };
406
407 /// Encrypt a message with Ascon-AEAD128.
408 ///
409 /// Parameters:
410 /// - c: Output buffer for ciphertext (must be same length as m)
411 /// - tag: Output buffer for authentication tag (16 bytes)
412 /// - m: Plaintext message to encrypt
413 /// - ad: Associated data to authenticate but not encrypt
414 /// - npub: Public nonce (16 bytes, must be unique per message)
415 /// - k: Secret key (16 bytes)
416 ///
417 /// Note: The ciphertext and tag must be transmitted together for decryption
418 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
419 debug.assert(c.len == m.len);
420
421 var state = AeadState.init(k, npub);
422
423 // Process associated data
424 state.processAd(ad);
425
426 // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
427 state.st.st[4] ^= 0x8000000000000000;
428
429 // Process plaintext
430 var i: usize = 0;
431 while (i + 16 <= m.len) : (i += 16) {
432 state.st.addBytes(m[i..][0..16]);
433 state.st.extractBytes(c[i..][0..16]);
434 state.st.permuteR(8);
435 }
436
437 // Process final partial block
438 const remaining = m.len - i;
439 if (remaining > 8) {
440 // Split between two words
441 state.st.addBytes(m[i..][0..8]);
442 state.st.extractBytes(c[i..][0..8]);
443
444 var buf: [8]u8 = @splat(0);
445 @memcpy(buf[0 .. remaining - 8], m[i + 8 ..]);
446 const m1 = mem.readInt(u64, &buf, .little);
447 state.st.st[1] ^= m1;
448 mem.writeInt(u64, buf[0..], state.st.st[1], .little);
449 @memcpy(c[i + 8 ..], buf[0 .. remaining - 8]);
450
451 // Add padding
452 state.st.st[1] ^= @as(u64, 0x01) << @intCast((remaining - 8) * 8);
453 } else if (remaining == 8) {
454 // Exactly 8 bytes - all in word 0, padding in word 1
455 state.st.addBytes(m[i..][0..8]);
456 state.st.extractBytes(c[i..][0..8]);
457
458 // Add padding to word 1 at position 0
459 state.st.st[1] ^= 0x01;
460 } else if (remaining > 0) {
461 // All in first word
462 var temp: [8]u8 = @splat(0);
463 @memcpy(temp[0..remaining], m[i..]);
464 state.st.addBytes(&temp);
465 state.st.extractBytes(c[i..][0..remaining]);
466 // Add padding
467 temp = @splat(0);
468 temp[remaining] = 0x01;
469 state.st.addBytes(&temp);
470 // Second word stays zero
471 } else {
472 // Empty message or exact multiple - add padding block
473 var padded: [16]u8 = @splat(0);
474 padded[0] = 0x01;
475 state.st.addBytes(&padded);
476 }
477
478 // Finalization
479 state.finalize();
480
481 // Extract tag
482 mem.writeInt(u64, tag[0..8], state.st.st[3], .little);
483 mem.writeInt(u64, tag[8..16], state.st.st[4], .little);
484 }
485
486 /// Decrypt a message with Ascon-AEAD128.
487 ///
488 /// Parameters:
489 /// - m: Output buffer for plaintext (must be same length as c)
490 /// - c: Ciphertext to decrypt
491 /// - tag: Authentication tag (16 bytes)
492 /// - ad: Associated data that was authenticated
493 /// - npub: Public nonce used during encryption (16 bytes)
494 /// - k: Secret key (16 bytes)
495 ///
496 /// Returns: AuthenticationError if tag verification fails
497 ///
498 /// Note: On authentication failure, the output buffer is securely zeroed
499 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
500 debug.assert(m.len == c.len);
501
502 var state = AeadState.init(k, npub);
503
504 // Process associated data
505 state.processAd(ad);
506
507 // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
508 state.st.st[4] ^= 0x8000000000000000;
509
510 // Process ciphertext
511 var i: usize = 0;
512 while (i + 16 <= c.len) : (i += 16) {
513 const ct_block = c[i..][0..16].*; // Save ciphertext block for in-place operation support
514 state.st.xorBytes(m[i..][0..16], &ct_block);
515 state.st.setBytes(&ct_block);
516 state.st.permuteR(8);
517 }
518
519 // Final partial ciphertext block
520 const crem = c.len - i;
521 if (crem > 8) {
522 // Save ciphertext for in-place operation support
523 var saved_ct: [16]u8 = undefined;
524 @memcpy(saved_ct[0..crem], c[i..]);
525
526 const c0 = mem.readInt(u64, saved_ct[0..8], .little);
527 state.st.st[0] ^= c0;
528 mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
529 state.st.st[0] = c0;
530
531 var buf: [8]u8 = @splat(0);
532 @memcpy(buf[0 .. crem - 8], saved_ct[8..][0 .. crem - 8]);
533 const c1 = mem.readInt(u64, &buf, .little);
534 const m1 = state.st.st[1] ^ c1;
535 mem.writeInt(u64, buf[0..], m1, .little);
536 @memcpy(m[i + 8 ..], buf[0 .. crem - 8]);
537
538 // Replace only the bytes we've read, keeping upper bytes intact
539 const mask = (@as(u64, 1) << @intCast((crem - 8) * 8)) - 1;
540 state.st.st[1] = (state.st.st[1] & ~mask) | (c1 & mask);
541
542 state.st.st[1] ^= @as(u64, 0x01) << @intCast((crem - 8) * 8);
543 } else if (crem == 8) {
544 // Exactly 8 bytes - process only word 0, add padding to word 1
545 const saved_ct = c[i..][0..8].*;
546
547 const c0 = mem.readInt(u64, &saved_ct, .little);
548 state.st.st[0] ^= c0;
549 mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
550 state.st.st[0] = c0;
551
552 // Add padding to word 1 at position 0
553 state.st.st[1] ^= 0x01;
554 } else if (crem > 0) {
555 var buf: [8]u8 = @splat(0);
556 @memcpy(buf[0..crem], c[i..]);
557 const c0 = mem.readInt(u64, &buf, .little);
558 const m0 = state.st.st[0] ^ c0;
559 mem.writeInt(u64, buf[0..], m0, .little);
560 @memcpy(m[i..], buf[0..crem]);
561
562 // Replace only the bytes we've read, keeping upper bytes intact
563 const mask = (@as(u64, 1) << @intCast(crem * 8)) - 1;
564 state.st.st[0] = (state.st.st[0] & ~mask) | (c0 & mask);
565
566 state.st.st[0] ^= @as(u64, 0x01) << @intCast(crem * 8);
567 } else {
568 state.st.st[0] ^= 0x01;
569 }
570
571 // Finalization
572 state.finalize();
573
574 // Verify tag
575 var computed_tag: [tag_length]u8 = undefined;
576 mem.writeInt(u64, computed_tag[0..8], state.st.st[3], .little);
577 mem.writeInt(u64, computed_tag[8..16], state.st.st[4], .little);
578
579 if (!crypto.timing_safe.eql([tag_length]u8, tag, computed_tag)) {
580 crypto.secureZero(u8, m);
581 return error.AuthenticationFailed;
582 }
583 }
584};
585
586/// Ascon-Hash256 as specified in NIST SP 800-232 Section 5
587pub const AsconHash256 = struct {
588 pub const digest_length = 32;
589 pub const block_length = 8;
590
591 st: AsconState,
592
593 pub const Options = struct {};
594
595 /// Initialize a new Ascon-Hash256 hasher.
596 ///
597 /// Parameters:
598 /// - options: Configuration options (currently unused)
599 ///
600 /// Returns: An initialized AsconHash256 hasher
601 pub fn init(options: Options) AsconHash256 {
602 _ = options;
603
604 // IV for Ascon-Hash256: 0x0000080100cc0002
605 const iv: u64 = 0x0000080100cc0002;
606 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
607 var st = AsconState.initFromWords(words);
608 st.permuteR(12);
609 return AsconHash256{ .st = st };
610 }
611
612 /// Compute Ascon-Hash256 hash of input data in one call.
613 ///
614 /// Parameters:
615 /// - b: Input data to hash
616 /// - out: Output buffer for 32-byte hash digest
617 /// - options: Configuration options (currently unused)
618 pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
619 var h = init(options);
620 h.update(b);
621 h.final(out);
622 }
623
624 /// Update the hash state with additional data.
625 ///
626 /// Parameters:
627 /// - b: Data to add to the hash
628 ///
629 /// Note: Can be called multiple times before final()
630 pub fn update(self: *AsconHash256, b: []const u8) void {
631 var i: usize = 0;
632
633 // Process full 64-bit blocks
634 while (i + 8 <= b.len) : (i += 8) {
635 self.st.addBytes(b[i..][0..8]);
636 self.st.permuteR(12);
637 }
638
639 // Store partial block for finalization
640 if (i < b.len) {
641 var padded: [8]u8 = @splat(0);
642 const remaining = b.len - i;
643 @memcpy(padded[0..remaining], b[i..]);
644 padded[remaining] = 0x01;
645 self.st.addBytes(&padded);
646 } else {
647 // Add padding block
648 var padded: [8]u8 = @splat(0);
649 padded[0] = 0x01;
650 self.st.addBytes(&padded);
651 }
652 }
653
654 /// Finalize the hash and output the digest.
655 ///
656 /// Parameters:
657 /// - out: Output buffer for 32-byte hash digest
658 ///
659 /// Note: After calling final(), the hasher should not be used again
660 pub fn final(self: *AsconHash256, out: *[digest_length]u8) void {
661 // Final permutation after padding
662 self.st.permuteR(12);
663
664 // Extract hash output (4 × 64 bits = 256 bits)
665 var h: [4]u64 = undefined;
666 for (0..4) |i| {
667 h[i] = self.st.st[0];
668 self.st.permuteR(12);
669 }
670
671 // Write output
672 for (0..4) |i| {
673 mem.writeInt(u64, out[i * 8 ..][0..8], h[i], .little);
674 }
675 }
676};
677
678/// Ascon-XOF128 as specified in NIST SP 800-232 Section 5
679pub const AsconXof128 = struct {
680 pub const block_length = 8;
681
682 st: AsconState,
683 squeezed: bool,
684
685 pub const Options = struct {};
686
687 /// Initialize a new Ascon-XOF128 extendable output function.
688 ///
689 /// Parameters:
690 /// - options: Configuration options (currently unused)
691 ///
692 /// Returns: An initialized AsconXof128 instance
693 pub fn init(options: Options) AsconXof128 {
694 _ = options;
695
696 // IV for Ascon-XOF128: 0x0000080000cc0003
697 const iv: u64 = 0x0000080000cc0003;
698 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
699 var st = AsconState.initFromWords(words);
700 st.permuteR(12);
701 return AsconXof128{ .st = st, .squeezed = false };
702 }
703
704 /// Hash a slice of bytes with variable-length output.
705 ///
706 /// Parameters:
707 /// - bytes: Input data to hash
708 /// - out: Output buffer (can be any length)
709 /// - options: Configuration options (currently unused)
710 ///
711 /// Note: Convenience function that combines init, update, and squeeze
712 pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
713 var st = init(options);
714 st.update(bytes);
715 st.squeeze(out);
716 }
717
718 /// Update the XOF state with additional data.
719 ///
720 /// Parameters:
721 /// - b: Data to absorb into the XOF state
722 ///
723 /// Note: Cannot be called after squeeze() has been called
724 pub fn update(self: *AsconXof128, b: []const u8) void {
725 debug.assert(!self.squeezed); // Cannot update after squeezing
726
727 var i: usize = 0;
728
729 // Process full 64-bit blocks
730 while (i + 8 <= b.len) : (i += 8) {
731 self.st.addBytes(b[i..][0..8]);
732 self.st.permuteR(12);
733 }
734
735 // Store partial block for finalization
736 if (i < b.len) {
737 var padded: [8]u8 = @splat(0);
738 const remaining = b.len - i;
739 @memcpy(padded[0..remaining], b[i..]);
740 padded[remaining] = 0x01;
741 self.st.addBytes(&padded);
742 } else {
743 // Add padding block
744 var padded: [8]u8 = @splat(0);
745 padded[0] = 0x01;
746 self.st.addBytes(&padded);
747 }
748 }
749
750 /// Squeeze output bytes from the XOF.
751 ///
752 /// Parameters:
753 /// - out: Output buffer to fill with pseudorandom bytes
754 ///
755 /// Note: Can be called multiple times to generate more output.
756 /// After first call, no more data can be absorbed with update().
757 pub fn squeeze(self: *AsconXof128, out: []u8) void {
758 if (!self.squeezed) {
759 // First squeeze - apply final permutation
760 self.st.permuteR(12);
761 self.squeezed = true;
762 }
763
764 var i: usize = 0;
765 while (i < out.len) {
766 const to_copy = @min(8, out.len - i);
767 var block: [8]u8 = undefined;
768 mem.writeInt(u64, &block, self.st.st[0], .little);
769 @memcpy(out[i..][0..to_copy], block[0..to_copy]);
770 i += to_copy;
771
772 if (i < out.len) {
773 self.st.permuteR(12);
774 }
775 }
776 }
777};
778
779/// Ascon-CXOF128 as specified in NIST SP 800-232 Section 5
780pub const AsconCxof128 = struct {
781 pub const block_length = 8;
782 pub const max_custom_length = 256; // 2048 bits
783
784 st: AsconState,
785 squeezed: bool,
786
787 pub const Options = struct { custom: []const u8 = "" };
788
789 /// Initialize a new Ascon-CXOF128 customizable XOF.
790 ///
791 /// Parameters:
792 /// - options: Configuration with optional customization string
793 /// - custom: Customization string (max 256 bytes)
794 ///
795 /// Returns: An initialized AsconCxof128 instance
796 ///
797 /// Note: Different customization strings produce independent XOF instances
798 pub fn init(options: Options) AsconCxof128 {
799 debug.assert(options.custom.len <= max_custom_length);
800
801 // IV for Ascon-CXOF128: 0x0000080000cc0004
802 const iv: u64 = 0x0000080000cc0004;
803 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
804 var st = AsconState.initFromWords(words);
805 st.permuteR(12);
806
807 var self = AsconCxof128{ .st = st, .squeezed = false };
808
809 // Process customization string - always process length and padding
810 // First block: length of customization string
811 const len_block = @as(u64, options.custom.len * 8); // Length in bits
812 self.st.st[0] ^= len_block;
813 self.st.permuteR(12);
814
815 if (options.custom.len > 0) {
816 // Process customization string blocks
817 var i: usize = 0;
818 while (i + 8 <= options.custom.len) : (i += 8) {
819 self.st.addBytes(options.custom[i..][0..8]);
820 self.st.permuteR(12);
821 }
822
823 // Process final partial block with padding
824 if (i < options.custom.len) {
825 var padded: [8]u8 = @splat(0);
826 const remaining = options.custom.len - i;
827 @memcpy(padded[0..remaining], options.custom[i..]);
828 padded[remaining] = 0x01;
829 self.st.addBytes(&padded);
830 self.st.permuteR(12);
831 } else {
832 // Add padding block
833 var padded: [8]u8 = @splat(0);
834 padded[0] = 0x01;
835 self.st.addBytes(&padded);
836 self.st.permuteR(12);
837 }
838 } else {
839 // Empty customization still needs padding
840 var padded: [8]u8 = @splat(0);
841 padded[0] = 0x01;
842 self.st.addBytes(&padded);
843 self.st.permuteR(12);
844 }
845
846 return self;
847 }
848
849 /// Hash a slice of bytes with customization and variable-length output.
850 ///
851 /// Parameters:
852 /// - bytes: Input data to hash
853 /// - out: Output buffer (can be any length)
854 /// - options: Configuration with optional customization string
855 ///
856 /// Note: Convenience function that combines init, update, and squeeze
857 pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
858 var st = init(options);
859 st.update(bytes);
860 st.squeeze(out);
861 }
862
863 /// Update the CXOF state with additional data.
864 ///
865 /// Parameters:
866 /// - b: Data to absorb into the CXOF state
867 ///
868 /// Note: Cannot be called after squeeze() has been called
869 pub fn update(self: *AsconCxof128, b: []const u8) void {
870 debug.assert(!self.squeezed);
871
872 var i: usize = 0;
873
874 // Process full 64-bit blocks
875 while (i + 8 <= b.len) : (i += 8) {
876 self.st.addBytes(b[i..][0..8]);
877 self.st.permuteR(12);
878 }
879
880 // Store partial block for finalization
881 if (i < b.len) {
882 var padded: [8]u8 = @splat(0);
883 const remaining = b.len - i;
884 @memcpy(padded[0..remaining], b[i..]);
885 padded[remaining] = 0x01;
886 self.st.addBytes(&padded);
887 } else {
888 // Add padding block
889 var padded: [8]u8 = @splat(0);
890 padded[0] = 0x01;
891 self.st.addBytes(&padded);
892 }
893 }
894
895 /// Squeeze output bytes from the customizable XOF.
896 ///
897 /// Parameters:
898 /// - out: Output buffer to fill with pseudorandom bytes
899 ///
900 /// Note: Can be called multiple times to generate more output.
901 /// After first call, no more data can be absorbed with update().
902 pub fn squeeze(self: *AsconCxof128, out: []u8) void {
903 if (!self.squeezed) {
904 // First squeeze - apply final permutation
905 self.st.permuteR(12);
906 self.squeezed = true;
907 }
908
909 var i: usize = 0;
910 while (i < out.len) {
911 const to_copy = @min(8, out.len - i);
912 var block: [8]u8 = undefined;
913 mem.writeInt(u64, &block, self.st.st[0], .little);
914 @memcpy(out[i..][0..to_copy], block[0..to_copy]);
915 i += to_copy;
916
917 if (i < out.len) {
918 self.st.permuteR(12);
919 }
920 }
921 }
922};
923
924test "Ascon-Hash256 basic test" {
925 const message = "The quick brown fox jumps over the lazy dog";
926 var hash: [32]u8 = undefined;
927
928 AsconHash256.hash(message, &hash, .{});
929
930 // Verify hash is generated (exact value depends on test vectors)
931 try testing.expect(hash.len == 32);
932}
933
934test "Ascon-XOF128 basic test" {
935 var xof = AsconXof128.init(.{});
936 xof.update("Hello, ");
937 xof.update("World!");
938
939 var out1: [16]u8 = undefined;
940 xof.squeeze(&out1);
941
942 var out2: [32]u8 = undefined;
943 xof.squeeze(&out2);
944
945 // XOF outputs should be continuous - out2 should NOT match out1
946 // Each squeeze produces new output
947 try testing.expect(!mem.eql(u8, &out1, out2[0..16]));
948}
949
950test "Ascon-CXOF128 with customization" {
951 const custom = "MyCustomString";
952 var xof = AsconCxof128.init(.{ .custom = custom });
953 xof.update("Test message");
954
955 var out: [32]u8 = undefined;
956 xof.squeeze(&out);
957
958 // Different customization should give different output
959 var xof2 = AsconCxof128.init(.{ .custom = "DifferentCustom" });
960 xof2.update("Test message");
961
962 var out2: [32]u8 = undefined;
963 xof2.squeeze(&out2);
964
965 try testing.expect(!mem.eql(u8, &out, &out2));
966}
967
968test "Ascon-AEAD128 round trip with various data sizes" {
969 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
970
971 const key = [_]u8{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
972 const nonce = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
973
974 // Test with empty plaintext
975 {
976 const plaintext = "";
977 const ad = "metadata";
978 var ciphertext: [plaintext.len]u8 = undefined;
979 var tag: [16]u8 = undefined;
980
981 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
982
983 var decrypted: [plaintext.len]u8 = undefined;
984 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
985 try testing.expectEqualStrings(plaintext, &decrypted);
986 }
987
988 // Test with small plaintext
989 {
990 const plaintext = "Short";
991 const ad = "";
992 var ciphertext: [plaintext.len]u8 = undefined;
993 var tag: [16]u8 = undefined;
994
995 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
996
997 var decrypted: [plaintext.len]u8 = undefined;
998 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
999 try testing.expectEqualStrings(plaintext, &decrypted);
1000 }
1001
1002 // Test with longer plaintext and associated data
1003 {
1004 const plaintext = "This is a longer message to test the round trip encryption and decryption process";
1005 const ad = "Additional authenticated data that is not encrypted but is authenticated";
1006 var ciphertext: [plaintext.len]u8 = undefined;
1007 var tag: [16]u8 = undefined;
1008
1009 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1010
1011 var decrypted: [plaintext.len]u8 = undefined;
1012 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1013 try testing.expectEqualStrings(plaintext, &decrypted);
1014 }
1015
1016 // Test authentication failure with tampered ciphertext
1017 {
1018 const plaintext = "Tamper test";
1019 const ad = "metadata";
1020 var ciphertext: [plaintext.len]u8 = undefined;
1021 var tag: [16]u8 = undefined;
1022
1023 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1024
1025 // Tamper with ciphertext
1026 ciphertext[0] ^= 0xFF;
1027
1028 var decrypted: [plaintext.len]u8 = undefined;
1029 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1030 try testing.expectError(error.AuthenticationFailed, result);
1031 }
1032
1033 // Test authentication failure with wrong tag
1034 {
1035 const plaintext = "Tag test";
1036 const ad = "metadata";
1037 var ciphertext: [plaintext.len]u8 = undefined;
1038 var tag: [16]u8 = undefined;
1039
1040 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1041
1042 // Tamper with tag
1043 var wrong_tag = tag;
1044 wrong_tag[0] ^= 0xFF;
1045
1046 var decrypted: [plaintext.len]u8 = undefined;
1047 const result = AsconAead128.decrypt(&decrypted, &ciphertext, wrong_tag, ad, nonce, key);
1048 try testing.expectError(error.AuthenticationFailed, result);
1049 }
1050
1051 // Test authentication failure with wrong associated data
1052 {
1053 const plaintext = "AD test";
1054 const ad = "original";
1055 var ciphertext: [plaintext.len]u8 = undefined;
1056 var tag: [16]u8 = undefined;
1057
1058 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1059
1060 var decrypted: [plaintext.len]u8 = undefined;
1061 const wrong_ad = "modified";
1062 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, wrong_ad, nonce, key);
1063 try testing.expectError(error.AuthenticationFailed, result);
1064 }
1065}
1066
1067// Test vectors from NIST SP 800-232 / ascon-c reference implementation
1068test "Ascon-AEAD128 official test vectors" {
1069
1070 // Test vector 1: Empty PT, Empty AD
1071 {
1072 var key: [16]u8 = undefined;
1073 var nonce: [16]u8 = undefined;
1074 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1075 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1076
1077 const plaintext = "";
1078 const ad = "";
1079 var ciphertext: [plaintext.len]u8 = undefined;
1080 var tag: [16]u8 = undefined;
1081
1082 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1083
1084 var expected_tag: [16]u8 = undefined;
1085 _ = std.fmt.hexToBytes(&expected_tag, "4F9C278211BEC9316BF68F46EE8B2EC6") catch unreachable;
1086 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1087 }
1088
1089 // Test vector 2: Empty PT, AD = "30"
1090 {
1091 var key: [16]u8 = undefined;
1092 var nonce: [16]u8 = undefined;
1093 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1094 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1095
1096 const plaintext = "";
1097 var ad: [1]u8 = undefined;
1098 _ = std.fmt.hexToBytes(&ad, "30") catch unreachable;
1099 var ciphertext: [plaintext.len]u8 = undefined;
1100 var tag: [16]u8 = undefined;
1101
1102 AsconAead128.encrypt(&ciphertext, &tag, plaintext, &ad, nonce, key);
1103
1104 var expected_tag: [16]u8 = undefined;
1105 _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;
1106 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1107 }
1108
1109 // Test vector 34: Single byte plaintext 0x20
1110 {
1111 var key: [16]u8 = undefined;
1112 var nonce: [16]u8 = undefined;
1113 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1114 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1115
1116 var plaintext: [1]u8 = undefined;
1117 _ = std.fmt.hexToBytes(&plaintext, "20") catch unreachable;
1118 const ad = "";
1119 var ciphertext: [1]u8 = undefined;
1120 var tag: [16]u8 = undefined;
1121
1122 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1123
1124 var expected_ct: [1]u8 = undefined;
1125 _ = std.fmt.hexToBytes(&expected_ct, "E8") catch unreachable;
1126 var expected_tag: [16]u8 = undefined;
1127 _ = std.fmt.hexToBytes(&expected_tag, "DD576ABA1CD3E6FC704DE02AEDB79588") catch unreachable;
1128
1129 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1130 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1131
1132 // Verify decryption
1133 var decrypted: [1]u8 = undefined;
1134 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1135 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1136 }
1137
1138 // Test vector with 3-byte plaintext
1139 {
1140 var key: [16]u8 = undefined;
1141 var nonce: [16]u8 = undefined;
1142 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1143 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1144
1145 var plaintext: [3]u8 = undefined;
1146 _ = std.fmt.hexToBytes(&plaintext, "202122") catch unreachable;
1147 const ad = "";
1148 var ciphertext: [3]u8 = undefined;
1149 var tag: [16]u8 = undefined;
1150
1151 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1152
1153 var expected_ct: [3]u8 = undefined;
1154 _ = std.fmt.hexToBytes(&expected_ct, "E8C3DE") catch unreachable;
1155 var expected_tag: [16]u8 = undefined;
1156 _ = std.fmt.hexToBytes(&expected_tag, "AF8E12816B8EDF39AD1571A9492B7CA2") catch unreachable;
1157
1158 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1159 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1160
1161 // Verify decryption
1162 var decrypted: [3]u8 = undefined;
1163 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1164 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1165 }
1166}
1167
1168test "Ascon-Hash256 official test vectors" {
1169
1170 // Test vector 1: Empty message
1171 {
1172 const message = "";
1173 var hash: [32]u8 = undefined;
1174 AsconHash256.hash(message, &hash, .{});
1175
1176 var expected: [32]u8 = undefined;
1177 _ = std.fmt.hexToBytes(&expected, "0B3BE5850F2F6B98CAF29F8FDEA89B64A1FA70AA249B8F839BD53BAA304D92B2") catch unreachable;
1178 try testing.expectEqualSlices(u8, &expected, &hash);
1179 }
1180
1181 // Test vector 2: Single byte 0x00
1182 {
1183 const message = [_]u8{0x00};
1184 var hash: [32]u8 = undefined;
1185 AsconHash256.hash(&message, &hash, .{});
1186
1187 var expected: [32]u8 = undefined;
1188 _ = std.fmt.hexToBytes(&expected, "0728621035AF3ED2BCA03BF6FDE900F9456F5330E4B5EE23E7F6A1E70291BC80") catch unreachable;
1189 try testing.expectEqualSlices(u8, &expected, &hash);
1190 }
1191
1192 // Test vector 3: 0x00, 0x01
1193 {
1194 const message = [_]u8{ 0x00, 0x01 };
1195 var hash: [32]u8 = undefined;
1196 AsconHash256.hash(&message, &hash, .{});
1197
1198 var expected: [32]u8 = undefined;
1199 _ = std.fmt.hexToBytes(&expected, "6115E7C9C4081C2797FC8FE1BC57A836AFA1C5381E556DD583860CA2DFB48DD2") catch unreachable;
1200 try testing.expectEqualSlices(u8, &expected, &hash);
1201 }
1202
1203 // Test vector 4: 0x00, 0x01, 0x02
1204 {
1205 const message = [_]u8{ 0x00, 0x01, 0x02 };
1206 var hash: [32]u8 = undefined;
1207 AsconHash256.hash(&message, &hash, .{});
1208
1209 var expected: [32]u8 = undefined;
1210 _ = std.fmt.hexToBytes(&expected, "265AB89A609F5A05DCA57E83FBBA700F9A2D2C4211BA4CC9F0A1A369E17B915C") catch unreachable;
1211 try testing.expectEqualSlices(u8, &expected, &hash);
1212 }
1213
1214 // Test vector 5: 0x00..0x03
1215 {
1216 const message = [_]u8{ 0x00, 0x01, 0x02, 0x03 };
1217 var hash: [32]u8 = undefined;
1218 AsconHash256.hash(&message, &hash, .{});
1219
1220 var expected: [32]u8 = undefined;
1221 _ = std.fmt.hexToBytes(&expected, "D7E4C7ED9B8A325CD08B9EF259F8877054ECD8304FE1B2D7FD847137DF6727EE") catch unreachable;
1222 try testing.expectEqualSlices(u8, &expected, &hash);
1223 }
1224}
1225
1226test "Ascon-XOF128 official test vectors" {
1227
1228 // Test vector 1: Empty message, 64-byte output
1229 {
1230 var xof = AsconXof128.init(.{});
1231 xof.update("");
1232
1233 var output: [64]u8 = undefined;
1234 xof.squeeze(&output);
1235
1236 var expected: [64]u8 = undefined;
1237 _ = std.fmt.hexToBytes(&expected, "473D5E6164F58B39DFD84AACDB8AE42EC2D91FED33388EE0D960D9B3993295C6AD77855A5D3B13FE6AD9E6098988373AF7D0956D05A8F1665D2C67D1A3AD10FF") catch unreachable;
1238 try testing.expectEqualSlices(u8, &expected, &output);
1239 }
1240
1241 // Test vector 2: Single byte 0x00, 64-byte output
1242 {
1243 var xof = AsconXof128.init(.{});
1244 const msg = [_]u8{0x00};
1245 xof.update(&msg);
1246
1247 var output: [64]u8 = undefined;
1248 xof.squeeze(&output);
1249
1250 var expected: [64]u8 = undefined;
1251 _ = std.fmt.hexToBytes(&expected, "51430E0438ECDF642B393630D977625F5F337656BA58AB1E960784AC32A16E0D446405551F5469384F8EA283CF12E64FA72C426BFEBAEA3AA1529E2C4AB23A2F") catch unreachable;
1252 try testing.expectEqualSlices(u8, &expected, &output);
1253 }
1254
1255 // Test vector 3: 0x00, 0x01, 64-byte output
1256 {
1257 var xof = AsconXof128.init(.{});
1258 const msg = [_]u8{ 0x00, 0x01 };
1259 xof.update(&msg);
1260
1261 var output: [64]u8 = undefined;
1262 xof.squeeze(&output);
1263
1264 var expected: [64]u8 = undefined;
1265 _ = std.fmt.hexToBytes(&expected, "A05383077AF971D3830BD37E7B981497A773D441DB077C6494CC73125953846EB6427FBA4CD308FF90A11385D51101341BF5379249217BFDACE9CCA1148CC966") catch unreachable;
1266 try testing.expectEqualSlices(u8, &expected, &output);
1267 }
1268}
1269
1270test "Ascon-CXOF128 official test vectors" {
1271
1272 // Test vector 1: Empty message, empty customization, 64-byte output
1273 {
1274 var xof = AsconCxof128.init(.{});
1275 xof.update("");
1276
1277 var output: [64]u8 = undefined;
1278 xof.squeeze(&output);
1279
1280 var expected: [64]u8 = undefined;
1281 _ = std.fmt.hexToBytes(&expected, "4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74") catch unreachable;
1282 try testing.expectEqualSlices(u8, &expected, &output);
1283 }
1284
1285 // Test vector 2: Empty message, customization = 0x10, 64-byte output
1286 {
1287 const custom = [_]u8{0x10};
1288 var xof = AsconCxof128.init(.{ .custom = &custom });
1289 xof.update("");
1290
1291 var output: [64]u8 = undefined;
1292 xof.squeeze(&output);
1293
1294 var expected: [64]u8 = undefined;
1295 _ = std.fmt.hexToBytes(&expected, "0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6") catch unreachable;
1296 try testing.expectEqualSlices(u8, &expected, &output);
1297 }
1298
1299 // Test vector 3: Empty message, customization = 0x10, 0x11, 64-byte output
1300 {
1301 const custom = [_]u8{ 0x10, 0x11 };
1302 var xof = AsconCxof128.init(.{ .custom = &custom });
1303 xof.update("");
1304
1305 var output: [64]u8 = undefined;
1306 xof.squeeze(&output);
1307
1308 var expected: [64]u8 = undefined;
1309 _ = std.fmt.hexToBytes(&expected, "D1106C7622E79FE955BD9D79E03B918E770FE0E0CDDDE28BEB924B02C5FC936B33ACCA299C89ECA5D71886CBBFA4D54A21C55FDE2B679F5E2488063A1719DC32") catch unreachable;
1310 try testing.expectEqualSlices(u8, &expected, &output);
1311 }
1312}