mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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:
parent
db8217f9a0
commit
28364166e8
@ -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;
|
||||
|
||||
@ -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
251
lib/std/crypto/keccak_p.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user