crypto.hash.sha3: make permutation generic and public, add SHAKE (#14756)

Make the Keccak permutation public, as it's useful for more than
SHA-3 (kMAC, SHAKE, TurboSHAKE, TupleHash, etc).

Our Keccak implementation was accepting f as a comptime parameter,
but always used 64-bit words and 200 byte states, so it actually
didn't work with anything besides f=1600.

That has been fixed. The ability to use reduced-round versions
was also added in order to support M14 and K12.

The state was constantly converted back and forth between bytes
and words, even though only a part of the state is actually used
for absorbing and squeezing bytes. It was changed to something
similar to the other permutations we have, so we can avoid extra
copies, and eventually add vectorized implementations.

In addition, the SHAKE extendable output function (XOF) was
added (SHAKE128, SHAKE256). It is required by newer schemes,
such as the Kyber post-quantum key exchange mechanism, whose
implementation is currently blocked by SHAKE missing from our
standard library.

Breaking change: `Keccak_256` and `Keccak_512` were renamed to
`Keccak256` and `Keccak512` for consistency with all other
hash functions.
This commit is contained in:
Frank Denis 2023-03-02 07:13:40 +01:00 committed by GitHub
parent db8217f9a0
commit 28364166e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 427 additions and 143 deletions

View File

@ -47,6 +47,8 @@ pub const auth = struct {
/// Core functions, that should rarely be used directly by applications.
pub const core = struct {
pub const aes = @import("crypto/aes.zig");
pub const keccak = @import("crypto/keccak_p.zig");
pub const Ascon = @import("crypto/ascon.zig").State;
pub const Gimli = @import("crypto/gimli.zig").State;
pub const Xoodoo = @import("crypto/xoodoo.zig").State;

View File

@ -25,6 +25,8 @@ const hashes = [_]Crypto{
Crypto{ .ty = crypto.hash.sha2.Sha512, .name = "sha512" },
Crypto{ .ty = crypto.hash.sha3.Sha3_256, .name = "sha3-256" },
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" },
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },

251
lib/std/crypto/keccak_p.zig Normal file
View File

@ -0,0 +1,251 @@
const std = @import("std");
const assert = std.debug.assert;
const math = std.math;
const mem = std.mem;
/// The Keccak-f permutation.
pub fn KeccakF(comptime f: u11) type {
comptime assert(f > 200 and f <= 1600 and f % 200 == 0); // invalid bit size
const T = std.meta.Int(.unsigned, f / 25);
const Block = [25]T;
const RC = [_]u64{
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000,
0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003,
0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a,
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
};
const RHO = [_]u6{
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
};
const PI = [_]u5{
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
};
return struct {
const Self = @This();
/// Number of bytes in the state.
pub const block_bytes = f / 8;
st: Block = [_]T{0} ** 25,
/// Initialize the state from a slice of bytes.
pub fn init(bytes: [block_bytes]u8) Self {
var self: Self = undefined;
inline for (&self.st, 0..) |*r, i| {
r.* = mem.readIntLittle(T, bytes[@sizeOf(T) * i ..][0..@sizeOf(T)]);
}
return self;
}
/// A representation of the state as bytes. The byte order is architecture-dependent.
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.
pub fn endianSwap(self: *Self) void {
for (&self.st) |*w| {
w.* = mem.littleTooNative(T, w.*);
}
}
/// Set bytes starting at the beginning of the state.
pub fn setBytes(self: *Self, bytes: []const u8) void {
var i: usize = 0;
while (i + @sizeOf(T) <= bytes.len) : (i += @sizeOf(T)) {
self.st[i / @sizeOf(T)] = mem.readIntLittle(T, bytes[i..][0..@sizeOf(T)]);
}
if (i < bytes.len) {
var padded = [_]u8{0} ** @sizeOf(T);
mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]);
self.st[i / @sizeOf(T)] = mem.readIntLittle(T, padded[0..]);
}
}
/// XOR a byte into the state at a given offset.
pub fn addByte(self: *Self, byte: u8, offset: usize) void {
const z = @sizeOf(T) * @truncate(math.Log2Int(T), offset % @sizeOf(T));
self.st[offset / @sizeOf(T)] ^= @as(T, byte) << z;
}
/// XOR bytes into the beginning of the state.
pub fn addBytes(self: *Self, bytes: []const u8) void {
var i: usize = 0;
while (i + @sizeOf(T) <= bytes.len) : (i += @sizeOf(T)) {
self.st[i / @sizeOf(T)] ^= mem.readIntLittle(T, bytes[i..][0..@sizeOf(T)]);
}
if (i < bytes.len) {
var padded = [_]u8{0} ** @sizeOf(T);
mem.copy(u8, padded[0 .. bytes.len - i], bytes[i..]);
self.st[i / @sizeOf(T)] ^= mem.readIntLittle(T, padded[0..]);
}
}
/// Extract the first bytes of the state.
pub fn extractBytes(self: *Self, out: []u8) void {
var i: usize = 0;
while (i + @sizeOf(T) <= out.len) : (i += @sizeOf(T)) {
mem.writeIntLittle(T, out[i..][0..@sizeOf(T)], self.st[i / @sizeOf(T)]);
}
if (i < out.len) {
var padded = [_]u8{0} ** @sizeOf(T);
mem.writeIntLittle(T, padded[0..], self.st[i / @sizeOf(T)]);
mem.copy(u8, out[i..], padded[0 .. out.len - i]);
}
}
/// XOR the first bytes of the state into a slice of bytes.
pub fn xorBytes(self: *Self, out: []u8, in: []const u8) void {
assert(out.len == in.len);
var i: usize = 0;
while (i + @sizeOf(T) <= in.len) : (i += @sizeOf(T)) {
const x = mem.readIntNative(T, in[i..][0..@sizeOf(T)]) ^ mem.nativeToLittle(T, self.st[i / @sizeOf(T)]);
mem.writeIntNative(T, out[i..][0..@sizeOf(T)], x);
}
if (i < in.len) {
var padded = [_]u8{0} ** @sizeOf(T);
mem.copy(u8, padded[0 .. in.len - i], in[i..]);
const x = mem.readIntNative(T, &padded) ^ mem.nativeToLittle(T, self.st[i / @sizeOf(T)]);
mem.writeIntNative(T, &padded, x);
mem.copy(u8, out[i..], padded[0 .. in.len - i]);
}
}
/// Set the words storing the bytes of a given range to zero.
pub fn clear(self: *Self, from: usize, to: usize) void {
mem.set(T, self.st[from / @sizeOf(T) .. (to + @sizeOf(T) - 1) / @sizeOf(T)], 0);
}
/// Clear the entire state, disabling compiler optimizations.
pub fn secureZero(self: *Self) void {
std.crypto.utils.secureZero(T, &self.st);
}
inline fn round(self: *Self, rc: T) void {
const st = &self.st;
// theta
var t = [_]T{0} ** 5;
inline for (0..5) |i| {
inline for (0..5) |j| {
t[i] ^= st[j * 5 + i];
}
}
inline for (0..5) |i| {
inline for (0..5) |j| {
st[j * 5 + i] ^= t[(i + 4) % 5] ^ math.rotl(T, t[(i + 1) % 5], 1);
}
}
// rho+pi
var last = st[1];
inline for (0..24) |i| {
const x = PI[i];
const tmp = st[x];
st[x] = math.rotl(T, last, RHO[i]);
last = tmp;
}
inline for (0..5) |i| {
inline for (0..5) |j| {
t[j] = st[i * 5 + j];
}
inline for (0..5) |j| {
st[i * 5 + j] = t[j] ^ (~t[(j + 1) % 5] & t[(j + 2) % 5]);
}
}
// iota
st[0] ^= rc;
}
/// Apply a (possibly) reduced-round permutation to the state.
pub fn permuteR(self: *Self, comptime rounds: u5) void {
var i = RC.len - rounds;
while (i < rounds - rounds % 3) : (i += 3) {
self.round(RC[i]);
self.round(RC[i + 1]);
self.round(RC[i + 2]);
}
while (i < rounds) : (i += 1) {
self.round(RC[i]);
}
}
/// Apply a full-round permutation to the state.
pub fn permute(self: *Self) void {
self.permuteR(comptime 12 + 2 * math.log2(f / 25));
}
};
}
/// A generic Keccak-P state.
pub fn State(comptime f: u11, comptime capacity: u11, comptime delim: u8, comptime rounds: u5) type {
comptime assert(f > 200 and f <= 1600 and f % 200 == 0); // invalid state size
comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size
return struct {
const Self = @This();
/// The block length, or rate, in bytes.
pub const rate = KeccakF(f).block_bytes - capacity / 8;
/// Keccak does not have any options.
pub const Options = struct {};
offset: usize = 0,
buf: [rate]u8 = undefined,
st: KeccakF(f) = .{},
/// Absorb a slice of bytes into the sponge.
pub fn absorb(self: *Self, bytes_: []const u8) void {
var bytes = bytes_;
if (self.offset > 0) {
const left = math.min(rate - self.offset, bytes.len);
mem.copy(u8, self.buf[self.offset..], bytes[0..left]);
self.offset += left;
if (self.offset == rate) {
self.offset = 0;
self.st.addBytes(self.buf[0..]);
self.st.permuteR(rounds);
}
if (left == bytes.len) return;
bytes = bytes[left..];
}
while (bytes.len >= rate) {
self.st.addBytes(bytes[0..rate]);
self.st.permuteR(rounds);
bytes = bytes[rate..];
}
if (bytes.len > 0) {
self.st.addBytes(bytes[0..]);
self.offset = bytes.len;
}
}
/// Mark the end of the input.
pub fn pad(self: *Self) void {
self.st.addBytes(self.buf[0..self.offset]);
self.st.addByte(delim, self.offset);
self.st.addByte(0x80, rate - 1);
self.st.permuteR(rounds);
self.offset = 0;
}
/// Squeeze a slice of bytes from the sponge.
pub fn squeeze(self: *Self, out: []u8) void {
var i: usize = 0;
while (i < out.len) : (i += rate) {
const left = math.min(rate, out.len - i);
self.st.extractBytes(out[i..][0..left]);
self.st.permuteR(rounds);
}
}
};
}

View File

@ -1,84 +1,63 @@
const std = @import("../std.zig");
const mem = std.mem;
const std = @import("std");
const assert = std.debug.assert;
const math = std.math;
const debug = std.debug;
const htest = @import("test.zig");
const mem = std.mem;
pub const Sha3_224 = Keccak(224, 0x06);
pub const Sha3_256 = Keccak(256, 0x06);
pub const Sha3_384 = Keccak(384, 0x06);
pub const Sha3_512 = Keccak(512, 0x06);
pub const Keccak_256 = Keccak(256, 0x01);
pub const Keccak_512 = Keccak(512, 0x01);
const KeccakState = std.crypto.core.keccak.State;
pub const Sha3_224 = Keccak(1600, 224, 0x06, 24);
pub const Sha3_256 = Keccak(1600, 256, 0x06, 24);
pub const Sha3_384 = Keccak(1600, 384, 0x06, 24);
pub const Sha3_512 = Keccak(1600, 512, 0x06, 24);
pub const Keccak256 = Keccak(1600, 256, 0x01, 24);
pub const Keccak512 = Keccak(1600, 512, 0x01, 24);
pub const Keccak_256 = @compileError("Deprecated: use `Keccak256` instead");
pub const Keccak_512 = @compileError("Deprecated: use `Keccak512` instead");
pub const Shake128 = Shake(128);
pub const Shake256 = Shake(256);
/// A generic Keccak hash function.
pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, comptime rounds: u5) type {
comptime assert(output_bits > 0 and output_bits * 2 < f and output_bits % 8 == 0); // invalid output length
const State = KeccakState(f, output_bits * 2, delim, rounds);
fn Keccak(comptime bits: usize, comptime delim: u8) type {
return struct {
const Self = @This();
st: State = .{},
/// The output length, in bytes.
pub const digest_length = bits / 8;
pub const digest_length = output_bits / 8;
/// The block length, or rate, in bytes.
pub const block_length = 200 - bits / 4;
pub const block_length = State.rate;
/// Keccak does not have any options.
pub const Options = struct {};
s: [200]u8,
offset: usize,
/// Initialize a Keccak hash function.
pub fn init(options: Options) Self {
_ = options;
return Self{ .s = [_]u8{0} ** 200, .offset = 0 };
return Self{};
}
pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
var d = Self.init(options);
d.update(b);
d.final(out);
/// Hash a slice of bytes.
pub fn hash(bytes: []const u8, out: *[digest_length]u8, options: Options) void {
var st = Self.init(options);
st.update(bytes);
st.final(out);
}
pub fn update(d: *Self, b: []const u8) void {
var ip: usize = 0;
var len = b.len;
var rate = block_length - d.offset;
var offset = d.offset;
// absorb
while (len >= rate) {
for (d.s[offset .. offset + rate], 0..) |*r, i|
r.* ^= b[ip..][i];
keccakF(1600, &d.s);
ip += rate;
len -= rate;
rate = block_length;
offset = 0;
}
for (d.s[offset .. offset + len], 0..) |*r, i|
r.* ^= b[ip..][i];
d.offset = offset + len;
/// Absorb a slice of bytes into the state.
pub fn update(self: *Self, bytes: []const u8) void {
self.st.absorb(bytes);
}
pub fn final(d: *Self, out: *[digest_length]u8) void {
// padding
d.s[d.offset] ^= delim;
d.s[block_length - 1] ^= 0x80;
keccakF(1600, &d.s);
// squeeze
var op: usize = 0;
var len: usize = bits / 8;
while (len >= block_length) {
mem.copy(u8, out[op..], d.s[0..block_length]);
keccakF(1600, &d.s);
op += block_length;
len -= block_length;
}
mem.copy(u8, out[op..], d.s[0..len]);
/// Return the hash of the absorbed bytes.
pub fn final(self: *Self, out: *[digest_length]u8) void {
self.st.pad();
self.st.squeeze(out[0..]);
}
pub const Error = error{};
@ -95,87 +74,101 @@ fn Keccak(comptime bits: usize, comptime delim: u8) type {
};
}
const RC = [_]u64{
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000,
0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003,
0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a,
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
};
/// The SHAKE extendable output hash function.
pub fn Shake(comptime security_level: u11) type {
const f = 1600;
const rounds = 24;
const State = KeccakState(f, security_level * 2, 0x1f, rounds);
const ROTC = [_]usize{
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
};
return struct {
const Self = @This();
const PIL = [_]usize{
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
};
st: State = .{},
buf: [State.rate]u8 = undefined,
offset: usize = 0,
padded: bool = false,
const M5 = [_]usize{
0, 1, 2, 3, 4, 0, 1, 2, 3, 4,
};
/// The recommended output length, in bytes.
pub const digest_length = security_level / 2;
/// The block length, or rate, in bytes.
pub const block_length = State.rate;
/// Keccak does not have any options.
pub const Options = struct {};
fn keccakF(comptime F: usize, d: *[F / 8]u8) void {
const B = F / 25;
const no_rounds = comptime x: {
break :x 12 + 2 * math.log2(B);
/// Initialize a SHAKE extensible hash function.
pub fn init(options: Options) Self {
_ = options;
return Self{};
}
/// Hash a slice of bytes.
/// `out` can be any length.
pub fn hash(bytes: []const u8, out: []u8, options: Options) void {
var st = Self.init(options);
st.update(bytes);
st.squeeze(out);
}
/// Absorb a slice of bytes into the state.
pub fn update(self: *Self, bytes: []const u8) void {
self.st.absorb(bytes);
}
/// Squeeze a slice of bytes from the state.
/// `out` can be any length, and the function can be called multiple times.
pub fn squeeze(self: *Self, out_: []u8) void {
if (!self.padded) {
self.st.pad();
self.padded = true;
}
var out = out_;
if (self.offset > 0) {
const left = self.buf.len - self.offset;
if (left > 0) {
const n = math.min(left, out.len);
mem.copy(u8, out[0..n], self.buf[self.offset..][0..n]);
out = out[n..];
self.offset += n;
if (out.len == 0) {
return;
}
}
}
const full_blocks = out[0 .. out.len - out.len % State.rate];
if (full_blocks.len > 0) {
self.st.squeeze(full_blocks);
out = out[full_blocks.len..];
}
if (out.len > 0) {
self.st.squeeze(self.buf[0..]);
mem.copy(u8, out[0..], self.buf[0..out.len]);
self.offset = out.len;
}
}
/// Return the hash of the absorbed bytes.
/// `out` can be of any length, but the function must not be called multiple times (use `squeeze` for that purpose instead).
pub fn final(self: *Self, out: []u8) void {
self.squeeze(out);
self.st.st.clear(0, State.rate);
}
pub const Error = error{};
pub const Writer = std.io.Writer(*Self, Error, write);
fn write(self: *Self, bytes: []const u8) Error!usize {
self.update(bytes);
return bytes.len;
}
pub fn writer(self: *Self) Writer {
return .{ .context = self };
}
};
var s = [_]u64{0} ** 25;
var t = [_]u64{0} ** 1;
var c = [_]u64{0} ** 5;
for (&s, 0..) |*r, i| {
r.* = mem.readIntLittle(u64, d[8 * i ..][0..8]);
}
for (RC[0..no_rounds]) |round| {
// theta
comptime var x: usize = 0;
inline while (x < 5) : (x += 1) {
c[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20];
}
x = 0;
inline while (x < 5) : (x += 1) {
t[0] = c[M5[x + 4]] ^ math.rotl(u64, c[M5[x + 1]], @as(usize, 1));
comptime var y: usize = 0;
inline while (y < 5) : (y += 1) {
s[x + y * 5] ^= t[0];
}
}
// rho+pi
t[0] = s[1];
x = 0;
inline while (x < 24) : (x += 1) {
c[0] = s[PIL[x]];
s[PIL[x]] = math.rotl(u64, t[0], ROTC[x]);
t[0] = c[0];
}
// chi
comptime var y: usize = 0;
inline while (y < 5) : (y += 1) {
x = 0;
inline while (x < 5) : (x += 1) {
c[x] = s[x + y * 5];
}
x = 0;
inline while (x < 5) : (x += 1) {
s[x + y * 5] = c[x] ^ (~c[M5[x + 1]] & c[M5[x + 2]]);
}
}
// iota
s[0] ^= round;
}
for (s, 0..) |r, i| {
mem.writeIntLittle(u64, d[8 * i ..][0..8], r);
}
}
const htest = @import("test.zig");
test "sha3-224 single" {
try htest.assertEqualHash(Sha3_224, "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", "");
try htest.assertEqualHash(Sha3_224, "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc");
@ -309,13 +302,49 @@ test "sha3-512 aligned final" {
}
test "keccak-256 single" {
try htest.assertEqualHash(Keccak_256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "");
try htest.assertEqualHash(Keccak_256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc");
try htest.assertEqualHash(Keccak_256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
try htest.assertEqualHash(Keccak256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "");
try htest.assertEqualHash(Keccak256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc");
try htest.assertEqualHash(Keccak256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
}
test "keccak-512 single" {
try htest.assertEqualHash(Keccak_512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", "");
try htest.assertEqualHash(Keccak_512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc");
try htest.assertEqualHash(Keccak_512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
try htest.assertEqualHash(Keccak512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", "");
try htest.assertEqualHash(Keccak512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc");
try htest.assertEqualHash(Keccak512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
}
test "SHAKE-128 single" {
var out: [10]u8 = undefined;
Shake128.hash("hello123", &out, .{});
try htest.assertEqual("1b85861510bc4d8e467d", &out);
}
test "SHAKE-128 multisqueeze" {
var out: [10]u8 = undefined;
var h = Shake128.init(.{});
h.update("hello123");
h.squeeze(out[0..4]);
h.squeeze(out[4..]);
try htest.assertEqual("1b85861510bc4d8e467d", &out);
}
test "SHAKE-128 multisqueeze with multiple blocks" {
var out: [100]u8 = undefined;
var out2: [100]u8 = undefined;
var h = Shake128.init(.{});
h.update("hello123");
h.squeeze(out[0..50]);
h.squeeze(out[50..]);
var h2 = Shake128.init(.{});
h2.update("hello123");
h2.squeeze(&out2);
try std.testing.expectEqualSlices(u8, &out, &out2);
}
test "SHAKE-256 single" {
var out: [10]u8 = undefined;
Shake256.hash("hello123", &out, .{});
try htest.assertEqual("ade612ba265f92de4a37", &out);
}