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}