| ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; | ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; |
| 4 | const math = std.math; | 4 | const math = std.math; |
| 5 | const mem = std.mem; | 5 | const mem = std.mem; |
| 6 | const native_endian = builtin.cpu.arch.endian(); | 6 | const native_endian = builtin.cpu.arch.endian(); |
| | 7 | const mode = @import("builtin").mode; |
| 7 | | 8 | |
| 8 | /// The Keccak-f permutation. | 9 | /// The Keccak-f permutation. |
| 9 | pub fn KeccakF(comptime f: u11) type { | 10 | pub fn KeccakF(comptime f: u11) type { |
| ... | @@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type | ... | @@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type |
| 199 | comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size | 200 | comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size |
| 200 | comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size | 201 | comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size |
| 201 | | 202 | |
| | 203 | // In debug mode, track transitions to prevent insecure ones. |
| | 204 | const Op = enum { uninitialized, initialized, updated, absorb, squeeze }; |
| | 205 | const TransitionTracker = if (mode == .Debug) struct { |
| | 206 | op: Op = .uninitialized, |
| | 207 | |
| | 208 | fn to(tracker: *@This(), next_op: Op) void { |
| | 209 | switch (next_op) { |
| | 210 | .updated => { |
| | 211 | switch (tracker.op) { |
| | 212 | .uninitialized => @panic("cannot permute before initializing"), |
| | 213 | else => {}, |
| | 214 | } |
| | 215 | }, |
| | 216 | .absorb => { |
| | 217 | switch (tracker.op) { |
| | 218 | .squeeze => @panic("cannot absorb right after squeezing"), |
| | 219 | else => {}, |
| | 220 | } |
| | 221 | }, |
| | 222 | .squeeze => { |
| | 223 | switch (tracker.op) { |
| | 224 | .uninitialized => @panic("cannot squeeze before initializing"), |
| | 225 | .initialized => @panic("cannot squeeze right after initializing"), |
| | 226 | .absorb => @panic("cannot squeeze right after absorbing"), |
| | 227 | else => {}, |
| | 228 | } |
| | 229 | }, |
| | 230 | .uninitialized => @panic("cannot transition to uninitialized"), |
| | 231 | .initialized => {}, |
| | 232 | } |
| | 233 | tracker.op = next_op; |
| | 234 | } |
| | 235 | } else struct { |
| | 236 | // No-op in non-debug modes. |
| | 237 | inline fn to(tracker: *@This(), next_op: Op) void { |
| | 238 | _ = tracker; // no-op |
| | 239 | _ = next_op; // no-op |
| | 240 | } |
| | 241 | }; |
| | 242 | |
| 202 | return struct { | 243 | return struct { |
| 203 | const Self = @This(); | 244 | const Self = @This(); |
| 204 | | 245 | |
| ... | @@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type | ... | @@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type |
| 215 | | 256 | |
| 216 | st: KeccakF(f) = .{}, | 257 | st: KeccakF(f) = .{}, |
| 217 | | 258 | |
| | 259 | transition: TransitionTracker = .{}, |
| | 260 | |
| 218 | /// Absorb a slice of bytes into the sponge. | 261 | /// Absorb a slice of bytes into the sponge. |
| 219 | pub fn absorb(self: *Self, bytes_: []const u8) void { | 262 | pub fn absorb(self: *Self, bytes: []const u8) void { |
| 220 | var bytes = bytes_; | 263 | self.transition.to(.absorb); |
| | 264 | var i: usize = 0; |
| 221 | if (self.offset > 0) { | 265 | if (self.offset > 0) { |
| 222 | const left = @min(rate - self.offset, bytes.len); | 266 | const left = @min(rate - self.offset, bytes.len); |
| 223 | @memcpy(self.buf[self.offset..][0..left], bytes[0..left]); | 267 | @memcpy(self.buf[self.offset..][0..left], bytes[0..left]); |
| 224 | self.offset += left; | 268 | self.offset += left; |
| | 269 | if (left == bytes.len) return; |
| 225 | if (self.offset == rate) { | 270 | if (self.offset == rate) { |
| 226 | self.offset = 0; | | |
| 227 | self.st.addBytes(self.buf[0..]); | 271 | self.st.addBytes(self.buf[0..]); |
| 228 | self.st.permuteR(rounds); | 272 | self.st.permuteR(rounds); |
| | 273 | self.offset = 0; |
| 229 | } | 274 | } |
| 230 | if (left == bytes.len) return; | 275 | i = left; |
| 231 | bytes = bytes[left..]; | | |
| 232 | } | 276 | } |
| 233 | while (bytes.len >= rate) { | 277 | while (i + rate < bytes.len) : (i += rate) { |
| 234 | self.st.addBytes(bytes[0..rate]); | 278 | self.st.addBytes(bytes[i..][0..rate]); |
| 235 | self.st.permuteR(rounds); | 279 | self.st.permuteR(rounds); |
| 236 | bytes = bytes[rate..]; | | |
| 237 | } | 280 | } |
| 238 | if (bytes.len > 0) { | 281 | const left = bytes.len - i; |
| 239 | @memcpy(self.buf[0..bytes.len], bytes); | 282 | if (left > 0) { |
| 240 | self.offset = bytes.len; | 283 | @memcpy(self.buf[0..left], bytes[i..][0..left]); |
| 241 | } | 284 | } |
| | 285 | self.offset = left; |
| 242 | } | 286 | } |
| 243 | | 287 | |
| 244 | /// Initialize the state from a slice of bytes. | 288 | /// Initialize the state from a slice of bytes. |
| 245 | pub fn init(bytes: [f / 8]u8) Self { | 289 | pub fn init(bytes: [f / 8]u8, delim: u8) Self { |
| 246 | return .{ .st = KeccakF(f).init(bytes) }; | 290 | var st = Self{ .st = KeccakF(f).init(bytes), .delim = delim }; |
| | 291 | st.transition.to(.initialized); |
| | 292 | return st; |
| 247 | } | 293 | } |
| 248 | | 294 | |
| 249 | /// Permute the state | 295 | /// Permute the state |
| 250 | pub fn permute(self: *Self) void { | 296 | pub fn permute(self: *Self) void { |
| | 297 | if (mode == .Debug) { |
| | 298 | if (self.transition.op == .absorb and self.offset > 0) { |
| | 299 | @panic("cannot permute with pending input - call fillBlock() or pad() instead"); |
| | 300 | } |
| | 301 | } |
| | 302 | self.transition.to(.updated); |
| 251 | self.st.permuteR(rounds); | 303 | self.st.permuteR(rounds); |
| 252 | self.offset = 0; | 304 | self.offset = 0; |
| 253 | } | 305 | } |
| 254 | | 306 | |
| 255 | /// Align the input to the rate boundary. | 307 | /// Align the input to the rate boundary and permute. |
| 256 | pub fn fillBlock(self: *Self) void { | 308 | pub fn fillBlock(self: *Self) void { |
| | 309 | self.transition.to(.absorb); |
| 257 | self.st.addBytes(self.buf[0..self.offset]); | 310 | self.st.addBytes(self.buf[0..self.offset]); |
| 258 | self.st.permuteR(rounds); | 311 | self.st.permuteR(rounds); |
| 259 | self.offset = 0; | 312 | self.offset = 0; |
| | 313 | self.transition.to(.updated); |
| 260 | } | 314 | } |
| 261 | | 315 | |
| 262 | /// Mark the end of the input. | 316 | /// Mark the end of the input. |
| 263 | pub fn pad(self: *Self) void { | 317 | pub fn pad(self: *Self) void { |
| | 318 | self.transition.to(.absorb); |
| 264 | self.st.addBytes(self.buf[0..self.offset]); | 319 | self.st.addBytes(self.buf[0..self.offset]); |
| | 320 | if (self.offset == rate) { |
| | 321 | self.st.permuteR(rounds); |
| | 322 | self.offset = 0; |
| | 323 | } |
| 265 | self.st.addByte(self.delim, self.offset); | 324 | self.st.addByte(self.delim, self.offset); |
| 266 | self.st.addByte(0x80, rate - 1); | 325 | self.st.addByte(0x80, rate - 1); |
| 267 | self.st.permuteR(rounds); | 326 | self.st.permuteR(rounds); |
| 268 | self.offset = 0; | 327 | self.offset = 0; |
| | 328 | self.transition.to(.updated); |
| 269 | } | 329 | } |
| 270 | | 330 | |
| 271 | /// Squeeze a slice of bytes from the sponge. | 331 | /// Squeeze a slice of bytes from the sponge. |
| | 332 | /// The function can be called multiple times. |
| 272 | pub fn squeeze(self: *Self, out: []u8) void { | 333 | pub fn squeeze(self: *Self, out: []u8) void { |
| | 334 | self.transition.to(.squeeze); |
| 273 | var i: usize = 0; | 335 | var i: usize = 0; |
| 274 | while (i < out.len) : (i += rate) { | 336 | if (self.offset == rate) { |
| 275 | const left = @min(rate, out.len - i); | 337 | self.st.permuteR(rounds); |
| 276 | self.st.extractBytes(out[i..][0..left]); | 338 | } else if (self.offset > 0) { |
| | 339 | @branchHint(.unlikely); |
| | 340 | var buf: [rate]u8 = undefined; |
| | 341 | self.st.extractBytes(buf[0..]); |
| | 342 | const left = @min(rate - self.offset, out.len); |
| | 343 | @memcpy(out[0..left], buf[self.offset..][0..left]); |
| | 344 | self.offset += left; |
| | 345 | if (left == out.len) return; |
| | 346 | if (self.offset == rate) { |
| | 347 | self.offset = 0; |
| | 348 | self.st.permuteR(rounds); |
| | 349 | } |
| | 350 | i = left; |
| | 351 | } |
| | 352 | while (i + rate < out.len) : (i += rate) { |
| | 353 | self.st.extractBytes(out[i..][0..rate]); |
| 277 | self.st.permuteR(rounds); | 354 | self.st.permuteR(rounds); |
| 278 | } | 355 | } |
| | 356 | const left = out.len - i; |
| | 357 | if (left > 0) { |
| | 358 | self.st.extractBytes(out[i..][0..left]); |
| | 359 | } |
| | 360 | self.offset = left; |
| 279 | } | 361 | } |
| 280 | }; | 362 | }; |
| 281 | } | 363 | } |
| ... | @@ -298,3 +380,26 @@ test "Keccak-f800" { | ... | @@ -298,3 +380,26 @@ test "Keccak-f800" { |
| 298 | }; | 380 | }; |
| 299 | try std.testing.expectEqualSlices(u32, &st.st, &expected); | 381 | try std.testing.expectEqualSlices(u32, &st.st, &expected); |
| 300 | } | 382 | } |
| | 383 | |
| | 384 | test "squeeze" { |
| | 385 | var st = State(800, 256, 22).init([_]u8{0x80} ** 100, 0x01); |
| | 386 | |
| | 387 | var out0: [15]u8 = undefined; |
| | 388 | var out1: [out0.len]u8 = undefined; |
| | 389 | st.permute(); |
| | 390 | var st0 = st; |
| | 391 | st0.squeeze(out0[0..]); |
| | 392 | var st1 = st; |
| | 393 | st1.squeeze(out1[0 .. out1.len / 2]); |
| | 394 | st1.squeeze(out1[out1.len / 2 ..]); |
| | 395 | try std.testing.expectEqualSlices(u8, &out0, &out1); |
| | 396 | |
| | 397 | var out2: [100]u8 = undefined; |
| | 398 | var out3: [out2.len]u8 = undefined; |
| | 399 | var st2 = st; |
| | 400 | st2.squeeze(out2[0..]); |
| | 401 | var st3 = st; |
| | 402 | st3.squeeze(out3[0 .. out2.len / 2]); |
| | 403 | st3.squeeze(out3[out2.len / 2 ..]); |
| | 404 | try std.testing.expectEqualSlices(u8, &out2, &out3); |
| | 405 | } |