| ... | ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; |
| 4 | 4 | const math = std.math; |
| 5 | 5 | const mem = std.mem; |
| 6 | 6 | const native_endian = builtin.cpu.arch.endian(); |
| 7 | const mode = @import("builtin").mode; |
| 7 | 8 | |
| 8 | 9 | /// The Keccak-f permutation. |
| 9 | 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 | 200 | comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size |
| 200 | 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 | 243 | return struct { |
| 203 | 244 | const Self = @This(); |
| 204 | 245 | |
| ... | ... | @@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type |
| 215 | 256 | |
| 216 | 257 | st: KeccakF(f) = .{}, |
| 217 | 258 | |
| 259 | transition: TransitionTracker = .{}, |
| 260 | |
| 218 | 261 | /// Absorb a slice of bytes into the sponge. |
| 219 | | pub fn absorb(self: *Self, bytes_: []const u8) void { |
| 220 | | var bytes = bytes_; |
| 262 | pub fn absorb(self: *Self, bytes: []const u8) void { |
| 263 | self.transition.to(.absorb); |
| 264 | var i: usize = 0; |
| 221 | 265 | if (self.offset > 0) { |
| 222 | 266 | const left = @min(rate - self.offset, bytes.len); |
| 223 | 267 | @memcpy(self.buf[self.offset..][0..left], bytes[0..left]); |
| 224 | 268 | self.offset += left; |
| 269 | if (left == bytes.len) return; |
| 225 | 270 | if (self.offset == rate) { |
| 226 | | self.offset = 0; |
| 227 | 271 | self.st.addBytes(self.buf[0..]); |
| 228 | 272 | self.st.permuteR(rounds); |
| 273 | self.offset = 0; |
| 229 | 274 | } |
| 230 | | if (left == bytes.len) return; |
| 231 | | bytes = bytes[left..]; |
| 275 | i = left; |
| 232 | 276 | } |
| 233 | | while (bytes.len >= rate) { |
| 234 | | self.st.addBytes(bytes[0..rate]); |
| 277 | while (i + rate < bytes.len) : (i += rate) { |
| 278 | self.st.addBytes(bytes[i..][0..rate]); |
| 235 | 279 | self.st.permuteR(rounds); |
| 236 | | bytes = bytes[rate..]; |
| 237 | 280 | } |
| 238 | | if (bytes.len > 0) { |
| 239 | | @memcpy(self.buf[0..bytes.len], bytes); |
| 240 | | self.offset = bytes.len; |
| 281 | const left = bytes.len - i; |
| 282 | if (left > 0) { |
| 283 | @memcpy(self.buf[0..left], bytes[i..][0..left]); |
| 241 | 284 | } |
| 285 | self.offset = left; |
| 242 | 286 | } |
| 243 | 287 | |
| 244 | 288 | /// Initialize the state from a slice of bytes. |
| 245 | | pub fn init(bytes: [f / 8]u8) Self { |
| 246 | | return .{ .st = KeccakF(f).init(bytes) }; |
| 289 | pub fn init(bytes: [f / 8]u8, delim: u8) Self { |
| 290 | var st = Self{ .st = KeccakF(f).init(bytes), .delim = delim }; |
| 291 | st.transition.to(.initialized); |
| 292 | return st; |
| 247 | 293 | } |
| 248 | 294 | |
| 249 | 295 | /// Permute the state |
| 250 | 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 | 303 | self.st.permuteR(rounds); |
| 252 | 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 | 308 | pub fn fillBlock(self: *Self) void { |
| 309 | self.transition.to(.absorb); |
| 257 | 310 | self.st.addBytes(self.buf[0..self.offset]); |
| 258 | 311 | self.st.permuteR(rounds); |
| 259 | 312 | self.offset = 0; |
| 313 | self.transition.to(.updated); |
| 260 | 314 | } |
| 261 | 315 | |
| 262 | 316 | /// Mark the end of the input. |
| 263 | 317 | pub fn pad(self: *Self) void { |
| 318 | self.transition.to(.absorb); |
| 264 | 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 | 324 | self.st.addByte(self.delim, self.offset); |
| 266 | 325 | self.st.addByte(0x80, rate - 1); |
| 267 | 326 | self.st.permuteR(rounds); |
| 268 | 327 | self.offset = 0; |
| 328 | self.transition.to(.updated); |
| 269 | 329 | } |
| 270 | 330 | |
| 271 | 331 | /// Squeeze a slice of bytes from the sponge. |
| 332 | /// The function can be called multiple times. |
| 272 | 333 | pub fn squeeze(self: *Self, out: []u8) void { |
| 334 | self.transition.to(.squeeze); |
| 273 | 335 | var i: usize = 0; |
| 274 | | while (i < out.len) : (i += rate) { |
| 275 | | const left = @min(rate, out.len - i); |
| 276 | | self.st.extractBytes(out[i..][0..left]); |
| 336 | if (self.offset == rate) { |
| 337 | self.st.permuteR(rounds); |
| 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 | 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 | 380 | }; |
| 299 | 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 | } |