diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 2a45c188b6..b4df48f402 100644 --- a/lib/std/crypto.zig +++ b/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; diff --git a/lib/std/crypto/ascon.zig b/lib/std/crypto/ascon.zig index c0ba5de939..f3edd59de4 100644 --- a/lib/std/crypto/ascon.zig +++ b/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); + } +} diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index d064149778..db63404c11 100644 --- a/lib/std/crypto/benchmark.zig +++ b/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(); } } }