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