* Implements #3768. This is a sweeping breaking change that requires many (trivial) edits to Zig source code. Array values no longer coerced to slices; however one may use `&` to obtain a reference to an array value, which may then be coerced to a slice. * Adds `IrInstruction::dump`, for debugging purposes. It's useful to call to inspect the instruction when debugging Zig IR. * Fixes bugs with result location semantics. See the new behavior test cases, and compile error test cases. * Fixes bugs with `@typeInfo` not properly resolving const values. * Behavior tests are passing but std lib tests are not yet. There is more work to do before merging this branch.
169 lines
5.4 KiB
Zig
169 lines
5.4 KiB
Zig
// Gimli is a 384-bit permutation designed to achieve high security with high
|
|
// performance across a broad range of platforms, including 64-bit Intel/AMD
|
|
// server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
|
|
// microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
|
|
// side-channel protection, and ASICs with side-channel protection.
|
|
//
|
|
// https://gimli.cr.yp.to/
|
|
// https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf
|
|
|
|
const std = @import("../std.zig");
|
|
const mem = std.mem;
|
|
const math = std.math;
|
|
const debug = std.debug;
|
|
const assert = std.debug.assert;
|
|
const testing = std.testing;
|
|
const htest = @import("test.zig");
|
|
|
|
pub const State = struct {
|
|
pub const BLOCKBYTES = 48;
|
|
pub const RATE = 16;
|
|
|
|
// TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
|
|
data: [BLOCKBYTES / 4]u32,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn toSlice(self: *Self) []u8 {
|
|
return @sliceToBytes(self.data[0..]);
|
|
}
|
|
|
|
pub fn toSliceConst(self: *Self) []const u8 {
|
|
return @sliceToBytes(self.data[0..]);
|
|
}
|
|
|
|
pub fn permute(self: *Self) void {
|
|
const state = &self.data;
|
|
var round = @as(u32, 24);
|
|
while (round > 0) : (round -= 1) {
|
|
var column = @as(usize, 0);
|
|
while (column < 4) : (column += 1) {
|
|
const x = math.rotl(u32, state[column], 24);
|
|
const y = math.rotl(u32, state[4 + column], 9);
|
|
const z = state[8 + column];
|
|
state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
|
|
state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
|
|
state[column] = ((z ^ y) ^ ((x & y) << 3));
|
|
}
|
|
switch (round & 3) {
|
|
0 => {
|
|
mem.swap(u32, &state[0], &state[1]);
|
|
mem.swap(u32, &state[2], &state[3]);
|
|
state[0] ^= round | 0x9e377900;
|
|
},
|
|
2 => {
|
|
mem.swap(u32, &state[0], &state[2]);
|
|
mem.swap(u32, &state[1], &state[3]);
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn squeeze(self: *Self, out: []u8) void {
|
|
var i = @as(usize, 0);
|
|
while (i + RATE <= out.len) : (i += RATE) {
|
|
self.permute();
|
|
mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]);
|
|
}
|
|
const leftover = out.len - i;
|
|
if (leftover != 0) {
|
|
self.permute();
|
|
mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]);
|
|
}
|
|
}
|
|
};
|
|
|
|
test "permute" {
|
|
// test vector from gimli-20170627
|
|
var state = State{
|
|
.data = blk: {
|
|
var input: [12]u32 = undefined;
|
|
var i = @as(u32, 0);
|
|
while (i < 12) : (i += 1) {
|
|
input[i] = i * i * i + i *% 0x9e3779b9;
|
|
}
|
|
testing.expectEqualSlices(u32, &input, &[_]u32{
|
|
0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46,
|
|
0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566,
|
|
0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026,
|
|
});
|
|
break :blk input;
|
|
},
|
|
};
|
|
state.permute();
|
|
testing.expectEqualSlices(u32, &state.data, &[_]u32{
|
|
0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68,
|
|
0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8,
|
|
0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6,
|
|
});
|
|
}
|
|
|
|
pub const Hash = struct {
|
|
state: State,
|
|
buf_off: usize,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn init() Self {
|
|
return Self{
|
|
.state = State{
|
|
.data = [_]u32{0} ** (State.BLOCKBYTES / 4),
|
|
},
|
|
.buf_off = 0,
|
|
};
|
|
}
|
|
|
|
/// Also known as 'absorb'
|
|
pub fn update(self: *Self, data: []const u8) void {
|
|
const buf = self.state.toSlice();
|
|
var in = data;
|
|
while (in.len > 0) {
|
|
var left = State.RATE - self.buf_off;
|
|
if (left == 0) {
|
|
self.state.permute();
|
|
self.buf_off = 0;
|
|
left = State.RATE;
|
|
}
|
|
const ps = math.min(in.len, left);
|
|
for (buf[self.buf_off .. self.buf_off + ps]) |*p, i| {
|
|
p.* ^= in[i];
|
|
}
|
|
self.buf_off += ps;
|
|
in = in[ps..];
|
|
}
|
|
}
|
|
|
|
/// Finish the current hashing operation, writing the hash to `out`
|
|
///
|
|
/// From 4.9 "Application to hashing"
|
|
/// By default, Gimli-Hash provides a fixed-length output of 32 bytes
|
|
/// (the concatenation of two 16-byte blocks). However, Gimli-Hash can
|
|
/// be used as an “extendable one-way function” (XOF).
|
|
pub fn final(self: *Self, out: []u8) void {
|
|
const buf = self.state.toSlice();
|
|
|
|
// XOR 1 into the next byte of the state
|
|
buf[self.buf_off] ^= 1;
|
|
// XOR 1 into the last byte of the state, position 47.
|
|
buf[buf.len - 1] ^= 1;
|
|
|
|
self.state.squeeze(out);
|
|
}
|
|
};
|
|
|
|
pub fn hash(out: []u8, in: []const u8) void {
|
|
var st = Hash.init();
|
|
st.update(in);
|
|
st.final(out);
|
|
}
|
|
|
|
test "hash" {
|
|
// a test vector (30) from NIST KAT submission.
|
|
var msg: [58 / 2]u8 = undefined;
|
|
try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C");
|
|
var md: [32]u8 = undefined;
|
|
hash(&md, &msg);
|
|
htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
|
|
}
|