| ... | ... | @@ -1,84 +1,63 @@ |
| 1 | | const std = @import("../std.zig"); |
| 2 | | const mem = std.mem; |
| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | 3 | const math = std.math; |
| 4 | | const debug = std.debug; |
| 5 | | const htest = @import("test.zig"); |
| 4 | const mem = std.mem; |
| 5 | |
| 6 | const KeccakState = std.crypto.core.keccak.State; |
| 7 | |
| 8 | pub const Sha3_224 = Keccak(1600, 224, 0x06, 24); |
| 9 | pub const Sha3_256 = Keccak(1600, 256, 0x06, 24); |
| 10 | pub const Sha3_384 = Keccak(1600, 384, 0x06, 24); |
| 11 | pub const Sha3_512 = Keccak(1600, 512, 0x06, 24); |
| 12 | |
| 13 | pub const Keccak256 = Keccak(1600, 256, 0x01, 24); |
| 14 | pub const Keccak512 = Keccak(1600, 512, 0x01, 24); |
| 15 | pub const Keccak_256 = @compileError("Deprecated: use `Keccak256` instead"); |
| 16 | pub const Keccak_512 = @compileError("Deprecated: use `Keccak512` instead"); |
| 17 | |
| 18 | pub const Shake128 = Shake(128); |
| 19 | pub const Shake256 = Shake(256); |
| 20 | |
| 21 | /// A generic Keccak hash function. |
| 22 | pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, comptime rounds: u5) type { |
| 23 | comptime assert(output_bits > 0 and output_bits * 2 < f and output_bits % 8 == 0); // invalid output length |
| 6 | 24 | |
| 7 | | pub const Sha3_224 = Keccak(224, 0x06); |
| 8 | | pub const Sha3_256 = Keccak(256, 0x06); |
| 9 | | pub const Sha3_384 = Keccak(384, 0x06); |
| 10 | | pub const Sha3_512 = Keccak(512, 0x06); |
| 11 | | pub const Keccak_256 = Keccak(256, 0x01); |
| 12 | | pub const Keccak_512 = Keccak(512, 0x01); |
| 25 | const State = KeccakState(f, output_bits * 2, delim, rounds); |
| 13 | 26 | |
| 14 | | fn Keccak(comptime bits: usize, comptime delim: u8) type { |
| 15 | 27 | return struct { |
| 16 | 28 | const Self = @This(); |
| 29 | |
| 30 | st: State = .{}, |
| 31 | |
| 17 | 32 | /// The output length, in bytes. |
| 18 | | pub const digest_length = bits / 8; |
| 33 | pub const digest_length = output_bits / 8; |
| 19 | 34 | /// The block length, or rate, in bytes. |
| 20 | | pub const block_length = 200 - bits / 4; |
| 35 | pub const block_length = State.rate; |
| 21 | 36 | /// Keccak does not have any options. |
| 22 | 37 | pub const Options = struct {}; |
| 23 | 38 | |
| 24 | | s: [200]u8, |
| 25 | | offset: usize, |
| 26 | | |
| 39 | /// Initialize a Keccak hash function. |
| 27 | 40 | pub fn init(options: Options) Self { |
| 28 | 41 | _ = options; |
| 29 | | return Self{ .s = [_]u8{0} ** 200, .offset = 0 }; |
| 42 | return Self{}; |
| 30 | 43 | } |
| 31 | 44 | |
| 32 | | pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void { |
| 33 | | var d = Self.init(options); |
| 34 | | d.update(b); |
| 35 | | d.final(out); |
| 45 | /// Hash a slice of bytes. |
| 46 | pub fn hash(bytes: []const u8, out: *[digest_length]u8, options: Options) void { |
| 47 | var st = Self.init(options); |
| 48 | st.update(bytes); |
| 49 | st.final(out); |
| 36 | 50 | } |
| 37 | 51 | |
| 38 | | pub fn update(d: *Self, b: []const u8) void { |
| 39 | | var ip: usize = 0; |
| 40 | | var len = b.len; |
| 41 | | var rate = block_length - d.offset; |
| 42 | | var offset = d.offset; |
| 43 | | |
| 44 | | // absorb |
| 45 | | while (len >= rate) { |
| 46 | | for (d.s[offset .. offset + rate], 0..) |*r, i| |
| 47 | | r.* ^= b[ip..][i]; |
| 48 | | |
| 49 | | keccakF(1600, &d.s); |
| 50 | | |
| 51 | | ip += rate; |
| 52 | | len -= rate; |
| 53 | | rate = block_length; |
| 54 | | offset = 0; |
| 55 | | } |
| 56 | | |
| 57 | | for (d.s[offset .. offset + len], 0..) |*r, i| |
| 58 | | r.* ^= b[ip..][i]; |
| 59 | | |
| 60 | | d.offset = offset + len; |
| 52 | /// Absorb a slice of bytes into the state. |
| 53 | pub fn update(self: *Self, bytes: []const u8) void { |
| 54 | self.st.absorb(bytes); |
| 61 | 55 | } |
| 62 | 56 | |
| 63 | | pub fn final(d: *Self, out: *[digest_length]u8) void { |
| 64 | | // padding |
| 65 | | d.s[d.offset] ^= delim; |
| 66 | | d.s[block_length - 1] ^= 0x80; |
| 67 | | |
| 68 | | keccakF(1600, &d.s); |
| 69 | | |
| 70 | | // squeeze |
| 71 | | var op: usize = 0; |
| 72 | | var len: usize = bits / 8; |
| 73 | | |
| 74 | | while (len >= block_length) { |
| 75 | | mem.copy(u8, out[op..], d.s[0..block_length]); |
| 76 | | keccakF(1600, &d.s); |
| 77 | | op += block_length; |
| 78 | | len -= block_length; |
| 79 | | } |
| 80 | | |
| 81 | | mem.copy(u8, out[op..], d.s[0..len]); |
| 57 | /// Return the hash of the absorbed bytes. |
| 58 | pub fn final(self: *Self, out: *[digest_length]u8) void { |
| 59 | self.st.pad(); |
| 60 | self.st.squeeze(out[0..]); |
| 82 | 61 | } |
| 83 | 62 | |
| 84 | 63 | pub const Error = error{}; |
| ... | ... | @@ -95,87 +74,101 @@ fn Keccak(comptime bits: usize, comptime delim: u8) type { |
| 95 | 74 | }; |
| 96 | 75 | } |
| 97 | 76 | |
| 98 | | const RC = [_]u64{ |
| 99 | | 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, |
| 100 | | 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, |
| 101 | | 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, |
| 102 | | 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, |
| 103 | | 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, |
| 104 | | 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, |
| 105 | | }; |
| 106 | | |
| 107 | | const ROTC = [_]usize{ |
| 108 | | 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, |
| 109 | | }; |
| 110 | | |
| 111 | | const PIL = [_]usize{ |
| 112 | | 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, |
| 113 | | }; |
| 114 | | |
| 115 | | const M5 = [_]usize{ |
| 116 | | 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, |
| 117 | | }; |
| 118 | | |
| 119 | | fn keccakF(comptime F: usize, d: *[F / 8]u8) void { |
| 120 | | const B = F / 25; |
| 121 | | const no_rounds = comptime x: { |
| 122 | | break :x 12 + 2 * math.log2(B); |
| 123 | | }; |
| 77 | /// The SHAKE extendable output hash function. |
| 78 | pub fn Shake(comptime security_level: u11) type { |
| 79 | const f = 1600; |
| 80 | const rounds = 24; |
| 81 | const State = KeccakState(f, security_level * 2, 0x1f, rounds); |
| 124 | 82 | |
| 125 | | var s = [_]u64{0} ** 25; |
| 126 | | var t = [_]u64{0} ** 1; |
| 127 | | var c = [_]u64{0} ** 5; |
| 83 | return struct { |
| 84 | const Self = @This(); |
| 128 | 85 | |
| 129 | | for (&s, 0..) |*r, i| { |
| 130 | | r.* = mem.readIntLittle(u64, d[8 * i ..][0..8]); |
| 131 | | } |
| 86 | st: State = .{}, |
| 87 | buf: [State.rate]u8 = undefined, |
| 88 | offset: usize = 0, |
| 89 | padded: bool = false, |
| 132 | 90 | |
| 133 | | for (RC[0..no_rounds]) |round| { |
| 134 | | // theta |
| 135 | | comptime var x: usize = 0; |
| 136 | | inline while (x < 5) : (x += 1) { |
| 137 | | c[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20]; |
| 91 | /// The recommended output length, in bytes. |
| 92 | pub const digest_length = security_level / 2; |
| 93 | /// The block length, or rate, in bytes. |
| 94 | pub const block_length = State.rate; |
| 95 | /// Keccak does not have any options. |
| 96 | pub const Options = struct {}; |
| 97 | |
| 98 | /// Initialize a SHAKE extensible hash function. |
| 99 | pub fn init(options: Options) Self { |
| 100 | _ = options; |
| 101 | return Self{}; |
| 138 | 102 | } |
| 139 | | x = 0; |
| 140 | | inline while (x < 5) : (x += 1) { |
| 141 | | t[0] = c[M5[x + 4]] ^ math.rotl(u64, c[M5[x + 1]], @as(usize, 1)); |
| 142 | | comptime var y: usize = 0; |
| 143 | | inline while (y < 5) : (y += 1) { |
| 144 | | s[x + y * 5] ^= t[0]; |
| 145 | | } |
| 103 | |
| 104 | /// Hash a slice of bytes. |
| 105 | /// `out` can be any length. |
| 106 | pub fn hash(bytes: []const u8, out: []u8, options: Options) void { |
| 107 | var st = Self.init(options); |
| 108 | st.update(bytes); |
| 109 | st.squeeze(out); |
| 146 | 110 | } |
| 147 | 111 | |
| 148 | | // rho+pi |
| 149 | | t[0] = s[1]; |
| 150 | | x = 0; |
| 151 | | inline while (x < 24) : (x += 1) { |
| 152 | | c[0] = s[PIL[x]]; |
| 153 | | s[PIL[x]] = math.rotl(u64, t[0], ROTC[x]); |
| 154 | | t[0] = c[0]; |
| 112 | /// Absorb a slice of bytes into the state. |
| 113 | pub fn update(self: *Self, bytes: []const u8) void { |
| 114 | self.st.absorb(bytes); |
| 155 | 115 | } |
| 156 | 116 | |
| 157 | | // chi |
| 158 | | comptime var y: usize = 0; |
| 159 | | inline while (y < 5) : (y += 1) { |
| 160 | | x = 0; |
| 161 | | inline while (x < 5) : (x += 1) { |
| 162 | | c[x] = s[x + y * 5]; |
| 117 | /// Squeeze a slice of bytes from the state. |
| 118 | /// `out` can be any length, and the function can be called multiple times. |
| 119 | pub fn squeeze(self: *Self, out_: []u8) void { |
| 120 | if (!self.padded) { |
| 121 | self.st.pad(); |
| 122 | self.padded = true; |
| 123 | } |
| 124 | var out = out_; |
| 125 | if (self.offset > 0) { |
| 126 | const left = self.buf.len - self.offset; |
| 127 | if (left > 0) { |
| 128 | const n = math.min(left, out.len); |
| 129 | mem.copy(u8, out[0..n], self.buf[self.offset..][0..n]); |
| 130 | out = out[n..]; |
| 131 | self.offset += n; |
| 132 | if (out.len == 0) { |
| 133 | return; |
| 134 | } |
| 135 | } |
| 163 | 136 | } |
| 164 | | x = 0; |
| 165 | | inline while (x < 5) : (x += 1) { |
| 166 | | s[x + y * 5] = c[x] ^ (~c[M5[x + 1]] & c[M5[x + 2]]); |
| 137 | const full_blocks = out[0 .. out.len - out.len % State.rate]; |
| 138 | if (full_blocks.len > 0) { |
| 139 | self.st.squeeze(full_blocks); |
| 140 | out = out[full_blocks.len..]; |
| 141 | } |
| 142 | if (out.len > 0) { |
| 143 | self.st.squeeze(self.buf[0..]); |
| 144 | mem.copy(u8, out[0..], self.buf[0..out.len]); |
| 145 | self.offset = out.len; |
| 167 | 146 | } |
| 168 | 147 | } |
| 169 | 148 | |
| 170 | | // iota |
| 171 | | s[0] ^= round; |
| 172 | | } |
| 149 | /// Return the hash of the absorbed bytes. |
| 150 | /// `out` can be of any length, but the function must not be called multiple times (use `squeeze` for that purpose instead). |
| 151 | pub fn final(self: *Self, out: []u8) void { |
| 152 | self.squeeze(out); |
| 153 | self.st.st.clear(0, State.rate); |
| 154 | } |
| 173 | 155 | |
| 174 | | for (s, 0..) |r, i| { |
| 175 | | mem.writeIntLittle(u64, d[8 * i ..][0..8], r); |
| 176 | | } |
| 156 | pub const Error = error{}; |
| 157 | pub const Writer = std.io.Writer(*Self, Error, write); |
| 158 | |
| 159 | fn write(self: *Self, bytes: []const u8) Error!usize { |
| 160 | self.update(bytes); |
| 161 | return bytes.len; |
| 162 | } |
| 163 | |
| 164 | pub fn writer(self: *Self) Writer { |
| 165 | return .{ .context = self }; |
| 166 | } |
| 167 | }; |
| 177 | 168 | } |
| 178 | 169 | |
| 170 | const htest = @import("test.zig"); |
| 171 | |
| 179 | 172 | test "sha3-224 single" { |
| 180 | 173 | try htest.assertEqualHash(Sha3_224, "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", ""); |
| 181 | 174 | try htest.assertEqualHash(Sha3_224, "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc"); |
| ... | ... | @@ -309,13 +302,49 @@ test "sha3-512 aligned final" { |
| 309 | 302 | } |
| 310 | 303 | |
| 311 | 304 | test "keccak-256 single" { |
| 312 | | try htest.assertEqualHash(Keccak_256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""); |
| 313 | | try htest.assertEqualHash(Keccak_256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"); |
| 314 | | try htest.assertEqualHash(Keccak_256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 305 | try htest.assertEqualHash(Keccak256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""); |
| 306 | try htest.assertEqualHash(Keccak256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"); |
| 307 | try htest.assertEqualHash(Keccak256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 315 | 308 | } |
| 316 | 309 | |
| 317 | 310 | test "keccak-512 single" { |
| 318 | | try htest.assertEqualHash(Keccak_512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""); |
| 319 | | try htest.assertEqualHash(Keccak_512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"); |
| 320 | | try htest.assertEqualHash(Keccak_512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 311 | try htest.assertEqualHash(Keccak512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""); |
| 312 | try htest.assertEqualHash(Keccak512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"); |
| 313 | try htest.assertEqualHash(Keccak512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 314 | } |
| 315 | |
| 316 | test "SHAKE-128 single" { |
| 317 | var out: [10]u8 = undefined; |
| 318 | Shake128.hash("hello123", &out, .{}); |
| 319 | try htest.assertEqual("1b85861510bc4d8e467d", &out); |
| 320 | } |
| 321 | |
| 322 | test "SHAKE-128 multisqueeze" { |
| 323 | var out: [10]u8 = undefined; |
| 324 | var h = Shake128.init(.{}); |
| 325 | h.update("hello123"); |
| 326 | h.squeeze(out[0..4]); |
| 327 | h.squeeze(out[4..]); |
| 328 | try htest.assertEqual("1b85861510bc4d8e467d", &out); |
| 329 | } |
| 330 | |
| 331 | test "SHAKE-128 multisqueeze with multiple blocks" { |
| 332 | var out: [100]u8 = undefined; |
| 333 | var out2: [100]u8 = undefined; |
| 334 | |
| 335 | var h = Shake128.init(.{}); |
| 336 | h.update("hello123"); |
| 337 | h.squeeze(out[0..50]); |
| 338 | h.squeeze(out[50..]); |
| 339 | |
| 340 | var h2 = Shake128.init(.{}); |
| 341 | h2.update("hello123"); |
| 342 | h2.squeeze(&out2); |
| 343 | try std.testing.expectEqualSlices(u8, &out, &out2); |
| 344 | } |
| 345 | |
| 346 | test "SHAKE-256 single" { |
| 347 | var out: [10]u8 = undefined; |
| 348 | Shake256.hash("hello123", &out, .{}); |
| 349 | try htest.assertEqual("ade612ba265f92de4a37", &out); |
| 321 | 350 | } |