Commit 119fc318a7

Frank Denis <github@pureftpd.org>
2021-03-14 22:30:25
std/crypto/chacha20: add round-reduced versions & cleanup internals
See https://eprint.iacr.org/2019/1492.pdf for justification. 8 rounds ChaCha20 provides a 2.5x speedup, and is still believed to be safe. Round-reduced versions are actually deployed (ex: Android filesystem encryption), and thanks to the magic of comptime, it doesn't take much to support them. This also makes the ChaCha20 code more consistent with the Salsa20 code, removing internal functions that were not part of the public API any more. No breaking changes; the public API remains backwards compatible.
1 parent 587243c
Changed files (3)
lib/std/crypto/benchmark.zig
@@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
 const aeads = [_]Crypto{
     Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
     Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
+    Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
     Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
     Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
     Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
lib/std/crypto/chacha20.zig
@@ -15,286 +15,357 @@ const Vector = std.meta.Vector;
 const Poly1305 = std.crypto.onetimeauth.Poly1305;
 const Error = std.crypto.Error;
 
+/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.
+pub const ChaCha20IETF = ChaChaIETF(20);
+
+/// IETF-variant of the ChaCha20 stream cipher, reduced to 12 rounds.
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha12IETF = ChaChaIETF(12);
+
+/// IETF-variant of the ChaCha20 stream cipher, reduced to 8 rounds.
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha8IETF = ChaChaIETF(8);
+
+/// Original ChaCha20 stream cipher.
+pub const ChaCha20With64BitNonce = ChaChaWith64BitNonce(20);
+
+/// Original ChaCha20 stream cipher, reduced to 12 rounds.
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha12With64BitNonce = ChaChaWith64BitNonce(12);
+
+/// Original ChaCha20 stream cipher, reduced to 8 rounds.
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha8With64BitNonce = ChaChaWith64BitNonce(8);
+
+/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher
+pub const XChaCha20IETF = XChaChaIETF(20);
+
+/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 12 rounds
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const XChaCha12IETF = XChaChaIETF(12);
+
+/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 8 rounds
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const XChaCha8IETF = XChaChaIETF(8);
+
+/// ChaCha20-Poly1305 authenticated cipher, as designed for TLS
+pub const ChaCha20Poly1305 = ChaChaPoly1305(20);
+
+/// ChaCha20-Poly1305 authenticated cipher, reduced to 12 rounds
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha12Poly1305 = ChaChaPoly1305(12);
+
+/// ChaCha20-Poly1305 authenticated cipher, reduced to 8 rounds
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const ChaCha8Poly1305 = ChaChaPoly1305(8);
+
+/// XChaCha20-Poly1305 authenticated cipher
+pub const XChaCha20Poly1305 = XChaChaPoly1305(20);
+
+/// XChaCha20-Poly1305 authenticated cipher
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const XChaCha12Poly1305 = XChaChaPoly1305(12);
+
+/// XChaCha20-Poly1305 authenticated cipher
+/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
+/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds.
+pub const XChaCha8Poly1305 = XChaChaPoly1305(8);
+
 // Vectorized implementation of the core function
-const ChaCha20VecImpl = struct {
-    const Lane = Vector(4, u32);
-    const BlockVec = [4]Lane;
-
-    fn initContext(key: [8]u32, d: [4]u32) BlockVec {
-        const c = "expand 32-byte k";
-        const constant_le = comptime Lane{
-            mem.readIntLittle(u32, c[0..4]),
-            mem.readIntLittle(u32, c[4..8]),
-            mem.readIntLittle(u32, c[8..12]),
-            mem.readIntLittle(u32, c[12..16]),
-        };
-        return BlockVec{
-            constant_le,
-            Lane{ key[0], key[1], key[2], key[3] },
-            Lane{ key[4], key[5], key[6], key[7] },
-            Lane{ d[0], d[1], d[2], d[3] },
-        };
-    }
+fn ChaChaVecImpl(comptime rounds_nb: usize) type {
+    return struct {
+        const Lane = Vector(4, u32);
+        const BlockVec = [4]Lane;
+
+        fn initContext(key: [8]u32, d: [4]u32) BlockVec {
+            const c = "expand 32-byte k";
+            const constant_le = comptime Lane{
+                mem.readIntLittle(u32, c[0..4]),
+                mem.readIntLittle(u32, c[4..8]),
+                mem.readIntLittle(u32, c[8..12]),
+                mem.readIntLittle(u32, c[12..16]),
+            };
+            return BlockVec{
+                constant_le,
+                Lane{ key[0], key[1], key[2], key[3] },
+                Lane{ key[4], key[5], key[6], key[7] },
+                Lane{ d[0], d[1], d[2], d[3] },
+            };
+        }
 
-    fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
-        x.* = input;
-
-        var r: usize = 0;
-        while (r < 20) : (r += 2) {
-            x[0] +%= x[1];
-            x[3] ^= x[0];
-            x[3] = math.rotl(Lane, x[3], 16);
-
-            x[2] +%= x[3];
-            x[1] ^= x[2];
-            x[1] = math.rotl(Lane, x[1], 12);
-
-            x[0] +%= x[1];
-            x[3] ^= x[0];
-            x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });
-            x[3] = math.rotl(Lane, x[3], 8);
-
-            x[2] +%= x[3];
-            x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
-            x[1] ^= x[2];
-            x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });
-            x[1] = math.rotl(Lane, x[1], 7);
-
-            x[0] +%= x[1];
-            x[3] ^= x[0];
-            x[3] = math.rotl(Lane, x[3], 16);
-
-            x[2] +%= x[3];
-            x[1] ^= x[2];
-            x[1] = math.rotl(Lane, x[1], 12);
-
-            x[0] +%= x[1];
-            x[3] ^= x[0];
-            x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });
-            x[3] = math.rotl(Lane, x[3], 8);
-
-            x[2] +%= x[3];
-            x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
-            x[1] ^= x[2];
-            x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });
-            x[1] = math.rotl(Lane, x[1], 7);
+        fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
+            x.* = input;
+
+            var r: usize = 0;
+            while (r < rounds_nb) : (r += 2) {
+                x[0] +%= x[1];
+                x[3] ^= x[0];
+                x[3] = math.rotl(Lane, x[3], 16);
+
+                x[2] +%= x[3];
+                x[1] ^= x[2];
+                x[1] = math.rotl(Lane, x[1], 12);
+
+                x[0] +%= x[1];
+                x[3] ^= x[0];
+                x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 });
+                x[3] = math.rotl(Lane, x[3], 8);
+
+                x[2] +%= x[3];
+                x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
+                x[1] ^= x[2];
+                x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 });
+                x[1] = math.rotl(Lane, x[1], 7);
+
+                x[0] +%= x[1];
+                x[3] ^= x[0];
+                x[3] = math.rotl(Lane, x[3], 16);
+
+                x[2] +%= x[3];
+                x[1] ^= x[2];
+                x[1] = math.rotl(Lane, x[1], 12);
+
+                x[0] +%= x[1];
+                x[3] ^= x[0];
+                x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 });
+                x[3] = math.rotl(Lane, x[3], 8);
+
+                x[2] +%= x[3];
+                x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 });
+                x[1] ^= x[2];
+                x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 });
+                x[1] = math.rotl(Lane, x[1], 7);
+            }
         }
-    }
 
-    fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
-        var i: usize = 0;
-        while (i < 4) : (i += 1) {
-            mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);
-            mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
-            mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
-            mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
+        fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
+            var i: usize = 0;
+            while (i < 4) : (i += 1) {
+                mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);
+                mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
+                mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
+                mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
+            }
         }
-    }
 
-    fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
-        x[0] +%= ctx[0];
-        x[1] +%= ctx[1];
-        x[2] +%= ctx[2];
-        x[3] +%= ctx[3];
-    }
+        fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
+            x[0] +%= ctx[0];
+            x[1] +%= ctx[1];
+            x[2] +%= ctx[2];
+            x[3] +%= ctx[3];
+        }
 
-    fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
-        var ctx = initContext(key, counter);
-        var x: BlockVec = undefined;
-        var buf: [64]u8 = undefined;
-        var i: usize = 0;
-        while (i + 64 <= in.len) : (i += 64) {
-            chacha20Core(x[0..], ctx);
-            contextFeedback(&x, ctx);
-            hashToBytes(buf[0..], x);
-
-            var xout = out[i..];
-            const xin = in[i..];
-            var j: usize = 0;
-            while (j < 64) : (j += 1) {
-                xout[j] = xin[j];
-            }
-            j = 0;
-            while (j < 64) : (j += 1) {
-                xout[j] ^= buf[j];
+        fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
+            var ctx = initContext(key, counter);
+            var x: BlockVec = undefined;
+            var buf: [64]u8 = undefined;
+            var i: usize = 0;
+            while (i + 64 <= in.len) : (i += 64) {
+                chacha20Core(x[0..], ctx);
+                contextFeedback(&x, ctx);
+                hashToBytes(buf[0..], x);
+
+                var xout = out[i..];
+                const xin = in[i..];
+                var j: usize = 0;
+                while (j < 64) : (j += 1) {
+                    xout[j] = xin[j];
+                }
+                j = 0;
+                while (j < 64) : (j += 1) {
+                    xout[j] ^= buf[j];
+                }
+                ctx[3][0] += 1;
             }
-            ctx[3][0] += 1;
-        }
-        if (i < in.len) {
-            chacha20Core(x[0..], ctx);
-            contextFeedback(&x, ctx);
-            hashToBytes(buf[0..], x);
-
-            var xout = out[i..];
-            const xin = in[i..];
-            var j: usize = 0;
-            while (j < in.len % 64) : (j += 1) {
-                xout[j] = xin[j] ^ buf[j];
+            if (i < in.len) {
+                chacha20Core(x[0..], ctx);
+                contextFeedback(&x, ctx);
+                hashToBytes(buf[0..], x);
+
+                var xout = out[i..];
+                const xin = in[i..];
+                var j: usize = 0;
+                while (j < in.len % 64) : (j += 1) {
+                    xout[j] = xin[j] ^ buf[j];
+                }
             }
         }
-    }
 
-    fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
-        var c: [4]u32 = undefined;
-        for (c) |_, i| {
-            c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
+        fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
+            var c: [4]u32 = undefined;
+            for (c) |_, i| {
+                c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
+            }
+            const ctx = initContext(keyToWords(key), c);
+            var x: BlockVec = undefined;
+            chacha20Core(x[0..], ctx);
+            var out: [32]u8 = undefined;
+            mem.writeIntLittle(u32, out[0..4], x[0][0]);
+            mem.writeIntLittle(u32, out[4..8], x[0][1]);
+            mem.writeIntLittle(u32, out[8..12], x[0][2]);
+            mem.writeIntLittle(u32, out[12..16], x[0][3]);
+            mem.writeIntLittle(u32, out[16..20], x[3][0]);
+            mem.writeIntLittle(u32, out[20..24], x[3][1]);
+            mem.writeIntLittle(u32, out[24..28], x[3][2]);
+            mem.writeIntLittle(u32, out[28..32], x[3][3]);
+            return out;
         }
-        const ctx = initContext(keyToWords(key), c);
-        var x: BlockVec = undefined;
-        chacha20Core(x[0..], ctx);
-        var out: [32]u8 = undefined;
-        mem.writeIntLittle(u32, out[0..4], x[0][0]);
-        mem.writeIntLittle(u32, out[4..8], x[0][1]);
-        mem.writeIntLittle(u32, out[8..12], x[0][2]);
-        mem.writeIntLittle(u32, out[12..16], x[0][3]);
-        mem.writeIntLittle(u32, out[16..20], x[3][0]);
-        mem.writeIntLittle(u32, out[20..24], x[3][1]);
-        mem.writeIntLittle(u32, out[24..28], x[3][2]);
-        mem.writeIntLittle(u32, out[28..32], x[3][3]);
-        return out;
-    }
-};
+    };
+}
 
 // Non-vectorized implementation of the core function
-const ChaCha20NonVecImpl = struct {
-    const BlockVec = [16]u32;
-
-    fn initContext(key: [8]u32, d: [4]u32) BlockVec {
-        const c = "expand 32-byte k";
-        const constant_le = comptime [4]u32{
-            mem.readIntLittle(u32, c[0..4]),
-            mem.readIntLittle(u32, c[4..8]),
-            mem.readIntLittle(u32, c[8..12]),
-            mem.readIntLittle(u32, c[12..16]),
-        };
-        return BlockVec{
-            constant_le[0], constant_le[1], constant_le[2], constant_le[3],
-            key[0],         key[1],         key[2],         key[3],
-            key[4],         key[5],         key[6],         key[7],
-            d[0],           d[1],           d[2],           d[3],
-        };
-    }
-
-    const QuarterRound = struct {
-        a: usize,
-        b: usize,
-        c: usize,
-        d: usize,
-    };
+fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
+    return struct {
+        const BlockVec = [16]u32;
+
+        fn initContext(key: [8]u32, d: [4]u32) BlockVec {
+            const c = "expand 32-byte k";
+            const constant_le = comptime [4]u32{
+                mem.readIntLittle(u32, c[0..4]),
+                mem.readIntLittle(u32, c[4..8]),
+                mem.readIntLittle(u32, c[8..12]),
+                mem.readIntLittle(u32, c[12..16]),
+            };
+            return BlockVec{
+                constant_le[0], constant_le[1], constant_le[2], constant_le[3],
+                key[0],         key[1],         key[2],         key[3],
+                key[4],         key[5],         key[6],         key[7],
+                d[0],           d[1],           d[2],           d[3],
+            };
+        }
 
-    fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
-        return QuarterRound{
-            .a = a,
-            .b = b,
-            .c = c,
-            .d = d,
+        const QuarterRound = struct {
+            a: usize,
+            b: usize,
+            c: usize,
+            d: usize,
         };
-    }
 
-    fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
-        x.* = input;
-
-        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),
-        };
+        fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
+            return QuarterRound{
+                .a = a,
+                .b = b,
+                .c = c,
+                .d = d,
+            };
+        }
 
-        comptime var j: usize = 0;
-        inline while (j < 20) : (j += 2) {
-            inline for (rounds) |r| {
-                x[r.a] +%= x[r.b];
-                x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
-                x[r.c] +%= x[r.d];
-                x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
-                x[r.a] +%= x[r.b];
-                x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
-                x[r.c] +%= x[r.d];
-                x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
+        fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
+            x.* = input;
+
+            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 < rounds_nb) : (j += 2) {
+                inline for (rounds) |r| {
+                    x[r.a] +%= x[r.b];
+                    x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
+                    x[r.c] +%= x[r.d];
+                    x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
+                    x[r.a] +%= x[r.b];
+                    x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
+                    x[r.c] +%= x[r.d];
+                    x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
+                }
             }
         }
-    }
 
-    fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
-        var i: usize = 0;
-        while (i < 4) : (i += 1) {
-            mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
-            mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
-            mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
-            mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
+        fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
+            var i: usize = 0;
+            while (i < 4) : (i += 1) {
+                mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
+                mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
+                mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
+                mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
+            }
         }
-    }
 
-    fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
-        var i: usize = 0;
-        while (i < 16) : (i += 1) {
-            x[i] +%= ctx[i];
+        fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
+            var i: usize = 0;
+            while (i < 16) : (i += 1) {
+                x[i] +%= ctx[i];
+            }
         }
-    }
 
-    fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
-        var ctx = initContext(key, counter);
-        var x: BlockVec = undefined;
-        var buf: [64]u8 = undefined;
-        var i: usize = 0;
-        while (i + 64 <= in.len) : (i += 64) {
-            chacha20Core(x[0..], ctx);
-            contextFeedback(&x, ctx);
-            hashToBytes(buf[0..], x);
-
-            var xout = out[i..];
-            const xin = in[i..];
-            var j: usize = 0;
-            while (j < 64) : (j += 1) {
-                xout[j] = xin[j];
-            }
-            j = 0;
-            while (j < 64) : (j += 1) {
-                xout[j] ^= buf[j];
+        fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
+            var ctx = initContext(key, counter);
+            var x: BlockVec = undefined;
+            var buf: [64]u8 = undefined;
+            var i: usize = 0;
+            while (i + 64 <= in.len) : (i += 64) {
+                chacha20Core(x[0..], ctx);
+                contextFeedback(&x, ctx);
+                hashToBytes(buf[0..], x);
+
+                var xout = out[i..];
+                const xin = in[i..];
+                var j: usize = 0;
+                while (j < 64) : (j += 1) {
+                    xout[j] = xin[j];
+                }
+                j = 0;
+                while (j < 64) : (j += 1) {
+                    xout[j] ^= buf[j];
+                }
+                ctx[12] += 1;
             }
-            ctx[12] += 1;
-        }
-        if (i < in.len) {
-            chacha20Core(x[0..], ctx);
-            contextFeedback(&x, ctx);
-            hashToBytes(buf[0..], x);
-
-            var xout = out[i..];
-            const xin = in[i..];
-            var j: usize = 0;
-            while (j < in.len % 64) : (j += 1) {
-                xout[j] = xin[j] ^ buf[j];
+            if (i < in.len) {
+                chacha20Core(x[0..], ctx);
+                contextFeedback(&x, ctx);
+                hashToBytes(buf[0..], x);
+
+                var xout = out[i..];
+                const xin = in[i..];
+                var j: usize = 0;
+                while (j < in.len % 64) : (j += 1) {
+                    xout[j] = xin[j] ^ buf[j];
+                }
             }
         }
-    }
 
-    fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
-        var c: [4]u32 = undefined;
-        for (c) |_, i| {
-            c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
+        fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
+            var c: [4]u32 = undefined;
+            for (c) |_, i| {
+                c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
+            }
+            const ctx = initContext(keyToWords(key), c);
+            var x: BlockVec = undefined;
+            chacha20Core(x[0..], ctx);
+            var out: [32]u8 = undefined;
+            mem.writeIntLittle(u32, out[0..4], x[0]);
+            mem.writeIntLittle(u32, out[4..8], x[1]);
+            mem.writeIntLittle(u32, out[8..12], x[2]);
+            mem.writeIntLittle(u32, out[12..16], x[3]);
+            mem.writeIntLittle(u32, out[16..20], x[12]);
+            mem.writeIntLittle(u32, out[20..24], x[13]);
+            mem.writeIntLittle(u32, out[24..28], x[14]);
+            mem.writeIntLittle(u32, out[28..32], x[15]);
+            return out;
         }
-        const ctx = initContext(keyToWords(key), c);
-        var x: BlockVec = undefined;
-        chacha20Core(x[0..], ctx);
-        var out: [32]u8 = undefined;
-        mem.writeIntLittle(u32, out[0..4], x[0]);
-        mem.writeIntLittle(u32, out[4..8], x[1]);
-        mem.writeIntLittle(u32, out[8..12], x[2]);
-        mem.writeIntLittle(u32, out[12..16], x[3]);
-        mem.writeIntLittle(u32, out[16..20], x[12]);
-        mem.writeIntLittle(u32, out[20..24], x[13]);
-        mem.writeIntLittle(u32, out[24..28], x[14]);
-        mem.writeIntLittle(u32, out[28..32], x[15]);
-        return out;
-    }
-};
+    };
+}
 
-const ChaCha20Impl = if (std.Target.current.cpu.arch == .x86_64) ChaCha20VecImpl else ChaCha20NonVecImpl;
+fn ChaChaImpl(comptime rounds_nb: usize) type {
+    return if (std.Target.current.cpu.arch == .x86_64) ChaChaVecImpl(rounds_nb) else ChaChaNonVecImpl(rounds_nb);
+}
 
 fn keyToWords(key: [32]u8) [8]u32 {
     var k: [8]u32 = undefined;
@@ -305,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 {
     return k;
 }
 
-/// 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 const ChaCha20IETF = struct {
-    pub fn xor(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 c: [4]u32 = undefined;
-        c[0] = counter;
-        c[1] = mem.readIntLittle(u32, nonce[0..4]);
-        c[2] = mem.readIntLittle(u32, nonce[4..8]);
-        c[3] = mem.readIntLittle(u32, nonce[8..12]);
-        ChaCha20Impl.chacha20Xor(out, in, keyToWords(key), 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 const ChaCha20With64BitNonce = struct {
-    pub fn xor(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;
-        const k = keyToWords(key);
-        var c: [4]u32 = undefined;
-        c[0] = @truncate(u32, counter);
-        c[1] = @truncate(u32, counter >> 32);
-        c[2] = mem.readIntLittle(u32, nonce[0..4]);
-        c[3] = mem.readIntLittle(u32, nonce[4..8]);
-
-        const block_length = (1 << 6);
-        // The full block size is greater than the address space on a 32bit machine
-        const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
-
-        // first partial big block
-        if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
-            ChaCha20Impl.chacha20Xor(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) {
-                    ChaCha20Impl.chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
-                    c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.
-                    cursor += big_block;
+fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } {
+    var subnonce: [12]u8 = undefined;
+    mem.set(u8, subnonce[0..4], 0);
+    mem.copy(u8, subnonce[4..], nonce[16..24]);
+    return .{
+        .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key),
+        .nonce = subnonce,
+    };
+}
+
+fn ChaChaIETF(comptime rounds_nb: usize) type {
+    return struct {
+        /// Nonce length in bytes.
+        pub const nonce_length = 12;
+        /// Key length in bytes.
+        pub const key_length = 32;
+
+        /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
+        /// WARNING: This function doesn't provide authenticated encryption.
+        /// Using the AEAD or one of the `box` versions is usually preferred.
+        pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
+            assert(in.len == out.len);
+            assert(in.len / 64 <= (1 << 32 - 1) - counter);
+
+            var d: [4]u32 = undefined;
+            d[0] = counter;
+            d[1] = mem.readIntLittle(u32, nonce[0..4]);
+            d[2] = mem.readIntLittle(u32, nonce[4..8]);
+            d[3] = mem.readIntLittle(u32, nonce[8..12]);
+            ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
+        }
+    };
+}
+
+fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
+    return struct {
+        /// Nonce length in bytes.
+        pub const nonce_length = 8;
+        /// Key length in bytes.
+        pub const key_length = 32;
+
+        /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
+        /// WARNING: This function doesn't provide authenticated encryption.
+        /// Using the AEAD or one of the `box` versions is usually preferred.
+        pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
+            assert(in.len == out.len);
+            assert(in.len / 64 <= (1 << 64 - 1) - counter);
+
+            var cursor: usize = 0;
+            const k = keyToWords(key);
+            var c: [4]u32 = undefined;
+            c[0] = @truncate(u32, counter);
+            c[1] = @truncate(u32, counter >> 32);
+            c[2] = mem.readIntLittle(u32, nonce[0..4]);
+            c[3] = mem.readIntLittle(u32, nonce[4..8]);
+
+            const block_length = (1 << 6);
+            // The full block size is greater than the address space on a 32bit machine
+            const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
+
+            // first partial big block
+            if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
+                ChaChaImpl(rounds_nb).chacha20Xor(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) {
+                        ChaChaImpl(rounds_nb).chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
+                        c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.
+                        cursor += big_block;
+                    }
                 }
             }
+            ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c);
+        }
+    };
+}
+
+fn XChaChaIETF(comptime rounds_nb: usize) type {
+    return struct {
+        /// Nonce length in bytes.
+        pub const nonce_length = 24;
+        /// Key length in bytes.
+        pub const key_length = 32;
+
+        /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`.
+        /// WARNING: This function doesn't provide authenticated encryption.
+        /// Using the AEAD or one of the `box` versions is usually preferred.
+        pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
+            const extended = extend(key, nonce, rounds_nb);
+            ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce);
+        }
+    };
+}
+
+fn ChaChaPoly1305(comptime rounds_nb: usize) type {
+    return struct {
+        pub const tag_length = 16;
+        pub const nonce_length = 12;
+        pub const key_length = 32;
+
+        /// c: ciphertext: output buffer should be of size m.len
+        /// tag: authentication tag: output MAC
+        /// m: message
+        /// ad: Associated Data
+        /// npub: public nonce
+        /// k: private key
+        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
+            assert(c.len == m.len);
+
+            var polyKey = [_]u8{0} ** 32;
+            ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
+
+            ChaChaIETF(rounds_nb).xor(c[0..m.len], m, 1, k, npub);
+
+            var mac = Poly1305.init(polyKey[0..]);
+            mac.update(ad);
+            if (ad.len % 16 != 0) {
+                const zeros = [_]u8{0} ** 16;
+                const padding = 16 - (ad.len % 16);
+                mac.update(zeros[0..padding]);
+            }
+            mac.update(c[0..m.len]);
+            if (m.len % 16 != 0) {
+                const zeros = [_]u8{0} ** 16;
+                const padding = 16 - (m.len % 16);
+                mac.update(zeros[0..padding]);
+            }
+            var lens: [16]u8 = undefined;
+            mem.writeIntLittle(u64, lens[0..8], ad.len);
+            mem.writeIntLittle(u64, lens[8..16], m.len);
+            mac.update(lens[0..]);
+            mac.final(tag);
+        }
+
+        /// m: message: output buffer should be of size c.len
+        /// c: ciphertext
+        /// tag: authentication tag
+        /// ad: Associated Data
+        /// npub: public nonce
+        /// k: private key
+        /// NOTE: the check of the authentication tag is currently not done in constant time
+        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
+            assert(c.len == m.len);
+
+            var polyKey = [_]u8{0} ** 32;
+            ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
+
+            var mac = Poly1305.init(polyKey[0..]);
+
+            mac.update(ad);
+            if (ad.len % 16 != 0) {
+                const zeros = [_]u8{0} ** 16;
+                const padding = 16 - (ad.len % 16);
+                mac.update(zeros[0..padding]);
+            }
+            mac.update(c);
+            if (c.len % 16 != 0) {
+                const zeros = [_]u8{0} ** 16;
+                const padding = 16 - (c.len % 16);
+                mac.update(zeros[0..padding]);
+            }
+            var lens: [16]u8 = undefined;
+            mem.writeIntLittle(u64, lens[0..8], ad.len);
+            mem.writeIntLittle(u64, lens[8..16], c.len);
+            mac.update(lens[0..]);
+            var computedTag: [16]u8 = undefined;
+            mac.final(computedTag[0..]);
+
+            var acc: u8 = 0;
+            for (computedTag) |_, i| {
+                acc |= computedTag[i] ^ tag[i];
+            }
+            if (acc != 0) {
+                return error.AuthenticationFailed;
+            }
+            ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);
+        }
+    };
+}
+
+fn XChaChaPoly1305(comptime rounds_nb: usize) type {
+    return struct {
+        pub const tag_length = 16;
+        pub const nonce_length = 24;
+        pub const key_length = 32;
+
+        /// c: ciphertext: output buffer should be of size m.len
+        /// tag: authentication tag: output MAC
+        /// m: message
+        /// ad: Associated Data
+        /// npub: public nonce
+        /// k: private key
+        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
+            const extended = extend(k, npub, rounds_nb);
+            return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);
         }
 
-        ChaCha20Impl.chacha20Xor(out[cursor..], in[cursor..], k, c);
+        /// m: message: output buffer should be of size c.len
+        /// c: ciphertext
+        /// tag: authentication tag
+        /// ad: Associated Data
+        /// npub: public nonce
+        /// k: private key
+        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
+            const extended = extend(k, npub, rounds_nb);
+            return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key);
+        }
+    };
+}
+
+test "chacha20 AEAD API" {
+    const aeads = [_]type{ ChaCha20Poly1305, XChaCha20Poly1305 };
+    const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
+    const ad = "Additional data";
+
+    inline for (aeads) |aead| {
+        const key = [_]u8{69} ** aead.key_length;
+        const nonce = [_]u8{42} ** aead.nonce_length;
+        var c: [m.len]u8 = undefined;
+        var tag: [aead.tag_length]u8 = undefined;
+        var out: [m.len]u8 = undefined;
+
+        aead.encrypt(c[0..], tag[0..], m, ad, nonce, key);
+        try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key);
+        testing.expectEqualSlices(u8, out[0..], m);
+        c[0] += 1;
+        testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key));
     }
-};
+}
 
 // https://tools.ietf.org/html/rfc7539#section-2.4.2
 test "crypto.chacha20 test vector sunscreen" {
@@ -387,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" {
         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.";
+    const m = "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,
@@ -401,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" {
         0, 0, 0, 0,
     };
 
-    ChaCha20IETF.xor(result[0..], input[0..], 1, key, nonce);
+    ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 
-    // Chacha20 is self-reversing.
-    var plaintext: [114]u8 = undefined;
-    ChaCha20IETF.xor(plaintext[0..], result[0..], 1, key, nonce);
-    testing.expect(mem.order(u8, input, &plaintext) == .eq);
+    var m2: [114]u8 = undefined;
+    ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce);
+    testing.expect(mem.order(u8, m, &m2) == .eq);
 }
 
 // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
@@ -422,7 +663,7 @@ test "crypto.chacha20 test vector 1" {
         0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
         0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
     };
-    const input = [_]u8{
+    const m = [_]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,
@@ -441,7 +682,7 @@ test "crypto.chacha20 test vector 1" {
     };
     const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
 
-    ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
+    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 }
 
@@ -456,7 +697,7 @@ test "crypto.chacha20 test vector 2" {
         0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,
         0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,
     };
-    const input = [_]u8{
+    const m = [_]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,
@@ -475,7 +716,7 @@ test "crypto.chacha20 test vector 2" {
     };
     const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
 
-    ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
+    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 }
 
@@ -490,7 +731,7 @@ test "crypto.chacha20 test vector 3" {
         0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,
         0x44, 0x5f, 0x41, 0xe3,
     };
-    const input = [_]u8{
+    const m = [_]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,
@@ -509,7 +750,7 @@ test "crypto.chacha20 test vector 3" {
     };
     const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };
 
-    ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
+    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 }
 
@@ -524,7 +765,7 @@ test "crypto.chacha20 test vector 4" {
         0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,
         0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,
     };
-    const input = [_]u8{
+    const m = [_]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,
@@ -543,7 +784,7 @@ test "crypto.chacha20 test vector 4" {
     };
     const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 };
 
-    ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
+    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 }
 
@@ -585,7 +826,7 @@ test "crypto.chacha20 test vector 5" {
         0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,
         0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,
     };
-    const input = [_]u8{
+    const m = [_]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,
@@ -615,147 +856,14 @@ test "crypto.chacha20 test vector 5" {
         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     };
 
-    ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce);
+    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
     testing.expectEqualSlices(u8, &expected_result, &result);
 }
 
-pub const chacha20poly1305_tag_length = 16;
-
-fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
-    assert(ciphertext.len == plaintext.len);
-
-    // derive poly1305 key
-    var polyKey = [_]u8{0} ** 32;
-    ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce);
-
-    // encrypt plaintext
-    ChaCha20IETF.xor(ciphertext[0..plaintext.len], plaintext, 1, key, nonce);
-
-    // construct mac
-    var mac = Poly1305.init(polyKey[0..]);
-    mac.update(data);
-    if (data.len % 16 != 0) {
-        const zeros = [_]u8{0} ** 16;
-        const padding = 16 - (data.len % 16);
-        mac.update(zeros[0..padding]);
-    }
-    mac.update(ciphertext[0..plaintext.len]);
-    if (plaintext.len % 16 != 0) {
-        const zeros = [_]u8{0} ** 16;
-        const padding = 16 - (plaintext.len % 16);
-        mac.update(zeros[0..padding]);
-    }
-    var lens: [16]u8 = undefined;
-    mem.writeIntLittle(u64, lens[0..8], data.len);
-    mem.writeIntLittle(u64, lens[8..16], plaintext.len);
-    mac.update(lens[0..]);
-    mac.final(tag);
-}
-
-fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void {
-    return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_length], plaintext, data, key, nonce);
-}
-
-/// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached.
-fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void {
-    // split ciphertext and tag
-    assert(dst.len == ciphertext.len);
-
-    // derive poly1305 key
-    var polyKey = [_]u8{0} ** 32;
-    ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce);
-
-    // construct mac
-    var mac = Poly1305.init(polyKey[0..]);
-
-    mac.update(data);
-    if (data.len % 16 != 0) {
-        const zeros = [_]u8{0} ** 16;
-        const padding = 16 - (data.len % 16);
-        mac.update(zeros[0..padding]);
-    }
-    mac.update(ciphertext);
-    if (ciphertext.len % 16 != 0) {
-        const zeros = [_]u8{0} ** 16;
-        const padding = 16 - (ciphertext.len % 16);
-        mac.update(zeros[0..padding]);
-    }
-    var lens: [16]u8 = undefined;
-    mem.writeIntLittle(u64, lens[0..8], data.len);
-    mem.writeIntLittle(u64, lens[8..16], ciphertext.len);
-    mac.update(lens[0..]);
-    var computedTag: [16]u8 = undefined;
-    mac.final(computedTag[0..]);
-
-    // verify mac in constant time
-    // TODO: we can't currently guarantee that this will run in constant time.
-    // See https://github.com/ziglang/zig/issues/1776
-    var acc: u8 = 0;
-    for (computedTag) |_, i| {
-        acc |= computedTag[i] ^ tag[i];
-    }
-    if (acc != 0) {
-        return error.AuthenticationFailed;
-    }
-
-    // decrypt ciphertext
-    ChaCha20IETF.xor(dst[0..ciphertext.len], ciphertext, 1, key, nonce);
-}
-
-/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal.
-fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void {
-    if (ciphertextAndTag.len < chacha20poly1305_tag_length) {
-        return error.AuthenticationFailed;
-    }
-    const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_length;
-    return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_length], data, key, nonce);
-}
-
-fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } {
-    var subnonce: [12]u8 = undefined;
-    mem.set(u8, subnonce[0..4], 0);
-    mem.copy(u8, subnonce[4..], nonce[16..24]);
-    return .{
-        .key = ChaCha20Impl.hchacha20(nonce[0..16].*, key),
-        .nonce = subnonce,
-    };
-}
-
-pub const XChaCha20IETF = struct {
-    pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void {
-        const extended = extend(key, nonce);
-        ChaCha20IETF.xor(out, in, counter, extended.key, extended.nonce);
-    }
-};
-
-pub const xchacha20poly1305_tag_length = 16;
-
-fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
-    const extended = extend(key, nonce);
-    return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce);
-}
-
-fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void {
-    const extended = extend(key, nonce);
-    return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce);
-}
-
-/// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached.
-fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void {
-    const extended = extend(key, nonce);
-    return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce);
-}
-
-/// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal.
-fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void {
-    const extended = extend(key, nonce);
-    return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce);
-}
-
 test "seal" {
     {
-        const plaintext = "";
-        const data = "";
+        const m = "";
+        const ad = "";
         const key = [_]u8{
             0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
             0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
@@ -764,11 +872,11 @@ test "seal" {
         const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
 
         var out: [exp_out.len]u8 = undefined;
-        chacha20poly1305Seal(out[0..], plaintext, data, key, nonce);
+        ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m, ad, nonce, key);
         testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
     }
     {
-        const plaintext = [_]u8{
+        const m = [_]u8{
             0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
             0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
             0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
@@ -778,7 +886,7 @@ test "seal" {
             0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
             0x74, 0x2e,
         };
-        const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
+        const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
         const key = [_]u8{
             0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
             0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
@@ -797,15 +905,15 @@ test "seal" {
         };
 
         var out: [exp_out.len]u8 = undefined;
-        chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce);
+        ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m[0..], ad[0..], nonce, key);
         testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
     }
 }
 
 test "open" {
     {
-        const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
-        const data = "";
+        const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
+        const ad = "";
         const key = [_]u8{
             0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
             0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
@@ -814,11 +922,11 @@ test "open" {
         const exp_out = "";
 
         var out: [exp_out.len]u8 = undefined;
-        try chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
+        try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
         testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
     }
     {
-        const ciphertext = [_]u8{
+        const c = [_]u8{
             0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
             0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8,  0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
             0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
@@ -829,7 +937,7 @@ test "open" {
             0x61, 0x16, 0x1a, 0xe1, 0xb,  0x59, 0x4f, 0x9,  0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
             0x6,  0x91,
         };
-        const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
+        const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
         const key = [_]u8{
             0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
             0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
@@ -847,126 +955,45 @@ test "open" {
         };
 
         var out: [exp_out.len]u8 = undefined;
-        try chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce);
+        try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
         testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
 
         // corrupting the ciphertext, data, key, or nonce should cause a failure
-        var bad_ciphertext = ciphertext;
-        bad_ciphertext[0] ^= 1;
-        testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce));
-        var bad_data = data;
-        bad_data[0] ^= 1;
-        testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce));
+        var bad_c = c;
+        bad_c[0] ^= 1;
+        testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key));
+        var bad_ad = ad;
+        bad_ad[0] ^= 1;
+        testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key));
         var bad_key = key;
         bad_key[0] ^= 1;
-        testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], bad_key, nonce));
+        testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], nonce, bad_key));
         var bad_nonce = nonce;
         bad_nonce[0] ^= 1;
-        testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, bad_nonce));
-
-        // a short ciphertext should result in a different error
-        testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce));
+        testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], bad_nonce, key));
     }
 }
 
 test "crypto.xchacha20" {
     const key = [_]u8{69} ** 32;
     const nonce = [_]u8{42} ** 24;
-    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.";
+    const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
     {
-        var ciphertext: [input.len]u8 = undefined;
-        XChaCha20IETF.xor(ciphertext[0..], input[0..], 0, key, nonce);
-        var buf: [2 * ciphertext.len]u8 = undefined;
-        testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
+        var c: [m.len]u8 = undefined;
+        XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce);
+        var buf: [2 * c.len]u8 = undefined;
+        testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
     }
     {
-        const data = "Additional data";
-        var ciphertext: [input.len + xchacha20poly1305_tag_length]u8 = undefined;
-        xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce);
-        var out: [input.len]u8 = undefined;
-        try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce);
-        var buf: [2 * ciphertext.len]u8 = undefined;
-        testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
-        testing.expectEqualSlices(u8, out[0..], input);
-        ciphertext[0] += 1;
-        testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce));
-    }
-}
-
-pub const Chacha20Poly1305 = struct {
-    pub const tag_length = 16;
-    pub const nonce_length = 12;
-    pub const key_length = 32;
-
-    /// c: ciphertext: output buffer should be of size m.len
-    /// tag: authentication tag: output MAC
-    /// m: message
-    /// ad: Associated Data
-    /// npub: public nonce
-    /// k: private key
-    pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
-        assert(c.len == m.len);
-        return chacha20poly1305SealDetached(c, tag, m, ad, k, npub);
-    }
-
-    /// m: message: output buffer should be of size c.len
-    /// c: ciphertext
-    /// tag: authentication tag
-    /// ad: Associated Data
-    /// npub: public nonce
-    /// k: private key
-    /// NOTE: the check of the authentication tag is currently not done in constant time
-    pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
-        assert(c.len == m.len);
-        return try chacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub);
-    }
-};
-
-pub const XChacha20Poly1305 = struct {
-    pub const tag_length = 16;
-    pub const nonce_length = 24;
-    pub const key_length = 32;
-
-    /// c: ciphertext: output buffer should be of size m.len
-    /// tag: authentication tag: output MAC
-    /// m: message
-    /// ad: Associated Data
-    /// npub: public nonce
-    /// k: private key
-    pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
-        assert(c.len == m.len);
-        return xchacha20poly1305SealDetached(c, tag, m, ad, k, npub);
-    }
-
-    /// m: message: output buffer should be of size c.len
-    /// c: ciphertext
-    /// tag: authentication tag
-    /// ad: Associated Data
-    /// npub: public nonce
-    /// k: private key
-    /// NOTE: the check of the authentication tag is currently not done in constant time
-    pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void {
-        assert(c.len == m.len);
-        return try xchacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub);
-    }
-};
-
-test "chacha20 AEAD API" {
-    const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 };
-    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.";
-    const data = "Additional data";
-
-    inline for (aeads) |aead| {
-        const key = [_]u8{69} ** aead.key_length;
-        const nonce = [_]u8{42} ** aead.nonce_length;
-        var ciphertext: [input.len]u8 = undefined;
-        var tag: [aead.tag_length]u8 = undefined;
-        var out: [input.len]u8 = undefined;
-
-        aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key);
-        try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key);
-        testing.expectEqualSlices(u8, out[0..], input);
-        ciphertext[0] += 1;
-        testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key));
+        const ad = "Additional data";
+        var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined;
+        XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key);
+        var out: [m.len]u8 = undefined;
+        try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key);
+        var buf: [2 * c.len]u8 = undefined;
+        testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
+        testing.expectEqualSlices(u8, out[0..], m);
+        c[0] += 1;
+        testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key));
     }
 }
lib/std/crypto.zig
@@ -24,8 +24,12 @@ pub const aead = struct {
     pub const Gimli = @import("crypto/gimli.zig").Aead;
 
     pub const chacha_poly = struct {
-        pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;
-        pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
+        pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
+        pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
+        pub const ChaCha8Poly1305 = @import("crypto/chacha20.zig").ChaCha8Poly1305;
+        pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChaCha20Poly1305;
+        pub const XChaCha12Poly1305 = @import("crypto/chacha20.zig").XChaCha12Poly1305;
+        pub const XChaCha8Poly1305 = @import("crypto/chacha20.zig").XChaCha8Poly1305;
     };
 
     pub const isap = @import("crypto/isap.zig");
@@ -119,8 +123,14 @@ pub const sign = struct {
 pub const stream = struct {
     pub const chacha = struct {
         pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;
+        pub const ChaCha12IETF = @import("crypto/chacha20.zig").ChaCha12IETF;
+        pub const ChaCha8IETF = @import("crypto/chacha20.zig").ChaCha8IETF;
         pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;
+        pub const ChaCha12With64BitNonce = @import("crypto/chacha20.zig").ChaCha12With64BitNonce;
+        pub const ChaCha8With64BitNonce = @import("crypto/chacha20.zig").ChaCha8With64BitNonce;
         pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;
+        pub const XChaCha12IETF = @import("crypto/chacha20.zig").XChaCha12IETF;
+        pub const XChaCha8IETF = @import("crypto/chacha20.zig").XChaCha8IETF;
     };
 
     pub const salsa = struct {