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 .. @divCeil(to, 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 buf: [block_length]u8,
685 buf_len: usize,
686
687 pub const Options = struct {};
688
689 /// Initialize a new Ascon-XOF128 extendable output function.
690 ///
691 /// Parameters:
692 /// - options: Configuration options (currently unused)
693 ///
694 /// Returns: An initialized AsconXof128 instance
695 pub fn init(options: Options) AsconXof128 {
696 _ = options;
697
698 // IV for Ascon-XOF128: 0x0000080000cc0003
699 const iv: u64 = 0x0000080000cc0003;
700 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
701 var st = AsconState.initFromWords(words);
702 st.permuteR(12);
703 return AsconXof128{ .st = st, .squeezed = false, .buf = @splat(0), .buf_len = 0 };
704 }
705
706 /// Hash a slice of bytes with variable-length output.
707 ///
708 /// Parameters:
709 /// - bytes: Input data to hash
710 /// - out: Output buffer (can be any length)
711 /// - options: Configuration options (currently unused)
712 ///
713 /// Note: Convenience function that combines init, update, and squeeze
714 pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
715 var st = init(options);
716 st.update(bytes);
717 st.squeeze(out);
718 }
719
720 /// Update the XOF state with additional data.
721 ///
722 /// Parameters:
723 /// - b: Data to absorb into the XOF state
724 ///
725 /// Note: Cannot be called after squeeze() has been called
726 pub fn update(self: *AsconXof128, b: []const u8) void {
727 debug.assert(!self.squeezed); // Cannot update after squeezing
728
729 var i: usize = 0;
730
731 if (self.buf_len > 0) {
732 const to_fill = @min(block_length - self.buf_len, b.len);
733 @memcpy(self.buf[self.buf_len..][0..to_fill], b[0..to_fill]);
734 self.buf_len += to_fill;
735 i += to_fill;
736 if (self.buf_len == block_length) {
737 self.st.addBytes(&self.buf);
738 self.st.permuteR(12);
739 self.buf_len = 0;
740 }
741 }
742
743 while (i + block_length <= b.len) : (i += block_length) {
744 self.st.addBytes(b[i..][0..block_length]);
745 self.st.permuteR(12);
746 }
747
748 if (i < b.len) {
749 self.buf_len = b.len - i;
750 @memcpy(self.buf[0..self.buf_len], b[i..]);
751 }
752 }
753
754 /// Squeeze output bytes from the XOF.
755 ///
756 /// Parameters:
757 /// - out: Output buffer to fill with pseudorandom bytes
758 ///
759 /// Note: Can be called multiple times to generate more output.
760 /// After first call, no more data can be absorbed with update().
761 pub fn squeeze(self: *AsconXof128, out: []u8) void {
762 if (!self.squeezed) {
763 var padded: [block_length]u8 = @splat(0);
764 @memcpy(padded[0..self.buf_len], self.buf[0..self.buf_len]);
765 padded[self.buf_len] = 0x01;
766 self.st.addBytes(&padded);
767 self.st.permuteR(12);
768 self.squeezed = true;
769 }
770
771 var i: usize = 0;
772 while (i < out.len) {
773 const to_copy = @min(8, out.len - i);
774 var block: [8]u8 = undefined;
775 mem.writeInt(u64, &block, self.st.st[0], .little);
776 @memcpy(out[i..][0..to_copy], block[0..to_copy]);
777 i += to_copy;
778
779 if (i < out.len) {
780 self.st.permuteR(12);
781 }
782 }
783 }
784};
785
786/// Ascon-CXOF128 as specified in NIST SP 800-232 Section 5
787pub const AsconCxof128 = struct {
788 pub const block_length = 8;
789 pub const max_custom_length = 256; // 2048 bits
790
791 st: AsconState,
792 squeezed: bool,
793 buf: [block_length]u8,
794 buf_len: usize,
795
796 pub const Options = struct { custom: []const u8 = "" };
797
798 /// Initialize a new Ascon-CXOF128 customizable XOF.
799 ///
800 /// Parameters:
801 /// - options: Configuration with optional customization string
802 /// - custom: Customization string (max 256 bytes)
803 ///
804 /// Returns: An initialized AsconCxof128 instance
805 ///
806 /// Note: Different customization strings produce independent XOF instances
807 pub fn init(options: Options) AsconCxof128 {
808 debug.assert(options.custom.len <= max_custom_length);
809
810 // IV for Ascon-CXOF128: 0x0000080000cc0004
811 const iv: u64 = 0x0000080000cc0004;
812 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
813 var st = AsconState.initFromWords(words);
814 st.permuteR(12);
815
816 var self = AsconCxof128{ .st = st, .squeezed = false, .buf = @splat(0), .buf_len = 0 };
817
818 // Process customization string - always process length and padding
819 // First block: length of customization string
820 const len_block = @as(u64, options.custom.len * 8); // Length in bits
821 self.st.st[0] ^= len_block;
822 self.st.permuteR(12);
823
824 if (options.custom.len > 0) {
825 // Process customization string blocks
826 var i: usize = 0;
827 while (i + 8 <= options.custom.len) : (i += 8) {
828 self.st.addBytes(options.custom[i..][0..8]);
829 self.st.permuteR(12);
830 }
831
832 // Process final partial block with padding
833 if (i < options.custom.len) {
834 var padded: [8]u8 = @splat(0);
835 const remaining = options.custom.len - i;
836 @memcpy(padded[0..remaining], options.custom[i..]);
837 padded[remaining] = 0x01;
838 self.st.addBytes(&padded);
839 self.st.permuteR(12);
840 } else {
841 // Add padding block
842 var padded: [8]u8 = @splat(0);
843 padded[0] = 0x01;
844 self.st.addBytes(&padded);
845 self.st.permuteR(12);
846 }
847 } else {
848 // Empty customization still needs padding
849 var padded: [8]u8 = @splat(0);
850 padded[0] = 0x01;
851 self.st.addBytes(&padded);
852 self.st.permuteR(12);
853 }
854
855 return self;
856 }
857
858 /// Hash a slice of bytes with customization and variable-length output.
859 ///
860 /// Parameters:
861 /// - bytes: Input data to hash
862 /// - out: Output buffer (can be any length)
863 /// - options: Configuration with optional customization string
864 ///
865 /// Note: Convenience function that combines init, update, and squeeze
866 pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
867 var st = init(options);
868 st.update(bytes);
869 st.squeeze(out);
870 }
871
872 /// Update the CXOF state with additional data.
873 ///
874 /// Parameters:
875 /// - b: Data to absorb into the CXOF state
876 ///
877 /// Note: Cannot be called after squeeze() has been called
878 pub fn update(self: *AsconCxof128, b: []const u8) void {
879 debug.assert(!self.squeezed); // Cannot update after squeezing
880
881 var i: usize = 0;
882
883 if (self.buf_len > 0) {
884 const to_fill = @min(block_length - self.buf_len, b.len);
885 @memcpy(self.buf[self.buf_len..][0..to_fill], b[0..to_fill]);
886 self.buf_len += to_fill;
887 i += to_fill;
888 if (self.buf_len == block_length) {
889 self.st.addBytes(&self.buf);
890 self.st.permuteR(12);
891 self.buf_len = 0;
892 }
893 }
894
895 while (i + block_length <= b.len) : (i += block_length) {
896 self.st.addBytes(b[i..][0..block_length]);
897 self.st.permuteR(12);
898 }
899
900 if (i < b.len) {
901 self.buf_len = b.len - i;
902 @memcpy(self.buf[0..self.buf_len], b[i..]);
903 }
904 }
905
906 /// Squeeze output bytes from the customizable XOF.
907 ///
908 /// Parameters:
909 /// - out: Output buffer to fill with pseudorandom bytes
910 ///
911 /// Note: Can be called multiple times to generate more output.
912 /// After first call, no more data can be absorbed with update().
913 pub fn squeeze(self: *AsconCxof128, out: []u8) void {
914 if (!self.squeezed) {
915 var padded: [block_length]u8 = @splat(0);
916 @memcpy(padded[0..self.buf_len], self.buf[0..self.buf_len]);
917 padded[self.buf_len] = 0x01;
918 self.st.addBytes(&padded);
919 self.st.permuteR(12);
920 self.squeezed = true;
921 }
922
923 var i: usize = 0;
924 while (i < out.len) {
925 const to_copy = @min(8, out.len - i);
926 var block: [8]u8 = undefined;
927 mem.writeInt(u64, &block, self.st.st[0], .little);
928 @memcpy(out[i..][0..to_copy], block[0..to_copy]);
929 i += to_copy;
930
931 if (i < out.len) {
932 self.st.permuteR(12);
933 }
934 }
935 }
936};
937
938test "Ascon-Hash256 basic test" {
939 const message = "The quick brown fox jumps over the lazy dog";
940 var hash: [32]u8 = undefined;
941
942 AsconHash256.hash(message, &hash, .{});
943
944 // Verify hash is generated (exact value depends on test vectors)
945 try testing.expect(hash.len == 32);
946}
947
948test "Ascon-XOF128 basic test" {
949 var xof = AsconXof128.init(.{});
950 xof.update("Hello, ");
951 xof.update("World!");
952
953 var out1: [16]u8 = undefined;
954 xof.squeeze(&out1);
955
956 var out2: [32]u8 = undefined;
957 xof.squeeze(&out2);
958
959 // XOF outputs should be continuous - out2 should NOT match out1
960 // Each squeeze produces new output
961 try testing.expect(!mem.eql(u8, &out1, out2[0..16]));
962}
963
964test "Ascon-CXOF128 with customization" {
965 const custom = "MyCustomString";
966 var xof = AsconCxof128.init(.{ .custom = custom });
967 xof.update("Test message");
968
969 var out: [32]u8 = undefined;
970 xof.squeeze(&out);
971
972 // Different customization should give different output
973 var xof2 = AsconCxof128.init(.{ .custom = "DifferentCustom" });
974 xof2.update("Test message");
975
976 var out2: [32]u8 = undefined;
977 xof2.squeeze(&out2);
978
979 try testing.expect(!mem.eql(u8, &out, &out2));
980}
981
982test "Ascon-AEAD128 round trip with various data sizes" {
983 const key = [_]u8{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
984 const nonce = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
985
986 // Test with empty plaintext
987 {
988 const plaintext = "";
989 const ad = "metadata";
990 var ciphertext: [plaintext.len]u8 = undefined;
991 var tag: [16]u8 = undefined;
992
993 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
994
995 var decrypted: [plaintext.len]u8 = undefined;
996 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
997 try testing.expectEqualStrings(plaintext, &decrypted);
998 }
999
1000 // Test with small plaintext
1001 {
1002 const plaintext = "Short";
1003 const ad = "";
1004 var ciphertext: [plaintext.len]u8 = undefined;
1005 var tag: [16]u8 = undefined;
1006
1007 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1008
1009 var decrypted: [plaintext.len]u8 = undefined;
1010 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1011 try testing.expectEqualStrings(plaintext, &decrypted);
1012 }
1013
1014 // Test with longer plaintext and associated data
1015 {
1016 const plaintext = "This is a longer message to test the round trip encryption and decryption process";
1017 const ad = "Additional authenticated data that is not encrypted but is authenticated";
1018 var ciphertext: [plaintext.len]u8 = undefined;
1019 var tag: [16]u8 = undefined;
1020
1021 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1022
1023 var decrypted: [plaintext.len]u8 = undefined;
1024 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1025 try testing.expectEqualStrings(plaintext, &decrypted);
1026 }
1027
1028 // Test authentication failure with tampered ciphertext
1029 {
1030 const plaintext = "Tamper test";
1031 const ad = "metadata";
1032 var ciphertext: [plaintext.len]u8 = undefined;
1033 var tag: [16]u8 = undefined;
1034
1035 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1036
1037 // Tamper with ciphertext
1038 ciphertext[0] ^= 0xFF;
1039
1040 var decrypted: [plaintext.len]u8 = undefined;
1041 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1042 try testing.expectError(error.AuthenticationFailed, result);
1043 }
1044
1045 // Test authentication failure with wrong tag
1046 {
1047 const plaintext = "Tag test";
1048 const ad = "metadata";
1049 var ciphertext: [plaintext.len]u8 = undefined;
1050 var tag: [16]u8 = undefined;
1051
1052 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1053
1054 // Tamper with tag
1055 var wrong_tag = tag;
1056 wrong_tag[0] ^= 0xFF;
1057
1058 var decrypted: [plaintext.len]u8 = undefined;
1059 const result = AsconAead128.decrypt(&decrypted, &ciphertext, wrong_tag, ad, nonce, key);
1060 try testing.expectError(error.AuthenticationFailed, result);
1061 }
1062
1063 // Test authentication failure with wrong associated data
1064 {
1065 const plaintext = "AD test";
1066 const ad = "original";
1067 var ciphertext: [plaintext.len]u8 = undefined;
1068 var tag: [16]u8 = undefined;
1069
1070 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1071
1072 var decrypted: [plaintext.len]u8 = undefined;
1073 const wrong_ad = "modified";
1074 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, wrong_ad, nonce, key);
1075 try testing.expectError(error.AuthenticationFailed, result);
1076 }
1077}
1078
1079// Test vectors from NIST SP 800-232 / ascon-c reference implementation
1080test "Ascon-AEAD128 official test vectors" {
1081
1082 // Test vector 1: Empty PT, Empty AD
1083 {
1084 var key: [16]u8 = undefined;
1085 var nonce: [16]u8 = undefined;
1086 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1087 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1088
1089 const plaintext = "";
1090 const ad = "";
1091 var ciphertext: [plaintext.len]u8 = undefined;
1092 var tag: [16]u8 = undefined;
1093
1094 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1095
1096 var expected_tag: [16]u8 = undefined;
1097 _ = std.fmt.hexToBytes(&expected_tag, "4F9C278211BEC9316BF68F46EE8B2EC6") catch unreachable;
1098 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1099 }
1100
1101 // Test vector 2: Empty PT, AD = "30"
1102 {
1103 var key: [16]u8 = undefined;
1104 var nonce: [16]u8 = undefined;
1105 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1106 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1107
1108 const plaintext = "";
1109 var ad: [1]u8 = undefined;
1110 _ = std.fmt.hexToBytes(&ad, "30") catch unreachable;
1111 var ciphertext: [plaintext.len]u8 = undefined;
1112 var tag: [16]u8 = undefined;
1113
1114 AsconAead128.encrypt(&ciphertext, &tag, plaintext, &ad, nonce, key);
1115
1116 var expected_tag: [16]u8 = undefined;
1117 _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;
1118 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1119 }
1120
1121 // Test vector 34: Single byte plaintext 0x20
1122 {
1123 var key: [16]u8 = undefined;
1124 var nonce: [16]u8 = undefined;
1125 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1126 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1127
1128 var plaintext: [1]u8 = undefined;
1129 _ = std.fmt.hexToBytes(&plaintext, "20") catch unreachable;
1130 const ad = "";
1131 var ciphertext: [1]u8 = undefined;
1132 var tag: [16]u8 = undefined;
1133
1134 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1135
1136 var expected_ct: [1]u8 = undefined;
1137 _ = std.fmt.hexToBytes(&expected_ct, "E8") catch unreachable;
1138 var expected_tag: [16]u8 = undefined;
1139 _ = std.fmt.hexToBytes(&expected_tag, "DD576ABA1CD3E6FC704DE02AEDB79588") catch unreachable;
1140
1141 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1142 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1143
1144 // Verify decryption
1145 var decrypted: [1]u8 = undefined;
1146 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1147 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1148 }
1149
1150 // Test vector with 3-byte plaintext
1151 {
1152 var key: [16]u8 = undefined;
1153 var nonce: [16]u8 = undefined;
1154 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1155 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1156
1157 var plaintext: [3]u8 = undefined;
1158 _ = std.fmt.hexToBytes(&plaintext, "202122") catch unreachable;
1159 const ad = "";
1160 var ciphertext: [3]u8 = undefined;
1161 var tag: [16]u8 = undefined;
1162
1163 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1164
1165 var expected_ct: [3]u8 = undefined;
1166 _ = std.fmt.hexToBytes(&expected_ct, "E8C3DE") catch unreachable;
1167 var expected_tag: [16]u8 = undefined;
1168 _ = std.fmt.hexToBytes(&expected_tag, "AF8E12816B8EDF39AD1571A9492B7CA2") catch unreachable;
1169
1170 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1171 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1172
1173 // Verify decryption
1174 var decrypted: [3]u8 = undefined;
1175 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1176 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1177 }
1178}
1179
1180test "Ascon-Hash256 official test vectors" {
1181
1182 // Test vector 1: Empty message
1183 {
1184 const message = "";
1185 var hash: [32]u8 = undefined;
1186 AsconHash256.hash(message, &hash, .{});
1187
1188 var expected: [32]u8 = undefined;
1189 _ = std.fmt.hexToBytes(&expected, "0B3BE5850F2F6B98CAF29F8FDEA89B64A1FA70AA249B8F839BD53BAA304D92B2") catch unreachable;
1190 try testing.expectEqualSlices(u8, &expected, &hash);
1191 }
1192
1193 // Test vector 2: Single byte 0x00
1194 {
1195 const message = [_]u8{0x00};
1196 var hash: [32]u8 = undefined;
1197 AsconHash256.hash(&message, &hash, .{});
1198
1199 var expected: [32]u8 = undefined;
1200 _ = std.fmt.hexToBytes(&expected, "0728621035AF3ED2BCA03BF6FDE900F9456F5330E4B5EE23E7F6A1E70291BC80") catch unreachable;
1201 try testing.expectEqualSlices(u8, &expected, &hash);
1202 }
1203
1204 // Test vector 3: 0x00, 0x01
1205 {
1206 const message = [_]u8{ 0x00, 0x01 };
1207 var hash: [32]u8 = undefined;
1208 AsconHash256.hash(&message, &hash, .{});
1209
1210 var expected: [32]u8 = undefined;
1211 _ = std.fmt.hexToBytes(&expected, "6115E7C9C4081C2797FC8FE1BC57A836AFA1C5381E556DD583860CA2DFB48DD2") catch unreachable;
1212 try testing.expectEqualSlices(u8, &expected, &hash);
1213 }
1214
1215 // Test vector 4: 0x00, 0x01, 0x02
1216 {
1217 const message = [_]u8{ 0x00, 0x01, 0x02 };
1218 var hash: [32]u8 = undefined;
1219 AsconHash256.hash(&message, &hash, .{});
1220
1221 var expected: [32]u8 = undefined;
1222 _ = std.fmt.hexToBytes(&expected, "265AB89A609F5A05DCA57E83FBBA700F9A2D2C4211BA4CC9F0A1A369E17B915C") catch unreachable;
1223 try testing.expectEqualSlices(u8, &expected, &hash);
1224 }
1225
1226 // Test vector 5: 0x00..0x03
1227 {
1228 const message = [_]u8{ 0x00, 0x01, 0x02, 0x03 };
1229 var hash: [32]u8 = undefined;
1230 AsconHash256.hash(&message, &hash, .{});
1231
1232 var expected: [32]u8 = undefined;
1233 _ = std.fmt.hexToBytes(&expected, "D7E4C7ED9B8A325CD08B9EF259F8877054ECD8304FE1B2D7FD847137DF6727EE") catch unreachable;
1234 try testing.expectEqualSlices(u8, &expected, &hash);
1235 }
1236}
1237
1238test "Ascon-XOF128 official test vectors" {
1239
1240 // Test vector 1: Empty message, 64-byte output
1241 {
1242 var xof = AsconXof128.init(.{});
1243 xof.update("");
1244
1245 var output: [64]u8 = undefined;
1246 xof.squeeze(&output);
1247
1248 var expected: [64]u8 = undefined;
1249 _ = std.fmt.hexToBytes(&expected, "473D5E6164F58B39DFD84AACDB8AE42EC2D91FED33388EE0D960D9B3993295C6AD77855A5D3B13FE6AD9E6098988373AF7D0956D05A8F1665D2C67D1A3AD10FF") catch unreachable;
1250 try testing.expectEqualSlices(u8, &expected, &output);
1251 }
1252
1253 // Test vector 2: Single byte 0x00, 64-byte output
1254 {
1255 var xof = AsconXof128.init(.{});
1256 const msg = [_]u8{0x00};
1257 xof.update(&msg);
1258
1259 var output: [64]u8 = undefined;
1260 xof.squeeze(&output);
1261
1262 var expected: [64]u8 = undefined;
1263 _ = std.fmt.hexToBytes(&expected, "51430E0438ECDF642B393630D977625F5F337656BA58AB1E960784AC32A16E0D446405551F5469384F8EA283CF12E64FA72C426BFEBAEA3AA1529E2C4AB23A2F") catch unreachable;
1264 try testing.expectEqualSlices(u8, &expected, &output);
1265 }
1266
1267 // Test vector 3: 0x00, 0x01, 64-byte output
1268 {
1269 var xof = AsconXof128.init(.{});
1270 const msg = [_]u8{ 0x00, 0x01 };
1271 xof.update(&msg);
1272
1273 var output: [64]u8 = undefined;
1274 xof.squeeze(&output);
1275
1276 var expected: [64]u8 = undefined;
1277 _ = std.fmt.hexToBytes(&expected, "A05383077AF971D3830BD37E7B981497A773D441DB077C6494CC73125953846EB6427FBA4CD308FF90A11385D51101341BF5379249217BFDACE9CCA1148CC966") catch unreachable;
1278 try testing.expectEqualSlices(u8, &expected, &output);
1279 }
1280}
1281
1282test "Ascon-XOF128/CXOF128 streaming chunking invariance" {
1283 const msg = "Hello, World!";
1284
1285 // XOF128: one-shot vs split must match
1286 var out1: [32]u8 = undefined;
1287 var out2: [32]u8 = undefined;
1288 var xof1 = AsconXof128.init(.{});
1289 xof1.update(msg);
1290 xof1.squeeze(&out1);
1291 var xof2 = AsconXof128.init(.{});
1292 xof2.update("Hello, ");
1293 xof2.update("World!");
1294 xof2.squeeze(&out2);
1295 try testing.expectEqualSlices(u8, &out1, &out2);
1296
1297 // CXOF128: one-shot vs split must match
1298 var cout1: [32]u8 = undefined;
1299 var cout2: [32]u8 = undefined;
1300 var cxof1 = AsconCxof128.init(.{ .custom = "cust" });
1301 cxof1.update(msg);
1302 cxof1.squeeze(&cout1);
1303 var cxof2 = AsconCxof128.init(.{ .custom = "cust" });
1304 cxof2.update("Hello, ");
1305 cxof2.update("World!");
1306 cxof2.squeeze(&cout2);
1307 try testing.expectEqualSlices(u8, &cout1, &cout2);
1308}
1309
1310test "Ascon-CXOF128 official test vectors" {
1311
1312 // Test vector 1: Empty message, empty customization, 64-byte output
1313 {
1314 var xof = AsconCxof128.init(.{});
1315 xof.update("");
1316
1317 var output: [64]u8 = undefined;
1318 xof.squeeze(&output);
1319
1320 var expected: [64]u8 = undefined;
1321 _ = std.fmt.hexToBytes(&expected, "4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74") catch unreachable;
1322 try testing.expectEqualSlices(u8, &expected, &output);
1323 }
1324
1325 // Test vector 2: Empty message, customization = 0x10, 64-byte output
1326 {
1327 const custom = [_]u8{0x10};
1328 var xof = AsconCxof128.init(.{ .custom = &custom });
1329 xof.update("");
1330
1331 var output: [64]u8 = undefined;
1332 xof.squeeze(&output);
1333
1334 var expected: [64]u8 = undefined;
1335 _ = std.fmt.hexToBytes(&expected, "0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6") catch unreachable;
1336 try testing.expectEqualSlices(u8, &expected, &output);
1337 }
1338
1339 // Test vector 3: Empty message, customization = 0x10, 0x11, 64-byte output
1340 {
1341 const custom = [_]u8{ 0x10, 0x11 };
1342 var xof = AsconCxof128.init(.{ .custom = &custom });
1343 xof.update("");
1344
1345 var output: [64]u8 = undefined;
1346 xof.squeeze(&output);
1347
1348 var expected: [64]u8 = undefined;
1349 _ = std.fmt.hexToBytes(&expected, "D1106C7622E79FE955BD9D79E03B918E770FE0E0CDDDE28BEB924B02C5FC936B33ACCA299C89ECA5D71886CBBFA4D54A21C55FDE2B679F5E2488063A1719DC32") catch unreachable;
1350 try testing.expectEqualSlices(u8, &expected, &output);
1351 }
1352}