Commit a7527389cc

Marc Tiehuis <marctiehuis@gmail.com>
2018-08-31 08:40:09
Make poly1305 and x25519 more idiomatic zig
This also adjusts the current hash/hmac functions to have a consistent interface allowing easier switching/testing.
1 parent 65b89f5
std/crypto/blake2.zig
@@ -34,8 +34,8 @@ pub const Blake2s256 = Blake2s(256);
 fn Blake2s(comptime out_len: usize) type {
     return struct {
         const Self = this;
-        const block_size = 64;
-        const digest_size = out_len / 8;
+        const block_length = 64;
+        const digest_length = out_len / 8;
 
         const iv = [8]u32{
             0x6A09E667,
@@ -250,8 +250,8 @@ test "blake2s256 streaming" {
 }
 
 test "blake2s256 aligned final" {
-    var block = []u8{0} ** Blake2s256.block_size;
-    var out: [Blake2s256.digest_size]u8 = undefined;
+    var block = []u8{0} ** Blake2s256.block_length;
+    var out: [Blake2s256.digest_length]u8 = undefined;
 
     var h = Blake2s256.init();
     h.update(block);
@@ -267,8 +267,8 @@ pub const Blake2b512 = Blake2b(512);
 fn Blake2b(comptime out_len: usize) type {
     return struct {
         const Self = this;
-        const block_size = 128;
-        const digest_size = out_len / 8;
+        const block_length = 128;
+        const digest_length = out_len / 8;
 
         const iv = [8]u64{
             0x6a09e667f3bcc908,
@@ -483,8 +483,8 @@ test "blake2b512 streaming" {
 }
 
 test "blake2b512 aligned final" {
-    var block = []u8{0} ** Blake2b512.block_size;
-    var out: [Blake2b512.digest_size]u8 = undefined;
+    var block = []u8{0} ** Blake2b512.block_length;
+    var out: [Blake2b512.digest_length]u8 = undefined;
 
     var h = Blake2b512.init();
     h.update(block);
std/crypto/hmac.zig
@@ -7,46 +7,63 @@ pub const HmacMd5 = Hmac(crypto.Md5);
 pub const HmacSha1 = Hmac(crypto.Sha1);
 pub const HmacSha256 = Hmac(crypto.Sha256);
 
-pub fn Hmac(comptime H: type) type {
+pub fn Hmac(comptime Hash: type) type {
     return struct {
-        const digest_size = H.digest_size;
+        const Self = this;
+        pub const mac_length = Hash.digest_length;
+        pub const minimum_key_length = 0;
 
-        pub fn hash(output: []u8, key: []const u8, message: []const u8) void {
-            debug.assert(output.len >= H.digest_size);
-            debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption
-            var scratch: [H.block_size]u8 = undefined;
+        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 > H.block_size) {
-                H.hash(key, scratch[0..H.digest_size]);
-                mem.set(u8, scratch[H.digest_size..H.block_size], 0);
-            } else if (key.len < H.block_size) {
-                mem.copy(u8, scratch[0..key.len], key);
-                mem.set(u8, scratch[key.len..H.block_size], 0);
+            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, scratch[0..], key);
+                mem.copy(u8, ctx.scratch[0..], key);
             }
 
-            var o_key_pad: [H.block_size]u8 = undefined;
-            for (o_key_pad) |*b, i| {
-                b.* = scratch[i] ^ 0x5c;
+            for (ctx.o_key_pad) |*b, i| {
+                b.* = ctx.scratch[i] ^ 0x5c;
             }
 
-            var i_key_pad: [H.block_size]u8 = undefined;
-            for (i_key_pad) |*b, i| {
-                b.* = scratch[i] ^ 0x36;
+            for (ctx.i_key_pad) |*b, i| {
+                b.* = ctx.scratch[i] ^ 0x36;
             }
 
-            // HMAC(k, m) = H(o_key_pad | H(i_key_pad | message)) where | is concatenation
-            var hmac = H.init();
-            hmac.update(i_key_pad[0..]);
-            hmac.update(message);
-            hmac.final(scratch[0..H.digest_size]);
+            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);
 
-            hmac.reset();
-            hmac.update(o_key_pad[0..]);
-            hmac.update(scratch[0..H.digest_size]);
-            hmac.final(output[0..H.digest_size]);
+            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]);
         }
     };
 }
@@ -54,28 +71,28 @@ pub fn Hmac(comptime H: type) type {
 const htest = @import("test.zig");
 
 test "hmac md5" {
-    var out: [crypto.Md5.digest_size]u8 = undefined;
-    HmacMd5.hash(out[0..], "", "");
+    var out: [HmacMd5.mac_length]u8 = undefined;
+    HmacMd5.create(out[0..], "", "");
     htest.assertEqual("74e6f7298a9c2d168935f58c001bad88", out[0..]);
 
-    HmacMd5.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog");
+    HmacMd5.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
     htest.assertEqual("80070713463e7749b90c2dc24911e275", out[0..]);
 }
 
 test "hmac sha1" {
-    var out: [crypto.Sha1.digest_size]u8 = undefined;
-    HmacSha1.hash(out[0..], "", "");
+    var out: [HmacSha1.mac_length]u8 = undefined;
+    HmacSha1.create(out[0..], "", "");
     htest.assertEqual("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", out[0..]);
 
-    HmacSha1.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog");
+    HmacSha1.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
     htest.assertEqual("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", out[0..]);
 }
 
 test "hmac sha256" {
-    var out: [crypto.Sha256.digest_size]u8 = undefined;
-    HmacSha256.hash(out[0..], "", "");
+    var out: [HmacSha256.mac_length]u8 = undefined;
+    HmacSha256.create(out[0..], "", "");
     htest.assertEqual("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", out[0..]);
 
-    HmacSha256.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog");
+    HmacSha256.create(out[0..], "The quick brown fox jumps over the lazy dog", "key");
     htest.assertEqual("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", out[0..]);
 }
std/crypto/index.zig
@@ -21,15 +21,15 @@ pub const Blake2b512 = blake2.Blake2b512;
 
 const hmac = @import("hmac.zig");
 pub const HmacMd5 = hmac.HmacMd5;
-pub const HmacSha1 = hmac.Sha1;
-pub const HmacSha256 = hmac.Sha256;
+pub const HmacSha1 = hmac.HmacSha1;
+pub const HmacSha256 = hmac.HmacSha256;
 
 const import_chaCha20 = @import("chacha20.zig");
 pub const chaCha20IETF = import_chaCha20.chaCha20IETF;
 pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce;
 
-const poly1305 = @import("poly1305.zig");
-const x25519 = @import("x25519.zig");
+pub const Poly1305 = @import("poly1305.zig").Poly1305;
+pub const X25519 = @import("x25519.zig").X25519;
 
 test "crypto" {
     _ = @import("md5.zig");
std/crypto/md5.zig
@@ -29,8 +29,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, k: usize, s: u32, t: u32) RoundPar
 
 pub const Md5 = struct {
     const Self = this;
-    const block_size = 64;
-    const digest_size = 16;
+    const block_length = 64;
+    const digest_length = 16;
 
     s: [4]u32,
     // Streaming Cache
@@ -271,8 +271,8 @@ test "md5 streaming" {
 }
 
 test "md5 aligned final" {
-    var block = []u8{0} ** Md5.block_size;
-    var out: [Md5.digest_size]u8 = undefined;
+    var block = []u8{0} ** Md5.block_length;
+    var out: [Md5.digest_length]u8 = undefined;
 
     var h = Md5.init();
     h.update(block);
std/crypto/poly1305.zig
@@ -9,7 +9,12 @@ const Endian = builtin.Endian;
 const readInt = std.mem.readInt;
 const writeInt = std.mem.writeInt;
 
-const crypto_poly1305_ctx = struct {
+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
@@ -21,190 +26,198 @@ const crypto_poly1305_ctx = struct {
     // How many bytes are there in the chunk.
     c_idx: usize,
 
-    fn secure_zero(self: *crypto_poly1305_ctx) void {
-        std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(crypto_poly1305_ctx)]);
+    fn secure_zero(self: *Poly1305) void {
+        std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Poly1305)]);
     }
-};
 
-// 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 poly_block(ctx: *crypto_poly1305_ctx) 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 poly_clear_c(ctx: *crypto_poly1305_ctx) void {
-    ctx.c[0] = 0;
-    ctx.c[1] = 0;
-    ctx.c[2] = 0;
-    ctx.c[3] = 0;
-    ctx.c_idx = 0;
-}
+    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);
 
-fn poly_take_input(ctx: *crypto_poly1305_ctx, 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 poly_update(ctx: *crypto_poly1305_ctx, message: []const u8) void {
-    for (message) |b| {
-        poly_take_input(ctx, b);
-        if (ctx.c_idx == 16) {
-            poly_block(ctx);
-            poly_clear_c(ctx);
-        }
+        var ctx = Poly1305.init(key);
+        ctx.update(msg);
+        ctx.final(out);
     }
-}
 
-pub fn crypto_poly1305_init(ctx: *crypto_poly1305_ctx, key: [32]u8) void {
-    // Initial hash is zero
-    {
-        var i: usize = 0;
-        while (i < 5) : (i += 1) {
-            ctx.h[i] = 0;
+    // 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;
-    poly_clear_c(ctx);
-
-    // load r and pad (r has some of its bits cleared)
-    {
-        var i: usize = 0;
-        while (i < 1) : (i += 1) {
-            ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff;
+        // add 2^130 to every input block
+        ctx.c[4] = 1;
+        poly_clear_c(&ctx);
+
+        // load r and pad (r has some of its bits cleared)
+        {
+            var i: usize = 0;
+            while (i < 1) : (i += 1) {
+                ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff;
+            }
         }
-    }
-    {
-        var i: usize = 1;
-        while (i < 4) : (i += 1) {
-            ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc;
+        {
+            var i: usize = 1;
+            while (i < 4) : (i += 1) {
+                ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc;
+            }
         }
-    }
-    {
-        var i: usize = 0;
-        while (i < 4) : (i += 1) {
-            ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little);
+        {
+            var i: usize = 0;
+            while (i < 4) : (i += 1) {
+                ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little);
+            }
         }
+
+        return ctx;
     }
-}
 
-inline fn alignto(x: usize, block_size: usize) usize {
-    return ((~x) +% 1) & (block_size - 1);
-}
+    // 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 poly_block(ctx: *Poly1305) 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
+    }
 
-pub fn crypto_poly1305_update(ctx: *crypto_poly1305_ctx, message: []const u8) void {
-    // Align ourselves with block boundaries
-    const alignm = std.math.min(alignto(ctx.c_idx, 16), message.len);
-    poly_update(ctx, message[0..alignm]);
-
-    var nmessage = message[alignm..];
-
-    // Process the message block by block
-    const nb_blocks = nmessage.len >> 4;
-    var i: usize = 0;
-    while (i < nb_blocks) : (i += 1) {
-        ctx.c[0] = readInt(nmessage[0..4], u32, Endian.Little);
-        ctx.c[1] = readInt(nmessage[4..8], u32, Endian.Little);
-        ctx.c[2] = readInt(nmessage[8..12], u32, Endian.Little);
-        ctx.c[3] = readInt(nmessage[12..16], u32, Endian.Little);
-        poly_block(ctx);
-        nmessage = nmessage[16..];
+    // (re-)initializes the input counter and input buffer
+    fn poly_clear_c(ctx: *Poly1305) void {
+        ctx.c[0] = 0;
+        ctx.c[1] = 0;
+        ctx.c[2] = 0;
+        ctx.c[3] = 0;
+        ctx.c_idx = 0;
     }
-    if (nb_blocks > 0) {
-        poly_clear_c(ctx);
+
+    fn poly_take_input(ctx: *Poly1305, 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;
     }
 
-    // remaining bytes
-    poly_update(ctx, nmessage[0..]);
-}
+    fn poly_update(ctx: *Poly1305, msg: []const u8) void {
+        for (msg) |b| {
+            poly_take_input(ctx, b);
+            if (ctx.c_idx == 16) {
+                poly_block(ctx);
+                poly_clear_c(ctx);
+            }
+        }
+    }
 
-pub fn crypto_poly1305_final(ctx: *crypto_poly1305_ctx, mac: []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;
-        poly_take_input(ctx, 1);
-        // one last hash update
-        poly_block(ctx);
+    inline fn alignto(x: usize, block_size: usize) usize {
+        return ((~x) +% 1) & (block_size - 1);
     }
 
-    // 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
-
-    writeInt(mac[0..], uu0, Endian.Little);
-    writeInt(mac[4..], uu1, Endian.Little);
-    writeInt(mac[8..], uu2, Endian.Little);
-    writeInt(mac[12..], uu3, Endian.Little);
-
-    ctx.secure_zero();
-}
+    // 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);
+        poly_update(ctx, msg[0..alignm]);
 
-pub fn crypto_poly1305(mac: []u8, message: []const u8, key: [32]u8) void {
-    std.debug.assert(mac.len >= 16);
+        var nmsg = msg[alignm..];
 
-    var ctx: crypto_poly1305_ctx = undefined;
-    crypto_poly1305_init(&ctx, key);
-    crypto_poly1305_update(&ctx, message);
-    crypto_poly1305_final(&ctx, mac);
-}
+        // 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] = readInt(nmsg[0..4], u32, Endian.Little);
+            ctx.c[1] = readInt(nmsg[4..8], u32, Endian.Little);
+            ctx.c[2] = readInt(nmsg[8..12], u32, Endian.Little);
+            ctx.c[3] = readInt(nmsg[12..16], u32, Endian.Little);
+            poly_block(ctx);
+            nmsg = nmsg[16..];
+        }
+        if (nb_blocks > 0) {
+            poly_clear_c(ctx);
+        }
+
+        // remaining bytes
+        poly_update(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;
+            poly_take_input(ctx, 1);
+            // one last hash update
+            poly_block(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
+
+        writeInt(out[0..], @truncate(u32, uu0), Endian.Little);
+        writeInt(out[4..], @truncate(u32, uu1), Endian.Little);
+        writeInt(out[8..], @truncate(u32, uu2), Endian.Little);
+        writeInt(out[12..], @truncate(u32, uu3), Endian.Little);
+
+        ctx.secure_zero();
+    }
+};
 
 test "poly1305 rfc7439 vector1" {
     const expected_mac = "\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9";
@@ -214,7 +227,7 @@ test "poly1305 rfc7439 vector1" {
         "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b";
 
     var mac: [16]u8 = undefined;
-    crypto_poly1305(mac[0..], msg, key);
+    Poly1305.create(mac[0..], msg, key);
 
     std.debug.assert(std.mem.eql(u8, mac, expected_mac));
 }
std/crypto/sha1.zig
@@ -26,8 +26,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam {
 
 pub const Sha1 = struct {
     const Self = this;
-    const block_size = 64;
-    const digest_size = 20;
+    const block_length = 64;
+    const digest_length = 20;
 
     s: [5]u32,
     // Streaming Cache
@@ -292,8 +292,8 @@ test "sha1 streaming" {
 }
 
 test "sha1 aligned final" {
-    var block = []u8{0} ** Sha1.block_size;
-    var out: [Sha1.digest_size]u8 = undefined;
+    var block = []u8{0} ** Sha1.block_length;
+    var out: [Sha1.digest_length]u8 = undefined;
 
     var h = Sha1.init();
     h.update(block);
std/crypto/sha2.zig
@@ -78,8 +78,8 @@ pub const Sha256 = Sha2_32(Sha256Params);
 fn Sha2_32(comptime params: Sha2Params32) type {
     return struct {
         const Self = this;
-        const block_size = 64;
-        const digest_size = params.out_len / 8;
+        const block_length = 64;
+        const digest_length = params.out_len / 8;
 
         s: [8]u32,
         // Streaming Cache
@@ -338,8 +338,8 @@ test "sha256 streaming" {
 }
 
 test "sha256 aligned final" {
-    var block = []u8{0} ** Sha256.block_size;
-    var out: [Sha256.digest_size]u8 = undefined;
+    var block = []u8{0} ** Sha256.block_length;
+    var out: [Sha256.digest_length]u8 = undefined;
 
     var h = Sha256.init();
     h.update(block);
@@ -419,8 +419,8 @@ pub const Sha512 = Sha2_64(Sha512Params);
 fn Sha2_64(comptime params: Sha2Params64) type {
     return struct {
         const Self = this;
-        const block_size = 128;
-        const digest_size = params.out_len / 8;
+        const block_length = 128;
+        const digest_length = params.out_len / 8;
 
         s: [8]u64,
         // Streaming Cache
@@ -715,8 +715,8 @@ test "sha512 streaming" {
 }
 
 test "sha512 aligned final" {
-    var block = []u8{0} ** Sha512.block_size;
-    var out: [Sha512.digest_size]u8 = undefined;
+    var block = []u8{0} ** Sha512.block_length;
+    var out: [Sha512.digest_length]u8 = undefined;
 
     var h = Sha512.init();
     h.update(block);
std/crypto/sha3.zig
@@ -13,8 +13,8 @@ pub const Sha3_512 = Keccak(512, 0x06);
 fn Keccak(comptime bits: usize, comptime delim: u8) type {
     return struct {
         const Self = this;
-        const block_size = 200;
-        const digest_size = bits / 8;
+        const block_length = 200;
+        const digest_length = bits / 8;
 
         s: [200]u8,
         offset: usize,
@@ -297,8 +297,8 @@ test "sha3-256 streaming" {
 }
 
 test "sha3-256 aligned final" {
-    var block = []u8{0} ** Sha3_256.block_size;
-    var out: [Sha3_256.digest_size]u8 = undefined;
+    var block = []u8{0} ** Sha3_256.block_length;
+    var out: [Sha3_256.digest_length]u8 = undefined;
 
     var h = Sha3_256.init();
     h.update(block);
@@ -368,8 +368,8 @@ test "sha3-512 streaming" {
 }
 
 test "sha3-512 aligned final" {
-    var block = []u8{0} ** Sha3_512.block_size;
-    var out: [Sha3_512.digest_size]u8 = undefined;
+    var block = []u8{0} ** Sha3_512.block_length;
+    var out: [Sha3_512.digest_length]u8 = undefined;
 
     var h = Sha3_512.init();
     h.update(block);
std/crypto/x25519.zig
@@ -9,6 +9,118 @@ const Endian = builtin.Endian;
 const readInt = std.mem.readInt;
 const writeInt = std.mem.writeInt;
 
+// Based on Supercop's ref10 implementation.
+pub const X25519 = struct {
+    pub const secret_length = 32;
+    pub const minimum_key_length = 32;
+
+    fn trim_scalar(s: []u8) void {
+        s[0] &= 248;
+        s[31] &= 127;
+        s[31] |= 64;
+    }
+
+    fn scalar_bit(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];
+        }
+        trim_scalar(e[0..]);
+
+        // computes the actual scalar product (the result is in x2 and z2)
+
+        // Montgomery ladder
+        // In projective coordinates, to avoid divisons: x = X / Z
+        // We don't care about the y coordinate, it's only 1 bit of information
+        fe_1(x2);
+        fe_0(z2); // "zero" point
+        fe_copy(x3, x1);
+        fe_1(z3);
+
+        var swap: i32 = 0;
+        var pos: isize = 254;
+        while (pos >= 0) : (pos -= 1) {
+            // constant time conditional swap before ladder step
+            const b = scalar_bit(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_mul121666(z3, t1);
+            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.secure_zero();
+        x2.secure_zero();
+        x3.secure_zero();
+        t0.secure_zero();
+        t1.secure_zero();
+        z2.secure_zero();
+        z3.secure_zero();
+        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: []const 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;
@@ -144,7 +256,9 @@ fn load24_le(s: []const u8) u32 {
     return s[0] | (u32(s[1]) << 8) | (u32(s[2]) << 16);
 }
 
-fn fe_frombytes(h: *Fe, s: [32]u8) void {
+fn fe_frombytes(h: *Fe, s: []const u8) void {
+    std.debug.assert(s.len >= 32);
+
     var t: [10]i64 = undefined;
 
     t[0] = readInt(s[0..4], u32, Endian.Little);
@@ -469,113 +583,6 @@ fn fe_isnonzero(f: *const Fe) bool {
     return isneg;
 }
 
-///////////////
-/// X-25519 /// Taken from Supercop's ref10 implementation.
-///////////////
-fn trim_scalar(s: []u8) void {
-    s[0] &= 248;
-    s[31] &= 127;
-    s[31] |= 64;
-}
-
-fn scalar_bit(s: []const u8, i: usize) i32 {
-    return (s[i >> 3] >> @intCast(u3, i & 7)) & 1;
-}
-
-pub fn crypto_x25519(raw_shared_secret: []u8, your_secret_key: [32]u8, their_public_key: [32]u8) bool {
-    std.debug.assert(raw_shared_secret.len >= 32);
-
-    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, their_public_key);
-
-    // restrict the possible scalar values
-    var e: [32]u8 = undefined;
-    for (e[0..]) |_, i| {
-        e[i] = your_secret_key[i];
-    }
-    trim_scalar(e[0..]);
-
-    // computes the actual scalar product (the result is in x2 and z2)
-
-    // Montgomery ladder
-    // In projective coordinates, to avoid divisons: x = X / Z
-    // We don't care about the y coordinate, it's only 1 bit of information
-    fe_1(x2);
-    fe_0(z2); // "zero" point
-    fe_copy(x3, x1);
-    fe_1(z3);
-
-    var swap: i32 = 0;
-    var pos: isize = 254;
-    while (pos >= 0) : (pos -= 1) {
-        // constant time conditional swap before ladder step
-        const b = scalar_bit(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_mul121666(z3, t1);
-        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(raw_shared_secret, x2);
-
-    x1.secure_zero();
-    x2.secure_zero();
-    x3.secure_zero();
-    t0.secure_zero();
-    t1.secure_zero();
-    z2.secure_zero();
-    z3.secure_zero();
-    std.mem.secureZero(u8, e[0..]);
-
-    // Returns false if the output is all zero
-    // (happens with some malicious public keys)
-    return !zerocmp(u8, raw_shared_secret);
-}
-
-pub fn crypto_x25519_public_key(public_key: []u8, secret_key: [32]u8) void {
-    var base_point = []u8{9} ++ []u8{0} ** 31;
-    crypto_x25519(public_key, secret_key, base_point);
-}
-
 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";
@@ -584,7 +591,7 @@ test "x25519 rfc7748 vector1" {
 
     var output: [32]u8 = undefined;
 
-    std.debug.assert(crypto_x25519(output[0..], secret_key, public_key));
+    std.debug.assert(X25519.create(output[0..], secret_key, public_key));
     std.debug.assert(std.mem.eql(u8, output, expected_output));
 }
 
@@ -596,7 +603,7 @@ test "x25519 rfc7748 vector2" {
 
     var output: [32]u8 = undefined;
 
-    std.debug.assert(crypto_x25519(output[0..], secret_key, public_key));
+    std.debug.assert(X25519.create(output[0..], secret_key, public_key));
     std.debug.assert(std.mem.eql(u8, output, expected_output));
 }
 
@@ -610,7 +617,7 @@ test "x25519 rfc7748 one iteration" {
     var i: usize = 0;
     while (i < 1) : (i += 1) {
         var output: [32]u8 = undefined;
-        std.debug.assert(crypto_x25519(output[0..], k, u));
+        std.debug.assert(X25519.create(output[0..], k, u));
 
         std.mem.copy(u8, u[0..], k[0..]);
         std.mem.copy(u8, k[0..], output[0..]);
@@ -634,7 +641,7 @@ test "x25519 rfc7748 1,000 iterations" {
     var i: usize = 0;
     while (i < 1000) : (i += 1) {
         var output: [32]u8 = undefined;
-        std.debug.assert(crypto_x25519(output[0..], k, u));
+        std.debug.assert(X25519.create(output[0..], k, u));
 
         std.mem.copy(u8, u[0..], k[0..]);
         std.mem.copy(u8, k[0..], output[0..]);
@@ -657,7 +664,7 @@ test "x25519 rfc7748 1,000,000 iterations" {
     var i: usize = 0;
     while (i < 1000000) : (i += 1) {
         var output: [32]u8 = undefined;
-        std.debug.assert(crypto_x25519(output[0..], k, u));
+        std.debug.assert(X25519.create(output[0..], k, u));
 
         std.mem.copy(u8, u[0..], k[0..]);
         std.mem.copy(u8, k[0..], output[0..]);