Commit 4406127cca

Frank Denis <124872+jedisct1@users.noreply.github.com>
2025-09-18 04:59:55
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.
1 parent 6dd0270
Changed files (3)
lib/std/crypto/ascon.zig
@@ -9,6 +9,7 @@
 
 const std = @import("std");
 const builtin = @import("builtin");
+const crypto = std.crypto;
 const debug = std.debug;
 const mem = std.mem;
 const testing = std.testing;
@@ -34,6 +35,11 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         st: Block,
 
         /// Initialize the state from a slice of bytes.
+        ///
+        /// Parameters:
+        ///   - initial_state: A 40-byte array to initialize the state
+        ///
+        /// Returns: A new State initialized with the provided bytes
         pub fn init(initial_state: [block_bytes]u8) Self {
             var state = Self{ .st = undefined };
             @memcpy(state.asBytes(), &initial_state);
@@ -42,11 +48,18 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// Initialize the state from u64 words in native endianness.
+        ///
+        /// Parameters:
+        ///   - initial_state: An array of 5 u64 words in native endianness
+        ///
+        /// Returns: A new State with the provided words
         pub fn initFromWords(initial_state: [5]u64) Self {
             return .{ .st = initial_state };
         }
 
-        /// Initialize the state for Ascon XOF
+        /// Initialize the state for Ascon XOF.
+        ///
+        /// Returns: A new State initialized with the Ascon XOF initialization vector
         pub fn initXof() Self {
             return Self{ .st = Block{
                 0xb57e273b814cd416,
@@ -57,7 +70,9 @@ pub fn State(comptime endian: std.builtin.Endian) type {
             } };
         }
 
-        /// Initialize the state for Ascon XOFa
+        /// Initialize the state for Ascon XOFa.
+        ///
+        /// Returns: A new State initialized with the Ascon XOFa initialization vector
         pub fn initXofA() Self {
             return Self{ .st = Block{
                 0x44906568b77b9832,
@@ -69,11 +84,15 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// A representation of the state as bytes. The byte order is architecture-dependent.
+        ///
+        /// Returns: A pointer to the state's internal byte representation
         pub fn asBytes(self: *Self) *[block_bytes]u8 {
             return mem.asBytes(&self.st);
         }
 
         /// Byte-swap the entire state if the architecture doesn't match the required endianness.
+        ///
+        /// This ensures the state is in the correct endianness for the current platform.
         pub fn endianSwap(self: *Self) void {
             for (&self.st) |*w| {
                 w.* = mem.toNative(u64, w.*, endian);
@@ -81,19 +100,28 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// Set bytes starting at the beginning of the state.
+        ///
+        /// Parameters:
+        ///   - bytes: Slice of bytes to write into the state (up to 40 bytes)
+        ///
+        /// Note: If bytes.len < 40, remaining state words are zero-padded
         pub fn setBytes(self: *Self, bytes: []const u8) void {
             var i: usize = 0;
             while (i + 8 <= bytes.len) : (i += 8) {
                 self.st[i / 8] = mem.readInt(u64, bytes[i..][0..8], endian);
             }
             if (i < bytes.len) {
-                var padded = [_]u8{0} ** 8;
+                var padded: [8]u8 = @splat(0);
                 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
                 self.st[i / 8] = mem.readInt(u64, padded[0..], endian);
             }
         }
 
         /// XOR a byte into the state at a given offset.
+        ///
+        /// Parameters:
+        ///   - byte: The byte to XOR into the state
+        ///   - offset: The byte offset in the state (0-39)
         pub fn addByte(self: *Self, byte: u8, offset: usize) void {
             const z = switch (endian) {
                 .big => 64 - 8 - 8 * @as(u6, @truncate(offset % 8)),
@@ -103,32 +131,48 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// XOR bytes into the beginning of the state.
+        ///
+        /// Parameters:
+        ///   - bytes: Slice of bytes to XOR into the state (up to 40 bytes)
+        ///
+        /// Note: Handles partial blocks with zero-padding
         pub fn addBytes(self: *Self, bytes: []const u8) void {
             var i: usize = 0;
             while (i + 8 <= bytes.len) : (i += 8) {
                 self.st[i / 8] ^= mem.readInt(u64, bytes[i..][0..8], endian);
             }
             if (i < bytes.len) {
-                var padded = [_]u8{0} ** 8;
+                var padded: [8]u8 = @splat(0);
                 @memcpy(padded[0 .. bytes.len - i], bytes[i..]);
                 self.st[i / 8] ^= mem.readInt(u64, padded[0..], endian);
             }
         }
 
         /// Extract the first bytes of the state.
+        ///
+        /// Parameters:
+        ///   - out: Output buffer to receive the extracted bytes
+        ///
+        /// Note: Extracts up to out.len bytes from the beginning of the state
         pub fn extractBytes(self: *Self, out: []u8) void {
             var i: usize = 0;
             while (i + 8 <= out.len) : (i += 8) {
                 mem.writeInt(u64, out[i..][0..8], self.st[i / 8], endian);
             }
             if (i < out.len) {
-                var padded = [_]u8{0} ** 8;
+                var padded: [8]u8 = @splat(0);
                 mem.writeInt(u64, padded[0..], self.st[i / 8], endian);
                 @memcpy(out[i..], padded[0 .. out.len - i]);
             }
         }
 
         /// XOR the first bytes of the state into a slice of bytes.
+        ///
+        /// Parameters:
+        ///   - out: Output buffer for the XORed result
+        ///   - in: Input bytes to XOR with the state
+        ///
+        /// Requires: out.len == in.len
         pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void {
             debug.assert(out.len == in.len);
 
@@ -138,7 +182,7 @@ pub fn State(comptime endian: std.builtin.Endian) type {
                 mem.writeInt(u64, out[i..][0..8], x, native_endian);
             }
             if (i < in.len) {
-                var padded = [_]u8{0} ** 8;
+                var padded: [8]u8 = @splat(0);
                 @memcpy(padded[0 .. in.len - i], in[i..]);
                 const x = mem.readInt(u64, &padded, native_endian) ^ mem.nativeTo(u64, self.st[i / 8], endian);
                 mem.writeInt(u64, &padded, x, native_endian);
@@ -147,16 +191,30 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// Set the words storing the bytes of a given range to zero.
+        ///
+        /// Parameters:
+        ///   - from: Starting byte offset (inclusive)
+        ///   - to: Ending byte offset (inclusive)
+        ///
+        /// Note: Clears complete words that contain the specified byte range
         pub fn clear(self: *Self, from: usize, to: usize) void {
             @memset(self.st[from / 8 .. (to + 7) / 8], 0);
         }
 
         /// Clear the entire state, disabling compiler optimizations.
+        ///
+        /// Uses secure zeroing to prevent the compiler from optimizing away
+        /// the clearing operation. Use for sensitive data cleanup.
         pub fn secureZero(self: *Self) void {
-            std.crypto.secureZero(u64, &self.st);
+            crypto.secureZero(u64, &self.st);
         }
 
         /// Apply a reduced-round permutation to the state.
+        ///
+        /// Parameters:
+        ///   - rounds: Number of rounds to apply (1-12)
+        ///
+        /// Note: Uses the last `rounds` round constants from the full set
         pub fn permuteR(state: *Self, comptime rounds: u4) void {
             const rks = [16]u64{ 0x3c, 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b };
             inline for (rks[rks.len - rounds ..]) |rk| {
@@ -165,12 +223,20 @@ pub fn State(comptime endian: std.builtin.Endian) type {
         }
 
         /// Apply a full-round permutation to the state.
+        ///
+        /// Applies the standard 12-round Ascon permutation.
         pub fn permute(state: *Self) void {
             state.permuteR(12);
         }
 
         /// Apply a permutation to the state and prevent backtracking.
-        /// The rate is expressed in bytes and must be a multiple of the word size (8).
+        ///
+        /// Parameters:
+        ///   - rounds: Number of permutation rounds to apply
+        ///   - rate: Rate in bytes (must be multiple of 8, < 40)
+        ///
+        /// The capacity portion is XORed before and after permutation to
+        /// provide forward security (ratcheting).
         pub fn permuteRatchet(state: *Self, comptime rounds: u4, comptime rate: u6) void {
             const capacity = block_bytes - rate;
             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 {
             inline for (mask, state.st[state.st.len - mask.len ..]) |m, *x| x.* ^= m;
         }
 
-        // Core Ascon permutation.
+        /// Core Ascon permutation round function.
+        ///
+        /// Parameters:
+        ///   - rk: Round constant for this round
+        ///
+        /// Implements one round of the Ascon permutation with S-box and linear layer.
         fn round(state: *Self, rk: u64) void {
             const x = &state.st;
             x[2] ^= rk;
@@ -216,7 +287,8 @@ pub fn State(comptime endian: std.builtin.Endian) type {
 
 test "ascon" {
     const Ascon = State(.big);
-    const bytes = [_]u8{0x01} ** Ascon.block_bytes;
+    var bytes: [Ascon.block_bytes]u8 = undefined;
+    @memset(&bytes, 1);
     var st = Ascon.init(bytes);
     var out: [Ascon.block_bytes]u8 = undefined;
     st.permute();
@@ -237,3 +309,1002 @@ test "ascon" {
     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 };
     try testing.expectEqualSlices(u8, &expected4, &out);
 }
+
+const AsconState = State(.little);
+const AuthenticationError = crypto.errors.AuthenticationError;
+
+/// Ascon-AEAD128 as specified in NIST SP 800-232 Section 4
+pub const AsconAead128 = struct {
+    pub const tag_length = 16;
+    pub const nonce_length = 16;
+    pub const key_length = 16;
+    pub const block_length = 16;
+
+    const AeadState = struct {
+        st: AsconState,
+        k0: u64,
+        k1: u64,
+
+        /// Initialize AEAD state with key and nonce.
+        ///
+        /// Parameters:
+        ///   - key: 16-byte secret key
+        ///   - nonce: 16-byte nonce
+        ///
+        /// Returns: Initialized AEAD state ready for processing
+        fn init(key: [16]u8, nonce: [16]u8) AeadState {
+            const k0 = mem.readInt(u64, key[0..8], .little);
+            const k1 = mem.readInt(u64, key[8..16], .little);
+            const n0 = mem.readInt(u64, nonce[0..8], .little);
+            const n1 = mem.readInt(u64, nonce[8..16], .little);
+
+            // IV for Ascon-AEAD128 (Ascon-128a)
+            const iv: u64 = 0x00001000808C0001;
+            const words: [5]u64 = .{ iv, k0, k1, n0, n1 };
+
+            var st = AsconState.initFromWords(words);
+            st.permuteR(12);
+
+            st.st[3] ^= k0;
+            st.st[4] ^= k1;
+
+            return AeadState{ .st = st, .k0 = k0, .k1 = k1 };
+        }
+
+        /// Process associated data for authentication.
+        ///
+        /// Parameters:
+        ///   - ad: Associated data to authenticate
+        ///
+        /// Updates the state to include AD in authentication tag computation.
+        fn processAd(self: *AeadState, ad: []const u8) void {
+            if (ad.len == 0) return;
+
+            var i: usize = 0;
+            // Process full 128-bit blocks
+            while (i + 16 <= ad.len) : (i += 16) {
+                self.st.addBytes(ad[i..][0..16]);
+                self.st.permuteR(8);
+            }
+
+            // Process final partial AD block
+            const adrem = ad.len - i;
+            if (adrem > 0) {
+                if (adrem >= 8) {
+                    var buf: [8]u8 = @splat(0);
+                    @memcpy(buf[0..8], ad[i..][0..8]);
+                    self.st.st[0] ^= mem.readInt(u64, &buf, .little);
+
+                    buf = @splat(0);
+                    @memcpy(buf[0 .. adrem - 8], ad[i + 8 ..]);
+                    buf[adrem - 8] = 0x01;
+                    self.st.st[1] ^= mem.readInt(u64, &buf, .little);
+                } else {
+                    var buf: [8]u8 = @splat(0);
+                    @memcpy(buf[0..adrem], ad[i..]);
+                    buf[adrem] = 0x01;
+                    self.st.st[0] ^= mem.readInt(u64, &buf, .little);
+                }
+                self.st.permuteR(8);
+            }
+        }
+
+        /// Finalize the AEAD operation and prepare tag.
+        ///
+        /// Applies final permutation and XORs key for tag generation.
+        fn finalize(self: *AeadState) void {
+            // XOR key before final permutation
+            self.st.st[2] ^= self.k0;
+            self.st.st[3] ^= self.k1;
+            self.st.permuteR(12);
+
+            // XOR key again for tag generation
+            self.st.st[3] ^= self.k0;
+            self.st.st[4] ^= self.k1;
+        }
+    };
+
+    /// Encrypt a message with Ascon-AEAD128.
+    ///
+    /// Parameters:
+    ///   - c: Output buffer for ciphertext (must be same length as m)
+    ///   - tag: Output buffer for authentication tag (16 bytes)
+    ///   - m: Plaintext message to encrypt
+    ///   - ad: Associated data to authenticate but not encrypt
+    ///   - npub: Public nonce (16 bytes, must be unique per message)
+    ///   - k: Secret key (16 bytes)
+    ///
+    /// Note: The ciphertext and tag must be transmitted together for decryption
+    pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
+        debug.assert(c.len == m.len);
+
+        var state = AeadState.init(k, npub);
+
+        // Process associated data
+        state.processAd(ad);
+
+        // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
+        state.st.st[4] ^= 0x8000000000000000;
+
+        // Process plaintext
+        var i: usize = 0;
+        while (i + 16 <= m.len) : (i += 16) {
+            state.st.addBytes(m[i..][0..16]);
+            state.st.extractBytes(c[i..][0..16]);
+            state.st.permuteR(8);
+        }
+
+        // Process final partial block
+        const remaining = m.len - i;
+        if (remaining > 8) {
+            // Split between two words
+            state.st.addBytes(m[i..][0..8]);
+            state.st.extractBytes(c[i..][0..8]);
+
+            var buf: [8]u8 = @splat(0);
+            @memcpy(buf[0 .. remaining - 8], m[i + 8 ..]);
+            const m1 = mem.readInt(u64, &buf, .little);
+            state.st.st[1] ^= m1;
+            mem.writeInt(u64, buf[0..], state.st.st[1], .little);
+            @memcpy(c[i + 8 ..], buf[0 .. remaining - 8]);
+
+            // Add padding
+            state.st.st[1] ^= @as(u64, 0x01) << @intCast((remaining - 8) * 8);
+        } else if (remaining == 8) {
+            // Exactly 8 bytes - all in word 0, padding in word 1
+            state.st.addBytes(m[i..][0..8]);
+            state.st.extractBytes(c[i..][0..8]);
+
+            // Add padding to word 1 at position 0
+            state.st.st[1] ^= 0x01;
+        } else if (remaining > 0) {
+            // All in first word
+            var temp: [8]u8 = @splat(0);
+            @memcpy(temp[0..remaining], m[i..]);
+            state.st.addBytes(&temp);
+            state.st.extractBytes(c[i..][0..remaining]);
+            // Add padding
+            temp = @splat(0);
+            temp[remaining] = 0x01;
+            state.st.addBytes(&temp);
+            // Second word stays zero
+        } else {
+            // Empty message or exact multiple - add padding block
+            var padded: [16]u8 = @splat(0);
+            padded[0] = 0x01;
+            state.st.addBytes(&padded);
+        }
+
+        // Finalization
+        state.finalize();
+
+        // Extract tag
+        mem.writeInt(u64, tag[0..8], state.st.st[3], .little);
+        mem.writeInt(u64, tag[8..16], state.st.st[4], .little);
+    }
+
+    /// Decrypt a message with Ascon-AEAD128.
+    ///
+    /// Parameters:
+    ///   - m: Output buffer for plaintext (must be same length as c)
+    ///   - c: Ciphertext to decrypt
+    ///   - tag: Authentication tag (16 bytes)
+    ///   - ad: Associated data that was authenticated
+    ///   - npub: Public nonce used during encryption (16 bytes)
+    ///   - k: Secret key (16 bytes)
+    ///
+    /// Returns: AuthenticationError if tag verification fails
+    ///
+    /// Note: On authentication failure, the output buffer is securely zeroed
+    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 {
+        debug.assert(m.len == c.len);
+
+        var state = AeadState.init(k, npub);
+
+        // Process associated data
+        state.processAd(ad);
+
+        // Domain separation (DSEP = 0x80 at byte 7 in little-endian)
+        state.st.st[4] ^= 0x8000000000000000;
+
+        // Process ciphertext
+        var i: usize = 0;
+        while (i + 16 <= c.len) : (i += 16) {
+            const ct_block = c[i..][0..16].*; // Save ciphertext block for in-place operation support
+            state.st.xorBytes(m[i..][0..16], &ct_block);
+            state.st.setBytes(&ct_block);
+            state.st.permuteR(8);
+        }
+
+        // Final partial ciphertext block
+        const crem = c.len - i;
+        if (crem > 8) {
+            // Save ciphertext for in-place operation support
+            var saved_ct: [16]u8 = undefined;
+            @memcpy(saved_ct[0..crem], c[i..]);
+
+            const c0 = mem.readInt(u64, saved_ct[0..8], .little);
+            state.st.st[0] ^= c0;
+            mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
+            state.st.st[0] = c0;
+
+            var buf: [8]u8 = @splat(0);
+            @memcpy(buf[0 .. crem - 8], saved_ct[8..][0 .. crem - 8]);
+            const c1 = mem.readInt(u64, &buf, .little);
+            const m1 = state.st.st[1] ^ c1;
+            mem.writeInt(u64, buf[0..], m1, .little);
+            @memcpy(m[i + 8 ..], buf[0 .. crem - 8]);
+
+            // Replace only the bytes we've read, keeping upper bytes intact
+            const mask = (@as(u64, 1) << @intCast((crem - 8) * 8)) - 1;
+            state.st.st[1] = (state.st.st[1] & ~mask) | (c1 & mask);
+
+            state.st.st[1] ^= @as(u64, 0x01) << @intCast((crem - 8) * 8);
+        } else if (crem == 8) {
+            // Exactly 8 bytes - process only word 0, add padding to word 1
+            const saved_ct = c[i..][0..8].*;
+
+            const c0 = mem.readInt(u64, &saved_ct, .little);
+            state.st.st[0] ^= c0;
+            mem.writeInt(u64, m[i..][0..8], state.st.st[0], .little);
+            state.st.st[0] = c0;
+
+            // Add padding to word 1 at position 0
+            state.st.st[1] ^= 0x01;
+        } else if (crem > 0) {
+            var buf: [8]u8 = @splat(0);
+            @memcpy(buf[0..crem], c[i..]);
+            const c0 = mem.readInt(u64, &buf, .little);
+            const m0 = state.st.st[0] ^ c0;
+            mem.writeInt(u64, buf[0..], m0, .little);
+            @memcpy(m[i..], buf[0..crem]);
+
+            // Replace only the bytes we've read, keeping upper bytes intact
+            const mask = (@as(u64, 1) << @intCast(crem * 8)) - 1;
+            state.st.st[0] = (state.st.st[0] & ~mask) | (c0 & mask);
+
+            state.st.st[0] ^= @as(u64, 0x01) << @intCast(crem * 8);
+        } else {
+            state.st.st[0] ^= 0x01;
+        }
+
+        // Finalization
+        state.finalize();
+
+        // Verify tag
+        var computed_tag: [tag_length]u8 = undefined;
+        mem.writeInt(u64, computed_tag[0..8], state.st.st[3], .little);
+        mem.writeInt(u64, computed_tag[8..16], state.st.st[4], .little);
+
+        if (!crypto.timing_safe.eql([tag_length]u8, tag, computed_tag)) {
+            crypto.secureZero(u8, m);
+            return error.AuthenticationFailed;
+        }
+    }
+};
+
+/// Ascon-Hash256 as specified in NIST SP 800-232 Section 5
+pub const AsconHash256 = struct {
+    pub const digest_length = 32;
+    pub const block_length = 8;
+
+    st: AsconState,
+
+    pub const Options = struct {};
+
+    /// Initialize a new Ascon-Hash256 hasher.
+    ///
+    /// Parameters:
+    ///   - options: Configuration options (currently unused)
+    ///
+    /// Returns: An initialized AsconHash256 hasher
+    pub fn init(options: Options) AsconHash256 {
+        _ = options;
+
+        // IV for Ascon-Hash256: 0x0000080100cc0002
+        const iv: u64 = 0x0000080100cc0002;
+        const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
+        var st = AsconState.initFromWords(words);
+        st.permuteR(12);
+        return AsconHash256{ .st = st };
+    }
+
+    /// Compute Ascon-Hash256 hash of input data in one call.
+    ///
+    /// Parameters:
+    ///   - b: Input data to hash
+    ///   - out: Output buffer for 32-byte hash digest
+    ///   - options: Configuration options (currently unused)
+    pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
+        var h = init(options);
+        h.update(b);
+        h.final(out);
+    }
+
+    /// Update the hash state with additional data.
+    ///
+    /// Parameters:
+    ///   - b: Data to add to the hash
+    ///
+    /// Note: Can be called multiple times before final()
+    pub fn update(self: *AsconHash256, b: []const u8) void {
+        var i: usize = 0;
+
+        // Process full 64-bit blocks
+        while (i + 8 <= b.len) : (i += 8) {
+            self.st.addBytes(b[i..][0..8]);
+            self.st.permuteR(12);
+        }
+
+        // Store partial block for finalization
+        if (i < b.len) {
+            var padded: [8]u8 = @splat(0);
+            const remaining = b.len - i;
+            @memcpy(padded[0..remaining], b[i..]);
+            padded[remaining] = 0x01;
+            self.st.addBytes(&padded);
+        } else {
+            // Add padding block
+            var padded: [8]u8 = @splat(0);
+            padded[0] = 0x01;
+            self.st.addBytes(&padded);
+        }
+    }
+
+    /// Finalize the hash and output the digest.
+    ///
+    /// Parameters:
+    ///   - out: Output buffer for 32-byte hash digest
+    ///
+    /// Note: After calling final(), the hasher should not be used again
+    pub fn final(self: *AsconHash256, out: *[digest_length]u8) void {
+        // Final permutation after padding
+        self.st.permuteR(12);
+
+        // Extract hash output (4 ร— 64 bits = 256 bits)
+        var h: [4]u64 = undefined;
+        for (0..4) |i| {
+            h[i] = self.st.st[0];
+            self.st.permuteR(12);
+        }
+
+        // Write output
+        for (0..4) |i| {
+            mem.writeInt(u64, out[i * 8 ..][0..8], h[i], .little);
+        }
+    }
+};
+
+/// Ascon-XOF128 as specified in NIST SP 800-232 Section 5
+pub const AsconXof128 = struct {
+    pub const block_length = 8;
+
+    st: AsconState,
+    squeezed: bool,
+
+    pub const Options = struct {};
+
+    /// Initialize a new Ascon-XOF128 extendable output function.
+    ///
+    /// Parameters:
+    ///   - options: Configuration options (currently unused)
+    ///
+    /// Returns: An initialized AsconXof128 instance
+    pub fn init(options: Options) AsconXof128 {
+        _ = options;
+
+        // IV for Ascon-XOF128: 0x0000080000cc0003
+        const iv: u64 = 0x0000080000cc0003;
+        const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
+        var st = AsconState.initFromWords(words);
+        st.permuteR(12);
+        return AsconXof128{ .st = st, .squeezed = false };
+    }
+
+    /// Hash a slice of bytes with variable-length output.
+    ///
+    /// Parameters:
+    ///   - bytes: Input data to hash
+    ///   - out: Output buffer (can be any length)
+    ///   - options: Configuration options (currently unused)
+    ///
+    /// Note: Convenience function that combines init, update, and squeeze
+    pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
+        var st = init(options);
+        st.update(bytes);
+        st.squeeze(out);
+    }
+
+    /// Update the XOF state with additional data.
+    ///
+    /// Parameters:
+    ///   - b: Data to absorb into the XOF state
+    ///
+    /// Note: Cannot be called after squeeze() has been called
+    pub fn update(self: *AsconXof128, b: []const u8) void {
+        debug.assert(!self.squeezed); // Cannot update after squeezing
+
+        var i: usize = 0;
+
+        // Process full 64-bit blocks
+        while (i + 8 <= b.len) : (i += 8) {
+            self.st.addBytes(b[i..][0..8]);
+            self.st.permuteR(12);
+        }
+
+        // Store partial block for finalization
+        if (i < b.len) {
+            var padded: [8]u8 = @splat(0);
+            const remaining = b.len - i;
+            @memcpy(padded[0..remaining], b[i..]);
+            padded[remaining] = 0x01;
+            self.st.addBytes(&padded);
+        } else {
+            // Add padding block
+            var padded: [8]u8 = @splat(0);
+            padded[0] = 0x01;
+            self.st.addBytes(&padded);
+        }
+    }
+
+    /// Squeeze output bytes from the XOF.
+    ///
+    /// Parameters:
+    ///   - out: Output buffer to fill with pseudorandom bytes
+    ///
+    /// Note: Can be called multiple times to generate more output.
+    /// After first call, no more data can be absorbed with update().
+    pub fn squeeze(self: *AsconXof128, out: []u8) void {
+        if (!self.squeezed) {
+            // First squeeze - apply final permutation
+            self.st.permuteR(12);
+            self.squeezed = true;
+        }
+
+        var i: usize = 0;
+        while (i < out.len) {
+            const to_copy = @min(8, out.len - i);
+            var block: [8]u8 = undefined;
+            mem.writeInt(u64, &block, self.st.st[0], .little);
+            @memcpy(out[i..][0..to_copy], block[0..to_copy]);
+            i += to_copy;
+
+            if (i < out.len) {
+                self.st.permuteR(12);
+            }
+        }
+    }
+};
+
+/// Ascon-CXOF128 as specified in NIST SP 800-232 Section 5
+pub const AsconCxof128 = struct {
+    pub const block_length = 8;
+    pub const max_custom_length = 256; // 2048 bits
+
+    st: AsconState,
+    squeezed: bool,
+
+    pub const Options = struct { custom: []const u8 = "" };
+
+    /// Initialize a new Ascon-CXOF128 customizable XOF.
+    ///
+    /// Parameters:
+    ///   - options: Configuration with optional customization string
+    ///     - custom: Customization string (max 256 bytes)
+    ///
+    /// Returns: An initialized AsconCxof128 instance
+    ///
+    /// Note: Different customization strings produce independent XOF instances
+    pub fn init(options: Options) AsconCxof128 {
+        debug.assert(options.custom.len <= max_custom_length);
+
+        // IV for Ascon-CXOF128: 0x0000080000cc0004
+        const iv: u64 = 0x0000080000cc0004;
+        const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
+        var st = AsconState.initFromWords(words);
+        st.permuteR(12);
+
+        var self = AsconCxof128{ .st = st, .squeezed = false };
+
+        // Process customization string - always process length and padding
+        // First block: length of customization string
+        const len_block = @as(u64, options.custom.len * 8); // Length in bits
+        self.st.st[0] ^= len_block;
+        self.st.permuteR(12);
+
+        if (options.custom.len > 0) {
+            // Process customization string blocks
+            var i: usize = 0;
+            while (i + 8 <= options.custom.len) : (i += 8) {
+                self.st.addBytes(options.custom[i..][0..8]);
+                self.st.permuteR(12);
+            }
+
+            // Process final partial block with padding
+            if (i < options.custom.len) {
+                var padded: [8]u8 = @splat(0);
+                const remaining = options.custom.len - i;
+                @memcpy(padded[0..remaining], options.custom[i..]);
+                padded[remaining] = 0x01;
+                self.st.addBytes(&padded);
+                self.st.permuteR(12);
+            } else {
+                // Add padding block
+                var padded: [8]u8 = @splat(0);
+                padded[0] = 0x01;
+                self.st.addBytes(&padded);
+                self.st.permuteR(12);
+            }
+        } else {
+            // Empty customization still needs padding
+            var padded: [8]u8 = @splat(0);
+            padded[0] = 0x01;
+            self.st.addBytes(&padded);
+            self.st.permuteR(12);
+        }
+
+        return self;
+    }
+
+    /// Hash a slice of bytes with customization and variable-length output.
+    ///
+    /// Parameters:
+    ///   - bytes: Input data to hash
+    ///   - out: Output buffer (can be any length)
+    ///   - options: Configuration with optional customization string
+    ///
+    /// Note: Convenience function that combines init, update, and squeeze
+    pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
+        var st = init(options);
+        st.update(bytes);
+        st.squeeze(out);
+    }
+
+    /// Update the CXOF state with additional data.
+    ///
+    /// Parameters:
+    ///   - b: Data to absorb into the CXOF state
+    ///
+    /// Note: Cannot be called after squeeze() has been called
+    pub fn update(self: *AsconCxof128, b: []const u8) void {
+        debug.assert(!self.squeezed);
+
+        var i: usize = 0;
+
+        // Process full 64-bit blocks
+        while (i + 8 <= b.len) : (i += 8) {
+            self.st.addBytes(b[i..][0..8]);
+            self.st.permuteR(12);
+        }
+
+        // Store partial block for finalization
+        if (i < b.len) {
+            var padded: [8]u8 = @splat(0);
+            const remaining = b.len - i;
+            @memcpy(padded[0..remaining], b[i..]);
+            padded[remaining] = 0x01;
+            self.st.addBytes(&padded);
+        } else {
+            // Add padding block
+            var padded: [8]u8 = @splat(0);
+            padded[0] = 0x01;
+            self.st.addBytes(&padded);
+        }
+    }
+
+    /// Squeeze output bytes from the customizable XOF.
+    ///
+    /// Parameters:
+    ///   - out: Output buffer to fill with pseudorandom bytes
+    ///
+    /// Note: Can be called multiple times to generate more output.
+    /// After first call, no more data can be absorbed with update().
+    pub fn squeeze(self: *AsconCxof128, out: []u8) void {
+        if (!self.squeezed) {
+            // First squeeze - apply final permutation
+            self.st.permuteR(12);
+            self.squeezed = true;
+        }
+
+        var i: usize = 0;
+        while (i < out.len) {
+            const to_copy = @min(8, out.len - i);
+            var block: [8]u8 = undefined;
+            mem.writeInt(u64, &block, self.st.st[0], .little);
+            @memcpy(out[i..][0..to_copy], block[0..to_copy]);
+            i += to_copy;
+
+            if (i < out.len) {
+                self.st.permuteR(12);
+            }
+        }
+    }
+};
+
+test "Ascon-Hash256 basic test" {
+    const message = "The quick brown fox jumps over the lazy dog";
+    var hash: [32]u8 = undefined;
+
+    AsconHash256.hash(message, &hash, .{});
+
+    // Verify hash is generated (exact value depends on test vectors)
+    try testing.expect(hash.len == 32);
+}
+
+test "Ascon-XOF128 basic test" {
+    var xof = AsconXof128.init(.{});
+    xof.update("Hello, ");
+    xof.update("World!");
+
+    var out1: [16]u8 = undefined;
+    xof.squeeze(&out1);
+
+    var out2: [32]u8 = undefined;
+    xof.squeeze(&out2);
+
+    // XOF outputs should be continuous - out2 should NOT match out1
+    // Each squeeze produces new output
+    try testing.expect(!mem.eql(u8, &out1, out2[0..16]));
+}
+
+test "Ascon-CXOF128 with customization" {
+    const custom = "MyCustomString";
+    var xof = AsconCxof128.init(.{ .custom = custom });
+    xof.update("Test message");
+
+    var out: [32]u8 = undefined;
+    xof.squeeze(&out);
+
+    // Different customization should give different output
+    var xof2 = AsconCxof128.init(.{ .custom = "DifferentCustom" });
+    xof2.update("Test message");
+
+    var out2: [32]u8 = undefined;
+    xof2.squeeze(&out2);
+
+    try testing.expect(!mem.eql(u8, &out, &out2));
+}
+
+test "Ascon-AEAD128 round trip with various data sizes" {
+    const key = [_]u8{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
+    const nonce = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
+
+    // Test with empty plaintext
+    {
+        const plaintext = "";
+        const ad = "metadata";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectEqualStrings(plaintext, &decrypted);
+    }
+
+    // Test with small plaintext
+    {
+        const plaintext = "Short";
+        const ad = "";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectEqualStrings(plaintext, &decrypted);
+    }
+
+    // Test with longer plaintext and associated data
+    {
+        const plaintext = "This is a longer message to test the round trip encryption and decryption process";
+        const ad = "Additional authenticated data that is not encrypted but is authenticated";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectEqualStrings(plaintext, &decrypted);
+    }
+
+    // Test authentication failure with tampered ciphertext
+    {
+        const plaintext = "Tamper test";
+        const ad = "metadata";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        // Tamper with ciphertext
+        ciphertext[0] ^= 0xFF;
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectError(error.AuthenticationFailed, result);
+    }
+
+    // Test authentication failure with wrong tag
+    {
+        const plaintext = "Tag test";
+        const ad = "metadata";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        // Tamper with tag
+        var wrong_tag = tag;
+        wrong_tag[0] ^= 0xFF;
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        const result = AsconAead128.decrypt(&decrypted, &ciphertext, wrong_tag, ad, nonce, key);
+        try testing.expectError(error.AuthenticationFailed, result);
+    }
+
+    // Test authentication failure with wrong associated data
+    {
+        const plaintext = "AD test";
+        const ad = "original";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        var decrypted: [plaintext.len]u8 = undefined;
+        const wrong_ad = "modified";
+        const result = AsconAead128.decrypt(&decrypted, &ciphertext, tag, wrong_ad, nonce, key);
+        try testing.expectError(error.AuthenticationFailed, result);
+    }
+}
+
+// Test vectors from NIST SP 800-232 / ascon-c reference implementation
+test "Ascon-AEAD128 official test vectors" {
+
+    // Test vector 1: Empty PT, Empty AD
+    {
+        var key: [16]u8 = undefined;
+        var nonce: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
+        _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
+
+        const plaintext = "";
+        const ad = "";
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, ad, nonce, key);
+
+        var expected_tag: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_tag, "4F9C278211BEC9316BF68F46EE8B2EC6") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected_tag, &tag);
+    }
+
+    // Test vector 2: Empty PT, AD = "30"
+    {
+        var key: [16]u8 = undefined;
+        var nonce: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
+        _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
+
+        const plaintext = "";
+        var ad: [1]u8 = undefined;
+        _ = std.fmt.hexToBytes(&ad, "30") catch unreachable;
+        var ciphertext: [plaintext.len]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, plaintext, &ad, nonce, key);
+
+        var expected_tag: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected_tag, &tag);
+    }
+
+    // Test vector 34: Single byte plaintext 0x20
+    {
+        var key: [16]u8 = undefined;
+        var nonce: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
+        _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
+
+        var plaintext: [1]u8 = undefined;
+        _ = std.fmt.hexToBytes(&plaintext, "20") catch unreachable;
+        const ad = "";
+        var ciphertext: [1]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
+
+        var expected_ct: [1]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_ct, "E8") catch unreachable;
+        var expected_tag: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_tag, "DD576ABA1CD3E6FC704DE02AEDB79588") catch unreachable;
+
+        try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
+        try testing.expectEqualSlices(u8, &expected_tag, &tag);
+
+        // Verify decryption
+        var decrypted: [1]u8 = undefined;
+        try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectEqualSlices(u8, &plaintext, &decrypted);
+    }
+
+    // Test vector with 3-byte plaintext
+    {
+        var key: [16]u8 = undefined;
+        var nonce: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F") catch unreachable;
+        _ = std.fmt.hexToBytes(&nonce, "101112131415161718191A1B1C1D1E1F") catch unreachable;
+
+        var plaintext: [3]u8 = undefined;
+        _ = std.fmt.hexToBytes(&plaintext, "202122") catch unreachable;
+        const ad = "";
+        var ciphertext: [3]u8 = undefined;
+        var tag: [16]u8 = undefined;
+
+        AsconAead128.encrypt(&ciphertext, &tag, &plaintext, ad, nonce, key);
+
+        var expected_ct: [3]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_ct, "E8C3DE") catch unreachable;
+        var expected_tag: [16]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected_tag, "AF8E12816B8EDF39AD1571A9492B7CA2") catch unreachable;
+
+        try testing.expectEqualSlices(u8, &expected_ct, &ciphertext);
+        try testing.expectEqualSlices(u8, &expected_tag, &tag);
+
+        // Verify decryption
+        var decrypted: [3]u8 = undefined;
+        try AsconAead128.decrypt(&decrypted, &ciphertext, tag, ad, nonce, key);
+        try testing.expectEqualSlices(u8, &plaintext, &decrypted);
+    }
+}
+
+test "Ascon-Hash256 official test vectors" {
+
+    // Test vector 1: Empty message
+    {
+        const message = "";
+        var hash: [32]u8 = undefined;
+        AsconHash256.hash(message, &hash, .{});
+
+        var expected: [32]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "0B3BE5850F2F6B98CAF29F8FDEA89B64A1FA70AA249B8F839BD53BAA304D92B2") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &hash);
+    }
+
+    // Test vector 2: Single byte 0x00
+    {
+        const message = [_]u8{0x00};
+        var hash: [32]u8 = undefined;
+        AsconHash256.hash(&message, &hash, .{});
+
+        var expected: [32]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "0728621035AF3ED2BCA03BF6FDE900F9456F5330E4B5EE23E7F6A1E70291BC80") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &hash);
+    }
+
+    // Test vector 3: 0x00, 0x01
+    {
+        const message = [_]u8{ 0x00, 0x01 };
+        var hash: [32]u8 = undefined;
+        AsconHash256.hash(&message, &hash, .{});
+
+        var expected: [32]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "6115E7C9C4081C2797FC8FE1BC57A836AFA1C5381E556DD583860CA2DFB48DD2") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &hash);
+    }
+
+    // Test vector 4: 0x00, 0x01, 0x02
+    {
+        const message = [_]u8{ 0x00, 0x01, 0x02 };
+        var hash: [32]u8 = undefined;
+        AsconHash256.hash(&message, &hash, .{});
+
+        var expected: [32]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "265AB89A609F5A05DCA57E83FBBA700F9A2D2C4211BA4CC9F0A1A369E17B915C") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &hash);
+    }
+
+    // Test vector 5: 0x00..0x03
+    {
+        const message = [_]u8{ 0x00, 0x01, 0x02, 0x03 };
+        var hash: [32]u8 = undefined;
+        AsconHash256.hash(&message, &hash, .{});
+
+        var expected: [32]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "D7E4C7ED9B8A325CD08B9EF259F8877054ECD8304FE1B2D7FD847137DF6727EE") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &hash);
+    }
+}
+
+test "Ascon-XOF128 official test vectors" {
+
+    // Test vector 1: Empty message, 64-byte output
+    {
+        var xof = AsconXof128.init(.{});
+        xof.update("");
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "473D5E6164F58B39DFD84AACDB8AE42EC2D91FED33388EE0D960D9B3993295C6AD77855A5D3B13FE6AD9E6098988373AF7D0956D05A8F1665D2C67D1A3AD10FF") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+
+    // Test vector 2: Single byte 0x00, 64-byte output
+    {
+        var xof = AsconXof128.init(.{});
+        const msg = [_]u8{0x00};
+        xof.update(&msg);
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "51430E0438ECDF642B393630D977625F5F337656BA58AB1E960784AC32A16E0D446405551F5469384F8EA283CF12E64FA72C426BFEBAEA3AA1529E2C4AB23A2F") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+
+    // Test vector 3: 0x00, 0x01, 64-byte output
+    {
+        var xof = AsconXof128.init(.{});
+        const msg = [_]u8{ 0x00, 0x01 };
+        xof.update(&msg);
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "A05383077AF971D3830BD37E7B981497A773D441DB077C6494CC73125953846EB6427FBA4CD308FF90A11385D51101341BF5379249217BFDACE9CCA1148CC966") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+}
+
+test "Ascon-CXOF128 official test vectors" {
+
+    // Test vector 1: Empty message, empty customization, 64-byte output
+    {
+        var xof = AsconCxof128.init(.{});
+        xof.update("");
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "4F50159EF70BB3DAD8807E034EAEBD44C4FA2CBBC8CF1F05511AB66CDCC529905CA12083FC186AD899B270B1473DC5F7EC88D1052082DCDFE69FB75D269E7B74") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+
+    // Test vector 2: Empty message, customization = 0x10, 64-byte output
+    {
+        const custom = [_]u8{0x10};
+        var xof = AsconCxof128.init(.{ .custom = &custom });
+        xof.update("");
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "0C93A483E7D574D49FE52CCE03EE646117977D57A8AA57704AB4DAF44B501430FF6AC11A5D1FD6F2154B5C65728268270C8BB578508487B8965718ADA6272FD6") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+
+    // Test vector 3: Empty message, customization = 0x10, 0x11, 64-byte output
+    {
+        const custom = [_]u8{ 0x10, 0x11 };
+        var xof = AsconCxof128.init(.{ .custom = &custom });
+        xof.update("");
+
+        var output: [64]u8 = undefined;
+        xof.squeeze(&output);
+
+        var expected: [64]u8 = undefined;
+        _ = std.fmt.hexToBytes(&expected, "D1106C7622E79FE955BD9D79E03B918E770FE0E0CDDDE28BEB924B02C5FC936B33ACCA299C89ECA5D71886CBBFA4D54A21C55FDE2B679F5E2488063A1719DC32") catch unreachable;
+        try testing.expectEqualSlices(u8, &expected, &output);
+    }
+}
lib/std/crypto/benchmark.zig
@@ -19,6 +19,7 @@ const Crypto = struct {
 };
 
 const hashes = [_]Crypto{
+    Crypto{ .ty = crypto.hash.ascon.AsconHash256, .name = "ascon-256" },
     Crypto{ .ty = crypto.hash.Md5, .name = "md5" },
     Crypto{ .ty = crypto.hash.Sha1, .name = "sha1" },
     Crypto{ .ty = crypto.hash.sha2.Sha256, .name = "sha256" },
@@ -283,6 +284,7 @@ pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_i
 }
 
 const aeads = [_]Crypto{
+    Crypto{ .ty = crypto.aead.ascon.AsconAead128, .name = "ascon-aead-128" },
     Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
     Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
     Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
@@ -458,7 +460,9 @@ fn mode(comptime x: comptime_int) comptime_int {
 }
 
 pub fn main() !void {
-    const stdout = std.fs.File.stdout().deprecatedWriter();
+    var stdout_buffer: [4096]u8 = undefined;
+    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
+    const stdout = &stdout_writer.interface;
 
     var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
     defer arena.deinit();
@@ -471,6 +475,7 @@ pub fn main() !void {
     while (i < args.len) : (i += 1) {
         if (std.mem.eql(u8, args[i], "--mode")) {
             try stdout.print("{}\n", .{builtin.mode});
+            try stdout.flush();
             return;
         } else if (std.mem.eql(u8, args[i], "--seed")) {
             i += 1;
@@ -502,6 +507,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
             const throughput = try benchmarkHash(H.ty, mode(128 * MiB));
             try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
+            try stdout.flush();
         }
     }
 
@@ -509,6 +515,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) {
             const throughput = try benchmarkMac(M.ty, mode(128 * MiB));
             try stdout.print("{s:>17}: {:10} MiB/s\n", .{ M.name, throughput / (1 * MiB) });
+            try stdout.flush();
         }
     }
 
@@ -516,6 +523,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkKeyExchange(E.ty, mode(1000));
             try stdout.print("{s:>17}: {:10} exchanges/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -523,6 +531,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkSignature(E.ty, mode(1000));
             try stdout.print("{s:>17}: {:10} signatures/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -530,6 +539,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkSignatureVerification(E.ty, mode(1000));
             try stdout.print("{s:>17}: {:10} verifications/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -537,6 +547,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkBatchSignatureVerification(E.ty, mode(1000));
             try stdout.print("{s:>17}: {:10} verifications/s (batch)\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -544,6 +555,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkAead(E.ty, mode(128 * MiB));
             try stdout.print("{s:>17}: {:10} MiB/s\n", .{ E.name, throughput / (1 * MiB) });
+            try stdout.flush();
         }
     }
 
@@ -551,6 +563,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkAes(E.ty, mode(100000000));
             try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -558,6 +571,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkAes8(E.ty, mode(10000000));
             try stdout.print("{s:>17}: {:10} ops/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -565,6 +579,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
             const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64));
             try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -572,6 +587,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkKem(E.ty, mode(1000));
             try stdout.print("{s:>17}: {:10} encaps/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -579,6 +595,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkKemDecaps(E.ty, mode(25000));
             try stdout.print("{s:>17}: {:10} decaps/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 
@@ -586,6 +603,7 @@ pub fn main() !void {
         if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
             const throughput = try benchmarkKemKeyGen(E.ty, mode(25000));
             try stdout.print("{s:>17}: {:10} keygen/s\n", .{ E.name, throughput });
+            try stdout.flush();
         }
     }
 }
lib/std/crypto.zig
@@ -36,6 +36,10 @@ pub const aead = struct {
         pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
     };
 
+    pub const ascon = struct {
+        pub const AsconAead128 = @import("crypto/ascon.zig").AsconAead128;
+    };
+
     pub const chacha_poly = struct {
         pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
         pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
@@ -115,6 +119,12 @@ pub const ecc = struct {
 
 /// Hash functions.
 pub const hash = struct {
+    pub const ascon = struct {
+        const variants = @import("crypto/ascon.zig");
+        pub const AsconHash256 = variants.AsconHash256;
+        pub const AsconXof128 = variants.AsconXof128;
+        pub const AsconCxof128 = variants.AsconCxof128;
+    };
     pub const blake2 = @import("crypto/blake2.zig");
     pub const Blake3 = @import("crypto/blake3.zig").Blake3;
     pub const Md5 = @import("crypto/md5.zig").Md5;
@@ -243,6 +253,8 @@ pub const SideChannelsMitigations = enum {
 pub const default_side_channels_mitigations = .medium;
 
 test {
+    _ = aead.ascon.AsconAead128;
+
     _ = aead.aegis.Aegis128L;
     _ = aead.aegis.Aegis256;
 
@@ -281,6 +293,7 @@ test {
     _ = ecc.Ristretto255;
     _ = ecc.Secp256k1;
 
+    _ = hash.ascon;
     _ = hash.blake2;
     _ = hash.Blake3;
     _ = hash.Md5;