authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2025-09-18 04:59:55+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-17 19:59:55-07:00
log4406127cca3b4dcf92ae7587e2e6f8b4e267abf6
treebcf8c3e8ea49ffaf8cd32bb35e9292656cbe0cf2
parent6dd0270a1926661e339d993a825fffa82be6bd51
signaturebadge-check Signed by PGP key B5690EEEBB952194

std.crypto: add Ascon-AEAD, Ascon-Hash, Ascon-CHash (#25239)

Ascon is the family of cryptographic constructions standardized by NIST for lightweight cryptography. The Zig standard library already included the Ascon permutation itself, but higher-level constructions built on top of it were intentionally postponed until NIST released the final specification. That specification has now been published as NIST SP 800-232: https://csrc.nist.gov/pubs/sp/800/232/final With this publication, we can now confidently include these constructions in the standard library.

3 files changed, 1113 insertions(+), 11 deletions(-)

lib/std/crypto.zig+13
......@@ -36,6 +36,10 @@ pub const aead = struct {
3636 pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
3737 };
3838
39 pub const ascon = struct {
40 pub const AsconAead128 = @import("crypto/ascon.zig").AsconAead128;
41 };
42
3943 pub const chacha_poly = struct {
4044 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
4145 pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
......@@ -115,6 +119,12 @@ pub const ecc = struct {
115119
116120/// Hash functions.
117121pub const hash = struct {
122 pub const ascon = struct {
123 const variants = @import("crypto/ascon.zig");
124 pub const AsconHash256 = variants.AsconHash256;
125 pub const AsconXof128 = variants.AsconXof128;
126 pub const AsconCxof128 = variants.AsconCxof128;
127 };
118128 pub const blake2 = @import("crypto/blake2.zig");
119129 pub const Blake3 = @import("crypto/blake3.zig").Blake3;
120130 pub const Md5 = @import("crypto/md5.zig").Md5;
......@@ -243,6 +253,8 @@ pub const SideChannelsMitigations = enum {
243253pub const default_side_channels_mitigations = .medium;
244254
245255test {
256 _ = aead.ascon.AsconAead128;
257
246258 _ = aead.aegis.Aegis128L;
247259 _ = aead.aegis.Aegis256;
248260
......@@ -281,6 +293,7 @@ test {
281293 _ = ecc.Ristretto255;
282294 _ = ecc.Secp256k1;
283295
296 _ = hash.ascon;
284297 _ = hash.blake2;
285298 _ = hash.Blake3;
286299 _ = hash.Md5;
lib/std/crypto/ascon.zig+1081-10
......@@ -9,6 +9,7 @@
99
1010const std = @import("std");
1111const builtin = @import("builtin");
12const crypto = std.crypto;
1213const debug = std.debug;
1314const mem = std.mem;
1415const testing = std.testing;
......@@ -34,6 +35,11 @@ pub fn State(comptime endian: std.builtin.Endian) type {
3435 st: Block,
3536
3637 /// 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
3743 pub fn init(initial_state: [block_bytes]u8) Self {
3844 var state = Self{ .st = undefined };
3945 @memcpy(state.asBytes(), &initial_state);
......@@ -42,11 +48,18 @@ pub fn State(comptime endian: std.builtin.Endian) type {
4248 }
4349
4450 /// 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
4556 pub fn initFromWords(initial_state: [5]u64) Self {
4657 return .{ .st = initial_state };
4758 }
4859
49 /// Initialize the state for Ascon XOF
60 /// Initialize the state for Ascon XOF.
61 ///
62 /// Returns: A new State initialized with the Ascon XOF initialization vector
5063 pub fn initXof() Self {
5164 return Self{ .st = Block{
5265 0xb57e273b814cd416,
......@@ -57,7 +70,9 @@ pub fn State(comptime endian: std.builtin.Endian) type {
5770 } };
5871 }
5972
60 /// Initialize the state for Ascon XOFa
73 /// Initialize the state for Ascon XOFa.
74 ///
75 /// Returns: A new State initialized with the Ascon XOFa initialization vector
6176 pub fn initXofA() Self {
6277 return Self{ .st = Block{
6378 0x44906568b77b9832,
......@@ -69,11 +84,15 @@ pub fn State(comptime endian: std.builtin.Endian) type {
6984 }
7085
7186 /// 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
7289 pub fn asBytes(self: *Self) *[block_bytes]u8 {
7390 return mem.asBytes(&self.st);
7491 }
7592
7693 /// 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.
7796 pub fn endianSwap(self: *Self) void {
7897 for (&self.st) |*w| {
7998 w.* = mem.toNative(u64, w.*, endian);
......@@ -81,19 +100,28 @@ pub fn State(comptime endian: std.builtin.Endian) type {
81100 }
82101
83102 /// 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
84108 pub fn setBytes(self: *Self, bytes: []const u8) void {
85109 var i: usize = 0;
86110 while (i + 8 <= bytes.len) : (i += 8) {
87111 self.st[i / 8] = mem.readInt(u64, bytes[i..][0..8], endian);
88112 }
89113 if (i < bytes.len) {
90 var padded = [_]u8{0} ** 8;
114 var padded: [8]u8 = @splat(0);
91115 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
92116 self.st[i / 8] = mem.readInt(u64, padded[0..], endian);
93117 }
94118 }
95119
96120 /// 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)
97125 pub fn addByte(self: *Self, byte: u8, offset: usize) void {
98126 const z = switch (endian) {
99127 .big => 64 - 8 - 8 * @as(u6, @truncate(offset % 8)),
......@@ -103,32 +131,48 @@ pub fn State(comptime endian: std.builtin.Endian) type {
103131 }
104132
105133 /// 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
106139 pub fn addBytes(self: *Self, bytes: []const u8) void {
107140 var i: usize = 0;
108141 while (i + 8 <= bytes.len) : (i += 8) {
109142 self.st[i / 8] ^= mem.readInt(u64, bytes[i..][0..8], endian);
110143 }
111144 if (i < bytes.len) {
112 var padded = [_]u8{0} ** 8;
145 var padded: [8]u8 = @splat(0);
113146 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
114147 self.st[i / 8] ^= mem.readInt(u64, padded[0..], endian);
115148 }
116149 }
117150
118151 /// 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
119157 pub fn extractBytes(self: *Self, out: []u8) void {
120158 var i: usize = 0;
121159 while (i + 8 <= out.len) : (i += 8) {
122160 mem.writeInt(u64, out[i..][0..8], self.st[i / 8], endian);
123161 }
124162 if (i < out.len) {
125 var padded = [_]u8{0} ** 8;
163 var padded: [8]u8 = @splat(0);
126164 mem.writeInt(u64, padded[0..], self.st[i / 8], endian);
127165 @memcpy(out[i..], padded[0 .. out.len - i]);
128166 }
129167 }
130168
131169 /// 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
132176 pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void {
133177 debug.assert(out.len == in.len);
134178
......@@ -138,7 +182,7 @@ pub fn State(comptime endian: std.builtin.Endian) type {
138182 mem.writeInt(u64, out[i..][0..8], x, native_endian);
139183 }
140184 if (i < in.len) {
141 var padded = [_]u8{0} ** 8;
185 var padded: [8]u8 = @splat(0);
142186 @memcpy(padded[0 .. in.len - i], in[i..]);
143187 const x = mem.readInt(u64, &padded, native_endian) ^ mem.nativeTo(u64, self.st[i / 8], endian);
144188 mem.writeInt(u64, &padded, x, native_endian);
......@@ -147,16 +191,30 @@ pub fn State(comptime endian: std.builtin.Endian) type {
147191 }
148192
149193 /// 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
150200 pub fn clear(self: *Self, from: usize, to: usize) void {
151201 @memset(self.st[from / 8 .. (to + 7) / 8], 0);
152202 }
153203
154204 /// 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.
155208 pub fn secureZero(self: *Self) void {
156 std.crypto.secureZero(u64, &self.st);
209 crypto.secureZero(u64, &self.st);
157210 }
158211
159212 /// 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
160218 pub fn permuteR(state: *Self, comptime rounds: u4) void {
161219 const rks = [16]u64{ 0x3c, 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
162220 inline for (rks[rks.len - rounds ..]) |rk| {
......@@ -165,12 +223,20 @@ pub fn State(comptime endian: std.builtin.Endian) type {
165223 }
166224
167225 /// Apply a full-round permutation to the state.
226 ///
227 /// Applies the standard 12-round Ascon permutation.
168228 pub fn permute(state: *Self) void {
169229 state.permuteR(12);
170230 }
171231
172232 /// Apply a permutation to the state and prevent backtracking.
173 /// The rate is expressed in bytes and must be a multiple of the word size (8).
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).
174240 pub fn permuteRatchet(state: *Self, comptime rounds: u4, comptime rate: u6) void {
175241 const capacity = block_bytes - rate;
176242 debug.assert(capacity > 0 and capacity % 8 == 0); // capacity must be a multiple of 64 bits
......@@ -180,7 +246,12 @@ pub fn State(comptime endian: std.builtin.Endian) type {
180246 inline for (mask, state.st[state.st.len - mask.len ..]) |m, *x| x.* ^= m;
181247 }
182248
183 // Core Ascon permutation.
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.
184255 fn round(state: *Self, rk: u64) void {
185256 const x = &state.st;
186257 x[2] ^= rk;
......@@ -216,7 +287,8 @@ pub fn State(comptime endian: std.builtin.Endian) type {
216287
217288test "ascon" {
218289 const Ascon = State(.big);
219 const bytes = [_]u8{0x01} ** Ascon.block_bytes;
290 var bytes: [Ascon.block_bytes]u8 = undefined;
291 @memset(&bytes, 1);
220292 var st = Ascon.init(bytes);
221293 var out: [Ascon.block_bytes]u8 = undefined;
222294 st.permute();
......@@ -237,3 +309,1002 @@ test "ascon" {
237309 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 };
238310 try testing.expectEqualSlices(u8, &expected4, &out);
239311}
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 const key = [_]u8{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
970 const nonce = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
971
972 // Test with empty plaintext
973 {
974 const plaintext = "";
975 const ad = "metadata";
976 var ciphertext: [plaintext.len]u8 = undefined;
977 var tag: [16]u8 = undefined;
978
979 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
980
981 var decrypted: [plaintext.len]u8 = undefined;
982 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
983 try testing.expectEqualStrings(plaintext, &decrypted);
984 }
985
986 // Test with small plaintext
987 {
988 const plaintext = "Short";
989 const ad = "";
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 longer plaintext and associated data
1001 {
1002 const plaintext = "This is a longer message to test the round trip encryption and decryption process";
1003 const ad = "Additional authenticated data that is not encrypted but is authenticated";
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 authentication failure with tampered ciphertext
1015 {
1016 const plaintext = "Tamper test";
1017 const ad = "metadata";
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 // Tamper with ciphertext
1024 ciphertext[0] ^= 0xFF;
1025
1026 var decrypted: [plaintext.len]u8 = undefined;
1027 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1028 try testing.expectError(error.AuthenticationFailed, result);
1029 }
1030
1031 // Test authentication failure with wrong tag
1032 {
1033 const plaintext = "Tag test";
1034 const ad = "metadata";
1035 var ciphertext: [plaintext.len]u8 = undefined;
1036 var tag: [16]u8 = undefined;
1037
1038 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1039
1040 // Tamper with tag
1041 var wrong_tag = tag;
1042 wrong_tag[0] ^= 0xFF;
1043
1044 var decrypted: [plaintext.len]u8 = undefined;
1045 const result = AsconAead128.decrypt(&decrypted, &ciphertext, wrong_tag, ad, nonce, key);
1046 try testing.expectError(error.AuthenticationFailed, result);
1047 }
1048
1049 // Test authentication failure with wrong associated data
1050 {
1051 const plaintext = "AD test";
1052 const ad = "original";
1053 var ciphertext: [plaintext.len]u8 = undefined;
1054 var tag: [16]u8 = undefined;
1055
1056 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1057
1058 var decrypted: [plaintext.len]u8 = undefined;
1059 const wrong_ad = "modified";
1060 const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, wrong_ad, nonce, key);
1061 try testing.expectError(error.AuthenticationFailed, result);
1062 }
1063}
1064
1065// Test vectors from NIST SP 800-232 / ascon-c reference implementation
1066test "Ascon-AEAD128 official test vectors" {
1067
1068 // Test vector 1: Empty PT, Empty AD
1069 {
1070 var key: [16]u8 = undefined;
1071 var nonce: [16]u8 = undefined;
1072 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1073 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1074
1075 const plaintext = "";
1076 const ad = "";
1077 var ciphertext: [plaintext.len]u8 = undefined;
1078 var tag: [16]u8 = undefined;
1079
1080 AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
1081
1082 var expected_tag: [16]u8 = undefined;
1083 _ = std.fmt.hexToBytes(&expected_tag, "4F9C278211BEC9316BF68F46EE8B2EC6") catch unreachable;
1084 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1085 }
1086
1087 // Test vector 2: Empty PT, AD = "30"
1088 {
1089 var key: [16]u8 = undefined;
1090 var nonce: [16]u8 = undefined;
1091 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1092 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1093
1094 const plaintext = "";
1095 var ad: [1]u8 = undefined;
1096 _ = std.fmt.hexToBytes(&ad, "30") catch unreachable;
1097 var ciphertext: [plaintext.len]u8 = undefined;
1098 var tag: [16]u8 = undefined;
1099
1100 AsconAead128.encrypt(&ciphertext, &tag, plaintext, &ad, nonce, key);
1101
1102 var expected_tag: [16]u8 = undefined;
1103 _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;
1104 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1105 }
1106
1107 // Test vector 34: Single byte plaintext 0x20
1108 {
1109 var key: [16]u8 = undefined;
1110 var nonce: [16]u8 = undefined;
1111 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1112 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1113
1114 var plaintext: [1]u8 = undefined;
1115 _ = std.fmt.hexToBytes(&plaintext, "20") catch unreachable;
1116 const ad = "";
1117 var ciphertext: [1]u8 = undefined;
1118 var tag: [16]u8 = undefined;
1119
1120 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1121
1122 var expected_ct: [1]u8 = undefined;
1123 _ = std.fmt.hexToBytes(&expected_ct, "E8") catch unreachable;
1124 var expected_tag: [16]u8 = undefined;
1125 _ = std.fmt.hexToBytes(&expected_tag, "DD576ABA1CD3E6FC704DE02AEDB79588") catch unreachable;
1126
1127 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1128 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1129
1130 // Verify decryption
1131 var decrypted: [1]u8 = undefined;
1132 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1133 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1134 }
1135
1136 // Test vector with 3-byte plaintext
1137 {
1138 var key: [16]u8 = undefined;
1139 var nonce: [16]u8 = undefined;
1140 _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
1141 _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
1142
1143 var plaintext: [3]u8 = undefined;
1144 _ = std.fmt.hexToBytes(&plaintext, "202122") catch unreachable;
1145 const ad = "";
1146 var ciphertext: [3]u8 = undefined;
1147 var tag: [16]u8 = undefined;
1148
1149 AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
1150
1151 var expected_ct: [3]u8 = undefined;
1152 _ = std.fmt.hexToBytes(&expected_ct, "E8C3DE") catch unreachable;
1153 var expected_tag: [16]u8 = undefined;
1154 _ = std.fmt.hexToBytes(&expected_tag, "AF8E12816B8EDF39AD1571A9492B7CA2") catch unreachable;
1155
1156 try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
1157 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1158
1159 // Verify decryption
1160 var decrypted: [3]u8 = undefined;
1161 try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
1162 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
1163 }
1164}
1165
1166test "Ascon-Hash256 official test vectors" {
1167
1168 // Test vector 1: Empty message
1169 {
1170 const message = "";
1171 var hash: [32]u8 = undefined;
1172 AsconHash256.hash(message, &hash, .{});
1173
1174 var expected: [32]u8 = undefined;
1175 _ = std.fmt.hexToBytes(&expected, "0B3BE5850F2F6B98CAF29F8FDEA89B64A1FA70AA249B8F839BD53BAA304D92B2") catch unreachable;
1176 try testing.expectEqualSlices(u8, &expected, &hash);
1177 }
1178
1179 // Test vector 2: Single byte 0x00
1180 {
1181 const message = [_]u8{0x00};
1182 var hash: [32]u8 = undefined;
1183 AsconHash256.hash(&message, &hash, .{});
1184
1185 var expected: [32]u8 = undefined;
1186 _ = std.fmt.hexToBytes(&expected, "0728621035AF3ED2BCA03BF6FDE900F9456F5330E4B5EE23E7F6A1E70291BC80") catch unreachable;
1187 try testing.expectEqualSlices(u8, &expected, &hash);
1188 }
1189
1190 // Test vector 3: 0x00, 0x01
1191 {
1192 const message = [_]u8{ 0x00, 0x01 };
1193 var hash: [32]u8 = undefined;
1194 AsconHash256.hash(&message, &hash, .{});
1195
1196 var expected: [32]u8 = undefined;
1197 _ = std.fmt.hexToBytes(&expected, "6115E7C9C4081C2797FC8FE1BC57A836AFA1C5381E556DD583860CA2DFB48DD2") catch unreachable;
1198 try testing.expectEqualSlices(u8, &expected, &hash);
1199 }
1200
1201 // Test vector 4: 0x00, 0x01, 0x02
1202 {
1203 const message = [_]u8{ 0x00, 0x01, 0x02 };
1204 var hash: [32]u8 = undefined;
1205 AsconHash256.hash(&message, &hash, .{});
1206
1207 var expected: [32]u8 = undefined;
1208 _ = std.fmt.hexToBytes(&expected, "265AB89A609F5A05DCA57E83FBBA700F9A2D2C4211BA4CC9F0A1A369E17B915C") catch unreachable;
1209 try testing.expectEqualSlices(u8, &expected, &hash);
1210 }
1211
1212 // Test vector 5: 0x00..0x03
1213 {
1214 const message = [_]u8{ 0x00, 0x01, 0x02, 0x03 };
1215 var hash: [32]u8 = undefined;
1216 AsconHash256.hash(&message, &hash, .{});
1217
1218 var expected: [32]u8 = undefined;
1219 _ = std.fmt.hexToBytes(&expected, "D7E4C7ED9B8A325CD08B9EF259F8877054ECD8304FE1B2D7FD847137DF6727EE") catch unreachable;
1220 try testing.expectEqualSlices(u8, &expected, &hash);
1221 }
1222}
1223
1224test "Ascon-XOF128 official test vectors" {
1225
1226 // Test vector 1: Empty message, 64-byte output
1227 {
1228 var xof = AsconXof128.init(.{});
1229 xof.update("");
1230
1231 var output: [64]u8 = undefined;
1232 xof.squeeze(&output);
1233
1234 var expected: [64]u8 = undefined;
1235 _ = std.fmt.hexToBytes(&expected, "473D5E6164F58B39DFD84AACDB8AE42EC2D91FED33388EE0D960D9B3993295C6AD77855A5D3B13FE6AD9E6098988373AF7D0956D05A8F1665D2C67D1A3AD10FF") catch unreachable;
1236 try testing.expectEqualSlices(u8, &expected, &output);
1237 }
1238
1239 // Test vector 2: Single byte 0x00, 64-byte output
1240 {
1241 var xof = AsconXof128.init(.{});
1242 const msg = [_]u8{0x00};
1243 xof.update(&msg);
1244
1245 var output: [64]u8 = undefined;
1246 xof.squeeze(&output);
1247
1248 var expected: [64]u8 = undefined;
1249 _ = std.fmt.hexToBytes(&expected, "51430E0438ECDF642B393630D977625F5F337656BA58AB1E960784AC32A16E0D446405551F5469384F8EA283CF12E64FA72C426BFEBAEA3AA1529E2C4AB23A2F") catch unreachable;
1250 try testing.expectEqualSlices(u8, &expected, &output);
1251 }
1252
1253 // Test vector 3: 0x00, 0x01, 64-byte output
1254 {
1255 var xof = AsconXof128.init(.{});
1256 const msg = [_]u8{ 0x00, 0x01 };
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, "A05383077AF971D3830BD37E7B981497A773D441DB077C6494CC73125953846EB6427FBA4CD308FF90A11385D51101341BF5379249217BFDACE9CCA1148CC966") catch unreachable;
1264 try testing.expectEqualSlices(u8, &expected, &output);
1265 }
1266}
1267
1268test "Ascon-CXOF128 official test vectors" {
1269
1270 // Test vector 1: Empty message, empty customization, 64-byte output
1271 {
1272 var xof = AsconCxof128.init(.{});
1273 xof.update("");
1274
1275 var output: [64]u8 = undefined;
1276 xof.squeeze(&output);
1277
1278 var expected: [64]u8 = undefined;
1279 _ = std.fmt.hexToBytes(&expected, "4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74") catch unreachable;
1280 try testing.expectEqualSlices(u8, &expected, &output);
1281 }
1282
1283 // Test vector 2: Empty message, customization = 0x10, 64-byte output
1284 {
1285 const custom = [_]u8{0x10};
1286 var xof = AsconCxof128.init(.{ .custom = &custom });
1287 xof.update("");
1288
1289 var output: [64]u8 = undefined;
1290 xof.squeeze(&output);
1291
1292 var expected: [64]u8 = undefined;
1293 _ = std.fmt.hexToBytes(&expected, "0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6") catch unreachable;
1294 try testing.expectEqualSlices(u8, &expected, &output);
1295 }
1296
1297 // Test vector 3: Empty message, customization = 0x10, 0x11, 64-byte output
1298 {
1299 const custom = [_]u8{ 0x10, 0x11 };
1300 var xof = AsconCxof128.init(.{ .custom = &custom });
1301 xof.update("");
1302
1303 var output: [64]u8 = undefined;
1304 xof.squeeze(&output);
1305
1306 var expected: [64]u8 = undefined;
1307 _ = std.fmt.hexToBytes(&expected, "D1106C7622E79FE955BD9D79E03B918E770FE0E0CDDDE28BEB924B02C5FC936B33ACCA299C89ECA5D71886CBBFA4D54A21C55FDE2B679F5E2488063A1719DC32") catch unreachable;
1308 try testing.expectEqualSlices(u8, &expected, &output);
1309 }
1310}
lib/std/crypto/benchmark.zig+19-1
......@@ -19,6 +19,7 @@ const Crypto = struct {
1919};
2020
2121const hashes = [_]Crypto{
22 Crypto{ .ty = crypto.hash.ascon.AsconHash256, .name = "ascon-256" },
2223 Crypto{ .ty = crypto.hash.Md5, .name = "md5" },
2324 Crypto{ .ty = crypto.hash.Sha1, .name = "sha1" },
2425 Crypto{ .ty = crypto.hash.sha2.Sha256, .name = "sha256" },
......@@ -283,6 +284,7 @@ pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_i
283284}
284285
285286const aeads = [_]Crypto{
287 Crypto{ .ty = crypto.aead.ascon.AsconAead128, .name = "ascon-aead-128" },
286288 Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
287289 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
288290 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
......@@ -458,7 +460,9 @@ fn mode(comptime x: comptime_int) comptime_int {
458460}
459461
460462pub fn main() !void {
461 const stdout = std.fs.File.stdout().deprecatedWriter();
463 var stdout_buffer: [4096]u8 = undefined;
464 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
465 const stdout = &stdout_writer.interface;
462466
463467 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
464468 defer arena.deinit();
......@@ -471,6 +475,7 @@ pub fn main() !void {
471475 while (i < args.len) : (i += 1) {
472476 if (std.mem.eql(u8, args[i], "--mode")) {
473477 try stdout.print("{}\n", .{builtin.mode});
478 try stdout.flush();
474479 return;
475480 } else if (std.mem.eql(u8, args[i], "--seed")) {
476481 i += 1;
......@@ -502,6 +507,7 @@ pub fn main() !void {
502507 if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
503508 const throughput = try benchmarkHash(H.ty, mode(128 * MiB));
504509 try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
510 try stdout.flush();
505511 }
506512 }
507513
......@@ -509,6 +515,7 @@ pub fn main() !void {
509515 if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) {
510516 const throughput = try benchmarkMac(M.ty, mode(128 * MiB));
511517 try stdout.print("{s:>17}: {:10} MiB/s\n", .{ M.name, throughput / (1 * MiB) });
518 try stdout.flush();
512519 }
513520 }
514521
......@@ -516,6 +523,7 @@ pub fn main() !void {
516523 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
517524 const throughput = try benchmarkKeyExchange(E.ty, mode(1000));
518525 try stdout.print("{s:>17}: {:10} exchanges/s\n", .{ E.name, throughput });
526 try stdout.flush();
519527 }
520528 }
521529
......@@ -523,6 +531,7 @@ pub fn main() !void {
523531 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
524532 const throughput = try benchmarkSignature(E.ty, mode(1000));
525533 try stdout.print("{s:>17}: {:10} signatures/s\n", .{ E.name, throughput });
534 try stdout.flush();
526535 }
527536 }
528537
......@@ -530,6 +539,7 @@ pub fn main() !void {
530539 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
531540 const throughput = try benchmarkSignatureVerification(E.ty, mode(1000));
532541 try stdout.print("{s:>17}: {:10} verifications/s\n", .{ E.name, throughput });
542 try stdout.flush();
533543 }
534544 }
535545
......@@ -537,6 +547,7 @@ pub fn main() !void {
537547 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
538548 const throughput = try benchmarkBatchSignatureVerification(E.ty, mode(1000));
539549 try stdout.print("{s:>17}: {:10} verifications/s (batch)\n", .{ E.name, throughput });
550 try stdout.flush();
540551 }
541552 }
542553
......@@ -544,6 +555,7 @@ pub fn main() !void {
544555 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
545556 const throughput = try benchmarkAead(E.ty, mode(128 * MiB));
546557 try stdout.print("{s:>17}: {:10} MiB/s\n", .{ E.name, throughput / (1 * MiB) });
558 try stdout.flush();
547559 }
548560 }
549561
......@@ -551,6 +563,7 @@ pub fn main() !void {
551563 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
552564 const throughput = try benchmarkAes(E.ty, mode(100000000));
553565 try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
566 try stdout.flush();
554567 }
555568 }
556569
......@@ -558,6 +571,7 @@ pub fn main() !void {
558571 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
559572 const throughput = try benchmarkAes8(E.ty, mode(10000000));
560573 try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
574 try stdout.flush();
561575 }
562576 }
563577
......@@ -565,6 +579,7 @@ pub fn main() !void {
565579 if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
566580 const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64));
567581 try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
582 try stdout.flush();
568583 }
569584 }
570585
......@@ -572,6 +587,7 @@ pub fn main() !void {
572587 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
573588 const throughput = try benchmarkKem(E.ty, mode(1000));
574589 try stdout.print("{s:>17}: {:10} encaps/s\n", .{ E.name, throughput });
590 try stdout.flush();
575591 }
576592 }
577593
......@@ -579,6 +595,7 @@ pub fn main() !void {
579595 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
580596 const throughput = try benchmarkKemDecaps(E.ty, mode(25000));
581597 try stdout.print("{s:>17}: {:10} decaps/s\n", .{ E.name, throughput });
598 try stdout.flush();
582599 }
583600 }
584601
......@@ -586,6 +603,7 @@ pub fn main() !void {
586603 if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
587604 const throughput = try benchmarkKemKeyGen(E.ty, mode(25000));
588605 try stdout.print("{s:>17}: {:10} keygen/s\n", .{ E.name, throughput });
606 try stdout.flush();
589607 }
590608 }
591609}