| ... | ... | @@ -0,0 +1,168 @@ |
| 1 | // Gimli is a 384-bit permutation designed to achieve high security with high |
| 2 | // performance across a broad range of platforms, including 64-bit Intel/AMD |
| 3 | // server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM |
| 4 | // microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without |
| 5 | // side-channel protection, and ASICs with side-channel protection. |
| 6 | // |
| 7 | // https://gimli.cr.yp.to/ |
| 8 | // https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf |
| 9 | |
| 10 | const std = @import("../std.zig"); |
| 11 | const mem = std.mem; |
| 12 | const math = std.math; |
| 13 | const debug = std.debug; |
| 14 | const assert = std.debug.assert; |
| 15 | const testing = std.testing; |
| 16 | const htest = @import("test.zig"); |
| 17 | |
| 18 | pub const State = struct { |
| 19 | pub const BLOCKBYTES = 48; |
| 20 | pub const RATE = 16; |
| 21 | |
| 22 | // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017 |
| 23 | data: [BLOCKBYTES / 4]u32, |
| 24 | |
| 25 | const Self = @This(); |
| 26 | |
| 27 | pub fn toSlice(self: *Self) []u8 { |
| 28 | return @sliceToBytes(self.data[0..]); |
| 29 | } |
| 30 | |
| 31 | pub fn toSliceConst(self: *Self) []const u8 { |
| 32 | return @sliceToBytes(self.data[0..]); |
| 33 | } |
| 34 | |
| 35 | pub fn permute(self: *Self) void { |
| 36 | const state = &self.data; |
| 37 | var round = u32(24); |
| 38 | while (round > 0) : (round -= 1) { |
| 39 | var column = usize(0); |
| 40 | while (column < 4) : (column += 1) { |
| 41 | const x = math.rotl(u32, state[column], 24); |
| 42 | const y = math.rotl(u32, state[4 + column], 9); |
| 43 | const z = state[8 + column]; |
| 44 | state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2)); |
| 45 | state[4 + column] = ((y ^ x) ^ ((x | z) << 1)); |
| 46 | state[column] = ((z ^ y) ^ ((x & y) << 3)); |
| 47 | } |
| 48 | switch (round & 3) { |
| 49 | 0 => { |
| 50 | mem.swap(u32, &state[0], &state[1]); |
| 51 | mem.swap(u32, &state[2], &state[3]); |
| 52 | state[0] ^= round | 0x9e377900; |
| 53 | }, |
| 54 | 2 => { |
| 55 | mem.swap(u32, &state[0], &state[2]); |
| 56 | mem.swap(u32, &state[1], &state[3]); |
| 57 | }, |
| 58 | else => {}, |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | pub fn squeeze(self: *Self, out: []u8) void { |
| 64 | var i = usize(0); |
| 65 | while (i + RATE <= out.len) : (i += RATE) { |
| 66 | self.permute(); |
| 67 | mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]); |
| 68 | } |
| 69 | const leftover = out.len - i; |
| 70 | if (leftover != 0) { |
| 71 | self.permute(); |
| 72 | mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]); |
| 73 | } |
| 74 | } |
| 75 | }; |
| 76 | |
| 77 | test "permute" { |
| 78 | // test vector from gimli-20170627 |
| 79 | var state = State{ |
| 80 | .data = blk: { |
| 81 | var input: [12]u32 = undefined; |
| 82 | var i = u32(0); |
| 83 | while (i < 12) : (i += 1) { |
| 84 | input[i] = i * i * i + i *% 0x9e3779b9; |
| 85 | } |
| 86 | testing.expectEqualSlices(u32, input, [_]u32{ |
| 87 | 0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46, |
| 88 | 0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566, |
| 89 | 0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026, |
| 90 | }); |
| 91 | break :blk input; |
| 92 | }, |
| 93 | }; |
| 94 | state.permute(); |
| 95 | testing.expectEqualSlices(u32, state.data, [_]u32{ |
| 96 | 0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68, |
| 97 | 0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8, |
| 98 | 0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6, |
| 99 | }); |
| 100 | } |
| 101 | |
| 102 | pub const Hash = struct { |
| 103 | state: State, |
| 104 | buf_off: usize, |
| 105 | |
| 106 | const Self = @This(); |
| 107 | |
| 108 | pub fn init() Self { |
| 109 | return Self{ |
| 110 | .state = State{ |
| 111 | .data = [_]u32{0} ** (State.BLOCKBYTES / 4), |
| 112 | }, |
| 113 | .buf_off = 0, |
| 114 | }; |
| 115 | } |
| 116 | |
| 117 | /// Also known as 'absorb' |
| 118 | pub fn update(self: *Self, data: []const u8) void { |
| 119 | const buf = self.state.toSlice(); |
| 120 | var in = data; |
| 121 | while (in.len > 0) { |
| 122 | var left = State.RATE - self.buf_off; |
| 123 | if (left == 0) { |
| 124 | self.state.permute(); |
| 125 | self.buf_off = 0; |
| 126 | left = State.RATE; |
| 127 | } |
| 128 | const ps = math.min(in.len, left); |
| 129 | for (buf[self.buf_off .. self.buf_off + ps]) |*p, i| { |
| 130 | p.* ^= in[i]; |
| 131 | } |
| 132 | self.buf_off += ps; |
| 133 | in = in[ps..]; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /// Finish the current hashing operation, writing the hash to `out` |
| 138 | /// |
| 139 | /// From 4.9 "Application to hashing" |
| 140 | /// By default, Gimli-Hash provides a fixed-length output of 32 bytes |
| 141 | /// (the concatenation of two 16-byte blocks). However, Gimli-Hash can |
| 142 | /// be used as an “extendable one-way function” (XOF). |
| 143 | pub fn final(self: *Self, out: []u8) void { |
| 144 | const buf = self.state.toSlice(); |
| 145 | |
| 146 | // XOR 1 into the next byte of the state |
| 147 | buf[self.buf_off] ^= 1; |
| 148 | // XOR 1 into the last byte of the state, position 47. |
| 149 | buf[buf.len - 1] ^= 1; |
| 150 | |
| 151 | self.state.squeeze(out); |
| 152 | } |
| 153 | }; |
| 154 | |
| 155 | pub fn hash(out: []u8, in: []const u8) void { |
| 156 | var st = Hash.init(); |
| 157 | st.update(in); |
| 158 | st.final(out); |
| 159 | } |
| 160 | |
| 161 | test "hash" { |
| 162 | // a test vector (30) from NIST KAT submission. |
| 163 | var msg: [58 / 2]u8 = undefined; |
| 164 | try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C"); |
| 165 | var md: [32]u8 = undefined; |
| 166 | hash(&md, msg); |
| 167 | htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", md); |
| 168 | } |