master
   1// Based on public domain Supercop by Daniel J. Bernstein
   2
   3const std = @import("../std.zig");
   4const builtin = @import("builtin");
   5const crypto = std.crypto;
   6const math = std.math;
   7const mem = std.mem;
   8const assert = std.debug.assert;
   9const testing = std.testing;
  10const maxInt = math.maxInt;
  11const Poly1305 = crypto.onetimeauth.Poly1305;
  12const AuthenticationError = crypto.errors.AuthenticationError;
  13
  14/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.
  15pub const ChaCha20IETF = ChaChaIETF(20);
  16
  17/// IETF-variant of the ChaCha20 stream cipher, reduced to 12 rounds.
  18/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  19/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  20pub const ChaCha12IETF = ChaChaIETF(12);
  21
  22/// IETF-variant of the ChaCha20 stream cipher, reduced to 8 rounds.
  23/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  24/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  25pub const ChaCha8IETF = ChaChaIETF(8);
  26
  27/// Original ChaCha20 stream cipher.
  28pub const ChaCha20With64BitNonce = ChaChaWith64BitNonce(20);
  29
  30/// Original ChaCha20 stream cipher, reduced to 12 rounds.
  31/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  32/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  33pub const ChaCha12With64BitNonce = ChaChaWith64BitNonce(12);
  34
  35/// Original ChaCha20 stream cipher, reduced to 8 rounds.
  36/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  37/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  38pub const ChaCha8With64BitNonce = ChaChaWith64BitNonce(8);
  39
  40/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher
  41pub const XChaCha20IETF = XChaChaIETF(20);
  42
  43/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 12 rounds
  44/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  45/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  46pub const XChaCha12IETF = XChaChaIETF(12);
  47
  48/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 8 rounds
  49/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  50/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  51pub const XChaCha8IETF = XChaChaIETF(8);
  52
  53/// ChaCha20-Poly1305 authenticated cipher, as designed for TLS
  54pub const ChaCha20Poly1305 = ChaChaPoly1305(20);
  55
  56/// ChaCha20-Poly1305 authenticated cipher, reduced to 12 rounds
  57/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  58/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  59pub const ChaCha12Poly1305 = ChaChaPoly1305(12);
  60
  61/// ChaCha20-Poly1305 authenticated cipher, reduced to 8 rounds
  62/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  63/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  64pub const ChaCha8Poly1305 = ChaChaPoly1305(8);
  65
  66/// XChaCha20-Poly1305 authenticated cipher
  67pub const XChaCha20Poly1305 = XChaChaPoly1305(20);
  68
  69/// XChaCha20-Poly1305 authenticated cipher
  70/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  71/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  72pub const XChaCha12Poly1305 = XChaChaPoly1305(12);
  73
  74/// XChaCha20-Poly1305 authenticated cipher
  75/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin.
  76/// However, ChaCha is still believed to have a comfortable security even with only 8 rounds.
  77pub const XChaCha8Poly1305 = XChaChaPoly1305(8);
  78
  79// Vectorized implementation of the core function
  80fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type {
  81    return struct {
  82        const Lane = @Vector(4 * degree, u32);
  83        const BlockVec = [4]Lane;
  84
  85        fn initContext(key: [8]u32, d: [4]u32) BlockVec {
  86            const c = "expand 32-byte k";
  87            switch (degree) {
  88                1 => {
  89                    const constant_le = Lane{
  90                        mem.readInt(u32, c[0..4], .little),
  91                        mem.readInt(u32, c[4..8], .little),
  92                        mem.readInt(u32, c[8..12], .little),
  93                        mem.readInt(u32, c[12..16], .little),
  94                    };
  95                    return BlockVec{
  96                        constant_le,
  97                        Lane{ key[0], key[1], key[2], key[3] },
  98                        Lane{ key[4], key[5], key[6], key[7] },
  99                        Lane{ d[0], d[1], d[2], d[3] },
 100                    };
 101                },
 102                2 => {
 103                    const constant_le = Lane{
 104                        mem.readInt(u32, c[0..4], .little),
 105                        mem.readInt(u32, c[4..8], .little),
 106                        mem.readInt(u32, c[8..12], .little),
 107                        mem.readInt(u32, c[12..16], .little),
 108                        mem.readInt(u32, c[0..4], .little),
 109                        mem.readInt(u32, c[4..8], .little),
 110                        mem.readInt(u32, c[8..12], .little),
 111                        mem.readInt(u32, c[12..16], .little),
 112                    };
 113                    const n1 = @addWithOverflow(d[0], 1);
 114                    return BlockVec{
 115                        constant_le,
 116                        Lane{ key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3] },
 117                        Lane{ key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7] },
 118                        Lane{ d[0], d[1], d[2], d[3], n1[0], d[1] +% n1[1], d[2], d[3] },
 119                    };
 120                },
 121                4 => {
 122                    const n1 = @addWithOverflow(d[0], 1);
 123                    const n2 = @addWithOverflow(d[0], 2);
 124                    const n3 = @addWithOverflow(d[0], 3);
 125                    const constant_le = Lane{
 126                        mem.readInt(u32, c[0..4], .little),
 127                        mem.readInt(u32, c[4..8], .little),
 128                        mem.readInt(u32, c[8..12], .little),
 129                        mem.readInt(u32, c[12..16], .little),
 130                        mem.readInt(u32, c[0..4], .little),
 131                        mem.readInt(u32, c[4..8], .little),
 132                        mem.readInt(u32, c[8..12], .little),
 133                        mem.readInt(u32, c[12..16], .little),
 134                        mem.readInt(u32, c[0..4], .little),
 135                        mem.readInt(u32, c[4..8], .little),
 136                        mem.readInt(u32, c[8..12], .little),
 137                        mem.readInt(u32, c[12..16], .little),
 138                        mem.readInt(u32, c[0..4], .little),
 139                        mem.readInt(u32, c[4..8], .little),
 140                        mem.readInt(u32, c[8..12], .little),
 141                        mem.readInt(u32, c[12..16], .little),
 142                    };
 143                    return BlockVec{
 144                        constant_le,
 145                        Lane{ key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3] },
 146                        Lane{ key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7] },
 147                        Lane{ d[0], d[1], d[2], d[3], n1[0], d[1] +% n1[1], d[2], d[3], n2[0], d[1] +% n2[1], d[2], d[3], n3[0], d[1] +% n3[1], d[2], d[3] },
 148                    };
 149                },
 150                else => @compileError("invalid degree"),
 151            }
 152        }
 153
 154        fn chacha20Core(x: *BlockVec, input: BlockVec) void {
 155            x.* = input;
 156
 157            const m0 = switch (degree) {
 158                1 => [_]i32{ 3, 0, 1, 2 },
 159                2 => [_]i32{ 3, 0, 1, 2 } ++ [_]i32{ 7, 4, 5, 6 },
 160                4 => [_]i32{ 3, 0, 1, 2 } ++ [_]i32{ 7, 4, 5, 6 } ++ [_]i32{ 11, 8, 9, 10 } ++ [_]i32{ 15, 12, 13, 14 },
 161                else => @compileError("invalid degree"),
 162            };
 163            const m1 = switch (degree) {
 164                1 => [_]i32{ 2, 3, 0, 1 },
 165                2 => [_]i32{ 2, 3, 0, 1 } ++ [_]i32{ 6, 7, 4, 5 },
 166                4 => [_]i32{ 2, 3, 0, 1 } ++ [_]i32{ 6, 7, 4, 5 } ++ [_]i32{ 10, 11, 8, 9 } ++ [_]i32{ 14, 15, 12, 13 },
 167                else => @compileError("invalid degree"),
 168            };
 169            const m2 = switch (degree) {
 170                1 => [_]i32{ 1, 2, 3, 0 },
 171                2 => [_]i32{ 1, 2, 3, 0 } ++ [_]i32{ 5, 6, 7, 4 },
 172                4 => [_]i32{ 1, 2, 3, 0 } ++ [_]i32{ 5, 6, 7, 4 } ++ [_]i32{ 9, 10, 11, 8 } ++ [_]i32{ 13, 14, 15, 12 },
 173                else => @compileError("invalid degree"),
 174            };
 175
 176            var r: usize = 0;
 177            while (r < rounds_nb) : (r += 2) {
 178                x[0] +%= x[1];
 179                x[3] ^= x[0];
 180                x[3] = math.rotl(Lane, x[3], 16);
 181
 182                x[2] +%= x[3];
 183                x[1] ^= x[2];
 184                x[1] = math.rotl(Lane, x[1], 12);
 185
 186                x[0] +%= x[1];
 187                x[3] ^= x[0];
 188                x[0] = @shuffle(u32, x[0], undefined, m0);
 189                x[3] = math.rotl(Lane, x[3], 8);
 190
 191                x[2] +%= x[3];
 192                x[3] = @shuffle(u32, x[3], undefined, m1);
 193                x[1] ^= x[2];
 194                x[2] = @shuffle(u32, x[2], undefined, m2);
 195                x[1] = math.rotl(Lane, x[1], 7);
 196
 197                x[0] +%= x[1];
 198                x[3] ^= x[0];
 199                x[3] = math.rotl(Lane, x[3], 16);
 200
 201                x[2] +%= x[3];
 202                x[1] ^= x[2];
 203                x[1] = math.rotl(Lane, x[1], 12);
 204
 205                x[0] +%= x[1];
 206                x[3] ^= x[0];
 207                x[0] = @shuffle(u32, x[0], undefined, m2);
 208                x[3] = math.rotl(Lane, x[3], 8);
 209
 210                x[2] +%= x[3];
 211                x[3] = @shuffle(u32, x[3], undefined, m1);
 212                x[1] ^= x[2];
 213                x[2] = @shuffle(u32, x[2], undefined, m0);
 214                x[1] = math.rotl(Lane, x[1], 7);
 215            }
 216        }
 217
 218        fn hashToBytes(comptime dm: usize, out: *[64 * dm]u8, x: *const BlockVec) void {
 219            inline for (0..dm) |d| {
 220                for (0..4) |i| {
 221                    mem.writeInt(u32, out[64 * d + 16 * i + 0 ..][0..4], x[i][0 + 4 * d], .little);
 222                    mem.writeInt(u32, out[64 * d + 16 * i + 4 ..][0..4], x[i][1 + 4 * d], .little);
 223                    mem.writeInt(u32, out[64 * d + 16 * i + 8 ..][0..4], x[i][2 + 4 * d], .little);
 224                    mem.writeInt(u32, out[64 * d + 16 * i + 12 ..][0..4], x[i][3 + 4 * d], .little);
 225                }
 226            }
 227        }
 228
 229        fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
 230            x[0] +%= ctx[0];
 231            x[1] +%= ctx[1];
 232            x[2] +%= ctx[2];
 233            x[3] +%= ctx[3];
 234        }
 235
 236        fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
 237            var ctx = initContext(key, nonce_and_counter);
 238            var x: BlockVec = undefined;
 239            var buf: [64 * degree]u8 = undefined;
 240            var i: usize = 0;
 241            inline for ([_]comptime_int{ 4, 2, 1 }) |d| {
 242                while (degree >= d and i + 64 * d <= in.len) : (i += 64 * d) {
 243                    chacha20Core(x[0..], ctx);
 244                    contextFeedback(&x, ctx);
 245                    hashToBytes(d, buf[0 .. 64 * d], &x);
 246
 247                    var xout = out[i..];
 248                    const xin = in[i..];
 249                    for (0..64 * d) |j| {
 250                        xout[j] = xin[j];
 251                    }
 252                    for (0..64 * d) |j| {
 253                        xout[j] ^= buf[j];
 254                    }
 255                    inline for (0..d) |d_| {
 256                        if (count64) {
 257                            const next = @addWithOverflow(ctx[3][4 * d_], d);
 258                            ctx[3][4 * d_] = next[0];
 259                            ctx[3][4 * d_ + 1] +%= next[1];
 260                        } else {
 261                            ctx[3][4 * d_] +%= d;
 262                        }
 263                    }
 264                }
 265            }
 266            if (i < in.len) {
 267                chacha20Core(x[0..], ctx);
 268                contextFeedback(&x, ctx);
 269                hashToBytes(1, buf[0..64], &x);
 270
 271                var xout = out[i..];
 272                const xin = in[i..];
 273                for (0..in.len % 64) |j| {
 274                    xout[j] = xin[j] ^ buf[j];
 275                }
 276            }
 277        }
 278
 279        fn chacha20Stream(out: []u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
 280            var ctx = initContext(key, nonce_and_counter);
 281            var x: BlockVec = undefined;
 282            var i: usize = 0;
 283            inline for ([_]comptime_int{ 4, 2, 1 }) |d| {
 284                while (degree >= d and i + 64 * d <= out.len) : (i += 64 * d) {
 285                    chacha20Core(x[0..], ctx);
 286                    contextFeedback(&x, ctx);
 287                    hashToBytes(d, out[i..][0 .. 64 * d], &x);
 288                    inline for (0..d) |d_| {
 289                        if (count64) {
 290                            const next = @addWithOverflow(ctx[3][4 * d_], d);
 291                            ctx[3][4 * d_] = next[0];
 292                            ctx[3][4 * d_ + 1] +%= next[1];
 293                        } else {
 294                            ctx[3][4 * d_] +%= d;
 295                        }
 296                    }
 297                }
 298            }
 299            if (i < out.len) {
 300                chacha20Core(x[0..], ctx);
 301                contextFeedback(&x, ctx);
 302
 303                var buf: [64]u8 = undefined;
 304                hashToBytes(1, buf[0..], &x);
 305                @memcpy(out[i..], buf[0 .. out.len - i]);
 306            }
 307        }
 308
 309        fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
 310            var c: [4]u32 = undefined;
 311            for (c, 0..) |_, i| {
 312                c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
 313            }
 314            const ctx = initContext(keyToWords(key), c);
 315            var x: BlockVec = undefined;
 316            chacha20Core(x[0..], ctx);
 317            var out: [32]u8 = undefined;
 318            mem.writeInt(u32, out[0..4], x[0][0], .little);
 319            mem.writeInt(u32, out[4..8], x[0][1], .little);
 320            mem.writeInt(u32, out[8..12], x[0][2], .little);
 321            mem.writeInt(u32, out[12..16], x[0][3], .little);
 322            mem.writeInt(u32, out[16..20], x[3][0], .little);
 323            mem.writeInt(u32, out[20..24], x[3][1], .little);
 324            mem.writeInt(u32, out[24..28], x[3][2], .little);
 325            mem.writeInt(u32, out[28..32], x[3][3], .little);
 326            return out;
 327        }
 328    };
 329}
 330
 331// Non-vectorized implementation of the core function
 332fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
 333    return struct {
 334        const BlockVec = [16]u32;
 335
 336        fn initContext(key: [8]u32, d: [4]u32) BlockVec {
 337            const c = "expand 32-byte k";
 338            const constant_le = comptime [4]u32{
 339                mem.readInt(u32, c[0..4], .little),
 340                mem.readInt(u32, c[4..8], .little),
 341                mem.readInt(u32, c[8..12], .little),
 342                mem.readInt(u32, c[12..16], .little),
 343            };
 344            return BlockVec{
 345                constant_le[0], constant_le[1], constant_le[2], constant_le[3],
 346                key[0],         key[1],         key[2],         key[3],
 347                key[4],         key[5],         key[6],         key[7],
 348                d[0],           d[1],           d[2],           d[3],
 349            };
 350        }
 351
 352        const QuarterRound = struct {
 353            a: usize,
 354            b: usize,
 355            c: usize,
 356            d: usize,
 357        };
 358
 359        fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
 360            return QuarterRound{
 361                .a = a,
 362                .b = b,
 363                .c = c,
 364                .d = d,
 365            };
 366        }
 367
 368        fn chacha20Core(x: *BlockVec, input: BlockVec) void {
 369            x.* = input;
 370
 371            const rounds = comptime [_]QuarterRound{
 372                Rp(0, 4, 8, 12),
 373                Rp(1, 5, 9, 13),
 374                Rp(2, 6, 10, 14),
 375                Rp(3, 7, 11, 15),
 376                Rp(0, 5, 10, 15),
 377                Rp(1, 6, 11, 12),
 378                Rp(2, 7, 8, 13),
 379                Rp(3, 4, 9, 14),
 380            };
 381
 382            comptime var j: usize = 0;
 383            inline while (j < rounds_nb) : (j += 2) {
 384                inline for (rounds) |r| {
 385                    x[r.a] +%= x[r.b];
 386                    x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
 387                    x[r.c] +%= x[r.d];
 388                    x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
 389                    x[r.a] +%= x[r.b];
 390                    x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
 391                    x[r.c] +%= x[r.d];
 392                    x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
 393                }
 394            }
 395        }
 396
 397        fn hashToBytes(out: *[64]u8, x: *const BlockVec) void {
 398            for (0..4) |i| {
 399                mem.writeInt(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0], .little);
 400                mem.writeInt(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1], .little);
 401                mem.writeInt(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2], .little);
 402                mem.writeInt(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3], .little);
 403            }
 404        }
 405
 406        fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
 407            for (0..16) |i| {
 408                x[i] +%= ctx[i];
 409            }
 410        }
 411
 412        fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
 413            var ctx = initContext(key, nonce_and_counter);
 414            var x: BlockVec = undefined;
 415            var buf: [64]u8 = undefined;
 416            var i: usize = 0;
 417            while (i + 64 <= in.len) : (i += 64) {
 418                chacha20Core(x[0..], ctx);
 419                contextFeedback(&x, ctx);
 420                hashToBytes(buf[0..], &x);
 421
 422                var xout = out[i..];
 423                const xin = in[i..];
 424                for (0..64) |j| {
 425                    xout[j] = xin[j];
 426                }
 427                for (0..64) |j| {
 428                    xout[j] ^= buf[j];
 429                }
 430                if (count64) {
 431                    const next = @addWithOverflow(ctx[12], 1);
 432                    ctx[12] = next[0];
 433                    ctx[13] +%= next[1];
 434                } else {
 435                    ctx[12] +%= 1;
 436                }
 437            }
 438            if (i < in.len) {
 439                chacha20Core(x[0..], ctx);
 440                contextFeedback(&x, ctx);
 441                hashToBytes(buf[0..], &x);
 442
 443                var xout = out[i..];
 444                const xin = in[i..];
 445                for (0..in.len % 64) |j| {
 446                    xout[j] = xin[j] ^ buf[j];
 447                }
 448            }
 449        }
 450
 451        fn chacha20Stream(out: []u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
 452            var ctx = initContext(key, nonce_and_counter);
 453            var x: BlockVec = undefined;
 454            var i: usize = 0;
 455            while (i + 64 <= out.len) : (i += 64) {
 456                chacha20Core(x[0..], ctx);
 457                contextFeedback(&x, ctx);
 458                hashToBytes(out[i..][0..64], &x);
 459                if (count64) {
 460                    const next = @addWithOverflow(ctx[12], 1);
 461                    ctx[12] = next[0];
 462                    ctx[13] +%= next[1];
 463                } else {
 464                    ctx[12] +%= 1;
 465                }
 466            }
 467            if (i < out.len) {
 468                chacha20Core(x[0..], ctx);
 469                contextFeedback(&x, ctx);
 470
 471                var buf: [64]u8 = undefined;
 472                hashToBytes(buf[0..], &x);
 473                @memcpy(out[i..], buf[0 .. out.len - i]);
 474            }
 475        }
 476
 477        fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
 478            var c: [4]u32 = undefined;
 479            for (c, 0..) |_, i| {
 480                c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
 481            }
 482            const ctx = initContext(keyToWords(key), c);
 483            var x: BlockVec = undefined;
 484            chacha20Core(x[0..], ctx);
 485            var out: [32]u8 = undefined;
 486            mem.writeInt(u32, out[0..4], x[0], .little);
 487            mem.writeInt(u32, out[4..8], x[1], .little);
 488            mem.writeInt(u32, out[8..12], x[2], .little);
 489            mem.writeInt(u32, out[12..16], x[3], .little);
 490            mem.writeInt(u32, out[16..20], x[12], .little);
 491            mem.writeInt(u32, out[20..24], x[13], .little);
 492            mem.writeInt(u32, out[24..28], x[14], .little);
 493            mem.writeInt(u32, out[28..32], x[15], .little);
 494            return out;
 495        }
 496    };
 497}
 498
 499fn ChaChaImpl(comptime rounds_nb: usize) type {
 500    switch (builtin.cpu.arch) {
 501        .x86_64 => {
 502            if (builtin.zig_backend != .stage2_x86_64 and builtin.cpu.has(.x86, .avx512f)) return ChaChaVecImpl(rounds_nb, 4);
 503            if (builtin.cpu.has(.x86, .avx2)) return ChaChaVecImpl(rounds_nb, 2);
 504            return ChaChaVecImpl(rounds_nb, 1);
 505        },
 506        .aarch64 => {
 507            if (builtin.zig_backend != .stage2_aarch64 and builtin.cpu.has(.aarch64, .neon)) return ChaChaVecImpl(rounds_nb, 4);
 508            return ChaChaNonVecImpl(rounds_nb);
 509        },
 510        else => return ChaChaNonVecImpl(rounds_nb),
 511    }
 512}
 513
 514fn keyToWords(key: [32]u8) [8]u32 {
 515    var k: [8]u32 = undefined;
 516    for (0..8) |i| {
 517        k[i] = mem.readInt(u32, key[i * 4 ..][0..4], .little);
 518    }
 519    return k;
 520}
 521
 522fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } {
 523    var subnonce: [12]u8 = undefined;
 524    @memset(subnonce[0..4], 0);
 525    subnonce[4..].* = nonce[16..24].*;
 526    return .{
 527        .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key),
 528        .nonce = subnonce,
 529    };
 530}
 531
 532fn ChaChaIETF(comptime rounds_nb: usize) type {
 533    return struct {
 534        /// Nonce length in bytes.
 535        pub const nonce_length = 12;
 536        /// Key length in bytes.
 537        pub const key_length = 32;
 538        /// Block length in bytes.
 539        pub const block_length = 64;
 540
 541        /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
 542        /// WARNING: This function doesn't provide authenticated encryption.
 543        /// Using the AEAD or one of the `box` versions is usually preferred.
 544        pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
 545            assert(in.len == out.len);
 546            assert(in.len <= 64 * (@as(u39, 1 << 32) - counter));
 547
 548            var d: [4]u32 = undefined;
 549            d[0] = counter;
 550            d[1] = mem.readInt(u32, nonce[0..4], .little);
 551            d[2] = mem.readInt(u32, nonce[4..8], .little);
 552            d[3] = mem.readInt(u32, nonce[8..12], .little);
 553            ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d, false);
 554        }
 555
 556        /// Write the output of the ChaCha20 stream cipher into `out`.
 557        pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
 558            assert(out.len <= 64 * (@as(u39, 1 << 32) - counter));
 559
 560            var d: [4]u32 = undefined;
 561            d[0] = counter;
 562            d[1] = mem.readInt(u32, nonce[0..4], .little);
 563            d[2] = mem.readInt(u32, nonce[4..8], .little);
 564            d[3] = mem.readInt(u32, nonce[8..12], .little);
 565            ChaChaImpl(rounds_nb).chacha20Stream(out, keyToWords(key), d, false);
 566        }
 567    };
 568}
 569
 570fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
 571    return struct {
 572        /// Nonce length in bytes.
 573        pub const nonce_length = 8;
 574        /// Key length in bytes.
 575        pub const key_length = 32;
 576        /// Block length in bytes.
 577        pub const block_length = 64;
 578
 579        /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
 580        /// WARNING: This function doesn't provide authenticated encryption.
 581        /// Using the AEAD or one of the `box` versions is usually preferred.
 582        pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
 583            assert(in.len == out.len);
 584            assert(in.len <= 64 * (@as(u71, 1 << 64) - counter));
 585
 586            const k = keyToWords(key);
 587            var c: [4]u32 = undefined;
 588            c[0] = @truncate(counter);
 589            c[1] = @truncate(counter >> 32);
 590            c[2] = mem.readInt(u32, nonce[0..4], .little);
 591            c[3] = mem.readInt(u32, nonce[4..8], .little);
 592            ChaChaImpl(rounds_nb).chacha20Xor(out, in, k, c, true);
 593        }
 594
 595        /// Write the output of the ChaCha20 stream cipher into `out`.
 596        pub fn stream(out: []u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
 597            assert(out.len <= 64 * (@as(u71, 1 << 64) - counter));
 598
 599            const k = keyToWords(key);
 600            var c: [4]u32 = undefined;
 601            c[0] = @truncate(counter);
 602            c[1] = @truncate(counter >> 32);
 603            c[2] = mem.readInt(u32, nonce[0..4], .little);
 604            c[3] = mem.readInt(u32, nonce[4..8], .little);
 605            ChaChaImpl(rounds_nb).chacha20Stream(out, k, c, true);
 606        }
 607    };
 608}
 609
 610fn XChaChaIETF(comptime rounds_nb: usize) type {
 611    return struct {
 612        /// Nonce length in bytes.
 613        pub const nonce_length = 24;
 614        /// Key length in bytes.
 615        pub const key_length = 32;
 616        /// Block length in bytes.
 617        pub const block_length = 64;
 618
 619        /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`.
 620        /// WARNING: This function doesn't provide authenticated encryption.
 621        /// Using the AEAD or one of the `box` versions is usually preferred.
 622        pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
 623            const extended = extend(key, nonce, rounds_nb);
 624            ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce);
 625        }
 626
 627        /// Write the output of the XChaCha20 stream cipher into `out`.
 628        pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
 629            const extended = extend(key, nonce, rounds_nb);
 630            ChaChaIETF(rounds_nb).stream(out, counter, extended.key, extended.nonce);
 631        }
 632    };
 633}
 634
 635fn ChaChaPoly1305(comptime rounds_nb: usize) type {
 636    return struct {
 637        pub const tag_length = 16;
 638        pub const nonce_length = 12;
 639        pub const key_length = 32;
 640
 641        /// c: ciphertext: output buffer should be of size m.len
 642        /// tag: authentication tag: output MAC
 643        /// m: message
 644        /// ad: Associated Data
 645        /// npub: public nonce
 646        /// k: private key
 647        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
 648            assert(c.len == m.len);
 649            assert(m.len <= 64 * (@as(u39, 1 << 32) - 1));
 650
 651            var polyKey = [_]u8{0} ** 32;
 652            ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
 653
 654            ChaChaIETF(rounds_nb).xor(c[0..m.len], m, 1, k, npub);
 655
 656            var mac = Poly1305.init(polyKey[0..]);
 657            mac.update(ad);
 658            if (ad.len % 16 != 0) {
 659                const zeros = [_]u8{0} ** 16;
 660                const padding = 16 - (ad.len % 16);
 661                mac.update(zeros[0..padding]);
 662            }
 663            mac.update(c[0..m.len]);
 664            if (m.len % 16 != 0) {
 665                const zeros = [_]u8{0} ** 16;
 666                const padding = 16 - (m.len % 16);
 667                mac.update(zeros[0..padding]);
 668            }
 669            var lens: [16]u8 = undefined;
 670            mem.writeInt(u64, lens[0..8], ad.len, .little);
 671            mem.writeInt(u64, lens[8..16], m.len, .little);
 672            mac.update(lens[0..]);
 673            mac.final(tag);
 674        }
 675
 676        /// `m`: Message
 677        /// `c`: Ciphertext
 678        /// `tag`: Authentication tag
 679        /// `ad`: Associated data
 680        /// `npub`: Public nonce
 681        /// `k`: Private key
 682        /// Asserts `c.len == m.len`.
 683        ///
 684        /// Contents of `m` are undefined if an error is returned.
 685        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
 686            assert(c.len == m.len);
 687
 688            var polyKey = [_]u8{0} ** 32;
 689            ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
 690
 691            var mac = Poly1305.init(polyKey[0..]);
 692
 693            mac.update(ad);
 694            if (ad.len % 16 != 0) {
 695                const zeros = [_]u8{0} ** 16;
 696                const padding = 16 - (ad.len % 16);
 697                mac.update(zeros[0..padding]);
 698            }
 699            mac.update(c);
 700            if (c.len % 16 != 0) {
 701                const zeros = [_]u8{0} ** 16;
 702                const padding = 16 - (c.len % 16);
 703                mac.update(zeros[0..padding]);
 704            }
 705            var lens: [16]u8 = undefined;
 706            mem.writeInt(u64, lens[0..8], ad.len, .little);
 707            mem.writeInt(u64, lens[8..16], c.len, .little);
 708            mac.update(lens[0..]);
 709            var computed_tag: [16]u8 = undefined;
 710            mac.final(computed_tag[0..]);
 711
 712            const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
 713            if (!verify) {
 714                crypto.secureZero(u8, &computed_tag);
 715                @memset(m, undefined);
 716                return error.AuthenticationFailed;
 717            }
 718            ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);
 719        }
 720    };
 721}
 722
 723fn XChaChaPoly1305(comptime rounds_nb: usize) type {
 724    return struct {
 725        pub const tag_length = 16;
 726        pub const nonce_length = 24;
 727        pub const key_length = 32;
 728
 729        /// c: ciphertext: output buffer should be of size m.len
 730        /// tag: authentication tag: output MAC
 731        /// m: message
 732        /// ad: Associated Data
 733        /// npub: public nonce
 734        /// k: private key
 735        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
 736            const extended = extend(k, npub, rounds_nb);
 737            return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);
 738        }
 739
 740        /// `m`: Message
 741        /// `c`: Ciphertext
 742        /// `tag`: Authentication tag
 743        /// `ad`: Associated data
 744        /// `npub`: Public nonce
 745        /// `k`: Private key
 746        /// Asserts `c.len == m.len`.
 747        ///
 748        /// Contents of `m` are undefined if an error is returned.
 749        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
 750            const extended = extend(k, npub, rounds_nb);
 751            return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key);
 752        }
 753    };
 754}
 755
 756test "AEAD API" {
 757    const aeads = [_]type{ ChaCha20Poly1305, XChaCha20Poly1305 };
 758    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.";
 759    const ad = "Additional data";
 760
 761    inline for (aeads) |aead| {
 762        const key = [_]u8{69} ** aead.key_length;
 763        const nonce = [_]u8{42} ** aead.nonce_length;
 764        var c: [m.len]u8 = undefined;
 765        var tag: [aead.tag_length]u8 = undefined;
 766        var out: [m.len]u8 = undefined;
 767
 768        aead.encrypt(c[0..], tag[0..], m, ad, nonce, key);
 769        try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key);
 770        try testing.expectEqualSlices(u8, out[0..], m);
 771        c[0] +%= 1;
 772        try testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key));
 773    }
 774}
 775
 776// https://tools.ietf.org/html/rfc7539#section-2.4.2
 777test "test vector sunscreen" {
 778    const expected_result = [_]u8{
 779        0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80,
 780        0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
 781        0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2,
 782        0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
 783        0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab,
 784        0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
 785        0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab,
 786        0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
 787        0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61,
 788        0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
 789        0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06,
 790        0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
 791        0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6,
 792        0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
 793        0x87, 0x4d,
 794    };
 795    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.";
 796    var result: [114]u8 = undefined;
 797    const key = [_]u8{
 798        0,  1,  2,  3,  4,  5,  6,  7,
 799        8,  9,  10, 11, 12, 13, 14, 15,
 800        16, 17, 18, 19, 20, 21, 22, 23,
 801        24, 25, 26, 27, 28, 29, 30, 31,
 802    };
 803    const nonce = [_]u8{
 804        0, 0, 0, 0,
 805        0, 0, 0, 0x4a,
 806        0, 0, 0, 0,
 807    };
 808
 809    ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce);
 810    try testing.expectEqualSlices(u8, &expected_result, &result);
 811
 812    var m2: [114]u8 = undefined;
 813    ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce);
 814    try testing.expect(mem.order(u8, m, &m2) == .eq);
 815}
 816
 817// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
 818test "test vector 1" {
 819    const expected_result = [_]u8{
 820        0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90,
 821        0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28,
 822        0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a,
 823        0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7,
 824        0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d,
 825        0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37,
 826        0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
 827        0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
 828    };
 829    const m = [_]u8{
 830        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 831        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 832        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 833        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 834        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 835        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 836        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 837        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 838    };
 839    var result: [64]u8 = undefined;
 840    const key = [_]u8{
 841        0, 0, 0, 0, 0, 0, 0, 0,
 842        0, 0, 0, 0, 0, 0, 0, 0,
 843        0, 0, 0, 0, 0, 0, 0, 0,
 844        0, 0, 0, 0, 0, 0, 0, 0,
 845    };
 846    const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
 847
 848    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
 849    try testing.expectEqualSlices(u8, &expected_result, &result);
 850}
 851
 852test "test vector 2" {
 853    const expected_result = [_]u8{
 854        0x45, 0x40, 0xf0, 0x5a, 0x9f, 0x1f, 0xb2, 0x96,
 855        0xd7, 0x73, 0x6e, 0x7b, 0x20, 0x8e, 0x3c, 0x96,
 856        0xeb, 0x4f, 0xe1, 0x83, 0x46, 0x88, 0xd2, 0x60,
 857        0x4f, 0x45, 0x09, 0x52, 0xed, 0x43, 0x2d, 0x41,
 858        0xbb, 0xe2, 0xa0, 0xb6, 0xea, 0x75, 0x66, 0xd2,
 859        0xa5, 0xd1, 0xe7, 0xe2, 0x0d, 0x42, 0xaf, 0x2c,
 860        0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81,
 861        0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63,
 862    };
 863    const m = [_]u8{
 864        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 865        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 866        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 867        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 868        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 869        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 870        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 871        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 872    };
 873    var result: [64]u8 = undefined;
 874    const key = [_]u8{
 875        0, 0, 0, 0, 0, 0, 0, 0,
 876        0, 0, 0, 0, 0, 0, 0, 0,
 877        0, 0, 0, 0, 0, 0, 0, 0,
 878        0, 0, 0, 0, 0, 0, 0, 1,
 879    };
 880    const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 };
 881
 882    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
 883    try testing.expectEqualSlices(u8, &expected_result, &result);
 884}
 885
 886test "test vector 3" {
 887    const expected_result = [_]u8{
 888        0xde, 0x9c, 0xba, 0x7b, 0xf3, 0xd6, 0x9e, 0xf5,
 889        0xe7, 0x86, 0xdc, 0x63, 0x97, 0x3f, 0x65, 0x3a,
 890        0x0b, 0x49, 0xe0, 0x15, 0xad, 0xbf, 0xf7, 0x13,
 891        0x4f, 0xcb, 0x7d, 0xf1, 0x37, 0x82, 0x10, 0x31,
 892        0xe8, 0x5a, 0x05, 0x02, 0x78, 0xa7, 0x08, 0x45,
 893        0x27, 0x21, 0x4f, 0x73, 0xef, 0xc7, 0xfa, 0x5b,
 894        0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e,
 895        0x44, 0x5f, 0x41, 0xe3,
 896    };
 897    const m = [_]u8{
 898        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 899        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 900        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 901        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 902        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 903        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 904        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 905        0x00, 0x00, 0x00, 0x00,
 906    };
 907    var result: [60]u8 = undefined;
 908    const key = [_]u8{
 909        0, 0, 0, 0, 0, 0, 0, 0,
 910        0, 0, 0, 0, 0, 0, 0, 0,
 911        0, 0, 0, 0, 0, 0, 0, 0,
 912        0, 0, 0, 0, 0, 0, 0, 0,
 913    };
 914    const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 };
 915
 916    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
 917    try testing.expectEqualSlices(u8, &expected_result, &result);
 918}
 919
 920test "test vector 4" {
 921    const expected_result = [_]u8{
 922        0xef, 0x3f, 0xdf, 0xd6, 0xc6, 0x15, 0x78, 0xfb,
 923        0xf5, 0xcf, 0x35, 0xbd, 0x3d, 0xd3, 0x3b, 0x80,
 924        0x09, 0x63, 0x16, 0x34, 0xd2, 0x1e, 0x42, 0xac,
 925        0x33, 0x96, 0x0b, 0xd1, 0x38, 0xe5, 0x0d, 0x32,
 926        0x11, 0x1e, 0x4c, 0xaf, 0x23, 0x7e, 0xe5, 0x3c,
 927        0xa8, 0xad, 0x64, 0x26, 0x19, 0x4a, 0x88, 0x54,
 928        0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d,
 929        0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b,
 930    };
 931    const m = [_]u8{
 932        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 933        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 934        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 935        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 936        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 937        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 938        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 939        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 940    };
 941    var result: [64]u8 = undefined;
 942    const key = [_]u8{
 943        0, 0, 0, 0, 0, 0, 0, 0,
 944        0, 0, 0, 0, 0, 0, 0, 0,
 945        0, 0, 0, 0, 0, 0, 0, 0,
 946        0, 0, 0, 0, 0, 0, 0, 0,
 947    };
 948    const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 };
 949
 950    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
 951    try testing.expectEqualSlices(u8, &expected_result, &result);
 952}
 953
 954test "test vector 5" {
 955    const expected_result = [_]u8{
 956        0xf7, 0x98, 0xa1, 0x89, 0xf1, 0x95, 0xe6, 0x69,
 957        0x82, 0x10, 0x5f, 0xfb, 0x64, 0x0b, 0xb7, 0x75,
 958        0x7f, 0x57, 0x9d, 0xa3, 0x16, 0x02, 0xfc, 0x93,
 959        0xec, 0x01, 0xac, 0x56, 0xf8, 0x5a, 0xc3, 0xc1,
 960        0x34, 0xa4, 0x54, 0x7b, 0x73, 0x3b, 0x46, 0x41,
 961        0x30, 0x42, 0xc9, 0x44, 0x00, 0x49, 0x17, 0x69,
 962        0x05, 0xd3, 0xbe, 0x59, 0xea, 0x1c, 0x53, 0xf1,
 963        0x59, 0x16, 0x15, 0x5c, 0x2b, 0xe8, 0x24, 0x1a,
 964
 965        0x38, 0x00, 0x8b, 0x9a, 0x26, 0xbc, 0x35, 0x94,
 966        0x1e, 0x24, 0x44, 0x17, 0x7c, 0x8a, 0xde, 0x66,
 967        0x89, 0xde, 0x95, 0x26, 0x49, 0x86, 0xd9, 0x58,
 968        0x89, 0xfb, 0x60, 0xe8, 0x46, 0x29, 0xc9, 0xbd,
 969        0x9a, 0x5a, 0xcb, 0x1c, 0xc1, 0x18, 0xbe, 0x56,
 970        0x3e, 0xb9, 0xb3, 0xa4, 0xa4, 0x72, 0xf8, 0x2e,
 971        0x09, 0xa7, 0xe7, 0x78, 0x49, 0x2b, 0x56, 0x2e,
 972        0xf7, 0x13, 0x0e, 0x88, 0xdf, 0xe0, 0x31, 0xc7,
 973
 974        0x9d, 0xb9, 0xd4, 0xf7, 0xc7, 0xa8, 0x99, 0x15,
 975        0x1b, 0x9a, 0x47, 0x50, 0x32, 0xb6, 0x3f, 0xc3,
 976        0x85, 0x24, 0x5f, 0xe0, 0x54, 0xe3, 0xdd, 0x5a,
 977        0x97, 0xa5, 0xf5, 0x76, 0xfe, 0x06, 0x40, 0x25,
 978        0xd3, 0xce, 0x04, 0x2c, 0x56, 0x6a, 0xb2, 0xc5,
 979        0x07, 0xb1, 0x38, 0xdb, 0x85, 0x3e, 0x3d, 0x69,
 980        0x59, 0x66, 0x09, 0x96, 0x54, 0x6c, 0xc9, 0xc4,
 981        0xa6, 0xea, 0xfd, 0xc7, 0x77, 0xc0, 0x40, 0xd7,
 982
 983        0x0e, 0xaf, 0x46, 0xf7, 0x6d, 0xad, 0x39, 0x79,
 984        0xe5, 0xc5, 0x36, 0x0c, 0x33, 0x17, 0x16, 0x6a,
 985        0x1c, 0x89, 0x4c, 0x94, 0xa3, 0x71, 0x87, 0x6a,
 986        0x94, 0xdf, 0x76, 0x28, 0xfe, 0x4e, 0xaa, 0xf2,
 987        0xcc, 0xb2, 0x7d, 0x5a, 0xaa, 0xe0, 0xad, 0x7a,
 988        0xd0, 0xf9, 0xd4, 0xb6, 0xad, 0x3b, 0x54, 0x09,
 989        0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a,
 990        0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9,
 991    };
 992    const m = [_]u8{
 993        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 994        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 995        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 996        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 997        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 998        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 999        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1000        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1001
1002        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1003        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1004        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1005        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1006        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1007        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1008        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1009        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1010    };
1011    var result: [256]u8 = undefined;
1012    const key = [_]u8{
1013        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1014        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
1015        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
1016        0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
1017    };
1018    const nonce = [_]u8{
1019        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1020    };
1021
1022    ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce);
1023    try testing.expectEqualSlices(u8, &expected_result, &result);
1024}
1025
1026test "seal" {
1027    {
1028        const m = "";
1029        const ad = "";
1030        const key = [_]u8{
1031            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
1032            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
1033        };
1034        const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
1035        const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
1036
1037        var out: [exp_out.len]u8 = undefined;
1038        ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m, ad, nonce, key);
1039        try testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
1040    }
1041    {
1042        const m = [_]u8{
1043            0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
1044            0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
1045            0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
1046            0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
1047            0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
1048            0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
1049            0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
1050            0x74, 0x2e,
1051        };
1052        const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
1053        const key = [_]u8{
1054            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
1055            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
1056        };
1057        const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
1058        const exp_out = [_]u8{
1059            0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
1060            0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8,  0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
1061            0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
1062            0x1a, 0x71, 0xde, 0xa,  0x9e, 0x6,  0xb,  0x29, 0x5,  0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
1063            0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3,  0xae, 0xe3, 0x28, 0x9,  0x1b, 0x58,
1064            0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
1065            0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
1066            0x61, 0x16, 0x1a, 0xe1, 0xb,  0x59, 0x4f, 0x9,  0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
1067            0x6,  0x91,
1068        };
1069
1070        var out: [exp_out.len]u8 = undefined;
1071        ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m[0..], ad[0..], nonce, key);
1072        try testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
1073    }
1074}
1075
1076test "open" {
1077    {
1078        const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 };
1079        const ad = "";
1080        const key = [_]u8{
1081            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
1082            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
1083        };
1084        const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
1085        const exp_out = "";
1086
1087        var out: [exp_out.len]u8 = undefined;
1088        try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
1089        try testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
1090    }
1091    {
1092        const c = [_]u8{
1093            0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
1094            0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8,  0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
1095            0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
1096            0x1a, 0x71, 0xde, 0xa,  0x9e, 0x6,  0xb,  0x29, 0x5,  0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
1097            0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x3,  0xae, 0xe3, 0x28, 0x9,  0x1b, 0x58,
1098            0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
1099            0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
1100            0x61, 0x16, 0x1a, 0xe1, 0xb,  0x59, 0x4f, 0x9,  0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60,
1101            0x6,  0x91,
1102        };
1103        const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
1104        const key = [_]u8{
1105            0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
1106            0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
1107        };
1108        const nonce = [_]u8{ 0x7, 0x0, 0x0, 0x0, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
1109        const exp_out = [_]u8{
1110            0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
1111            0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
1112            0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
1113            0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
1114            0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
1115            0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
1116            0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
1117            0x74, 0x2e,
1118        };
1119
1120        var out: [exp_out.len]u8 = undefined;
1121        try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key);
1122        try testing.expectEqualSlices(u8, exp_out[0..], out[0..]);
1123
1124        // corrupting the ciphertext, data, key, or nonce should cause a failure
1125        var bad_c = c;
1126        bad_c[0] ^= 1;
1127        try testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key));
1128        var bad_ad = ad;
1129        bad_ad[0] ^= 1;
1130        try testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key));
1131        var bad_key = key;
1132        bad_key[0] ^= 1;
1133        try testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], nonce, bad_key));
1134        var bad_nonce = nonce;
1135        bad_nonce[0] ^= 1;
1136        try testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], bad_nonce, key));
1137    }
1138}
1139
1140test "xchacha20" {
1141    const key = [_]u8{69} ** 32;
1142    const nonce = [_]u8{42} ** 24;
1143    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.";
1144    {
1145        var c: [m.len]u8 = undefined;
1146        XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce);
1147        var buf: [2 * c.len]u8 = undefined;
1148        try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&c}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D");
1149    }
1150    {
1151        const ad = "Additional data";
1152        var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined;
1153        XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key);
1154        var out: [m.len]u8 = undefined;
1155        try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key);
1156        var buf: [2 * c.len]u8 = undefined;
1157        try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&c}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
1158        try testing.expectEqualSlices(u8, out[0..], m);
1159        c[0] +%= 1;
1160        try testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key));
1161    }
1162}