aboutsummaryrefslogtreecommitdiff
path: root/lib/std/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/crypto')
-rw-r--r--lib/std/crypto/benchmark.zig198
-rw-r--r--lib/std/crypto/blake2.zig495
-rw-r--r--lib/std/crypto/chacha20.zig436
-rw-r--r--lib/std/crypto/gimli.zig168
-rw-r--r--lib/std/crypto/hmac.zig99
-rw-r--r--lib/std/crypto/md5.zig281
-rw-r--r--lib/std/crypto/poly1305.zig234
-rw-r--r--lib/std/crypto/sha1.zig302
-rw-r--r--lib/std/crypto/sha2.zig726
-rw-r--r--lib/std/crypto/sha3.zig305
-rw-r--r--lib/std/crypto/test.zig22
-rw-r--r--lib/std/crypto/x25519.zig676
12 files changed, 3942 insertions, 0 deletions
diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig
new file mode 100644
index 0000000000..ae5087a847
--- /dev/null
+++ b/lib/std/crypto/benchmark.zig
@@ -0,0 +1,198 @@
+// zig run benchmark.zig --release-fast --override-std-dir ..
+
+const builtin = @import("builtin");
+const std = @import("../std.zig");
+const time = std.time;
+const Timer = time.Timer;
+const crypto = std.crypto;
+
+const KiB = 1024;
+const MiB = 1024 * KiB;
+
+var prng = std.rand.DefaultPrng.init(0);
+
+const Crypto = struct {
+ ty: type,
+ name: []const u8,
+};
+
+const hashes = [_]Crypto{
+ Crypto{ .ty = crypto.Md5, .name = "md5" },
+ Crypto{ .ty = crypto.Sha1, .name = "sha1" },
+ Crypto{ .ty = crypto.Sha256, .name = "sha256" },
+ Crypto{ .ty = crypto.Sha512, .name = "sha512" },
+ Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },
+ Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },
+ Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },
+ Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },
+};
+
+pub fn benchmarkHash(comptime Hash: var, comptime bytes: comptime_int) !u64 {
+ var h = Hash.init();
+
+ var block: [Hash.digest_length]u8 = undefined;
+ prng.random.bytes(block[0..]);
+
+ var offset: usize = 0;
+ var timer = try Timer.start();
+ const start = timer.lap();
+ while (offset < bytes) : (offset += block.len) {
+ h.update(block[0..]);
+ }
+ const end = timer.read();
+
+ const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
+ const throughput = @floatToInt(u64, bytes / elapsed_s);
+
+ return throughput;
+}
+
+const macs = [_]Crypto{
+ Crypto{ .ty = crypto.Poly1305, .name = "poly1305" },
+ Crypto{ .ty = crypto.HmacMd5, .name = "hmac-md5" },
+ Crypto{ .ty = crypto.HmacSha1, .name = "hmac-sha1" },
+ Crypto{ .ty = crypto.HmacSha256, .name = "hmac-sha256" },
+};
+
+pub fn benchmarkMac(comptime Mac: var, comptime bytes: comptime_int) !u64 {
+ std.debug.assert(32 >= Mac.mac_length and 32 >= Mac.minimum_key_length);
+
+ var in: [1 * MiB]u8 = undefined;
+ prng.random.bytes(in[0..]);
+
+ var key: [32]u8 = undefined;
+ prng.random.bytes(key[0..]);
+
+ var offset: usize = 0;
+ var timer = try Timer.start();
+ const start = timer.lap();
+ while (offset < bytes) : (offset += in.len) {
+ Mac.create(key[0..], in[0..], key);
+ }
+ const end = timer.read();
+
+ const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
+ const throughput = @floatToInt(u64, bytes / elapsed_s);
+
+ return throughput;
+}
+
+const exchanges = [_]Crypto{Crypto{ .ty = crypto.X25519, .name = "x25519" }};
+
+pub fn benchmarkKeyExchange(comptime DhKeyExchange: var, comptime exchange_count: comptime_int) !u64 {
+ std.debug.assert(DhKeyExchange.minimum_key_length >= DhKeyExchange.secret_length);
+
+ var in: [DhKeyExchange.minimum_key_length]u8 = undefined;
+ prng.random.bytes(in[0..]);
+
+ var out: [DhKeyExchange.minimum_key_length]u8 = undefined;
+ prng.random.bytes(out[0..]);
+
+ var offset: usize = 0;
+ var timer = try Timer.start();
+ const start = timer.lap();
+ {
+ var i: usize = 0;
+ while (i < exchange_count) : (i += 1) {
+ _ = DhKeyExchange.create(out[0..], out, in);
+ }
+ }
+ const end = timer.read();
+
+ const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
+ const throughput = @floatToInt(u64, exchange_count / elapsed_s);
+
+ return throughput;
+}
+
+fn usage() void {
+ std.debug.warn(
+ \\throughput_test [options]
+ \\
+ \\Options:
+ \\ --filter [test-name]
+ \\ --seed [int]
+ \\ --help
+ \\
+ );
+}
+
+fn mode(comptime x: comptime_int) comptime_int {
+ return if (builtin.mode == builtin.Mode.Debug) x / 64 else x;
+}
+
+// TODO(#1358): Replace with builtin formatted padding when available.
+fn printPad(stdout: var, s: []const u8) !void {
+ var i: usize = 0;
+ while (i < 12 - s.len) : (i += 1) {
+ try stdout.print(" ");
+ }
+ try stdout.print("{}", s);
+}
+
+pub fn main() !void {
+ var stdout_file = try std.io.getStdOut();
+ var stdout_out_stream = stdout_file.outStream();
+ const stdout = &stdout_out_stream.stream;
+
+ var buffer: [1024]u8 = undefined;
+ var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
+ const args = try std.process.argsAlloc(&fixed.allocator);
+
+ var filter: ?[]u8 = "";
+
+ var i: usize = 1;
+ while (i < args.len) : (i += 1) {
+ if (std.mem.eql(u8, args[i], "--mode")) {
+ try stdout.print("{}\n", builtin.mode);
+ return;
+ } else if (std.mem.eql(u8, args[i], "--seed")) {
+ i += 1;
+ if (i == args.len) {
+ usage();
+ std.os.exit(1);
+ }
+
+ const seed = try std.fmt.parseUnsigned(u32, args[i], 10);
+ prng.seed(seed);
+ } else if (std.mem.eql(u8, args[i], "--filter")) {
+ i += 1;
+ if (i == args.len) {
+ usage();
+ std.os.exit(1);
+ }
+
+ filter = args[i];
+ } else if (std.mem.eql(u8, args[i], "--help")) {
+ usage();
+ return;
+ } else {
+ usage();
+ std.os.exit(1);
+ }
+ }
+
+ inline for (hashes) |H| {
+ if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
+ const throughput = try benchmarkHash(H.ty, mode(32 * MiB));
+ try printPad(stdout, H.name);
+ try stdout.print(": {} MiB/s\n", throughput / (1 * MiB));
+ }
+ }
+
+ inline for (macs) |M| {
+ if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) {
+ const throughput = try benchmarkMac(M.ty, mode(128 * MiB));
+ try printPad(stdout, M.name);
+ try stdout.print(": {} MiB/s\n", throughput / (1 * MiB));
+ }
+ }
+
+ inline for (exchanges) |E| {
+ if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
+ const throughput = try benchmarkKeyExchange(E.ty, mode(1000));
+ try printPad(stdout, E.name);
+ try stdout.print(": {} exchanges/s\n", throughput);
+ }
+ }
+}
diff --git a/lib/std/crypto/blake2.zig b/lib/std/crypto/blake2.zig
new file mode 100644
index 0000000000..6bb2764b92
--- /dev/null
+++ b/lib/std/crypto/blake2.zig
@@ -0,0 +1,495 @@
+const mem = @import("../mem.zig");
+const math = @import("../math.zig");
+const endian = @import("../endian.zig");
+const debug = @import("../debug.zig");
+const builtin = @import("builtin");
+const htest = @import("test.zig");
+
+const RoundParam = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+ x: usize,
+ y: usize,
+};
+
+fn Rp(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundParam {
+ return RoundParam{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ .x = x,
+ .y = y,
+ };
+}
+
+/////////////////////
+// Blake2s
+
+pub const Blake2s224 = Blake2s(224);
+pub const Blake2s256 = Blake2s(256);
+
+fn Blake2s(comptime out_len: usize) type {
+ return struct {
+ const Self = @This();
+ pub const block_length = 64;
+ pub const digest_length = out_len / 8;
+
+ const iv = [8]u32{
+ 0x6A09E667,
+ 0xBB67AE85,
+ 0x3C6EF372,
+ 0xA54FF53A,
+ 0x510E527F,
+ 0x9B05688C,
+ 0x1F83D9AB,
+ 0x5BE0CD19,
+ };
+
+ const sigma = [10][16]u8{
+ [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ [_]u8{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ [_]u8{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ [_]u8{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ [_]u8{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ [_]u8{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ [_]u8{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ [_]u8{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ [_]u8{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ [_]u8{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+ };
+
+ h: [8]u32,
+ t: u64,
+ // Streaming cache
+ buf: [64]u8,
+ buf_len: u8,
+
+ pub fn init() Self {
+ debug.assert(8 <= out_len and out_len <= 512);
+
+ var s: Self = undefined;
+ s.reset();
+ return s;
+ }
+
+ pub fn reset(d: *Self) void {
+ mem.copy(u32, d.h[0..], iv[0..]);
+
+ // No key plus default parameters
+ d.h[0] ^= 0x01010000 ^ @intCast(u32, out_len >> 3);
+ d.t = 0;
+ d.buf_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Self.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 64) {
+ off += 64 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+ d.t += 64;
+ d.round(d.buf[0..], false);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 64 <= b.len) : (off += 64) {
+ d.t += 64;
+ d.round(b[off .. off + 64], false);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ debug.assert(out.len >= out_len / 8);
+
+ mem.set(u8, d.buf[d.buf_len..], 0);
+ d.t += d.buf_len;
+ d.round(d.buf[0..], true);
+
+ const rr = d.h[0 .. out_len / 32];
+
+ for (rr) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceLittle(u32, out[4 * j .. 4 * j + 4], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8, last: bool) void {
+ debug.assert(b.len == 64);
+
+ var m: [16]u32 = undefined;
+ var v: [16]u32 = undefined;
+
+ for (m) |*r, i| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ r.* = mem.readIntSliceLittle(u32, b[4 * i .. 4 * i + 4]);
+ }
+
+ var k: usize = 0;
+ while (k < 8) : (k += 1) {
+ v[k] = d.h[k];
+ v[k + 8] = iv[k];
+ }
+
+ v[12] ^= @truncate(u32, d.t);
+ v[13] ^= @intCast(u32, d.t >> 32);
+ if (last) v[14] = ~v[14];
+
+ const rounds = comptime [_]RoundParam{
+ Rp(0, 4, 8, 12, 0, 1),
+ Rp(1, 5, 9, 13, 2, 3),
+ Rp(2, 6, 10, 14, 4, 5),
+ Rp(3, 7, 11, 15, 6, 7),
+ Rp(0, 5, 10, 15, 8, 9),
+ Rp(1, 6, 11, 12, 10, 11),
+ Rp(2, 7, 8, 13, 12, 13),
+ Rp(3, 4, 9, 14, 14, 15),
+ };
+
+ comptime var j: usize = 0;
+ inline while (j < 10) : (j += 1) {
+ inline for (rounds) |r| {
+ v[r.a] = v[r.a] +% v[r.b] +% m[sigma[j][r.x]];
+ v[r.d] = math.rotr(u32, v[r.d] ^ v[r.a], usize(16));
+ v[r.c] = v[r.c] +% v[r.d];
+ v[r.b] = math.rotr(u32, v[r.b] ^ v[r.c], usize(12));
+ v[r.a] = v[r.a] +% v[r.b] +% m[sigma[j][r.y]];
+ v[r.d] = math.rotr(u32, v[r.d] ^ v[r.a], usize(8));
+ v[r.c] = v[r.c] +% v[r.d];
+ v[r.b] = math.rotr(u32, v[r.b] ^ v[r.c], usize(7));
+ }
+ }
+
+ for (d.h) |*r, i| {
+ r.* ^= v[i] ^ v[i + 8];
+ }
+ }
+ };
+}
+
+test "blake2s224 single" {
+ const h1 = "1fa1291e65248b37b3433475b2a0dd63d54a11ecc4e3e034e7bc1ef4";
+ htest.assertEqualHash(Blake2s224, h1, "");
+
+ const h2 = "0b033fc226df7abde29f67a05d3dc62cf271ef3dfea4d387407fbd55";
+ htest.assertEqualHash(Blake2s224, h2, "abc");
+
+ const h3 = "e4e5cb6c7cae41982b397bf7b7d2d9d1949823ae78435326e8db4912";
+ htest.assertEqualHash(Blake2s224, h3, "The quick brown fox jumps over the lazy dog");
+}
+
+test "blake2s224 streaming" {
+ var h = Blake2s224.init();
+ var out: [28]u8 = undefined;
+
+ const h1 = "1fa1291e65248b37b3433475b2a0dd63d54a11ecc4e3e034e7bc1ef4";
+
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "0b033fc226df7abde29f67a05d3dc62cf271ef3dfea4d387407fbd55";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "blake2s256 single" {
+ const h1 = "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9";
+ htest.assertEqualHash(Blake2s256, h1, "");
+
+ const h2 = "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982";
+ htest.assertEqualHash(Blake2s256, h2, "abc");
+
+ const h3 = "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812";
+ htest.assertEqualHash(Blake2s256, h3, "The quick brown fox jumps over the lazy dog");
+}
+
+test "blake2s256 streaming" {
+ var h = Blake2s256.init();
+ var out: [32]u8 = undefined;
+
+ const h1 = "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9";
+
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "blake2s256 aligned final" {
+ var block = [_]u8{0} ** Blake2s256.block_length;
+ var out: [Blake2s256.digest_length]u8 = undefined;
+
+ var h = Blake2s256.init();
+ h.update(block);
+ h.final(out[0..]);
+}
+
+/////////////////////
+// Blake2b
+
+pub const Blake2b384 = Blake2b(384);
+pub const Blake2b512 = Blake2b(512);
+
+fn Blake2b(comptime out_len: usize) type {
+ return struct {
+ const Self = @This();
+ pub const block_length = 128;
+ pub const digest_length = out_len / 8;
+
+ const iv = [8]u64{
+ 0x6a09e667f3bcc908,
+ 0xbb67ae8584caa73b,
+ 0x3c6ef372fe94f82b,
+ 0xa54ff53a5f1d36f1,
+ 0x510e527fade682d1,
+ 0x9b05688c2b3e6c1f,
+ 0x1f83d9abfb41bd6b,
+ 0x5be0cd19137e2179,
+ };
+
+ const sigma = [12][16]u8{
+ [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ [_]u8{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ [_]u8{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ [_]u8{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ [_]u8{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ [_]u8{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ [_]u8{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ [_]u8{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ [_]u8{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ [_]u8{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+ [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ [_]u8{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ };
+
+ h: [8]u64,
+ t: u128,
+ // Streaming cache
+ buf: [128]u8,
+ buf_len: u8,
+
+ pub fn init() Self {
+ debug.assert(8 <= out_len and out_len <= 512);
+
+ var s: Self = undefined;
+ s.reset();
+ return s;
+ }
+
+ pub fn reset(d: *Self) void {
+ mem.copy(u64, d.h[0..], iv[0..]);
+
+ // No key plus default parameters
+ d.h[0] ^= 0x01010000 ^ (out_len >> 3);
+ d.t = 0;
+ d.buf_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Self.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 128) {
+ off += 128 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+ d.t += 128;
+ d.round(d.buf[0..], false);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 128 <= b.len) : (off += 128) {
+ d.t += 128;
+ d.round(b[off .. off + 128], false);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ mem.set(u8, d.buf[d.buf_len..], 0);
+ d.t += d.buf_len;
+ d.round(d.buf[0..], true);
+
+ const rr = d.h[0 .. out_len / 64];
+
+ for (rr) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceLittle(u64, out[8 * j .. 8 * j + 8], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8, last: bool) void {
+ debug.assert(b.len == 128);
+
+ var m: [16]u64 = undefined;
+ var v: [16]u64 = undefined;
+
+ for (m) |*r, i| {
+ r.* = mem.readIntSliceLittle(u64, b[8 * i .. 8 * i + 8]);
+ }
+
+ var k: usize = 0;
+ while (k < 8) : (k += 1) {
+ v[k] = d.h[k];
+ v[k + 8] = iv[k];
+ }
+
+ v[12] ^= @truncate(u64, d.t);
+ v[13] ^= @intCast(u64, d.t >> 64);
+ if (last) v[14] = ~v[14];
+
+ const rounds = comptime [_]RoundParam{
+ Rp(0, 4, 8, 12, 0, 1),
+ Rp(1, 5, 9, 13, 2, 3),
+ Rp(2, 6, 10, 14, 4, 5),
+ Rp(3, 7, 11, 15, 6, 7),
+ Rp(0, 5, 10, 15, 8, 9),
+ Rp(1, 6, 11, 12, 10, 11),
+ Rp(2, 7, 8, 13, 12, 13),
+ Rp(3, 4, 9, 14, 14, 15),
+ };
+
+ comptime var j: usize = 0;
+ inline while (j < 12) : (j += 1) {
+ inline for (rounds) |r| {
+ v[r.a] = v[r.a] +% v[r.b] +% m[sigma[j][r.x]];
+ v[r.d] = math.rotr(u64, v[r.d] ^ v[r.a], usize(32));
+ v[r.c] = v[r.c] +% v[r.d];
+ v[r.b] = math.rotr(u64, v[r.b] ^ v[r.c], usize(24));
+ v[r.a] = v[r.a] +% v[r.b] +% m[sigma[j][r.y]];
+ v[r.d] = math.rotr(u64, v[r.d] ^ v[r.a], usize(16));
+ v[r.c] = v[r.c] +% v[r.d];
+ v[r.b] = math.rotr(u64, v[r.b] ^ v[r.c], usize(63));
+ }
+ }
+
+ for (d.h) |*r, i| {
+ r.* ^= v[i] ^ v[i + 8];
+ }
+ }
+ };
+}
+
+test "blake2b384 single" {
+ const h1 = "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100";
+ htest.assertEqualHash(Blake2b384, h1, "");
+
+ const h2 = "6f56a82c8e7ef526dfe182eb5212f7db9df1317e57815dbda46083fc30f54ee6c66ba83be64b302d7cba6ce15bb556f4";
+ htest.assertEqualHash(Blake2b384, h2, "abc");
+
+ const h3 = "b7c81b228b6bd912930e8f0b5387989691c1cee1e65aade4da3b86a3c9f678fc8018f6ed9e2906720c8d2a3aeda9c03d";
+ htest.assertEqualHash(Blake2b384, h3, "The quick brown fox jumps over the lazy dog");
+}
+
+test "blake2b384 streaming" {
+ var h = Blake2b384.init();
+ var out: [48]u8 = undefined;
+
+ const h1 = "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100";
+
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "6f56a82c8e7ef526dfe182eb5212f7db9df1317e57815dbda46083fc30f54ee6c66ba83be64b302d7cba6ce15bb556f4";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "blake2b512 single" {
+ const h1 = "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce";
+ htest.assertEqualHash(Blake2b512, h1, "");
+
+ const h2 = "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923";
+ htest.assertEqualHash(Blake2b512, h2, "abc");
+
+ const h3 = "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918";
+ htest.assertEqualHash(Blake2b512, h3, "The quick brown fox jumps over the lazy dog");
+}
+
+test "blake2b512 streaming" {
+ var h = Blake2b512.init();
+ var out: [64]u8 = undefined;
+
+ const h1 = "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce";
+
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "blake2b512 aligned final" {
+ var block = [_]u8{0} ** Blake2b512.block_length;
+ var out: [Blake2b512.digest_length]u8 = undefined;
+
+ var h = Blake2b512.init();
+ h.update(block);
+ h.final(out[0..]);
+}
diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig
new file mode 100644
index 0000000000..0d997e0d14
--- /dev/null
+++ b/lib/std/crypto/chacha20.zig
@@ -0,0 +1,436 @@
+// Based on public domain Supercop by Daniel J. Bernstein
+
+const std = @import("../std.zig");
+const mem = std.mem;
+const endian = std.endian;
+const assert = std.debug.assert;
+const testing = std.testing;
+const builtin = @import("builtin");
+const maxInt = std.math.maxInt;
+
+const QuarterRound = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+};
+
+fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
+ return QuarterRound{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ };
+}
+
+// The chacha family of ciphers are based on the salsa family.
+fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {
+ assert(out.len >= 64);
+
+ var x: [16]u32 = undefined;
+
+ for (x) |_, i|
+ x[i] = input[i];
+
+ const rounds = comptime [_]QuarterRound{
+ Rp(0, 4, 8, 12),
+ Rp(1, 5, 9, 13),
+ Rp(2, 6, 10, 14),
+ Rp(3, 7, 11, 15),
+ Rp(0, 5, 10, 15),
+ Rp(1, 6, 11, 12),
+ Rp(2, 7, 8, 13),
+ Rp(3, 4, 9, 14),
+ };
+
+ comptime var j: usize = 0;
+ inline while (j < 20) : (j += 2) {
+ // two-round cycles
+ inline for (rounds) |r| {
+ x[r.a] +%= x[r.b];
+ x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(16));
+ x[r.c] +%= x[r.d];
+ x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(12));
+ x[r.a] +%= x[r.b];
+ x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(8));
+ x[r.c] +%= x[r.d];
+ x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(7));
+ }
+ }
+
+ for (x) |_, i| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceLittle(u32, out[4 * i .. 4 * i + 4], x[i] +% input[i]);
+ }
+}
+
+fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
+ var ctx: [16]u32 = undefined;
+ var remaining: usize = if (in.len > out.len) in.len else out.len;
+ var cursor: usize = 0;
+
+ const c = "expand 32-byte k";
+ const constant_le = [_]u32{
+ mem.readIntSliceLittle(u32, c[0..4]),
+ mem.readIntSliceLittle(u32, c[4..8]),
+ mem.readIntSliceLittle(u32, c[8..12]),
+ mem.readIntSliceLittle(u32, c[12..16]),
+ };
+
+ mem.copy(u32, ctx[0..], constant_le[0..4]);
+ mem.copy(u32, ctx[4..12], key[0..8]);
+ mem.copy(u32, ctx[12..16], counter[0..4]);
+
+ while (true) {
+ var buf: [64]u8 = undefined;
+ salsa20_wordtobyte(buf[0..], ctx);
+
+ if (remaining < 64) {
+ var i: usize = 0;
+ while (i < remaining) : (i += 1)
+ out[cursor + i] = in[cursor + i] ^ buf[i];
+ return;
+ }
+
+ var i: usize = 0;
+ while (i < 64) : (i += 1)
+ out[cursor + i] = in[cursor + i] ^ buf[i];
+
+ cursor += 64;
+ remaining -= 64;
+
+ ctx[12] += 1;
+ }
+}
+
+/// ChaCha20 avoids the possibility of timing attacks, as there are no branches
+/// on secret key data.
+///
+/// in and out should be the same length.
+/// counter should generally be 0 or 1
+///
+/// ChaCha20 is self-reversing. To decrypt just run the cipher with the same
+/// counter, nonce, and key.
+pub fn chaCha20IETF(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void {
+ assert(in.len >= out.len);
+ assert((in.len >> 6) + counter <= maxInt(u32));
+
+ var k: [8]u32 = undefined;
+ var c: [4]u32 = undefined;
+
+ k[0] = mem.readIntSliceLittle(u32, key[0..4]);
+ k[1] = mem.readIntSliceLittle(u32, key[4..8]);
+ k[2] = mem.readIntSliceLittle(u32, key[8..12]);
+ k[3] = mem.readIntSliceLittle(u32, key[12..16]);
+ k[4] = mem.readIntSliceLittle(u32, key[16..20]);
+ k[5] = mem.readIntSliceLittle(u32, key[20..24]);
+ k[6] = mem.readIntSliceLittle(u32, key[24..28]);
+ k[7] = mem.readIntSliceLittle(u32, key[28..32]);
+
+ c[0] = counter;
+ c[1] = mem.readIntSliceLittle(u32, nonce[0..4]);
+ c[2] = mem.readIntSliceLittle(u32, nonce[4..8]);
+ c[3] = mem.readIntSliceLittle(u32, nonce[8..12]);
+ chaCha20_internal(out, in, k, c);
+}
+
+/// This is the original ChaCha20 before RFC 7539, which recommends using the
+/// orgininal version on applications such as disk or file encryption that might
+/// exceed the 256 GiB limit of the 96-bit nonce version.
+pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void {
+ assert(in.len >= out.len);
+ assert(counter +% (in.len >> 6) >= counter);
+
+ var cursor: usize = 0;
+ var k: [8]u32 = undefined;
+ var c: [4]u32 = undefined;
+
+ k[0] = mem.readIntSliceLittle(u32, key[0..4]);
+ k[1] = mem.readIntSliceLittle(u32, key[4..8]);
+ k[2] = mem.readIntSliceLittle(u32, key[8..12]);
+ k[3] = mem.readIntSliceLittle(u32, key[12..16]);
+ k[4] = mem.readIntSliceLittle(u32, key[16..20]);
+ k[5] = mem.readIntSliceLittle(u32, key[20..24]);
+ k[6] = mem.readIntSliceLittle(u32, key[24..28]);
+ k[7] = mem.readIntSliceLittle(u32, key[28..32]);
+
+ c[0] = @truncate(u32, counter);
+ c[1] = @truncate(u32, counter >> 32);
+ c[2] = mem.readIntSliceLittle(u32, nonce[0..4]);
+ c[3] = mem.readIntSliceLittle(u32, nonce[4..8]);
+
+ const block_size = (1 << 6);
+ // The full block size is greater than the address space on a 32bit machine
+ const big_block = if (@sizeOf(usize) > 4) (block_size << 32) else maxInt(usize);
+
+ // first partial big block
+ if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
+ chaCha20_internal(out[cursor..big_block], in[cursor..big_block], k, c);
+ cursor = big_block - cursor;
+ c[1] += 1;
+ if (comptime @sizeOf(usize) > 4) {
+ // A big block is giant: 256 GiB, but we can avoid this limitation
+ var remaining_blocks: u32 = @intCast(u32, (in.len / big_block));
+ var i: u32 = 0;
+ while (remaining_blocks > 0) : (remaining_blocks -= 1) {
+ chaCha20_internal(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
+ c[1] += 1; // upper 32-bit of counter, generic chaCha20_internal() doesn't know about this.
+ cursor += big_block;
+ }
+ }
+ }
+
+ chaCha20_internal(out[cursor..], in[cursor..], k, c);
+}
+
+// https://tools.ietf.org/html/rfc7539#section-2.4.2
+test "crypto.chacha20 test vector sunscreen" {
+ const expected_result = [_]u8{
+ 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80,
+ 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
+ 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2,
+ 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
+ 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab,
+ 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
+ 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab,
+ 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
+ 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61,
+ 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
+ 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06,
+ 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
+ 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6,
+ 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
+ 0x87, 0x4d,
+ };
+ const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
+ var result: [114]u8 = undefined;
+ const key = [_]u8{
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ };
+ const nonce = [_]u8{
+ 0, 0, 0, 0,
+ 0, 0, 0, 0x4a,
+ 0, 0, 0, 0,
+ };
+
+ chaCha20IETF(result[0..], input[0..], 1, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+
+ // Chacha20 is self-reversing.
+ var plaintext: [114]u8 = undefined;
+ chaCha20IETF(plaintext[0..], result[0..], 1, key, nonce);
+ testing.expect(mem.compare(u8, input, plaintext) == mem.Compare.Equal);
+}
+
+// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
+test "crypto.chacha20 test vector 1" {
+ const expected_result = [_]u8{
+ 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90,
+ 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28,
+ 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a,
+ 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7,
+ 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d,
+ 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37,
+ 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
+ 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
+ };
+ const input = [_]u8{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ var result: [64]u8 = undefined;
+ const key = [_]u8{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+}
+
+test "crypto.chacha20 test vector 2" {
+ const expected_result = [_]u8{
+ 0x45, 0x40, 0xf0, 0x5a, 0x9f, 0x1f, 0xb2, 0x96,
+ 0xd7, 0x73, 0x6e, 0x7b, 0x20, 0x8e, 0x3c, 0x96,
+ 0xeb, 0x4f, 0xe1, 0x83, 0x46, 0x88, 0xd2, 0x60,
+ 0x4f, 0x45, 0x09, 0x52, 0xed, 0x43, 0x2d, 0x41,
+ 0xbb, 0xe2, 0xa0, 0xb6, 0xea, 0x75, 0x66, 0xd2,
+ 0xa5, 0xd1, 0xe7, 0xe2, 0x0d, 0x42, 0xaf, 0x2c,
+ 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,
+ 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,
+ };
+ const input = [_]u8{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ var result: [64]u8 = undefined;
+ const key = [_]u8{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ };
+ const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+}
+
+test "crypto.chacha20 test vector 3" {
+ const expected_result = [_]u8{
+ 0xde, 0x9c, 0xba, 0x7b, 0xf3, 0xd6, 0x9e, 0xf5,
+ 0xe7, 0x86, 0xdc, 0x63, 0x97, 0x3f, 0x65, 0x3a,
+ 0x0b, 0x49, 0xe0, 0x15, 0xad, 0xbf, 0xf7, 0x13,
+ 0x4f, 0xcb, 0x7d, 0xf1, 0x37, 0x82, 0x10, 0x31,
+ 0xe8, 0x5a, 0x05, 0x02, 0x78, 0xa7, 0x08, 0x45,
+ 0x27, 0x21, 0x4f, 0x73, 0xef, 0xc7, 0xfa, 0x5b,
+ 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,
+ 0x44, 0x5f, 0x41, 0xe3,
+ };
+ const input = [_]u8{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ var result: [60]u8 = undefined;
+ const key = [_]u8{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };
+
+ chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+}
+
+test "crypto.chacha20 test vector 4" {
+ const expected_result = [_]u8{
+ 0xef, 0x3f, 0xdf, 0xd6, 0xc6, 0x15, 0x78, 0xfb,
+ 0xf5, 0xcf, 0x35, 0xbd, 0x3d, 0xd3, 0x3b, 0x80,
+ 0x09, 0x63, 0x16, 0x34, 0xd2, 0x1e, 0x42, 0xac,
+ 0x33, 0x96, 0x0b, 0xd1, 0x38, 0xe5, 0x0d, 0x32,
+ 0x11, 0x1e, 0x4c, 0xaf, 0x23, 0x7e, 0xe5, 0x3c,
+ 0xa8, 0xad, 0x64, 0x26, 0x19, 0x4a, 0x88, 0x54,
+ 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,
+ 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,
+ };
+ const input = [_]u8{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ var result: [64]u8 = undefined;
+ const key = [_]u8{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 };
+
+ chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+}
+
+test "crypto.chacha20 test vector 5" {
+ const expected_result = [_]u8{
+ 0xf7, 0x98, 0xa1, 0x89, 0xf1, 0x95, 0xe6, 0x69,
+ 0x82, 0x10, 0x5f, 0xfb, 0x64, 0x0b, 0xb7, 0x75,
+ 0x7f, 0x57, 0x9d, 0xa3, 0x16, 0x02, 0xfc, 0x93,
+ 0xec, 0x01, 0xac, 0x56, 0xf8, 0x5a, 0xc3, 0xc1,
+ 0x34, 0xa4, 0x54, 0x7b, 0x73, 0x3b, 0x46, 0x41,
+ 0x30, 0x42, 0xc9, 0x44, 0x00, 0x49, 0x17, 0x69,
+ 0x05, 0xd3, 0xbe, 0x59, 0xea, 0x1c, 0x53, 0xf1,
+ 0x59, 0x16, 0x15, 0x5c, 0x2b, 0xe8, 0x24, 0x1a,
+
+ 0x38, 0x00, 0x8b, 0x9a, 0x26, 0xbc, 0x35, 0x94,
+ 0x1e, 0x24, 0x44, 0x17, 0x7c, 0x8a, 0xde, 0x66,
+ 0x89, 0xde, 0x95, 0x26, 0x49, 0x86, 0xd9, 0x58,
+ 0x89, 0xfb, 0x60, 0xe8, 0x46, 0x29, 0xc9, 0xbd,
+ 0x9a, 0x5a, 0xcb, 0x1c, 0xc1, 0x18, 0xbe, 0x56,
+ 0x3e, 0xb9, 0xb3, 0xa4, 0xa4, 0x72, 0xf8, 0x2e,
+ 0x09, 0xa7, 0xe7, 0x78, 0x49, 0x2b, 0x56, 0x2e,
+ 0xf7, 0x13, 0x0e, 0x88, 0xdf, 0xe0, 0x31, 0xc7,
+
+ 0x9d, 0xb9, 0xd4, 0xf7, 0xc7, 0xa8, 0x99, 0x15,
+ 0x1b, 0x9a, 0x47, 0x50, 0x32, 0xb6, 0x3f, 0xc3,
+ 0x85, 0x24, 0x5f, 0xe0, 0x54, 0xe3, 0xdd, 0x5a,
+ 0x97, 0xa5, 0xf5, 0x76, 0xfe, 0x06, 0x40, 0x25,
+ 0xd3, 0xce, 0x04, 0x2c, 0x56, 0x6a, 0xb2, 0xc5,
+ 0x07, 0xb1, 0x38, 0xdb, 0x85, 0x3e, 0x3d, 0x69,
+ 0x59, 0x66, 0x09, 0x96, 0x54, 0x6c, 0xc9, 0xc4,
+ 0xa6, 0xea, 0xfd, 0xc7, 0x77, 0xc0, 0x40, 0xd7,
+
+ 0x0e, 0xaf, 0x46, 0xf7, 0x6d, 0xad, 0x39, 0x79,
+ 0xe5, 0xc5, 0x36, 0x0c, 0x33, 0x17, 0x16, 0x6a,
+ 0x1c, 0x89, 0x4c, 0x94, 0xa3, 0x71, 0x87, 0x6a,
+ 0x94, 0xdf, 0x76, 0x28, 0xfe, 0x4e, 0xaa, 0xf2,
+ 0xcc, 0xb2, 0x7d, 0x5a, 0xaa, 0xe0, 0xad, 0x7a,
+ 0xd0, 0xf9, 0xd4, 0xb6, 0xad, 0x3b, 0x54, 0x09,
+ 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,
+ 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,
+ };
+ const input = [_]u8{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result: [256]u8 = undefined;
+ const key = [_]u8{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ };
+ const nonce = [_]u8{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ };
+
+ chaCha20With64BitNonce(result[0..], input[0..], 0, key, nonce);
+ testing.expectEqualSlices(u8, expected_result, result);
+}
diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig
new file mode 100644
index 0000000000..0a0a5056c6
--- /dev/null
+++ b/lib/std/crypto/gimli.zig
@@ -0,0 +1,168 @@
+// Gimli is a 384-bit permutation designed to achieve high security with high
+// performance across a broad range of platforms, including 64-bit Intel/AMD
+// server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
+// microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
+// side-channel protection, and ASICs with side-channel protection.
+//
+// https://gimli.cr.yp.to/
+// https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf
+
+const std = @import("../std.zig");
+const mem = std.mem;
+const math = std.math;
+const debug = std.debug;
+const assert = std.debug.assert;
+const testing = std.testing;
+const htest = @import("test.zig");
+
+pub const State = struct {
+ pub const BLOCKBYTES = 48;
+ pub const RATE = 16;
+
+ // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
+ data: [BLOCKBYTES / 4]u32,
+
+ const Self = @This();
+
+ pub fn toSlice(self: *Self) []u8 {
+ return @sliceToBytes(self.data[0..]);
+ }
+
+ pub fn toSliceConst(self: *Self) []const u8 {
+ return @sliceToBytes(self.data[0..]);
+ }
+
+ pub fn permute(self: *Self) void {
+ const state = &self.data;
+ var round = u32(24);
+ while (round > 0) : (round -= 1) {
+ var column = usize(0);
+ while (column < 4) : (column += 1) {
+ const x = math.rotl(u32, state[column], 24);
+ const y = math.rotl(u32, state[4 + column], 9);
+ const z = state[8 + column];
+ state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
+ state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
+ state[column] = ((z ^ y) ^ ((x & y) << 3));
+ }
+ switch (round & 3) {
+ 0 => {
+ mem.swap(u32, &state[0], &state[1]);
+ mem.swap(u32, &state[2], &state[3]);
+ state[0] ^= round | 0x9e377900;
+ },
+ 2 => {
+ mem.swap(u32, &state[0], &state[2]);
+ mem.swap(u32, &state[1], &state[3]);
+ },
+ else => {},
+ }
+ }
+ }
+
+ pub fn squeeze(self: *Self, out: []u8) void {
+ var i = usize(0);
+ while (i + RATE <= out.len) : (i += RATE) {
+ self.permute();
+ mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]);
+ }
+ const leftover = out.len - i;
+ if (leftover != 0) {
+ self.permute();
+ mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]);
+ }
+ }
+};
+
+test "permute" {
+ // test vector from gimli-20170627
+ var state = State{
+ .data = blk: {
+ var input: [12]u32 = undefined;
+ var i = u32(0);
+ while (i < 12) : (i += 1) {
+ input[i] = i * i * i + i *% 0x9e3779b9;
+ }
+ testing.expectEqualSlices(u32, input, [_]u32{
+ 0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46,
+ 0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566,
+ 0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026,
+ });
+ break :blk input;
+ },
+ };
+ state.permute();
+ testing.expectEqualSlices(u32, state.data, [_]u32{
+ 0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68,
+ 0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8,
+ 0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6,
+ });
+}
+
+pub const Hash = struct {
+ state: State,
+ buf_off: usize,
+
+ const Self = @This();
+
+ pub fn init() Self {
+ return Self{
+ .state = State{
+ .data = [_]u32{0} ** (State.BLOCKBYTES / 4),
+ },
+ .buf_off = 0,
+ };
+ }
+
+ /// Also known as 'absorb'
+ pub fn update(self: *Self, data: []const u8) void {
+ const buf = self.state.toSlice();
+ var in = data;
+ while (in.len > 0) {
+ var left = State.RATE - self.buf_off;
+ if (left == 0) {
+ self.state.permute();
+ self.buf_off = 0;
+ left = State.RATE;
+ }
+ const ps = math.min(in.len, left);
+ for (buf[self.buf_off .. self.buf_off + ps]) |*p, i| {
+ p.* ^= in[i];
+ }
+ self.buf_off += ps;
+ in = in[ps..];
+ }
+ }
+
+ /// Finish the current hashing operation, writing the hash to `out`
+ ///
+ /// From 4.9 "Application to hashing"
+ /// By default, Gimli-Hash provides a fixed-length output of 32 bytes
+ /// (the concatenation of two 16-byte blocks). However, Gimli-Hash can
+ /// be used as an “extendable one-way function” (XOF).
+ pub fn final(self: *Self, out: []u8) void {
+ const buf = self.state.toSlice();
+
+ // XOR 1 into the next byte of the state
+ buf[self.buf_off] ^= 1;
+ // XOR 1 into the last byte of the state, position 47.
+ buf[buf.len - 1] ^= 1;
+
+ self.state.squeeze(out);
+ }
+};
+
+pub fn hash(out: []u8, in: []const u8) void {
+ var st = Hash.init();
+ st.update(in);
+ st.final(out);
+}
+
+test "hash" {
+ // a test vector (30) from NIST KAT submission.
+ var msg: [58 / 2]u8 = undefined;
+ try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C");
+ var md: [32]u8 = undefined;
+ hash(&md, msg);
+ htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", md);
+}
diff --git a/lib/std/crypto/hmac.zig b/lib/std/crypto/hmac.zig
new file mode 100644
index 0000000000..69c1b86386
--- /dev/null
+++ b/lib/std/crypto/hmac.zig
@@ -0,0 +1,99 @@
+const std = @import("../std.zig");
+const crypto = std.crypto;
+const debug = std.debug;
+const mem = std.mem;
+
+pub const HmacMd5 = Hmac(crypto.Md5);
+pub const HmacSha1 = Hmac(crypto.Sha1);
+pub const HmacSha256 = Hmac(crypto.Sha256);
+pub const HmacBlake2s256 = Hmac(crypto.Blake2s256);
+
+pub fn Hmac(comptime Hash: type) type {
+ return struct {
+ const Self = @This();
+ pub const mac_length = Hash.digest_length;
+ pub const minimum_key_length = 0;
+
+ o_key_pad: [Hash.block_length]u8,
+ i_key_pad: [Hash.block_length]u8,
+ scratch: [Hash.block_length]u8,
+ hash: Hash,
+
+ // HMAC(k, m) = H(o_key_pad | H(i_key_pad | msg)) where | is concatenation
+ pub fn create(out: []u8, msg: []const u8, key: []const u8) void {
+ var ctx = Self.init(key);
+ ctx.update(msg);
+ ctx.final(out[0..]);
+ }
+
+ pub fn init(key: []const u8) Self {
+ var ctx: Self = undefined;
+
+ // Normalize key length to block size of hash
+ if (key.len > Hash.block_length) {
+ Hash.hash(key, ctx.scratch[0..mac_length]);
+ mem.set(u8, ctx.scratch[mac_length..Hash.block_length], 0);
+ } else if (key.len < Hash.block_length) {
+ mem.copy(u8, ctx.scratch[0..key.len], key);
+ mem.set(u8, ctx.scratch[key.len..Hash.block_length], 0);
+ } else {
+ mem.copy(u8, ctx.scratch[0..], key);
+ }
+
+ for (ctx.o_key_pad) |*b, i| {
+ b.* = ctx.scratch[i] ^ 0x5c;
+ }
+
+ for (ctx.i_key_pad) |*b, i| {
+ b.* = ctx.scratch[i] ^ 0x36;
+ }
+
+ ctx.hash = Hash.init();
+ ctx.hash.update(ctx.i_key_pad[0..]);
+ return ctx;
+ }
+
+ pub fn update(ctx: *Self, msg: []const u8) void {
+ ctx.hash.update(msg);
+ }
+
+ pub fn final(ctx: *Self, out: []u8) void {
+ debug.assert(Hash.block_length >= out.len and out.len >= mac_length);
+
+ ctx.hash.final(ctx.scratch[0..mac_length]);
+ ctx.hash.reset();
+ ctx.hash.update(ctx.o_key_pad[0..]);
+ ctx.hash.update(ctx.scratch[0..mac_length]);
+ ctx.hash.final(out[0..mac_length]);
+ }
+ };
+}
+
+const htest = @import("test.zig");
+
+test "hmac md5" {
+ var out: [HmacMd5.mac_length]u8 = undefined;
+ HmacMd5.create(out[0..], "", "");
+ htest.assertEqual("74e6f7298a9c2d168935f58c001bad88", out[0..]);
+
+ HmacMd5.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
+ htest.assertEqual("80070713463e7749b90c2dc24911e275", out[0..]);
+}
+
+test "hmac sha1" {
+ var out: [HmacSha1.mac_length]u8 = undefined;
+ HmacSha1.create(out[0..], "", "");
+ htest.assertEqual("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", out[0..]);
+
+ HmacSha1.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
+ htest.assertEqual("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", out[0..]);
+}
+
+test "hmac sha256" {
+ var out: [HmacSha256.mac_length]u8 = undefined;
+ HmacSha256.create(out[0..], "", "");
+ htest.assertEqual("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", out[0..]);
+
+ HmacSha256.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
+ htest.assertEqual("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", out[0..]);
+}
diff --git a/lib/std/crypto/md5.zig b/lib/std/crypto/md5.zig
new file mode 100644
index 0000000000..ddbb39a9df
--- /dev/null
+++ b/lib/std/crypto/md5.zig
@@ -0,0 +1,281 @@
+const mem = @import("../mem.zig");
+const math = @import("../math.zig");
+const endian = @import("../endian.zig");
+const builtin = @import("builtin");
+const debug = @import("../debug.zig");
+const fmt = @import("../fmt.zig");
+
+const RoundParam = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+ k: usize,
+ s: u32,
+ t: u32,
+};
+
+fn Rp(a: usize, b: usize, c: usize, d: usize, k: usize, s: u32, t: u32) RoundParam {
+ return RoundParam{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ .k = k,
+ .s = s,
+ .t = t,
+ };
+}
+
+pub const Md5 = struct {
+ const Self = @This();
+ pub const block_length = 64;
+ pub const digest_length = 16;
+
+ s: [4]u32,
+ // Streaming Cache
+ buf: [64]u8,
+ buf_len: u8,
+ total_len: u64,
+
+ pub fn init() Self {
+ var d: Self = undefined;
+ d.reset();
+ return d;
+ }
+
+ pub fn reset(d: *Self) void {
+ d.s[0] = 0x67452301;
+ d.s[1] = 0xEFCDAB89;
+ d.s[2] = 0x98BADCFE;
+ d.s[3] = 0x10325476;
+ d.buf_len = 0;
+ d.total_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Md5.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 64) {
+ off += 64 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+
+ d.round(d.buf[0..]);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 64 <= b.len) : (off += 64) {
+ d.round(b[off .. off + 64]);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+
+ // Md5 uses the bottom 64-bits for length padding
+ d.total_len +%= b.len;
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ debug.assert(out.len >= 16);
+
+ // The buffer here will never be completely full.
+ mem.set(u8, d.buf[d.buf_len..], 0);
+
+ // Append padding bits.
+ d.buf[d.buf_len] = 0x80;
+ d.buf_len += 1;
+
+ // > 448 mod 512 so need to add an extra round to wrap around.
+ if (64 - d.buf_len < 8) {
+ d.round(d.buf[0..]);
+ mem.set(u8, d.buf[0..], 0);
+ }
+
+ // Append message length.
+ var i: usize = 1;
+ var len = d.total_len >> 5;
+ d.buf[56] = @intCast(u8, d.total_len & 0x1f) << 3;
+ while (i < 8) : (i += 1) {
+ d.buf[56 + i] = @intCast(u8, len & 0xff);
+ len >>= 8;
+ }
+
+ d.round(d.buf[0..]);
+
+ for (d.s) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceLittle(u32, out[4 * j .. 4 * j + 4], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8) void {
+ debug.assert(b.len == 64);
+
+ var s: [16]u32 = undefined;
+
+ var i: usize = 0;
+ while (i < 16) : (i += 1) {
+ // NOTE: Performing or's separately improves perf by ~10%
+ s[i] = 0;
+ s[i] |= u32(b[i * 4 + 0]);
+ s[i] |= u32(b[i * 4 + 1]) << 8;
+ s[i] |= u32(b[i * 4 + 2]) << 16;
+ s[i] |= u32(b[i * 4 + 3]) << 24;
+ }
+
+ var v: [4]u32 = [_]u32{
+ d.s[0],
+ d.s[1],
+ d.s[2],
+ d.s[3],
+ };
+
+ const round0 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 0, 7, 0xD76AA478),
+ Rp(3, 0, 1, 2, 1, 12, 0xE8C7B756),
+ Rp(2, 3, 0, 1, 2, 17, 0x242070DB),
+ Rp(1, 2, 3, 0, 3, 22, 0xC1BDCEEE),
+ Rp(0, 1, 2, 3, 4, 7, 0xF57C0FAF),
+ Rp(3, 0, 1, 2, 5, 12, 0x4787C62A),
+ Rp(2, 3, 0, 1, 6, 17, 0xA8304613),
+ Rp(1, 2, 3, 0, 7, 22, 0xFD469501),
+ Rp(0, 1, 2, 3, 8, 7, 0x698098D8),
+ Rp(3, 0, 1, 2, 9, 12, 0x8B44F7AF),
+ Rp(2, 3, 0, 1, 10, 17, 0xFFFF5BB1),
+ Rp(1, 2, 3, 0, 11, 22, 0x895CD7BE),
+ Rp(0, 1, 2, 3, 12, 7, 0x6B901122),
+ Rp(3, 0, 1, 2, 13, 12, 0xFD987193),
+ Rp(2, 3, 0, 1, 14, 17, 0xA679438E),
+ Rp(1, 2, 3, 0, 15, 22, 0x49B40821),
+ };
+ inline for (round0) |r| {
+ v[r.a] = v[r.a] +% (v[r.d] ^ (v[r.b] & (v[r.c] ^ v[r.d]))) +% r.t +% s[r.k];
+ v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
+ }
+
+ const round1 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 1, 5, 0xF61E2562),
+ Rp(3, 0, 1, 2, 6, 9, 0xC040B340),
+ Rp(2, 3, 0, 1, 11, 14, 0x265E5A51),
+ Rp(1, 2, 3, 0, 0, 20, 0xE9B6C7AA),
+ Rp(0, 1, 2, 3, 5, 5, 0xD62F105D),
+ Rp(3, 0, 1, 2, 10, 9, 0x02441453),
+ Rp(2, 3, 0, 1, 15, 14, 0xD8A1E681),
+ Rp(1, 2, 3, 0, 4, 20, 0xE7D3FBC8),
+ Rp(0, 1, 2, 3, 9, 5, 0x21E1CDE6),
+ Rp(3, 0, 1, 2, 14, 9, 0xC33707D6),
+ Rp(2, 3, 0, 1, 3, 14, 0xF4D50D87),
+ Rp(1, 2, 3, 0, 8, 20, 0x455A14ED),
+ Rp(0, 1, 2, 3, 13, 5, 0xA9E3E905),
+ Rp(3, 0, 1, 2, 2, 9, 0xFCEFA3F8),
+ Rp(2, 3, 0, 1, 7, 14, 0x676F02D9),
+ Rp(1, 2, 3, 0, 12, 20, 0x8D2A4C8A),
+ };
+ inline for (round1) |r| {
+ v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.d] & (v[r.b] ^ v[r.c]))) +% r.t +% s[r.k];
+ v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
+ }
+
+ const round2 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 5, 4, 0xFFFA3942),
+ Rp(3, 0, 1, 2, 8, 11, 0x8771F681),
+ Rp(2, 3, 0, 1, 11, 16, 0x6D9D6122),
+ Rp(1, 2, 3, 0, 14, 23, 0xFDE5380C),
+ Rp(0, 1, 2, 3, 1, 4, 0xA4BEEA44),
+ Rp(3, 0, 1, 2, 4, 11, 0x4BDECFA9),
+ Rp(2, 3, 0, 1, 7, 16, 0xF6BB4B60),
+ Rp(1, 2, 3, 0, 10, 23, 0xBEBFBC70),
+ Rp(0, 1, 2, 3, 13, 4, 0x289B7EC6),
+ Rp(3, 0, 1, 2, 0, 11, 0xEAA127FA),
+ Rp(2, 3, 0, 1, 3, 16, 0xD4EF3085),
+ Rp(1, 2, 3, 0, 6, 23, 0x04881D05),
+ Rp(0, 1, 2, 3, 9, 4, 0xD9D4D039),
+ Rp(3, 0, 1, 2, 12, 11, 0xE6DB99E5),
+ Rp(2, 3, 0, 1, 15, 16, 0x1FA27CF8),
+ Rp(1, 2, 3, 0, 2, 23, 0xC4AC5665),
+ };
+ inline for (round2) |r| {
+ v[r.a] = v[r.a] +% (v[r.b] ^ v[r.c] ^ v[r.d]) +% r.t +% s[r.k];
+ v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
+ }
+
+ const round3 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 0, 6, 0xF4292244),
+ Rp(3, 0, 1, 2, 7, 10, 0x432AFF97),
+ Rp(2, 3, 0, 1, 14, 15, 0xAB9423A7),
+ Rp(1, 2, 3, 0, 5, 21, 0xFC93A039),
+ Rp(0, 1, 2, 3, 12, 6, 0x655B59C3),
+ Rp(3, 0, 1, 2, 3, 10, 0x8F0CCC92),
+ Rp(2, 3, 0, 1, 10, 15, 0xFFEFF47D),
+ Rp(1, 2, 3, 0, 1, 21, 0x85845DD1),
+ Rp(0, 1, 2, 3, 8, 6, 0x6FA87E4F),
+ Rp(3, 0, 1, 2, 15, 10, 0xFE2CE6E0),
+ Rp(2, 3, 0, 1, 6, 15, 0xA3014314),
+ Rp(1, 2, 3, 0, 13, 21, 0x4E0811A1),
+ Rp(0, 1, 2, 3, 4, 6, 0xF7537E82),
+ Rp(3, 0, 1, 2, 11, 10, 0xBD3AF235),
+ Rp(2, 3, 0, 1, 2, 15, 0x2AD7D2BB),
+ Rp(1, 2, 3, 0, 9, 21, 0xEB86D391),
+ };
+ inline for (round3) |r| {
+ v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.b] | ~v[r.d])) +% r.t +% s[r.k];
+ v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
+ }
+
+ d.s[0] +%= v[0];
+ d.s[1] +%= v[1];
+ d.s[2] +%= v[2];
+ d.s[3] +%= v[3];
+ }
+};
+
+const htest = @import("test.zig");
+
+test "md5 single" {
+ htest.assertEqualHash(Md5, "d41d8cd98f00b204e9800998ecf8427e", "");
+ htest.assertEqualHash(Md5, "0cc175b9c0f1b6a831c399e269772661", "a");
+ htest.assertEqualHash(Md5, "900150983cd24fb0d6963f7d28e17f72", "abc");
+ htest.assertEqualHash(Md5, "f96b697d7cb7938d525a2f31aaf161d0", "message digest");
+ htest.assertEqualHash(Md5, "c3fcd3d76192e4007dfb496cca67e13b", "abcdefghijklmnopqrstuvwxyz");
+ htest.assertEqualHash(Md5, "d174ab98d277d9f5a5611c2c9f419d9f", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ htest.assertEqualHash(Md5, "57edf4a22be3c955ac49da2e2107b67a", "12345678901234567890123456789012345678901234567890123456789012345678901234567890");
+}
+
+test "md5 streaming" {
+ var h = Md5.init();
+ var out: [16]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("d41d8cd98f00b204e9800998ecf8427e", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("900150983cd24fb0d6963f7d28e17f72", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+
+ htest.assertEqual("900150983cd24fb0d6963f7d28e17f72", out[0..]);
+}
+
+test "md5 aligned final" {
+ var block = [_]u8{0} ** Md5.block_length;
+ var out: [Md5.digest_length]u8 = undefined;
+
+ var h = Md5.init();
+ h.update(block);
+ h.final(out[0..]);
+}
diff --git a/lib/std/crypto/poly1305.zig b/lib/std/crypto/poly1305.zig
new file mode 100644
index 0000000000..bd0b33e586
--- /dev/null
+++ b/lib/std/crypto/poly1305.zig
@@ -0,0 +1,234 @@
+// Translated from monocypher which is licensed under CC-0/BSD-3.
+//
+// https://monocypher.org/
+
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+
+const Endian = builtin.Endian;
+const readIntSliceLittle = std.mem.readIntSliceLittle;
+const writeIntSliceLittle = std.mem.writeIntSliceLittle;
+
+pub const Poly1305 = struct {
+ const Self = @This();
+
+ pub const mac_length = 16;
+ pub const minimum_key_length = 32;
+
+ // constant multiplier (from the secret key)
+ r: [4]u32,
+ // accumulated hash
+ h: [5]u32,
+ // chunk of the message
+ c: [5]u32,
+ // random number added at the end (from the secret key)
+ pad: [4]u32,
+ // How many bytes are there in the chunk.
+ c_idx: usize,
+
+ fn secureZero(self: *Self) void {
+ std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Poly1305)]);
+ }
+
+ pub fn create(out: []u8, msg: []const u8, key: []const u8) void {
+ std.debug.assert(out.len >= mac_length);
+ std.debug.assert(key.len >= minimum_key_length);
+
+ var ctx = Poly1305.init(key);
+ ctx.update(msg);
+ ctx.final(out);
+ }
+
+ // Initialize the MAC context.
+ // - key.len is sufficient size.
+ pub fn init(key: []const u8) Self {
+ var ctx: Poly1305 = undefined;
+
+ // Initial hash is zero
+ {
+ var i: usize = 0;
+ while (i < 5) : (i += 1) {
+ ctx.h[i] = 0;
+ }
+ }
+ // add 2^130 to every input block
+ ctx.c[4] = 1;
+ polyClearC(&ctx);
+
+ // load r and pad (r has some of its bits cleared)
+ {
+ var i: usize = 0;
+ while (i < 1) : (i += 1) {
+ ctx.r[0] = readIntSliceLittle(u32, key[0..4]) & 0x0fffffff;
+ }
+ }
+ {
+ var i: usize = 1;
+ while (i < 4) : (i += 1) {
+ ctx.r[i] = readIntSliceLittle(u32, key[i * 4 .. i * 4 + 4]) & 0x0ffffffc;
+ }
+ }
+ {
+ var i: usize = 0;
+ while (i < 4) : (i += 1) {
+ ctx.pad[i] = readIntSliceLittle(u32, key[i * 4 + 16 .. i * 4 + 16 + 4]);
+ }
+ }
+
+ return ctx;
+ }
+
+ // h = (h + c) * r
+ // preconditions:
+ // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+ // ctx->c <= 1_ffffffff_ffffffff_ffffffff_ffffffff
+ // ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff
+ // Postcondition:
+ // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+ fn polyBlock(ctx: *Self) void {
+ // s = h + c, without carry propagation
+ const s0 = u64(ctx.h[0]) + ctx.c[0]; // s0 <= 1_fffffffe
+ const s1 = u64(ctx.h[1]) + ctx.c[1]; // s1 <= 1_fffffffe
+ const s2 = u64(ctx.h[2]) + ctx.c[2]; // s2 <= 1_fffffffe
+ const s3 = u64(ctx.h[3]) + ctx.c[3]; // s3 <= 1_fffffffe
+ const s4 = u64(ctx.h[4]) + ctx.c[4]; // s4 <= 5
+
+ // Local all the things!
+ const r0 = ctx.r[0]; // r0 <= 0fffffff
+ const r1 = ctx.r[1]; // r1 <= 0ffffffc
+ const r2 = ctx.r[2]; // r2 <= 0ffffffc
+ const r3 = ctx.r[3]; // r3 <= 0ffffffc
+ const rr0 = (r0 >> 2) * 5; // rr0 <= 13fffffb // lose 2 bits...
+ const rr1 = (r1 >> 2) + r1; // rr1 <= 13fffffb // rr1 == (r1 >> 2) * 5
+ const rr2 = (r2 >> 2) + r2; // rr2 <= 13fffffb // rr1 == (r2 >> 2) * 5
+ const rr3 = (r3 >> 2) + r3; // rr3 <= 13fffffb // rr1 == (r3 >> 2) * 5
+
+ // (h + c) * r, without carry propagation
+ const x0 = s0 * r0 + s1 * rr3 + s2 * rr2 + s3 * rr1 + s4 * rr0; //<=97ffffe007fffff8
+ const x1 = s0 * r1 + s1 * r0 + s2 * rr3 + s3 * rr2 + s4 * rr1; //<=8fffffe20ffffff6
+ const x2 = s0 * r2 + s1 * r1 + s2 * r0 + s3 * rr3 + s4 * rr2; //<=87ffffe417fffff4
+ const x3 = s0 * r3 + s1 * r2 + s2 * r1 + s3 * r0 + s4 * rr3; //<=7fffffe61ffffff2
+ const x4 = s4 * (r0 & 3); // ...recover 2 bits //<= f
+
+ // partial reduction modulo 2^130 - 5
+ const _u5 = @truncate(u32, x4 + (x3 >> 32)); // u5 <= 7ffffff5
+ const _u0 = (_u5 >> 2) * 5 + (x0 & 0xffffffff);
+ const _u1 = (_u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32);
+ const _u2 = (_u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32);
+ const _u3 = (_u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32);
+ const _u4 = (_u3 >> 32) + (_u5 & 3);
+
+ // Update the hash
+ ctx.h[0] = @truncate(u32, _u0); // u0 <= 1_9ffffff0
+ ctx.h[1] = @truncate(u32, _u1); // u1 <= 1_97ffffe0
+ ctx.h[2] = @truncate(u32, _u2); // u2 <= 1_8fffffe2
+ ctx.h[3] = @truncate(u32, _u3); // u3 <= 1_87ffffe4
+ ctx.h[4] = @truncate(u32, _u4); // u4 <= 4
+ }
+
+ // (re-)initializes the input counter and input buffer
+ fn polyClearC(ctx: *Self) void {
+ ctx.c[0] = 0;
+ ctx.c[1] = 0;
+ ctx.c[2] = 0;
+ ctx.c[3] = 0;
+ ctx.c_idx = 0;
+ }
+
+ fn polyTakeInput(ctx: *Self, input: u8) void {
+ const word = ctx.c_idx >> 2;
+ const byte = ctx.c_idx & 3;
+ ctx.c[word] |= std.math.shl(u32, input, byte * 8);
+ ctx.c_idx += 1;
+ }
+
+ fn polyUpdate(ctx: *Self, msg: []const u8) void {
+ for (msg) |b| {
+ polyTakeInput(ctx, b);
+ if (ctx.c_idx == 16) {
+ polyBlock(ctx);
+ polyClearC(ctx);
+ }
+ }
+ }
+
+ fn alignTo(x: usize, block_size: usize) usize {
+ return ((~x) +% 1) & (block_size - 1);
+ }
+
+ // Feed data into the MAC context.
+ pub fn update(ctx: *Self, msg: []const u8) void {
+ // Align ourselves with block boundaries
+ const alignm = std.math.min(alignTo(ctx.c_idx, 16), msg.len);
+ polyUpdate(ctx, msg[0..alignm]);
+
+ var nmsg = msg[alignm..];
+
+ // Process the msg block by block
+ const nb_blocks = nmsg.len >> 4;
+ var i: usize = 0;
+ while (i < nb_blocks) : (i += 1) {
+ ctx.c[0] = readIntSliceLittle(u32, nmsg[0..4]);
+ ctx.c[1] = readIntSliceLittle(u32, nmsg[4..8]);
+ ctx.c[2] = readIntSliceLittle(u32, nmsg[8..12]);
+ ctx.c[3] = readIntSliceLittle(u32, nmsg[12..16]);
+ polyBlock(ctx);
+ nmsg = nmsg[16..];
+ }
+ if (nb_blocks > 0) {
+ polyClearC(ctx);
+ }
+
+ // remaining bytes
+ polyUpdate(ctx, nmsg[0..]);
+ }
+
+ // Finalize the MAC and output into buffer provided by caller.
+ pub fn final(ctx: *Self, out: []u8) void {
+ // Process the last block (if any)
+ if (ctx.c_idx != 0) {
+ // move the final 1 according to remaining input length
+ // (We may add less than 2^130 to the last input block)
+ ctx.c[4] = 0;
+ polyTakeInput(ctx, 1);
+ // one last hash update
+ polyBlock(ctx);
+ }
+
+ // check if we should subtract 2^130-5 by performing the
+ // corresponding carry propagation.
+ const _u0 = u64(5) + ctx.h[0]; // <= 1_00000004
+ const _u1 = (_u0 >> 32) + ctx.h[1]; // <= 1_00000000
+ const _u2 = (_u1 >> 32) + ctx.h[2]; // <= 1_00000000
+ const _u3 = (_u2 >> 32) + ctx.h[3]; // <= 1_00000000
+ const _u4 = (_u3 >> 32) + ctx.h[4]; // <= 5
+ // u4 indicates how many times we should subtract 2^130-5 (0 or 1)
+
+ // h + pad, minus 2^130-5 if u4 exceeds 3
+ const uu0 = (_u4 >> 2) * 5 + ctx.h[0] + ctx.pad[0]; // <= 2_00000003
+ const uu1 = (uu0 >> 32) + ctx.h[1] + ctx.pad[1]; // <= 2_00000000
+ const uu2 = (uu1 >> 32) + ctx.h[2] + ctx.pad[2]; // <= 2_00000000
+ const uu3 = (uu2 >> 32) + ctx.h[3] + ctx.pad[3]; // <= 2_00000000
+
+ // TODO https://github.com/ziglang/zig/issues/863
+ writeIntSliceLittle(u32, out[0..], @truncate(u32, uu0));
+ writeIntSliceLittle(u32, out[4..], @truncate(u32, uu1));
+ writeIntSliceLittle(u32, out[8..], @truncate(u32, uu2));
+ writeIntSliceLittle(u32, out[12..], @truncate(u32, uu3));
+
+ ctx.secureZero();
+ }
+};
+
+test "poly1305 rfc7439 vector1" {
+ const expected_mac = "\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9";
+
+ const msg = "Cryptographic Forum Research Group";
+ const key = "\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe\x42\xd5\x06\xa8" ++
+ "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b";
+
+ var mac: [16]u8 = undefined;
+ Poly1305.create(mac[0..], msg, key);
+
+ std.testing.expectEqualSlices(u8, expected_mac, mac);
+}
diff --git a/lib/std/crypto/sha1.zig b/lib/std/crypto/sha1.zig
new file mode 100644
index 0000000000..c5160a1f37
--- /dev/null
+++ b/lib/std/crypto/sha1.zig
@@ -0,0 +1,302 @@
+const mem = @import("../mem.zig");
+const math = @import("../math.zig");
+const endian = @import("../endian.zig");
+const debug = @import("../debug.zig");
+const builtin = @import("builtin");
+
+const RoundParam = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+ e: usize,
+ i: u32,
+};
+
+fn Rp(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam {
+ return RoundParam{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ .e = e,
+ .i = i,
+ };
+}
+
+pub const Sha1 = struct {
+ const Self = @This();
+ pub const block_length = 64;
+ pub const digest_length = 20;
+
+ s: [5]u32,
+ // Streaming Cache
+ buf: [64]u8,
+ buf_len: u8,
+ total_len: u64,
+
+ pub fn init() Self {
+ var d: Self = undefined;
+ d.reset();
+ return d;
+ }
+
+ pub fn reset(d: *Self) void {
+ d.s[0] = 0x67452301;
+ d.s[1] = 0xEFCDAB89;
+ d.s[2] = 0x98BADCFE;
+ d.s[3] = 0x10325476;
+ d.s[4] = 0xC3D2E1F0;
+ d.buf_len = 0;
+ d.total_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Sha1.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 64) {
+ off += 64 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+
+ d.round(d.buf[0..]);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 64 <= b.len) : (off += 64) {
+ d.round(b[off .. off + 64]);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+
+ d.total_len += b.len;
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ debug.assert(out.len >= 20);
+
+ // The buffer here will never be completely full.
+ mem.set(u8, d.buf[d.buf_len..], 0);
+
+ // Append padding bits.
+ d.buf[d.buf_len] = 0x80;
+ d.buf_len += 1;
+
+ // > 448 mod 512 so need to add an extra round to wrap around.
+ if (64 - d.buf_len < 8) {
+ d.round(d.buf[0..]);
+ mem.set(u8, d.buf[0..], 0);
+ }
+
+ // Append message length.
+ var i: usize = 1;
+ var len = d.total_len >> 5;
+ d.buf[63] = @intCast(u8, d.total_len & 0x1f) << 3;
+ while (i < 8) : (i += 1) {
+ d.buf[63 - i] = @intCast(u8, len & 0xff);
+ len >>= 8;
+ }
+
+ d.round(d.buf[0..]);
+
+ for (d.s) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceBig(u32, out[4 * j .. 4 * j + 4], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8) void {
+ debug.assert(b.len == 64);
+
+ var s: [16]u32 = undefined;
+
+ var v: [5]u32 = [_]u32{
+ d.s[0],
+ d.s[1],
+ d.s[2],
+ d.s[3],
+ d.s[4],
+ };
+
+ const round0a = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 4, 0),
+ Rp(4, 0, 1, 2, 3, 1),
+ Rp(3, 4, 0, 1, 2, 2),
+ Rp(2, 3, 4, 0, 1, 3),
+ Rp(1, 2, 3, 4, 0, 4),
+ Rp(0, 1, 2, 3, 4, 5),
+ Rp(4, 0, 1, 2, 3, 6),
+ Rp(3, 4, 0, 1, 2, 7),
+ Rp(2, 3, 4, 0, 1, 8),
+ Rp(1, 2, 3, 4, 0, 9),
+ Rp(0, 1, 2, 3, 4, 10),
+ Rp(4, 0, 1, 2, 3, 11),
+ Rp(3, 4, 0, 1, 2, 12),
+ Rp(2, 3, 4, 0, 1, 13),
+ Rp(1, 2, 3, 4, 0, 14),
+ Rp(0, 1, 2, 3, 4, 15),
+ };
+ inline for (round0a) |r| {
+ s[r.i] = (u32(b[r.i * 4 + 0]) << 24) | (u32(b[r.i * 4 + 1]) << 16) | (u32(b[r.i * 4 + 2]) << 8) | (u32(b[r.i * 4 + 3]) << 0);
+
+ v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
+ v[r.b] = math.rotl(u32, v[r.b], u32(30));
+ }
+
+ const round0b = comptime [_]RoundParam{
+ Rp(4, 0, 1, 2, 3, 16),
+ Rp(3, 4, 0, 1, 2, 17),
+ Rp(2, 3, 4, 0, 1, 18),
+ Rp(1, 2, 3, 4, 0, 19),
+ };
+ inline for (round0b) |r| {
+ const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
+ s[r.i & 0xf] = math.rotl(u32, t, u32(1));
+
+ v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
+ v[r.b] = math.rotl(u32, v[r.b], u32(30));
+ }
+
+ const round1 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 4, 20),
+ Rp(4, 0, 1, 2, 3, 21),
+ Rp(3, 4, 0, 1, 2, 22),
+ Rp(2, 3, 4, 0, 1, 23),
+ Rp(1, 2, 3, 4, 0, 24),
+ Rp(0, 1, 2, 3, 4, 25),
+ Rp(4, 0, 1, 2, 3, 26),
+ Rp(3, 4, 0, 1, 2, 27),
+ Rp(2, 3, 4, 0, 1, 28),
+ Rp(1, 2, 3, 4, 0, 29),
+ Rp(0, 1, 2, 3, 4, 30),
+ Rp(4, 0, 1, 2, 3, 31),
+ Rp(3, 4, 0, 1, 2, 32),
+ Rp(2, 3, 4, 0, 1, 33),
+ Rp(1, 2, 3, 4, 0, 34),
+ Rp(0, 1, 2, 3, 4, 35),
+ Rp(4, 0, 1, 2, 3, 36),
+ Rp(3, 4, 0, 1, 2, 37),
+ Rp(2, 3, 4, 0, 1, 38),
+ Rp(1, 2, 3, 4, 0, 39),
+ };
+ inline for (round1) |r| {
+ const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
+ s[r.i & 0xf] = math.rotl(u32, t, u32(1));
+
+ v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x6ED9EBA1 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
+ v[r.b] = math.rotl(u32, v[r.b], u32(30));
+ }
+
+ const round2 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 4, 40),
+ Rp(4, 0, 1, 2, 3, 41),
+ Rp(3, 4, 0, 1, 2, 42),
+ Rp(2, 3, 4, 0, 1, 43),
+ Rp(1, 2, 3, 4, 0, 44),
+ Rp(0, 1, 2, 3, 4, 45),
+ Rp(4, 0, 1, 2, 3, 46),
+ Rp(3, 4, 0, 1, 2, 47),
+ Rp(2, 3, 4, 0, 1, 48),
+ Rp(1, 2, 3, 4, 0, 49),
+ Rp(0, 1, 2, 3, 4, 50),
+ Rp(4, 0, 1, 2, 3, 51),
+ Rp(3, 4, 0, 1, 2, 52),
+ Rp(2, 3, 4, 0, 1, 53),
+ Rp(1, 2, 3, 4, 0, 54),
+ Rp(0, 1, 2, 3, 4, 55),
+ Rp(4, 0, 1, 2, 3, 56),
+ Rp(3, 4, 0, 1, 2, 57),
+ Rp(2, 3, 4, 0, 1, 58),
+ Rp(1, 2, 3, 4, 0, 59),
+ };
+ inline for (round2) |r| {
+ const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
+ s[r.i & 0xf] = math.rotl(u32, t, u32(1));
+
+ v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x8F1BBCDC +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) ^ (v[r.b] & v[r.d]) ^ (v[r.c] & v[r.d]));
+ v[r.b] = math.rotl(u32, v[r.b], u32(30));
+ }
+
+ const round3 = comptime [_]RoundParam{
+ Rp(0, 1, 2, 3, 4, 60),
+ Rp(4, 0, 1, 2, 3, 61),
+ Rp(3, 4, 0, 1, 2, 62),
+ Rp(2, 3, 4, 0, 1, 63),
+ Rp(1, 2, 3, 4, 0, 64),
+ Rp(0, 1, 2, 3, 4, 65),
+ Rp(4, 0, 1, 2, 3, 66),
+ Rp(3, 4, 0, 1, 2, 67),
+ Rp(2, 3, 4, 0, 1, 68),
+ Rp(1, 2, 3, 4, 0, 69),
+ Rp(0, 1, 2, 3, 4, 70),
+ Rp(4, 0, 1, 2, 3, 71),
+ Rp(3, 4, 0, 1, 2, 72),
+ Rp(2, 3, 4, 0, 1, 73),
+ Rp(1, 2, 3, 4, 0, 74),
+ Rp(0, 1, 2, 3, 4, 75),
+ Rp(4, 0, 1, 2, 3, 76),
+ Rp(3, 4, 0, 1, 2, 77),
+ Rp(2, 3, 4, 0, 1, 78),
+ Rp(1, 2, 3, 4, 0, 79),
+ };
+ inline for (round3) |r| {
+ const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
+ s[r.i & 0xf] = math.rotl(u32, t, u32(1));
+
+ v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0xCA62C1D6 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
+ v[r.b] = math.rotl(u32, v[r.b], u32(30));
+ }
+
+ d.s[0] +%= v[0];
+ d.s[1] +%= v[1];
+ d.s[2] +%= v[2];
+ d.s[3] +%= v[3];
+ d.s[4] +%= v[4];
+ }
+};
+
+const htest = @import("test.zig");
+
+test "sha1 single" {
+ htest.assertEqualHash(Sha1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "");
+ htest.assertEqualHash(Sha1, "a9993e364706816aba3e25717850c26c9cd0d89d", "abc");
+ htest.assertEqualHash(Sha1, "a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha1 streaming" {
+ var h = Sha1.init();
+ var out: [20]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("da39a3ee5e6b4b0d3255bfef95601890afd80709", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
+}
+
+test "sha1 aligned final" {
+ var block = [_]u8{0} ** Sha1.block_length;
+ var out: [Sha1.digest_length]u8 = undefined;
+
+ var h = Sha1.init();
+ h.update(block);
+ h.final(out[0..]);
+}
diff --git a/lib/std/crypto/sha2.zig b/lib/std/crypto/sha2.zig
new file mode 100644
index 0000000000..b40a39d579
--- /dev/null
+++ b/lib/std/crypto/sha2.zig
@@ -0,0 +1,726 @@
+const mem = @import("../mem.zig");
+const math = @import("../math.zig");
+const endian = @import("../endian.zig");
+const debug = @import("../debug.zig");
+const builtin = @import("builtin");
+const htest = @import("test.zig");
+
+/////////////////////
+// Sha224 + Sha256
+
+const RoundParam256 = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+ e: usize,
+ f: usize,
+ g: usize,
+ h: usize,
+ i: usize,
+ k: u32,
+};
+
+fn Rp256(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize, g: usize, h: usize, i: usize, k: u32) RoundParam256 {
+ return RoundParam256{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ .e = e,
+ .f = f,
+ .g = g,
+ .h = h,
+ .i = i,
+ .k = k,
+ };
+}
+
+const Sha2Params32 = struct {
+ iv0: u32,
+ iv1: u32,
+ iv2: u32,
+ iv3: u32,
+ iv4: u32,
+ iv5: u32,
+ iv6: u32,
+ iv7: u32,
+ out_len: usize,
+};
+
+const Sha224Params = Sha2Params32{
+ .iv0 = 0xC1059ED8,
+ .iv1 = 0x367CD507,
+ .iv2 = 0x3070DD17,
+ .iv3 = 0xF70E5939,
+ .iv4 = 0xFFC00B31,
+ .iv5 = 0x68581511,
+ .iv6 = 0x64F98FA7,
+ .iv7 = 0xBEFA4FA4,
+ .out_len = 224,
+};
+
+const Sha256Params = Sha2Params32{
+ .iv0 = 0x6A09E667,
+ .iv1 = 0xBB67AE85,
+ .iv2 = 0x3C6EF372,
+ .iv3 = 0xA54FF53A,
+ .iv4 = 0x510E527F,
+ .iv5 = 0x9B05688C,
+ .iv6 = 0x1F83D9AB,
+ .iv7 = 0x5BE0CD19,
+ .out_len = 256,
+};
+
+pub const Sha224 = Sha2_32(Sha224Params);
+pub const Sha256 = Sha2_32(Sha256Params);
+
+fn Sha2_32(comptime params: Sha2Params32) type {
+ return struct {
+ const Self = @This();
+ pub const block_length = 64;
+ pub const digest_length = params.out_len / 8;
+
+ s: [8]u32,
+ // Streaming Cache
+ buf: [64]u8,
+ buf_len: u8,
+ total_len: u64,
+
+ pub fn init() Self {
+ var d: Self = undefined;
+ d.reset();
+ return d;
+ }
+
+ pub fn reset(d: *Self) void {
+ d.s[0] = params.iv0;
+ d.s[1] = params.iv1;
+ d.s[2] = params.iv2;
+ d.s[3] = params.iv3;
+ d.s[4] = params.iv4;
+ d.s[5] = params.iv5;
+ d.s[6] = params.iv6;
+ d.s[7] = params.iv7;
+ d.buf_len = 0;
+ d.total_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Self.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 64) {
+ off += 64 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+
+ d.round(d.buf[0..]);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 64 <= b.len) : (off += 64) {
+ d.round(b[off .. off + 64]);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+
+ d.total_len += b.len;
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ debug.assert(out.len >= params.out_len / 8);
+
+ // The buffer here will never be completely full.
+ mem.set(u8, d.buf[d.buf_len..], 0);
+
+ // Append padding bits.
+ d.buf[d.buf_len] = 0x80;
+ d.buf_len += 1;
+
+ // > 448 mod 512 so need to add an extra round to wrap around.
+ if (64 - d.buf_len < 8) {
+ d.round(d.buf[0..]);
+ mem.set(u8, d.buf[0..], 0);
+ }
+
+ // Append message length.
+ var i: usize = 1;
+ var len = d.total_len >> 5;
+ d.buf[63] = @intCast(u8, d.total_len & 0x1f) << 3;
+ while (i < 8) : (i += 1) {
+ d.buf[63 - i] = @intCast(u8, len & 0xff);
+ len >>= 8;
+ }
+
+ d.round(d.buf[0..]);
+
+ // May truncate for possible 224 output
+ const rr = d.s[0 .. params.out_len / 32];
+
+ for (rr) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceBig(u32, out[4 * j .. 4 * j + 4], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8) void {
+ debug.assert(b.len == 64);
+
+ var s: [64]u32 = undefined;
+
+ var i: usize = 0;
+ while (i < 16) : (i += 1) {
+ s[i] = 0;
+ s[i] |= u32(b[i * 4 + 0]) << 24;
+ s[i] |= u32(b[i * 4 + 1]) << 16;
+ s[i] |= u32(b[i * 4 + 2]) << 8;
+ s[i] |= u32(b[i * 4 + 3]) << 0;
+ }
+ while (i < 64) : (i += 1) {
+ s[i] = s[i - 16] +% s[i - 7] +% (math.rotr(u32, s[i - 15], u32(7)) ^ math.rotr(u32, s[i - 15], u32(18)) ^ (s[i - 15] >> 3)) +% (math.rotr(u32, s[i - 2], u32(17)) ^ math.rotr(u32, s[i - 2], u32(19)) ^ (s[i - 2] >> 10));
+ }
+
+ var v: [8]u32 = [_]u32{
+ d.s[0],
+ d.s[1],
+ d.s[2],
+ d.s[3],
+ d.s[4],
+ d.s[5],
+ d.s[6],
+ d.s[7],
+ };
+
+ const round0 = comptime [_]RoundParam256{
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 0, 0x428A2F98),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 1, 0x71374491),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 2, 0xB5C0FBCF),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 3, 0xE9B5DBA5),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 4, 0x3956C25B),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 5, 0x59F111F1),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 6, 0x923F82A4),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 7, 0xAB1C5ED5),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 8, 0xD807AA98),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 9, 0x12835B01),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 10, 0x243185BE),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 11, 0x550C7DC3),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 12, 0x72BE5D74),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 13, 0x80DEB1FE),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 14, 0x9BDC06A7),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 15, 0xC19BF174),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 16, 0xE49B69C1),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 17, 0xEFBE4786),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 18, 0x0FC19DC6),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 19, 0x240CA1CC),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 20, 0x2DE92C6F),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 21, 0x4A7484AA),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 22, 0x5CB0A9DC),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 23, 0x76F988DA),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 24, 0x983E5152),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 25, 0xA831C66D),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 26, 0xB00327C8),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 27, 0xBF597FC7),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 28, 0xC6E00BF3),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 29, 0xD5A79147),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 30, 0x06CA6351),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 31, 0x14292967),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 32, 0x27B70A85),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 33, 0x2E1B2138),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 34, 0x4D2C6DFC),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 35, 0x53380D13),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 36, 0x650A7354),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 37, 0x766A0ABB),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 38, 0x81C2C92E),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 39, 0x92722C85),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 40, 0xA2BFE8A1),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 41, 0xA81A664B),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 42, 0xC24B8B70),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 43, 0xC76C51A3),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 44, 0xD192E819),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 45, 0xD6990624),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 46, 0xF40E3585),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 47, 0x106AA070),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 48, 0x19A4C116),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 49, 0x1E376C08),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 50, 0x2748774C),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 51, 0x34B0BCB5),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 52, 0x391C0CB3),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 53, 0x4ED8AA4A),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 54, 0x5B9CCA4F),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 55, 0x682E6FF3),
+ Rp256(0, 1, 2, 3, 4, 5, 6, 7, 56, 0x748F82EE),
+ Rp256(7, 0, 1, 2, 3, 4, 5, 6, 57, 0x78A5636F),
+ Rp256(6, 7, 0, 1, 2, 3, 4, 5, 58, 0x84C87814),
+ Rp256(5, 6, 7, 0, 1, 2, 3, 4, 59, 0x8CC70208),
+ Rp256(4, 5, 6, 7, 0, 1, 2, 3, 60, 0x90BEFFFA),
+ Rp256(3, 4, 5, 6, 7, 0, 1, 2, 61, 0xA4506CEB),
+ Rp256(2, 3, 4, 5, 6, 7, 0, 1, 62, 0xBEF9A3F7),
+ Rp256(1, 2, 3, 4, 5, 6, 7, 0, 63, 0xC67178F2),
+ };
+ inline for (round0) |r| {
+ v[r.h] = v[r.h] +% (math.rotr(u32, v[r.e], u32(6)) ^ math.rotr(u32, v[r.e], u32(11)) ^ math.rotr(u32, v[r.e], u32(25))) +% (v[r.g] ^ (v[r.e] & (v[r.f] ^ v[r.g]))) +% r.k +% s[r.i];
+
+ v[r.d] = v[r.d] +% v[r.h];
+
+ v[r.h] = v[r.h] +% (math.rotr(u32, v[r.a], u32(2)) ^ math.rotr(u32, v[r.a], u32(13)) ^ math.rotr(u32, v[r.a], u32(22))) +% ((v[r.a] & (v[r.b] | v[r.c])) | (v[r.b] & v[r.c]));
+ }
+
+ d.s[0] +%= v[0];
+ d.s[1] +%= v[1];
+ d.s[2] +%= v[2];
+ d.s[3] +%= v[3];
+ d.s[4] +%= v[4];
+ d.s[5] +%= v[5];
+ d.s[6] +%= v[6];
+ d.s[7] +%= v[7];
+ }
+ };
+}
+
+test "sha224 single" {
+ htest.assertEqualHash(Sha224, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "");
+ htest.assertEqualHash(Sha224, "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "abc");
+ htest.assertEqualHash(Sha224, "c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha224 streaming" {
+ var h = Sha224.init();
+ var out: [28]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual("23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", out[0..]);
+}
+
+test "sha256 single" {
+ htest.assertEqualHash(Sha256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "");
+ htest.assertEqualHash(Sha256, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "abc");
+ htest.assertEqualHash(Sha256, "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha256 streaming" {
+ var h = Sha256.init();
+ var out: [32]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", out[0..]);
+}
+
+test "sha256 aligned final" {
+ var block = [_]u8{0} ** Sha256.block_length;
+ var out: [Sha256.digest_length]u8 = undefined;
+
+ var h = Sha256.init();
+ h.update(block);
+ h.final(out[0..]);
+}
+
+/////////////////////
+// Sha384 + Sha512
+
+const RoundParam512 = struct {
+ a: usize,
+ b: usize,
+ c: usize,
+ d: usize,
+ e: usize,
+ f: usize,
+ g: usize,
+ h: usize,
+ i: usize,
+ k: u64,
+};
+
+fn Rp512(a: usize, b: usize, c: usize, d: usize, e: usize, f: usize, g: usize, h: usize, i: usize, k: u64) RoundParam512 {
+ return RoundParam512{
+ .a = a,
+ .b = b,
+ .c = c,
+ .d = d,
+ .e = e,
+ .f = f,
+ .g = g,
+ .h = h,
+ .i = i,
+ .k = k,
+ };
+}
+
+const Sha2Params64 = struct {
+ iv0: u64,
+ iv1: u64,
+ iv2: u64,
+ iv3: u64,
+ iv4: u64,
+ iv5: u64,
+ iv6: u64,
+ iv7: u64,
+ out_len: usize,
+};
+
+const Sha384Params = Sha2Params64{
+ .iv0 = 0xCBBB9D5DC1059ED8,
+ .iv1 = 0x629A292A367CD507,
+ .iv2 = 0x9159015A3070DD17,
+ .iv3 = 0x152FECD8F70E5939,
+ .iv4 = 0x67332667FFC00B31,
+ .iv5 = 0x8EB44A8768581511,
+ .iv6 = 0xDB0C2E0D64F98FA7,
+ .iv7 = 0x47B5481DBEFA4FA4,
+ .out_len = 384,
+};
+
+const Sha512Params = Sha2Params64{
+ .iv0 = 0x6A09E667F3BCC908,
+ .iv1 = 0xBB67AE8584CAA73B,
+ .iv2 = 0x3C6EF372FE94F82B,
+ .iv3 = 0xA54FF53A5F1D36F1,
+ .iv4 = 0x510E527FADE682D1,
+ .iv5 = 0x9B05688C2B3E6C1F,
+ .iv6 = 0x1F83D9ABFB41BD6B,
+ .iv7 = 0x5BE0CD19137E2179,
+ .out_len = 512,
+};
+
+pub const Sha384 = Sha2_64(Sha384Params);
+pub const Sha512 = Sha2_64(Sha512Params);
+
+fn Sha2_64(comptime params: Sha2Params64) type {
+ return struct {
+ const Self = @This();
+ pub const block_length = 128;
+ pub const digest_length = params.out_len / 8;
+
+ s: [8]u64,
+ // Streaming Cache
+ buf: [128]u8,
+ buf_len: u8,
+ total_len: u128,
+
+ pub fn init() Self {
+ var d: Self = undefined;
+ d.reset();
+ return d;
+ }
+
+ pub fn reset(d: *Self) void {
+ d.s[0] = params.iv0;
+ d.s[1] = params.iv1;
+ d.s[2] = params.iv2;
+ d.s[3] = params.iv3;
+ d.s[4] = params.iv4;
+ d.s[5] = params.iv5;
+ d.s[6] = params.iv6;
+ d.s[7] = params.iv7;
+ d.buf_len = 0;
+ d.total_len = 0;
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Self.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var off: usize = 0;
+
+ // Partial buffer exists from previous update. Copy into buffer then hash.
+ if (d.buf_len != 0 and d.buf_len + b.len > 128) {
+ off += 128 - d.buf_len;
+ mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
+
+ d.round(d.buf[0..]);
+ d.buf_len = 0;
+ }
+
+ // Full middle blocks.
+ while (off + 128 <= b.len) : (off += 128) {
+ d.round(b[off .. off + 128]);
+ }
+
+ // Copy any remainder for next pass.
+ mem.copy(u8, d.buf[d.buf_len..], b[off..]);
+ d.buf_len += @intCast(u8, b[off..].len);
+
+ d.total_len += b.len;
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ debug.assert(out.len >= params.out_len / 8);
+
+ // The buffer here will never be completely full.
+ mem.set(u8, d.buf[d.buf_len..], 0);
+
+ // Append padding bits.
+ d.buf[d.buf_len] = 0x80;
+ d.buf_len += 1;
+
+ // > 896 mod 1024 so need to add an extra round to wrap around.
+ if (128 - d.buf_len < 16) {
+ d.round(d.buf[0..]);
+ mem.set(u8, d.buf[0..], 0);
+ }
+
+ // Append message length.
+ var i: usize = 1;
+ var len = d.total_len >> 5;
+ d.buf[127] = @intCast(u8, d.total_len & 0x1f) << 3;
+ while (i < 16) : (i += 1) {
+ d.buf[127 - i] = @intCast(u8, len & 0xff);
+ len >>= 8;
+ }
+
+ d.round(d.buf[0..]);
+
+ // May truncate for possible 384 output
+ const rr = d.s[0 .. params.out_len / 64];
+
+ for (rr) |s, j| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceBig(u64, out[8 * j .. 8 * j + 8], s);
+ }
+ }
+
+ fn round(d: *Self, b: []const u8) void {
+ debug.assert(b.len == 128);
+
+ var s: [80]u64 = undefined;
+
+ var i: usize = 0;
+ while (i < 16) : (i += 1) {
+ s[i] = 0;
+ s[i] |= u64(b[i * 8 + 0]) << 56;
+ s[i] |= u64(b[i * 8 + 1]) << 48;
+ s[i] |= u64(b[i * 8 + 2]) << 40;
+ s[i] |= u64(b[i * 8 + 3]) << 32;
+ s[i] |= u64(b[i * 8 + 4]) << 24;
+ s[i] |= u64(b[i * 8 + 5]) << 16;
+ s[i] |= u64(b[i * 8 + 6]) << 8;
+ s[i] |= u64(b[i * 8 + 7]) << 0;
+ }
+ while (i < 80) : (i += 1) {
+ s[i] = s[i - 16] +% s[i - 7] +% (math.rotr(u64, s[i - 15], u64(1)) ^ math.rotr(u64, s[i - 15], u64(8)) ^ (s[i - 15] >> 7)) +% (math.rotr(u64, s[i - 2], u64(19)) ^ math.rotr(u64, s[i - 2], u64(61)) ^ (s[i - 2] >> 6));
+ }
+
+ var v: [8]u64 = [_]u64{
+ d.s[0],
+ d.s[1],
+ d.s[2],
+ d.s[3],
+ d.s[4],
+ d.s[5],
+ d.s[6],
+ d.s[7],
+ };
+
+ const round0 = comptime [_]RoundParam512{
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 0, 0x428A2F98D728AE22),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 1, 0x7137449123EF65CD),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 2, 0xB5C0FBCFEC4D3B2F),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 3, 0xE9B5DBA58189DBBC),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 4, 0x3956C25BF348B538),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 5, 0x59F111F1B605D019),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 6, 0x923F82A4AF194F9B),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 7, 0xAB1C5ED5DA6D8118),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 8, 0xD807AA98A3030242),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 9, 0x12835B0145706FBE),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 10, 0x243185BE4EE4B28C),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 11, 0x550C7DC3D5FFB4E2),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 12, 0x72BE5D74F27B896F),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 13, 0x80DEB1FE3B1696B1),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 14, 0x9BDC06A725C71235),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 15, 0xC19BF174CF692694),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 16, 0xE49B69C19EF14AD2),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 17, 0xEFBE4786384F25E3),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 18, 0x0FC19DC68B8CD5B5),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 19, 0x240CA1CC77AC9C65),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 20, 0x2DE92C6F592B0275),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 21, 0x4A7484AA6EA6E483),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 22, 0x5CB0A9DCBD41FBD4),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 23, 0x76F988DA831153B5),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 24, 0x983E5152EE66DFAB),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 25, 0xA831C66D2DB43210),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 26, 0xB00327C898FB213F),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 27, 0xBF597FC7BEEF0EE4),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 28, 0xC6E00BF33DA88FC2),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 29, 0xD5A79147930AA725),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 30, 0x06CA6351E003826F),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 31, 0x142929670A0E6E70),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 32, 0x27B70A8546D22FFC),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 33, 0x2E1B21385C26C926),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 34, 0x4D2C6DFC5AC42AED),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 35, 0x53380D139D95B3DF),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 36, 0x650A73548BAF63DE),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 37, 0x766A0ABB3C77B2A8),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 38, 0x81C2C92E47EDAEE6),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 39, 0x92722C851482353B),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 40, 0xA2BFE8A14CF10364),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 41, 0xA81A664BBC423001),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 42, 0xC24B8B70D0F89791),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 43, 0xC76C51A30654BE30),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 44, 0xD192E819D6EF5218),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 45, 0xD69906245565A910),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 46, 0xF40E35855771202A),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 47, 0x106AA07032BBD1B8),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 48, 0x19A4C116B8D2D0C8),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 49, 0x1E376C085141AB53),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 50, 0x2748774CDF8EEB99),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 51, 0x34B0BCB5E19B48A8),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 52, 0x391C0CB3C5C95A63),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 53, 0x4ED8AA4AE3418ACB),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 54, 0x5B9CCA4F7763E373),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 55, 0x682E6FF3D6B2B8A3),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 56, 0x748F82EE5DEFB2FC),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 57, 0x78A5636F43172F60),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 58, 0x84C87814A1F0AB72),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 59, 0x8CC702081A6439EC),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 60, 0x90BEFFFA23631E28),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 61, 0xA4506CEBDE82BDE9),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 62, 0xBEF9A3F7B2C67915),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 63, 0xC67178F2E372532B),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 64, 0xCA273ECEEA26619C),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 65, 0xD186B8C721C0C207),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 66, 0xEADA7DD6CDE0EB1E),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 67, 0xF57D4F7FEE6ED178),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 68, 0x06F067AA72176FBA),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 69, 0x0A637DC5A2C898A6),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 70, 0x113F9804BEF90DAE),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 71, 0x1B710B35131C471B),
+ Rp512(0, 1, 2, 3, 4, 5, 6, 7, 72, 0x28DB77F523047D84),
+ Rp512(7, 0, 1, 2, 3, 4, 5, 6, 73, 0x32CAAB7B40C72493),
+ Rp512(6, 7, 0, 1, 2, 3, 4, 5, 74, 0x3C9EBE0A15C9BEBC),
+ Rp512(5, 6, 7, 0, 1, 2, 3, 4, 75, 0x431D67C49C100D4C),
+ Rp512(4, 5, 6, 7, 0, 1, 2, 3, 76, 0x4CC5D4BECB3E42B6),
+ Rp512(3, 4, 5, 6, 7, 0, 1, 2, 77, 0x597F299CFC657E2A),
+ Rp512(2, 3, 4, 5, 6, 7, 0, 1, 78, 0x5FCB6FAB3AD6FAEC),
+ Rp512(1, 2, 3, 4, 5, 6, 7, 0, 79, 0x6C44198C4A475817),
+ };
+ inline for (round0) |r| {
+ v[r.h] = v[r.h] +% (math.rotr(u64, v[r.e], u64(14)) ^ math.rotr(u64, v[r.e], u64(18)) ^ math.rotr(u64, v[r.e], u64(41))) +% (v[r.g] ^ (v[r.e] & (v[r.f] ^ v[r.g]))) +% r.k +% s[r.i];
+
+ v[r.d] = v[r.d] +% v[r.h];
+
+ v[r.h] = v[r.h] +% (math.rotr(u64, v[r.a], u64(28)) ^ math.rotr(u64, v[r.a], u64(34)) ^ math.rotr(u64, v[r.a], u64(39))) +% ((v[r.a] & (v[r.b] | v[r.c])) | (v[r.b] & v[r.c]));
+ }
+
+ d.s[0] +%= v[0];
+ d.s[1] +%= v[1];
+ d.s[2] +%= v[2];
+ d.s[3] +%= v[3];
+ d.s[4] +%= v[4];
+ d.s[5] +%= v[5];
+ d.s[6] +%= v[6];
+ d.s[7] +%= v[7];
+ }
+ };
+}
+
+test "sha384 single" {
+ const h1 = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b";
+ htest.assertEqualHash(Sha384, h1, "");
+
+ const h2 = "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7";
+ htest.assertEqualHash(Sha384, h2, "abc");
+
+ const h3 = "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039";
+ htest.assertEqualHash(Sha384, h3, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha384 streaming" {
+ var h = Sha384.init();
+ var out: [48]u8 = undefined;
+
+ const h1 = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b";
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "sha512 single" {
+ const h1 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+ htest.assertEqualHash(Sha512, h1, "");
+
+ const h2 = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f";
+ htest.assertEqualHash(Sha512, h2, "abc");
+
+ const h3 = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909";
+ htest.assertEqualHash(Sha512, h3, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha512 streaming" {
+ var h = Sha512.init();
+ var out: [64]u8 = undefined;
+
+ const h1 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f";
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "sha512 aligned final" {
+ var block = [_]u8{0} ** Sha512.block_length;
+ var out: [Sha512.digest_length]u8 = undefined;
+
+ var h = Sha512.init();
+ h.update(block);
+ h.final(out[0..]);
+}
diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig
new file mode 100644
index 0000000000..659e7a254f
--- /dev/null
+++ b/lib/std/crypto/sha3.zig
@@ -0,0 +1,305 @@
+const mem = @import("../mem.zig");
+const math = @import("../math.zig");
+const endian = @import("../endian.zig");
+const debug = @import("../debug.zig");
+const builtin = @import("builtin");
+const htest = @import("test.zig");
+
+pub const Sha3_224 = Keccak(224, 0x06);
+pub const Sha3_256 = Keccak(256, 0x06);
+pub const Sha3_384 = Keccak(384, 0x06);
+pub const Sha3_512 = Keccak(512, 0x06);
+
+fn Keccak(comptime bits: usize, comptime delim: u8) type {
+ return struct {
+ const Self = @This();
+ pub const block_length = 200;
+ pub const digest_length = bits / 8;
+
+ s: [200]u8,
+ offset: usize,
+ rate: usize,
+
+ pub fn init() Self {
+ var d: Self = undefined;
+ d.reset();
+ return d;
+ }
+
+ pub fn reset(d: *Self) void {
+ mem.set(u8, d.s[0..], 0);
+ d.offset = 0;
+ d.rate = 200 - (bits / 4);
+ }
+
+ pub fn hash(b: []const u8, out: []u8) void {
+ var d = Self.init();
+ d.update(b);
+ d.final(out);
+ }
+
+ pub fn update(d: *Self, b: []const u8) void {
+ var ip: usize = 0;
+ var len = b.len;
+ var rate = d.rate - d.offset;
+ var offset = d.offset;
+
+ // absorb
+ while (len >= rate) {
+ for (d.s[offset .. offset + rate]) |*r, i|
+ r.* ^= b[ip..][i];
+
+ keccak_f(1600, d.s[0..]);
+
+ ip += rate;
+ len -= rate;
+ rate = d.rate;
+ offset = 0;
+ }
+
+ for (d.s[offset .. offset + len]) |*r, i|
+ r.* ^= b[ip..][i];
+
+ d.offset = offset + len;
+ }
+
+ pub fn final(d: *Self, out: []u8) void {
+ // padding
+ d.s[d.offset] ^= delim;
+ d.s[d.rate - 1] ^= 0x80;
+
+ keccak_f(1600, d.s[0..]);
+
+ // squeeze
+ var op: usize = 0;
+ var len: usize = bits / 8;
+
+ while (len >= d.rate) {
+ mem.copy(u8, out[op..], d.s[0..d.rate]);
+ keccak_f(1600, d.s[0..]);
+ op += d.rate;
+ len -= d.rate;
+ }
+
+ mem.copy(u8, out[op..], d.s[0..len]);
+ }
+ };
+}
+
+const RC = [_]u64{
+ 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000,
+ 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
+ 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
+ 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003,
+ 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a,
+ 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
+};
+
+const ROTC = [_]usize{
+ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
+};
+
+const PIL = [_]usize{
+ 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
+};
+
+const M5 = [_]usize{
+ 0, 1, 2, 3, 4, 0, 1, 2, 3, 4,
+};
+
+fn keccak_f(comptime F: usize, d: []u8) void {
+ debug.assert(d.len == F / 8);
+
+ const B = F / 25;
+ const no_rounds = comptime x: {
+ break :x 12 + 2 * math.log2(B);
+ };
+
+ var s = [_]u64{0} ** 25;
+ var t = [_]u64{0} ** 1;
+ var c = [_]u64{0} ** 5;
+
+ for (s) |*r, i| {
+ r.* = mem.readIntSliceLittle(u64, d[8 * i .. 8 * i + 8]);
+ }
+
+ comptime var x: usize = 0;
+ comptime var y: usize = 0;
+ for (RC[0..no_rounds]) |round| {
+ // theta
+ x = 0;
+ inline while (x < 5) : (x += 1) {
+ c[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20];
+ }
+ x = 0;
+ inline while (x < 5) : (x += 1) {
+ t[0] = c[M5[x + 4]] ^ math.rotl(u64, c[M5[x + 1]], usize(1));
+ y = 0;
+ inline while (y < 5) : (y += 1) {
+ s[x + y * 5] ^= t[0];
+ }
+ }
+
+ // rho+pi
+ t[0] = s[1];
+ x = 0;
+ inline while (x < 24) : (x += 1) {
+ c[0] = s[PIL[x]];
+ s[PIL[x]] = math.rotl(u64, t[0], ROTC[x]);
+ t[0] = c[0];
+ }
+
+ // chi
+ y = 0;
+ inline while (y < 5) : (y += 1) {
+ x = 0;
+ inline while (x < 5) : (x += 1) {
+ c[x] = s[x + y * 5];
+ }
+ x = 0;
+ inline while (x < 5) : (x += 1) {
+ s[x + y * 5] = c[x] ^ (~c[M5[x + 1]] & c[M5[x + 2]]);
+ }
+ }
+
+ // iota
+ s[0] ^= round;
+ }
+
+ for (s) |r, i| {
+ // TODO https://github.com/ziglang/zig/issues/863
+ mem.writeIntSliceLittle(u64, d[8 * i .. 8 * i + 8], r);
+ }
+}
+
+test "sha3-224 single" {
+ htest.assertEqualHash(Sha3_224, "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", "");
+ htest.assertEqualHash(Sha3_224, "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc");
+ htest.assertEqualHash(Sha3_224, "543e6868e1666c1a643630df77367ae5a62a85070a51c14cbf665cbc", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha3-224 streaming" {
+ var h = Sha3_224.init();
+ var out: [28]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual("e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", out[0..]);
+}
+
+test "sha3-256 single" {
+ htest.assertEqualHash(Sha3_256, "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", "");
+ htest.assertEqualHash(Sha3_256, "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", "abc");
+ htest.assertEqualHash(Sha3_256, "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha3-256 streaming" {
+ var h = Sha3_256.init();
+ var out: [32]u8 = undefined;
+
+ h.final(out[0..]);
+ htest.assertEqual("a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", out[0..]);
+
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual("3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual("3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", out[0..]);
+}
+
+test "sha3-256 aligned final" {
+ var block = [_]u8{0} ** Sha3_256.block_length;
+ var out: [Sha3_256.digest_length]u8 = undefined;
+
+ var h = Sha3_256.init();
+ h.update(block);
+ h.final(out[0..]);
+}
+
+test "sha3-384 single" {
+ const h1 = "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004";
+ htest.assertEqualHash(Sha3_384, h1, "");
+ const h2 = "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25";
+ htest.assertEqualHash(Sha3_384, h2, "abc");
+ const h3 = "79407d3b5916b59c3e30b09822974791c313fb9ecc849e406f23592d04f625dc8c709b98b43b3852b337216179aa7fc7";
+ htest.assertEqualHash(Sha3_384, h3, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha3-384 streaming" {
+ var h = Sha3_384.init();
+ var out: [48]u8 = undefined;
+
+ const h1 = "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004";
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25";
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "sha3-512 single" {
+ const h1 = "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26";
+ htest.assertEqualHash(Sha3_512, h1, "");
+ const h2 = "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0";
+ htest.assertEqualHash(Sha3_512, h2, "abc");
+ const h3 = "afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185";
+ htest.assertEqualHash(Sha3_512, h3, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
+}
+
+test "sha3-512 streaming" {
+ var h = Sha3_512.init();
+ var out: [64]u8 = undefined;
+
+ const h1 = "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26";
+ h.final(out[0..]);
+ htest.assertEqual(h1, out[0..]);
+
+ const h2 = "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0";
+ h.reset();
+ h.update("abc");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+
+ h.reset();
+ h.update("a");
+ h.update("b");
+ h.update("c");
+ h.final(out[0..]);
+ htest.assertEqual(h2, out[0..]);
+}
+
+test "sha3-512 aligned final" {
+ var block = [_]u8{0} ** Sha3_512.block_length;
+ var out: [Sha3_512.digest_length]u8 = undefined;
+
+ var h = Sha3_512.init();
+ h.update(block);
+ h.final(out[0..]);
+}
diff --git a/lib/std/crypto/test.zig b/lib/std/crypto/test.zig
new file mode 100644
index 0000000000..a0ddad6c83
--- /dev/null
+++ b/lib/std/crypto/test.zig
@@ -0,0 +1,22 @@
+const std = @import("../std.zig");
+const testing = std.testing;
+const mem = std.mem;
+const fmt = std.fmt;
+
+// Hash using the specified hasher `H` asserting `expected == H(input)`.
+pub fn assertEqualHash(comptime Hasher: var, comptime expected: []const u8, input: []const u8) void {
+ var h: [expected.len / 2]u8 = undefined;
+ Hasher.hash(input, h[0..]);
+
+ assertEqual(expected, h);
+}
+
+// Assert `expected` == `input` where `input` is a bytestring.
+pub fn assertEqual(comptime expected: []const u8, input: []const u8) void {
+ var expected_bytes: [expected.len / 2]u8 = undefined;
+ for (expected_bytes) |*r, i| {
+ r.* = fmt.parseInt(u8, expected[2 * i .. 2 * i + 2], 16) catch unreachable;
+ }
+
+ testing.expectEqualSlices(u8, expected_bytes, input);
+}
diff --git a/lib/std/crypto/x25519.zig b/lib/std/crypto/x25519.zig
new file mode 100644
index 0000000000..7f9220c3f4
--- /dev/null
+++ b/lib/std/crypto/x25519.zig
@@ -0,0 +1,676 @@
+// Translated from monocypher which is licensed under CC-0/BSD-3.
+//
+// https://monocypher.org/
+
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const fmt = std.fmt;
+
+const Endian = builtin.Endian;
+const readIntSliceLittle = std.mem.readIntSliceLittle;
+const writeIntSliceLittle = std.mem.writeIntSliceLittle;
+
+// Based on Supercop's ref10 implementation.
+pub const X25519 = struct {
+ pub const secret_length = 32;
+ pub const minimum_key_length = 32;
+
+ fn trimScalar(s: []u8) void {
+ s[0] &= 248;
+ s[31] &= 127;
+ s[31] |= 64;
+ }
+
+ fn scalarBit(s: []const u8, i: usize) i32 {
+ return (s[i >> 3] >> @intCast(u3, i & 7)) & 1;
+ }
+
+ pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool {
+ std.debug.assert(out.len >= secret_length);
+ std.debug.assert(private_key.len >= minimum_key_length);
+ std.debug.assert(public_key.len >= minimum_key_length);
+
+ var storage: [7]Fe = undefined;
+ var x1 = &storage[0];
+ var x2 = &storage[1];
+ var z2 = &storage[2];
+ var x3 = &storage[3];
+ var z3 = &storage[4];
+ var t0 = &storage[5];
+ var t1 = &storage[6];
+
+ // computes the scalar product
+ Fe.fromBytes(x1, public_key);
+
+ // restrict the possible scalar values
+ var e: [32]u8 = undefined;
+ for (e[0..]) |_, i| {
+ e[i] = private_key[i];
+ }
+ trimScalar(e[0..]);
+
+ // computes the actual scalar product (the result is in x2 and z2)
+
+ // Montgomery ladder
+ // In projective coordinates, to avoid divisions: x = X / Z
+ // We don't care about the y coordinate, it's only 1 bit of information
+ Fe.init1(x2);
+ Fe.init0(z2); // "zero" point
+ Fe.copy(x3, x1);
+ Fe.init1(z3);
+
+ var swap: i32 = 0;
+ var pos: isize = 254;
+ while (pos >= 0) : (pos -= 1) {
+ // constant time conditional swap before ladder step
+ const b = scalarBit(e, @intCast(usize, pos));
+ swap ^= b; // xor trick avoids swapping at the end of the loop
+ Fe.cswap(x2, x3, swap);
+ Fe.cswap(z2, z3, swap);
+ swap = b; // anticipates one last swap after the loop
+
+ // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3)
+ // with differential addition
+ Fe.sub(t0, x3, z3);
+ Fe.sub(t1, x2, z2);
+ Fe.add(x2, x2, z2);
+ Fe.add(z2, x3, z3);
+ Fe.mul(z3, t0, x2);
+ Fe.mul(z2, z2, t1);
+ Fe.sq(t0, t1);
+ Fe.sq(t1, x2);
+ Fe.add(x3, z3, z2);
+ Fe.sub(z2, z3, z2);
+ Fe.mul(x2, t1, t0);
+ Fe.sub(t1, t1, t0);
+ Fe.sq(z2, z2);
+ Fe.mulSmall(z3, t1, 121666);
+ Fe.sq(x3, x3);
+ Fe.add(t0, t0, z3);
+ Fe.mul(z3, x1, z2);
+ Fe.mul(z2, t1, t0);
+ }
+
+ // last swap is necessary to compensate for the xor trick
+ // Note: after this swap, P3 == P2 + P1.
+ Fe.cswap(x2, x3, swap);
+ Fe.cswap(z2, z3, swap);
+
+ // normalises the coordinates: x == X / Z
+ Fe.invert(z2, z2);
+ Fe.mul(x2, x2, z2);
+ Fe.toBytes(out, x2);
+
+ x1.secureZero();
+ x2.secureZero();
+ x3.secureZero();
+ t0.secureZero();
+ t1.secureZero();
+ z2.secureZero();
+ z3.secureZero();
+ std.mem.secureZero(u8, e[0..]);
+
+ // Returns false if the output is all zero
+ // (happens with some malicious public keys)
+ return !zerocmp(u8, out);
+ }
+
+ pub fn createPublicKey(public_key: []u8, private_key: []const u8) bool {
+ var base_point = [_]u8{9} ++ [_]u8{0} ** 31;
+ return create(public_key, private_key, base_point);
+ }
+};
+
+// Constant time compare to zero.
+fn zerocmp(comptime T: type, a: []const T) bool {
+ var s: T = 0;
+ for (a) |b| {
+ s |= b;
+ }
+ return s == 0;
+}
+
+////////////////////////////////////
+/// Arithmetic modulo 2^255 - 19 ///
+////////////////////////////////////
+// Taken from Supercop's ref10 implementation.
+// A bit bigger than TweetNaCl, over 4 times faster.
+
+// field element
+const Fe = struct {
+ b: [10]i32,
+
+ fn secureZero(self: *Fe) void {
+ std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Fe)]);
+ }
+
+ fn init0(h: *Fe) void {
+ for (h.b) |*e| {
+ e.* = 0;
+ }
+ }
+
+ fn init1(h: *Fe) void {
+ for (h.b[1..]) |*e| {
+ e.* = 0;
+ }
+ h.b[0] = 1;
+ }
+
+ fn copy(h: *Fe, f: *const Fe) void {
+ for (h.b) |_, i| {
+ h.b[i] = f.b[i];
+ }
+ }
+
+ fn neg(h: *Fe, f: *const Fe) void {
+ for (h.b) |_, i| {
+ h.b[i] = -f.b[i];
+ }
+ }
+
+ fn add(h: *Fe, f: *const Fe, g: *const Fe) void {
+ for (h.b) |_, i| {
+ h.b[i] = f.b[i] + g.b[i];
+ }
+ }
+
+ fn sub(h: *Fe, f: *const Fe, g: *const Fe) void {
+ for (h.b) |_, i| {
+ h.b[i] = f.b[i] - g.b[i];
+ }
+ }
+
+ fn cswap(f: *Fe, g: *Fe, b: i32) void {
+ for (f.b) |_, i| {
+ const x = (f.b[i] ^ g.b[i]) & -b;
+ f.b[i] ^= x;
+ g.b[i] ^= x;
+ }
+ }
+
+ fn ccopy(f: *Fe, g: *const Fe, b: i32) void {
+ for (f.b) |_, i| {
+ const x = (f.b[i] ^ g.b[i]) & -b;
+ f.b[i] ^= x;
+ }
+ }
+
+ inline fn carryRound(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int, comptime mult: comptime_int) void {
+ const j = (i + 1) % 10;
+
+ c[i] = (t[i] + (i64(1) << shift)) >> (shift + 1);
+ t[j] += c[i] * mult;
+ t[i] -= c[i] * (i64(1) << (shift + 1));
+ }
+
+ fn carry1(h: *Fe, t: []i64) void {
+ var c: [10]i64 = undefined;
+
+ var sc = c[0..];
+ var st = t[0..];
+
+ carryRound(sc, st, 9, 24, 19);
+ carryRound(sc, st, 1, 24, 1);
+ carryRound(sc, st, 3, 24, 1);
+ carryRound(sc, st, 5, 24, 1);
+ carryRound(sc, st, 7, 24, 1);
+ carryRound(sc, st, 0, 25, 1);
+ carryRound(sc, st, 2, 25, 1);
+ carryRound(sc, st, 4, 25, 1);
+ carryRound(sc, st, 6, 25, 1);
+ carryRound(sc, st, 8, 25, 1);
+
+ for (h.b) |_, i| {
+ h.b[i] = @intCast(i32, t[i]);
+ }
+ }
+
+ fn carry2(h: *Fe, t: []i64) void {
+ var c: [10]i64 = undefined;
+
+ var sc = c[0..];
+ var st = t[0..];
+
+ carryRound(sc, st, 0, 25, 1);
+ carryRound(sc, st, 4, 25, 1);
+ carryRound(sc, st, 1, 24, 1);
+ carryRound(sc, st, 5, 24, 1);
+ carryRound(sc, st, 2, 25, 1);
+ carryRound(sc, st, 6, 25, 1);
+ carryRound(sc, st, 3, 24, 1);
+ carryRound(sc, st, 7, 24, 1);
+ carryRound(sc, st, 4, 25, 1);
+ carryRound(sc, st, 8, 25, 1);
+ carryRound(sc, st, 9, 24, 19);
+ carryRound(sc, st, 0, 25, 1);
+
+ for (h.b) |_, i| {
+ h.b[i] = @intCast(i32, t[i]);
+ }
+ }
+
+ fn fromBytes(h: *Fe, s: []const u8) void {
+ std.debug.assert(s.len >= 32);
+
+ var t: [10]i64 = undefined;
+
+ t[0] = readIntSliceLittle(u32, s[0..4]);
+ t[1] = u32(readIntSliceLittle(u24, s[4..7])) << 6;
+ t[2] = u32(readIntSliceLittle(u24, s[7..10])) << 5;
+ t[3] = u32(readIntSliceLittle(u24, s[10..13])) << 3;
+ t[4] = u32(readIntSliceLittle(u24, s[13..16])) << 2;
+ t[5] = readIntSliceLittle(u32, s[16..20]);
+ t[6] = u32(readIntSliceLittle(u24, s[20..23])) << 7;
+ t[7] = u32(readIntSliceLittle(u24, s[23..26])) << 5;
+ t[8] = u32(readIntSliceLittle(u24, s[26..29])) << 4;
+ t[9] = (u32(readIntSliceLittle(u24, s[29..32])) & 0x7fffff) << 2;
+
+ carry1(h, t[0..]);
+ }
+
+ fn mulSmall(h: *Fe, f: *const Fe, comptime g: comptime_int) void {
+ var t: [10]i64 = undefined;
+
+ for (t[0..]) |_, i| {
+ t[i] = i64(f.b[i]) * g;
+ }
+
+ carry1(h, t[0..]);
+ }
+
+ fn mul(h: *Fe, f1: *const Fe, g1: *const Fe) void {
+ const f = f1.b;
+ const g = g1.b;
+
+ var F: [10]i32 = undefined;
+ var G: [10]i32 = undefined;
+
+ F[1] = f[1] * 2;
+ F[3] = f[3] * 2;
+ F[5] = f[5] * 2;
+ F[7] = f[7] * 2;
+ F[9] = f[9] * 2;
+
+ G[1] = g[1] * 19;
+ G[2] = g[2] * 19;
+ G[3] = g[3] * 19;
+ G[4] = g[4] * 19;
+ G[5] = g[5] * 19;
+ G[6] = g[6] * 19;
+ G[7] = g[7] * 19;
+ G[8] = g[8] * 19;
+ G[9] = g[9] * 19;
+
+ // t's become h
+ var t: [10]i64 = undefined;
+
+ t[0] = f[0] * i64(g[0]) + F[1] * i64(G[9]) + f[2] * i64(G[8]) + F[3] * i64(G[7]) + f[4] * i64(G[6]) + F[5] * i64(G[5]) + f[6] * i64(G[4]) + F[7] * i64(G[3]) + f[8] * i64(G[2]) + F[9] * i64(G[1]);
+ t[1] = f[0] * i64(g[1]) + f[1] * i64(g[0]) + f[2] * i64(G[9]) + f[3] * i64(G[8]) + f[4] * i64(G[7]) + f[5] * i64(G[6]) + f[6] * i64(G[5]) + f[7] * i64(G[4]) + f[8] * i64(G[3]) + f[9] * i64(G[2]);
+ t[2] = f[0] * i64(g[2]) + F[1] * i64(g[1]) + f[2] * i64(g[0]) + F[3] * i64(G[9]) + f[4] * i64(G[8]) + F[5] * i64(G[7]) + f[6] * i64(G[6]) + F[7] * i64(G[5]) + f[8] * i64(G[4]) + F[9] * i64(G[3]);
+ t[3] = f[0] * i64(g[3]) + f[1] * i64(g[2]) + f[2] * i64(g[1]) + f[3] * i64(g[0]) + f[4] * i64(G[9]) + f[5] * i64(G[8]) + f[6] * i64(G[7]) + f[7] * i64(G[6]) + f[8] * i64(G[5]) + f[9] * i64(G[4]);
+ t[4] = f[0] * i64(g[4]) + F[1] * i64(g[3]) + f[2] * i64(g[2]) + F[3] * i64(g[1]) + f[4] * i64(g[0]) + F[5] * i64(G[9]) + f[6] * i64(G[8]) + F[7] * i64(G[7]) + f[8] * i64(G[6]) + F[9] * i64(G[5]);
+ t[5] = f[0] * i64(g[5]) + f[1] * i64(g[4]) + f[2] * i64(g[3]) + f[3] * i64(g[2]) + f[4] * i64(g[1]) + f[5] * i64(g[0]) + f[6] * i64(G[9]) + f[7] * i64(G[8]) + f[8] * i64(G[7]) + f[9] * i64(G[6]);
+ t[6] = f[0] * i64(g[6]) + F[1] * i64(g[5]) + f[2] * i64(g[4]) + F[3] * i64(g[3]) + f[4] * i64(g[2]) + F[5] * i64(g[1]) + f[6] * i64(g[0]) + F[7] * i64(G[9]) + f[8] * i64(G[8]) + F[9] * i64(G[7]);
+ t[7] = f[0] * i64(g[7]) + f[1] * i64(g[6]) + f[2] * i64(g[5]) + f[3] * i64(g[4]) + f[4] * i64(g[3]) + f[5] * i64(g[2]) + f[6] * i64(g[1]) + f[7] * i64(g[0]) + f[8] * i64(G[9]) + f[9] * i64(G[8]);
+ t[8] = f[0] * i64(g[8]) + F[1] * i64(g[7]) + f[2] * i64(g[6]) + F[3] * i64(g[5]) + f[4] * i64(g[4]) + F[5] * i64(g[3]) + f[6] * i64(g[2]) + F[7] * i64(g[1]) + f[8] * i64(g[0]) + F[9] * i64(G[9]);
+ t[9] = f[0] * i64(g[9]) + f[1] * i64(g[8]) + f[2] * i64(g[7]) + f[3] * i64(g[6]) + f[4] * i64(g[5]) + f[5] * i64(g[4]) + f[6] * i64(g[3]) + f[7] * i64(g[2]) + f[8] * i64(g[1]) + f[9] * i64(g[0]);
+
+ carry2(h, t[0..]);
+ }
+
+ // we could use Fe.mul() for this, but this is significantly faster
+ fn sq(h: *Fe, fz: *const Fe) void {
+ const f0 = fz.b[0];
+ const f1 = fz.b[1];
+ const f2 = fz.b[2];
+ const f3 = fz.b[3];
+ const f4 = fz.b[4];
+ const f5 = fz.b[5];
+ const f6 = fz.b[6];
+ const f7 = fz.b[7];
+ const f8 = fz.b[8];
+ const f9 = fz.b[9];
+
+ const f0_2 = f0 * 2;
+ const f1_2 = f1 * 2;
+ const f2_2 = f2 * 2;
+ const f3_2 = f3 * 2;
+ const f4_2 = f4 * 2;
+ const f5_2 = f5 * 2;
+ const f6_2 = f6 * 2;
+ const f7_2 = f7 * 2;
+ const f5_38 = f5 * 38;
+ const f6_19 = f6 * 19;
+ const f7_38 = f7 * 38;
+ const f8_19 = f8 * 19;
+ const f9_38 = f9 * 38;
+
+ var t: [10]i64 = undefined;
+
+ t[0] = f0 * i64(f0) + f1_2 * i64(f9_38) + f2_2 * i64(f8_19) + f3_2 * i64(f7_38) + f4_2 * i64(f6_19) + f5 * i64(f5_38);
+ t[1] = f0_2 * i64(f1) + f2 * i64(f9_38) + f3_2 * i64(f8_19) + f4 * i64(f7_38) + f5_2 * i64(f6_19);
+ t[2] = f0_2 * i64(f2) + f1_2 * i64(f1) + f3_2 * i64(f9_38) + f4_2 * i64(f8_19) + f5_2 * i64(f7_38) + f6 * i64(f6_19);
+ t[3] = f0_2 * i64(f3) + f1_2 * i64(f2) + f4 * i64(f9_38) + f5_2 * i64(f8_19) + f6 * i64(f7_38);
+ t[4] = f0_2 * i64(f4) + f1_2 * i64(f3_2) + f2 * i64(f2) + f5_2 * i64(f9_38) + f6_2 * i64(f8_19) + f7 * i64(f7_38);
+ t[5] = f0_2 * i64(f5) + f1_2 * i64(f4) + f2_2 * i64(f3) + f6 * i64(f9_38) + f7_2 * i64(f8_19);
+ t[6] = f0_2 * i64(f6) + f1_2 * i64(f5_2) + f2_2 * i64(f4) + f3_2 * i64(f3) + f7_2 * i64(f9_38) + f8 * i64(f8_19);
+ t[7] = f0_2 * i64(f7) + f1_2 * i64(f6) + f2_2 * i64(f5) + f3_2 * i64(f4) + f8 * i64(f9_38);
+ t[8] = f0_2 * i64(f8) + f1_2 * i64(f7_2) + f2_2 * i64(f6) + f3_2 * i64(f5_2) + f4 * i64(f4) + f9 * i64(f9_38);
+ t[9] = f0_2 * i64(f9) + f1_2 * i64(f8) + f2_2 * i64(f7) + f3_2 * i64(f6) + f4 * i64(f5_2);
+
+ carry2(h, t[0..]);
+ }
+
+ fn sq2(h: *Fe, f: *const Fe) void {
+ Fe.sq(h, f);
+ Fe.mul_small(h, h, 2);
+ }
+
+ // This could be simplified, but it would be slower
+ fn invert(out: *Fe, z: *const Fe) void {
+ var i: usize = undefined;
+
+ var t: [4]Fe = undefined;
+ var t0 = &t[0];
+ var t1 = &t[1];
+ var t2 = &t[2];
+ var t3 = &t[3];
+
+ Fe.sq(t0, z);
+ Fe.sq(t1, t0);
+ Fe.sq(t1, t1);
+ Fe.mul(t1, z, t1);
+ Fe.mul(t0, t0, t1);
+
+ Fe.sq(t2, t0);
+ Fe.mul(t1, t1, t2);
+
+ Fe.sq(t2, t1);
+ i = 1;
+ while (i < 5) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t1, t2, t1);
+
+ Fe.sq(t2, t1);
+ i = 1;
+ while (i < 10) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t2, t2, t1);
+
+ Fe.sq(t3, t2);
+ i = 1;
+ while (i < 20) : (i += 1) Fe.sq(t3, t3);
+ Fe.mul(t2, t3, t2);
+
+ Fe.sq(t2, t2);
+ i = 1;
+ while (i < 10) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t1, t2, t1);
+
+ Fe.sq(t2, t1);
+ i = 1;
+ while (i < 50) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t2, t2, t1);
+
+ Fe.sq(t3, t2);
+ i = 1;
+ while (i < 100) : (i += 1) Fe.sq(t3, t3);
+ Fe.mul(t2, t3, t2);
+
+ Fe.sq(t2, t2);
+ i = 1;
+ while (i < 50) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t1, t2, t1);
+
+ Fe.sq(t1, t1);
+ i = 1;
+ while (i < 5) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(out, t1, t0);
+
+ t0.secureZero();
+ t1.secureZero();
+ t2.secureZero();
+ t3.secureZero();
+ }
+
+ // This could be simplified, but it would be slower
+ fn pow22523(out: *Fe, z: *const Fe) void {
+ var i: usize = undefined;
+
+ var t: [3]Fe = undefined;
+ var t0 = &t[0];
+ var t1 = &t[1];
+ var t2 = &t[2];
+
+ Fe.sq(t0, z);
+ Fe.sq(t1, t0);
+ Fe.sq(t1, t1);
+ Fe.mul(t1, z, t1);
+ Fe.mul(t0, t0, t1);
+
+ Fe.sq(t0, t0);
+ Fe.mul(t0, t1, t0);
+
+ Fe.sq(t1, t0);
+ i = 1;
+ while (i < 5) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(t0, t1, t0);
+
+ Fe.sq(t1, t0);
+ i = 1;
+ while (i < 10) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(t1, t1, t0);
+
+ Fe.sq(t2, t1);
+ i = 1;
+ while (i < 20) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t1, t2, t1);
+
+ Fe.sq(t1, t1);
+ i = 1;
+ while (i < 10) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(t0, t1, t0);
+
+ Fe.sq(t1, t0);
+ i = 1;
+ while (i < 50) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(t1, t1, t0);
+
+ Fe.sq(t2, t1);
+ i = 1;
+ while (i < 100) : (i += 1) Fe.sq(t2, t2);
+ Fe.mul(t1, t2, t1);
+
+ Fe.sq(t1, t1);
+ i = 1;
+ while (i < 50) : (i += 1) Fe.sq(t1, t1);
+ Fe.mul(t0, t1, t0);
+
+ Fe.sq(t0, t0);
+ i = 1;
+ while (i < 2) : (i += 1) Fe.sq(t0, t0);
+ Fe.mul(out, t0, z);
+
+ t0.secureZero();
+ t1.secureZero();
+ t2.secureZero();
+ }
+
+ inline fn toBytesRound(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int) void {
+ c[i] = t[i] >> shift;
+ if (i + 1 < 10) {
+ t[i + 1] += c[i];
+ }
+ t[i] -= c[i] * (i32(1) << shift);
+ }
+
+ fn toBytes(s: []u8, h: *const Fe) void {
+ std.debug.assert(s.len >= 32);
+
+ var t: [10]i64 = undefined;
+ for (h.b[0..]) |_, i| {
+ t[i] = h.b[i];
+ }
+
+ var q = (19 * t[9] + ((i32(1) << 24))) >> 25;
+ {
+ var i: usize = 0;
+ while (i < 5) : (i += 1) {
+ q += t[2 * i];
+ q >>= 26;
+ q += t[2 * i + 1];
+ q >>= 25;
+ }
+ }
+ t[0] += 19 * q;
+
+ var c: [10]i64 = undefined;
+
+ var st = t[0..];
+ var sc = c[0..];
+
+ toBytesRound(sc, st, 0, 26);
+ toBytesRound(sc, st, 1, 25);
+ toBytesRound(sc, st, 2, 26);
+ toBytesRound(sc, st, 3, 25);
+ toBytesRound(sc, st, 4, 26);
+ toBytesRound(sc, st, 5, 25);
+ toBytesRound(sc, st, 6, 26);
+ toBytesRound(sc, st, 7, 25);
+ toBytesRound(sc, st, 8, 26);
+ toBytesRound(sc, st, 9, 25);
+
+ var ut: [10]u32 = undefined;
+ for (ut[0..]) |_, i| {
+ ut[i] = @bitCast(u32, @intCast(i32, t[i]));
+ }
+
+ // TODO https://github.com/ziglang/zig/issues/863
+ writeIntSliceLittle(u32, s[0..4], (ut[0] >> 0) | (ut[1] << 26));
+ writeIntSliceLittle(u32, s[4..8], (ut[1] >> 6) | (ut[2] << 19));
+ writeIntSliceLittle(u32, s[8..12], (ut[2] >> 13) | (ut[3] << 13));
+ writeIntSliceLittle(u32, s[12..16], (ut[3] >> 19) | (ut[4] << 6));
+ writeIntSliceLittle(u32, s[16..20], (ut[5] >> 0) | (ut[6] << 25));
+ writeIntSliceLittle(u32, s[20..24], (ut[6] >> 7) | (ut[7] << 19));
+ writeIntSliceLittle(u32, s[24..28], (ut[7] >> 13) | (ut[8] << 12));
+ writeIntSliceLittle(u32, s[28..], (ut[8] >> 20) | (ut[9] << 6));
+
+ std.mem.secureZero(i64, t[0..]);
+ }
+
+ // Parity check. Returns 0 if even, 1 if odd
+ fn isNegative(f: *const Fe) bool {
+ var s: [32]u8 = undefined;
+ Fe.toBytes(s[0..], f);
+ const isneg = s[0] & 1;
+ s.secureZero();
+ return isneg;
+ }
+
+ fn isNonZero(f: *const Fe) bool {
+ var s: [32]u8 = undefined;
+ Fe.toBytes(s[0..], f);
+ const isnonzero = zerocmp(u8, s[0..]);
+ s.secureZero();
+ return isneg;
+ }
+};
+
+test "x25519 public key calculation from secret key" {
+ var sk: [32]u8 = undefined;
+ var pk_expected: [32]u8 = undefined;
+ var pk_calculated: [32]u8 = undefined;
+ try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
+ try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
+ std.testing.expect(X25519.createPublicKey(pk_calculated[0..], sk));
+ std.testing.expect(std.mem.eql(u8, pk_calculated, pk_expected));
+}
+
+test "x25519 rfc7748 vector1" {
+ const secret_key = "\xa5\x46\xe3\x6b\xf0\x52\x7c\x9d\x3b\x16\x15\x4b\x82\x46\x5e\xdd\x62\x14\x4c\x0a\xc1\xfc\x5a\x18\x50\x6a\x22\x44\xba\x44\x9a\xc4";
+ const public_key = "\xe6\xdb\x68\x67\x58\x30\x30\xdb\x35\x94\xc1\xa4\x24\xb1\x5f\x7c\x72\x66\x24\xec\x26\xb3\x35\x3b\x10\xa9\x03\xa6\xd0\xab\x1c\x4c";
+
+ const expected_output = "\xc3\xda\x55\x37\x9d\xe9\xc6\x90\x8e\x94\xea\x4d\xf2\x8d\x08\x4f\x32\xec\xcf\x03\x49\x1c\x71\xf7\x54\xb4\x07\x55\x77\xa2\x85\x52";
+
+ var output: [32]u8 = undefined;
+
+ std.testing.expect(X25519.create(output[0..], secret_key, public_key));
+ std.testing.expect(std.mem.eql(u8, output, expected_output));
+}
+
+test "x25519 rfc7748 vector2" {
+ const secret_key = "\x4b\x66\xe9\xd4\xd1\xb4\x67\x3c\x5a\xd2\x26\x91\x95\x7d\x6a\xf5\xc1\x1b\x64\x21\xe0\xea\x01\xd4\x2c\xa4\x16\x9e\x79\x18\xba\x0d";
+ const public_key = "\xe5\x21\x0f\x12\x78\x68\x11\xd3\xf4\xb7\x95\x9d\x05\x38\xae\x2c\x31\xdb\xe7\x10\x6f\xc0\x3c\x3e\xfc\x4c\xd5\x49\xc7\x15\xa4\x93";
+
+ const expected_output = "\x95\xcb\xde\x94\x76\xe8\x90\x7d\x7a\xad\xe4\x5c\xb4\xb8\x73\xf8\x8b\x59\x5a\x68\x79\x9f\xa1\x52\xe6\xf8\xf7\x64\x7a\xac\x79\x57";
+
+ var output: [32]u8 = undefined;
+
+ std.testing.expect(X25519.create(output[0..], secret_key, public_key));
+ std.testing.expect(std.mem.eql(u8, output, expected_output));
+}
+
+test "x25519 rfc7748 one iteration" {
+ const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+ const expected_output = "\x42\x2c\x8e\x7a\x62\x27\xd7\xbc\xa1\x35\x0b\x3e\x2b\xb7\x27\x9f\x78\x97\xb8\x7b\xb6\x85\x4b\x78\x3c\x60\xe8\x03\x11\xae\x30\x79";
+
+ var k: [32]u8 = initial_value;
+ var u: [32]u8 = initial_value;
+
+ var i: usize = 0;
+ while (i < 1) : (i += 1) {
+ var output: [32]u8 = undefined;
+ std.testing.expect(X25519.create(output[0..], k, u));
+
+ std.mem.copy(u8, u[0..], k[0..]);
+ std.mem.copy(u8, k[0..], output[0..]);
+ }
+
+ std.testing.expect(std.mem.eql(u8, k[0..], expected_output));
+}
+
+test "x25519 rfc7748 1,000 iterations" {
+ // These iteration tests are slow so we always skip them. Results have been verified.
+ if (true) {
+ return error.SkipZigTest;
+ }
+
+ const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+ const expected_output = "\x68\x4c\xf5\x9b\xa8\x33\x09\x55\x28\x00\xef\x56\x6f\x2f\x4d\x3c\x1c\x38\x87\xc4\x93\x60\xe3\x87\x5f\x2e\xb9\x4d\x99\x53\x2c\x51";
+
+ var k: [32]u8 = initial_value;
+ var u: [32]u8 = initial_value;
+
+ var i: usize = 0;
+ while (i < 1000) : (i += 1) {
+ var output: [32]u8 = undefined;
+ std.testing.expect(X25519.create(output[0..], k, u));
+
+ std.mem.copy(u8, u[0..], k[0..]);
+ std.mem.copy(u8, k[0..], output[0..]);
+ }
+
+ std.testing.expect(std.mem.eql(u8, k[0..], expected_output));
+}
+
+test "x25519 rfc7748 1,000,000 iterations" {
+ if (true) {
+ return error.SkipZigTest;
+ }
+
+ const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+ const expected_output = "\x7c\x39\x11\xe0\xab\x25\x86\xfd\x86\x44\x97\x29\x7e\x57\x5e\x6f\x3b\xc6\x01\xc0\x88\x3c\x30\xdf\x5f\x4d\xd2\xd2\x4f\x66\x54\x24";
+
+ var k: [32]u8 = initial_value;
+ var u: [32]u8 = initial_value;
+
+ var i: usize = 0;
+ while (i < 1000000) : (i += 1) {
+ var output: [32]u8 = undefined;
+ std.testing.expect(X25519.create(output[0..], k, u));
+
+ std.mem.copy(u8, u[0..], k[0..]);
+ std.mem.copy(u8, k[0..], output[0..]);
+ }
+
+ std.testing.expect(std.mem.eql(u8, k[0..], expected_output));
+}