| ... | @@ -1,84 +1,63 @@ | ... | @@ -1,84 +1,63 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | 2 | const assert = std.debug.assert; |
| 3 | const math = std.math; | 3 | const math = std.math; |
| 4 | const debug = std.debug; | 4 | const mem = std.mem; |
| 5 | const htest = @import("test.zig"); | 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); | 25 | const State = KeccakState(f, output_bits * 2, delim, rounds); |
| 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); | | |
| 13 | | 26 | |
| 14 | fn Keccak(comptime bits: usize, comptime delim: u8) type { | | |
| 15 | return struct { | 27 | return struct { |
| 16 | const Self = @This(); | 28 | const Self = @This(); |
| | 29 | |
| | 30 | st: State = .{}, |
| | 31 | |
| 17 | /// The output length, in bytes. | 32 | /// The output length, in bytes. |
| 18 | pub const digest_length = bits / 8; | 33 | pub const digest_length = output_bits / 8; |
| 19 | /// The block length, or rate, in bytes. | 34 | /// The block length, or rate, in bytes. |
| 20 | pub const block_length = 200 - bits / 4; | 35 | pub const block_length = State.rate; |
| 21 | /// Keccak does not have any options. | 36 | /// Keccak does not have any options. |
| 22 | pub const Options = struct {}; | 37 | pub const Options = struct {}; |
| 23 | | 38 | |
| 24 | s: [200]u8, | 39 | /// Initialize a Keccak hash function. |
| 25 | offset: usize, | | |
| 26 | | | |
| 27 | pub fn init(options: Options) Self { | 40 | pub fn init(options: Options) Self { |
| 28 | _ = options; | 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 { | 45 | /// Hash a slice of bytes. |
| 33 | var d = Self.init(options); | 46 | pub fn hash(bytes: []const u8, out: *[digest_length]u8, options: Options) void { |
| 34 | d.update(b); | 47 | var st = Self.init(options); |
| 35 | d.final(out); | 48 | st.update(bytes); |
| | 49 | st.final(out); |
| 36 | } | 50 | } |
| 37 | | 51 | |
| 38 | pub fn update(d: *Self, b: []const u8) void { | 52 | /// Absorb a slice of bytes into the state. |
| 39 | var ip: usize = 0; | 53 | pub fn update(self: *Self, bytes: []const u8) void { |
| 40 | var len = b.len; | 54 | self.st.absorb(bytes); |
| 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; | | |
| 61 | } | 55 | } |
| 62 | | 56 | |
| 63 | pub fn final(d: *Self, out: *[digest_length]u8) void { | 57 | /// Return the hash of the absorbed bytes. |
| 64 | // padding | 58 | pub fn final(self: *Self, out: *[digest_length]u8) void { |
| 65 | d.s[d.offset] ^= delim; | 59 | self.st.pad(); |
| 66 | d.s[block_length - 1] ^= 0x80; | 60 | self.st.squeeze(out[0..]); |
| 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]); | | |
| 82 | } | 61 | } |
| 83 | | 62 | |
| 84 | pub const Error = error{}; | 63 | pub const Error = error{}; |
| ... | @@ -95,87 +74,101 @@ fn Keccak(comptime bits: usize, comptime delim: u8) type { | ... | @@ -95,87 +74,101 @@ fn Keccak(comptime bits: usize, comptime delim: u8) type { |
| 95 | }; | 74 | }; |
| 96 | } | 75 | } |
| 97 | | 76 | |
| 98 | const RC = [_]u64{ | 77 | /// The SHAKE extendable output hash function. |
| 99 | 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, | 78 | pub fn Shake(comptime security_level: u11) type { |
| 100 | 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, | 79 | const f = 1600; |
| 101 | 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, | 80 | const rounds = 24; |
| 102 | 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, | 81 | const State = KeccakState(f, security_level * 2, 0x1f, rounds); |
| 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 | }; | | |
| 124 | | 82 | |
| 125 | var s = [_]u64{0} ** 25; | 83 | return struct { |
| 126 | var t = [_]u64{0} ** 1; | 84 | const Self = @This(); |
| 127 | var c = [_]u64{0} ** 5; | | |
| 128 | | 85 | |
| 129 | for (&s, 0..) |*r, i| { | 86 | st: State = .{}, |
| 130 | r.* = mem.readIntLittle(u64, d[8 * i ..][0..8]); | 87 | buf: [State.rate]u8 = undefined, |
| 131 | } | 88 | offset: usize = 0, |
| | 89 | padded: bool = false, |
| 132 | | 90 | |
| 133 | for (RC[0..no_rounds]) |round| { | 91 | /// The recommended output length, in bytes. |
| 134 | // theta | 92 | pub const digest_length = security_level / 2; |
| 135 | comptime var x: usize = 0; | 93 | /// The block length, or rate, in bytes. |
| 136 | inline while (x < 5) : (x += 1) { | 94 | pub const block_length = State.rate; |
| 137 | c[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20]; | 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; | 103 | |
| 140 | inline while (x < 5) : (x += 1) { | 104 | /// Hash a slice of bytes. |
| 141 | t[0] = c[M5[x + 4]] ^ math.rotl(u64, c[M5[x + 1]], @as(usize, 1)); | 105 | /// `out` can be any length. |
| 142 | comptime var y: usize = 0; | 106 | pub fn hash(bytes: []const u8, out: []u8, options: Options) void { |
| 143 | inline while (y < 5) : (y += 1) { | 107 | var st = Self.init(options); |
| 144 | s[x + y * 5] ^= t[0]; | 108 | st.update(bytes); |
| 145 | } | 109 | st.squeeze(out); |
| 146 | } | 110 | } |
| 147 | | 111 | |
| 148 | // rho+pi | 112 | /// Absorb a slice of bytes into the state. |
| 149 | t[0] = s[1]; | 113 | pub fn update(self: *Self, bytes: []const u8) void { |
| 150 | x = 0; | 114 | self.st.absorb(bytes); |
| 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]; | | |
| 155 | } | 115 | } |
| 156 | | 116 | |
| 157 | // chi | 117 | /// Squeeze a slice of bytes from the state. |
| 158 | comptime var y: usize = 0; | 118 | /// `out` can be any length, and the function can be called multiple times. |
| 159 | inline while (y < 5) : (y += 1) { | 119 | pub fn squeeze(self: *Self, out_: []u8) void { |
| 160 | x = 0; | 120 | if (!self.padded) { |
| 161 | inline while (x < 5) : (x += 1) { | 121 | self.st.pad(); |
| 162 | c[x] = s[x + y * 5]; | 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; | 137 | const full_blocks = out[0 .. out.len - out.len % State.rate]; |
| 165 | inline while (x < 5) : (x += 1) { | 138 | if (full_blocks.len > 0) { |
| 166 | s[x + y * 5] = c[x] ^ (~c[M5[x + 1]] & c[M5[x + 2]]); | 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 | 149 | /// Return the hash of the absorbed bytes. |
| 171 | s[0] ^= round; | 150 | /// `out` can be of any length, but the function must not be called multiple times (use `squeeze` for that purpose instead). |
| 172 | } | 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| { | 156 | pub const Error = error{}; |
| 175 | mem.writeIntLittle(u64, d[8 * i ..][0..8], r); | 157 | pub const Writer = std.io.Writer(*Self, Error, write); |
| 176 | } | 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 | test "sha3-224 single" { | 172 | test "sha3-224 single" { |
| 180 | try htest.assertEqualHash(Sha3_224, "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", ""); | 173 | try htest.assertEqualHash(Sha3_224, "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", ""); |
| 181 | try htest.assertEqualHash(Sha3_224, "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc"); | 174 | try htest.assertEqualHash(Sha3_224, "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc"); |
| ... | @@ -309,13 +302,49 @@ test "sha3-512 aligned final" { | ... | @@ -309,13 +302,49 @@ test "sha3-512 aligned final" { |
| 309 | } | 302 | } |
| 310 | | 303 | |
| 311 | test "keccak-256 single" { | 304 | test "keccak-256 single" { |
| 312 | try htest.assertEqualHash(Keccak_256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""); | 305 | try htest.assertEqualHash(Keccak256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""); |
| 313 | try htest.assertEqualHash(Keccak_256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"); | 306 | try htest.assertEqualHash(Keccak256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"); |
| 314 | try htest.assertEqualHash(Keccak_256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); | 307 | try htest.assertEqualHash(Keccak256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 315 | } | 308 | } |
| 316 | | 309 | |
| 317 | test "keccak-512 single" { | 310 | test "keccak-512 single" { |
| 318 | try htest.assertEqualHash(Keccak_512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""); | 311 | try htest.assertEqualHash(Keccak512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""); |
| 319 | try htest.assertEqualHash(Keccak_512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"); | 312 | try htest.assertEqualHash(Keccak512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"); |
| 320 | try htest.assertEqualHash(Keccak_512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); | 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 | } |