authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2023-03-21 05:54:10+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-03-21 04:54:10+00:00
logdff4bbfd2426ce4943972782d2bcffc89b3fd26d
treed5432f82eed373e5713add5336464f2d6bfc50f4
parentbc0f246911a35324473f72b770cc5715902cc912
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Remove Gimli and Xoodoo from the standard library (#14928)

These are great permutations, and there's nothing wrong with them from a practical security perspective. However, both were competing in the NIST lightweight crypto competition. Gimli didn't pass the 3rd selection round, and is not much used in the wild besides Zig and libhydrogen. It will never be standardized and is unlikely to get more traction in the future. Xoodyak, that Xoodoo is the permutation of, was a finalist. It has a lot of advantages and *might* be standardized without NIST. But this is too early to tell, and too risky to commit to it in a standard library. For lightweight crypto, Ascon is the one that we know NIST will standardize and that we can safely rely on from a usage perspective. Switch to a traditional ChaCha-based CSPRNG, with an Ascon-based one as an option for constrained systems. Add a RNG benchmark by the way. Gimli and Xoodoo served us well. Their code will be maintained, but outside the standard library.

13 files changed, 441 insertions(+), 784 deletions(-)

lib/std/crypto.zig-11
......@@ -17,8 +17,6 @@ pub const aead = struct {
1717 pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
1818 };
1919
20 pub const Gimli = @import("crypto/gimli.zig").Aead;
21
2220 pub const chacha_poly = struct {
2321 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
2422 pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
......@@ -52,8 +50,6 @@ pub const core = struct {
5250 pub const keccak = @import("crypto/keccak_p.zig");
5351
5452 pub const Ascon = @import("crypto/ascon.zig").State;
55 pub const Gimli = @import("crypto/gimli.zig").State;
56 pub const Xoodoo = @import("crypto/xoodoo.zig").State;
5753
5854 /// Modes are generic compositions to construct encryption/decryption functions from block ciphers and permutations.
5955 ///
......@@ -87,7 +83,6 @@ pub const ecc = struct {
8783pub const hash = struct {
8884 pub const blake2 = @import("crypto/blake2.zig");
8985 pub const Blake3 = @import("crypto/blake3.zig").Blake3;
90 pub const Gimli = @import("crypto/gimli.zig").Hash;
9186 pub const Md5 = @import("crypto/md5.zig").Md5;
9287 pub const Sha1 = @import("crypto/sha1.zig").Sha1;
9388 pub const sha2 = @import("crypto/sha2.zig");
......@@ -221,8 +216,6 @@ test {
221216 _ = aead.aes_ocb.Aes128Ocb;
222217 _ = aead.aes_ocb.Aes256Ocb;
223218
224 _ = aead.Gimli;
225
226219 _ = aead.chacha_poly.ChaCha20Poly1305;
227220 _ = aead.chacha_poly.ChaCha12Poly1305;
228221 _ = aead.chacha_poly.ChaCha8Poly1305;
......@@ -239,8 +232,6 @@ test {
239232
240233 _ = core.aes;
241234 _ = core.Ascon;
242 _ = core.Gimli;
243 _ = core.Xoodoo;
244235 _ = core.modes;
245236
246237 _ = dh.X25519;
......@@ -256,7 +247,6 @@ test {
256247
257248 _ = hash.blake2;
258249 _ = hash.Blake3;
259 _ = hash.Gimli;
260250 _ = hash.Md5;
261251 _ = hash.Sha1;
262252 _ = hash.sha2;
......@@ -334,7 +324,6 @@ test "issue #4532: no index out of bounds" {
334324 hash.blake2.Blake2b256,
335325 hash.blake2.Blake2b384,
336326 hash.blake2.Blake2b512,
337 hash.Gimli,
338327 };
339328
340329 inline for (types) |Hasher| {
lib/std/crypto/ascon.zig+11
......@@ -168,6 +168,17 @@ pub fn State(comptime endian: builtin.Endian) type {
168168 state.permuteR(12);
169169 }
170170
171 /// Apply a permutation to the state and prevent backtracking.
172 /// The rate is expressed in bytes and must be a multiple of the word size (8).
173 pub inline fn permuteRatchet(state: *Self, comptime rounds: u4, comptime rate: u6) void {
174 const capacity = block_bytes - rate;
175 debug.assert(capacity > 0 and capacity % 8 == 0); // capacity must be a multiple of 64 bits
176 var mask: [capacity / 8]u64 = undefined;
177 inline for (&mask, state.st[state.st.len - mask.len ..]) |*m, x| m.* = x;
178 state.permuteR(rounds);
179 inline for (mask, state.st[state.st.len - mask.len ..]) |m, *x| x.* ^= m;
180 }
181
171182 // Core Ascon permutation.
172183 inline fn round(state: *Self, rk: u64) void {
173184 const x = &state.st;
lib/std/crypto/benchmark.zig-2
......@@ -29,7 +29,6 @@ const hashes = [_]Crypto{
2929 Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
3030 Crypto{ .ty = crypto.hash.sha3.TurboShake128(null), .name = "turboshake-128" },
3131 Crypto{ .ty = crypto.hash.sha3.TurboShake256(null), .name = "turboshake-256" },
32 Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" },
3332 Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
3433 Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
3534 Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
......@@ -274,7 +273,6 @@ const aeads = [_]Crypto{
274273 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
275274 Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
276275 Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
277 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
278276 Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
279277 Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
280278 Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
lib/std/crypto/chacha20.zig+76-1
......@@ -195,6 +195,26 @@ fn ChaChaVecImpl(comptime rounds_nb: usize) type {
195195 }
196196 }
197197
198 fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
199 var ctx = initContext(key, counter);
200 var x: BlockVec = undefined;
201 var i: usize = 0;
202 while (i + 64 <= out.len) : (i += 64) {
203 chacha20Core(x[0..], ctx);
204 contextFeedback(&x, ctx);
205 hashToBytes(out[i..][0..64], x);
206 ctx[3][0] += 1;
207 }
208 if (i < out.len) {
209 chacha20Core(x[0..], ctx);
210 contextFeedback(&x, ctx);
211
212 var buf: [64]u8 = undefined;
213 hashToBytes(buf[0..], x);
214 mem.copy(u8, out[i..], buf[0 .. out.len - i]);
215 }
216 }
217
198218 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
199219 var c: [4]u32 = undefined;
200220 for (c, 0..) |_, i| {
......@@ -336,6 +356,26 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
336356 }
337357 }
338358
359 fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
360 var ctx = initContext(key, counter);
361 var x: BlockVec = undefined;
362 var i: usize = 0;
363 while (i + 64 <= out.len) : (i += 64) {
364 chacha20Core(x[0..], ctx);
365 contextFeedback(&x, ctx);
366 hashToBytes(out[i..][0..64], x);
367 ctx[12] += 1;
368 }
369 if (i < out.len) {
370 chacha20Core(x[0..], ctx);
371 contextFeedback(&x, ctx);
372
373 var buf: [64]u8 = undefined;
374 hashToBytes(buf[0..], x);
375 mem.copy(u8, out[i..], buf[0 .. out.len - i]);
376 }
377 }
378
339379 fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
340380 var c: [4]u32 = undefined;
341381 for (c, 0..) |_, i| {
......@@ -387,6 +427,8 @@ fn ChaChaIETF(comptime rounds_nb: usize) type {
387427 pub const nonce_length = 12;
388428 /// Key length in bytes.
389429 pub const key_length = 32;
430 /// Block length in bytes.
431 pub const block_length = 64;
390432
391433 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
392434 /// WARNING: This function doesn't provide authenticated encryption.
......@@ -402,6 +444,18 @@ fn ChaChaIETF(comptime rounds_nb: usize) type {
402444 d[3] = mem.readIntLittle(u32, nonce[8..12]);
403445 ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
404446 }
447
448 /// Write the output of the ChaCha20 stream cipher into `out`.
449 pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
450 assert(out.len / 64 <= (1 << 32 - 1) - counter);
451
452 var d: [4]u32 = undefined;
453 d[0] = counter;
454 d[1] = mem.readIntLittle(u32, nonce[0..4]);
455 d[2] = mem.readIntLittle(u32, nonce[4..8]);
456 d[3] = mem.readIntLittle(u32, nonce[8..12]);
457 ChaChaImpl(rounds_nb).chacha20Stream(out, keyToWords(key), d);
458 }
405459 };
406460}
407461
......@@ -411,6 +465,8 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
411465 pub const nonce_length = 8;
412466 /// Key length in bytes.
413467 pub const key_length = 32;
468 /// Block length in bytes.
469 pub const block_length = 64;
414470
415471 /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
416472 /// WARNING: This function doesn't provide authenticated encryption.
......@@ -427,7 +483,6 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
427483 c[2] = mem.readIntLittle(u32, nonce[0..4]);
428484 c[3] = mem.readIntLittle(u32, nonce[4..8]);
429485
430 const block_length = (1 << 6);
431486 // The full block size is greater than the address space on a 32bit machine
432487 const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
433488
......@@ -448,6 +503,18 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
448503 }
449504 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c);
450505 }
506
507 /// Write the output of the ChaCha20 stream cipher into `out`.
508 pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
509 assert(out.len / 64 <= (1 << 32 - 1) - counter);
510 const k = keyToWords(key);
511 var c: [4]u32 = undefined;
512 c[0] = @truncate(u32, counter);
513 c[1] = @truncate(u32, counter >> 32);
514 c[2] = mem.readIntLittle(u32, nonce[0..4]);
515 c[3] = mem.readIntLittle(u32, nonce[4..8]);
516 ChaChaImpl(rounds_nb).chacha20Stream(out, k, c);
517 }
451518 };
452519}
453520
......@@ -457,6 +524,8 @@ fn XChaChaIETF(comptime rounds_nb: usize) type {
457524 pub const nonce_length = 24;
458525 /// Key length in bytes.
459526 pub const key_length = 32;
527 /// Block length in bytes.
528 pub const block_length = 64;
460529
461530 /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`.
462531 /// WARNING: This function doesn't provide authenticated encryption.
......@@ -465,6 +534,12 @@ fn XChaChaIETF(comptime rounds_nb: usize) type {
465534 const extended = extend(key, nonce, rounds_nb);
466535 ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce);
467536 }
537
538 /// Write the output of the XChaCha20 stream cipher into `out`.
539 pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
540 const extended = extend(key, nonce, rounds_nb);
541 ChaChaIETF(rounds_nb).xor(out, counter, extended.key, extended.nonce);
542 }
468543 };
469544}
470545
lib/std/crypto/gimli.zig deleted-527
......@@ -1,527 +0,0 @@
1//! Gimli is a 384-bit permutation designed to achieve high security with high
2//! performance across a broad range of platforms, including 64-bit Intel/AMD
3//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
4//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
5//! side-channel protection, and ASICs with side-channel protection.
6//!
7//! https://gimli.cr.yp.to/
8//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf
9
10const std = @import("../std.zig");
11const builtin = @import("builtin");
12const mem = std.mem;
13const math = std.math;
14const debug = std.debug;
15const assert = std.debug.assert;
16const testing = std.testing;
17const htest = @import("test.zig");
18const AuthenticationError = std.crypto.errors.AuthenticationError;
19
20pub const State = struct {
21 pub const BLOCKBYTES = 48;
22 pub const RATE = 16;
23
24 data: [BLOCKBYTES / 4]u32 align(16),
25
26 const Self = @This();
27
28 pub fn init(initial_state: [State.BLOCKBYTES]u8) Self {
29 var data: [BLOCKBYTES / 4]u32 = undefined;
30 var i: usize = 0;
31 while (i < State.BLOCKBYTES) : (i += 4) {
32 data[i / 4] = mem.readIntNative(u32, initial_state[i..][0..4]);
33 }
34 return Self{ .data = data };
35 }
36
37 /// TODO follow the span() convention instead of having this and `toSliceConst`
38 pub fn toSlice(self: *Self) *[BLOCKBYTES]u8 {
39 return mem.asBytes(&self.data);
40 }
41
42 /// TODO follow the span() convention instead of having this and `toSlice`
43 pub fn toSliceConst(self: *const Self) *const [BLOCKBYTES]u8 {
44 return mem.asBytes(&self.data);
45 }
46
47 inline fn endianSwap(self: *Self) void {
48 for (&self.data) |*w| {
49 w.* = mem.littleToNative(u32, w.*);
50 }
51 }
52
53 fn permute_unrolled(self: *Self) void {
54 self.endianSwap();
55 const state = &self.data;
56 comptime var round = @as(u32, 24);
57 inline while (round > 0) : (round -= 1) {
58 var column = @as(usize, 0);
59 while (column < 4) : (column += 1) {
60 const x = math.rotl(u32, state[column], 24);
61 const y = math.rotl(u32, state[4 + column], 9);
62 const z = state[8 + column];
63 state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
64 state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
65 state[column] = ((z ^ y) ^ ((x & y) << 3));
66 }
67 switch (round & 3) {
68 0 => {
69 mem.swap(u32, &state[0], &state[1]);
70 mem.swap(u32, &state[2], &state[3]);
71 state[0] ^= round | 0x9e377900;
72 },
73 2 => {
74 mem.swap(u32, &state[0], &state[2]);
75 mem.swap(u32, &state[1], &state[3]);
76 },
77 else => {},
78 }
79 }
80 self.endianSwap();
81 }
82
83 fn permute_small(self: *Self) void {
84 self.endianSwap();
85 const state = &self.data;
86 var round = @as(u32, 24);
87 while (round > 0) : (round -= 1) {
88 var column = @as(usize, 0);
89 while (column < 4) : (column += 1) {
90 const x = math.rotl(u32, state[column], 24);
91 const y = math.rotl(u32, state[4 + column], 9);
92 const z = state[8 + column];
93 state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
94 state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
95 state[column] = ((z ^ y) ^ ((x & y) << 3));
96 }
97 switch (round & 3) {
98 0 => {
99 mem.swap(u32, &state[0], &state[1]);
100 mem.swap(u32, &state[2], &state[3]);
101 state[0] ^= round | 0x9e377900;
102 },
103 2 => {
104 mem.swap(u32, &state[0], &state[2]);
105 mem.swap(u32, &state[1], &state[3]);
106 },
107 else => {},
108 }
109 }
110 self.endianSwap();
111 }
112
113 const Lane = @Vector(4, u32);
114
115 inline fn shift(x: Lane, comptime n: comptime_int) Lane {
116 return x << @splat(4, @as(u5, n));
117 }
118
119 fn permute_vectorized(self: *Self) void {
120 self.endianSwap();
121 const state = &self.data;
122 var x = Lane{ state[0], state[1], state[2], state[3] };
123 var y = Lane{ state[4], state[5], state[6], state[7] };
124 var z = Lane{ state[8], state[9], state[10], state[11] };
125 var round = @as(u32, 24);
126 while (round > 0) : (round -= 1) {
127 x = math.rotl(Lane, x, 24);
128 y = math.rotl(Lane, y, 9);
129 const newz = x ^ shift(z, 1) ^ shift(y & z, 2);
130 const newy = y ^ x ^ shift(x | z, 1);
131 const newx = z ^ y ^ shift(x & y, 3);
132 x = newx;
133 y = newy;
134 z = newz;
135 switch (round & 3) {
136 0 => {
137 x = @shuffle(u32, x, undefined, [_]i32{ 1, 0, 3, 2 });
138 x[0] ^= round | 0x9e377900;
139 },
140 2 => {
141 x = @shuffle(u32, x, undefined, [_]i32{ 2, 3, 0, 1 });
142 },
143 else => {},
144 }
145 }
146 comptime var i: usize = 0;
147 inline while (i < 4) : (i += 1) {
148 state[0 + i] = x[i];
149 state[4 + i] = y[i];
150 state[8 + i] = z[i];
151 }
152 self.endianSwap();
153 }
154
155 pub const permute = if (builtin.cpu.arch == .x86_64) impl: {
156 break :impl permute_vectorized;
157 } else if (builtin.mode == .ReleaseSmall) impl: {
158 break :impl permute_small;
159 } else impl: {
160 break :impl permute_unrolled;
161 };
162
163 pub fn squeeze(self: *Self, out: []u8) void {
164 var i = @as(usize, 0);
165 while (i + RATE <= out.len) : (i += RATE) {
166 self.permute();
167 mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]);
168 }
169 const leftover = out.len - i;
170 if (leftover != 0) {
171 self.permute();
172 mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]);
173 }
174 }
175};
176
177test "permute" {
178 // test vector from gimli-20170627
179 const tv_input = [3][4]u32{
180 [4]u32{ 0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46 },
181 [4]u32{ 0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566 },
182 [4]u32{ 0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026 },
183 };
184 var input: [48]u8 = undefined;
185 var i: usize = 0;
186 while (i < 12) : (i += 1) {
187 mem.writeIntLittle(u32, input[i * 4 ..][0..4], tv_input[i / 4][i % 4]);
188 }
189
190 var state = State.init(input);
191 state.permute();
192
193 const tv_output = [3][4]u32{
194 [4]u32{ 0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68 },
195 [4]u32{ 0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8 },
196 [4]u32{ 0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6 },
197 };
198 var expected_output: [48]u8 = undefined;
199 i = 0;
200 while (i < 12) : (i += 1) {
201 mem.writeIntLittle(u32, expected_output[i * 4 ..][0..4], tv_output[i / 4][i % 4]);
202 }
203 try testing.expectEqualSlices(u8, state.toSliceConst(), expected_output[0..]);
204}
205
206pub const Hash = struct {
207 state: State,
208 buf_off: usize,
209
210 pub const block_length = State.RATE;
211 pub const digest_length = 32;
212 pub const Options = struct {};
213
214 const Self = @This();
215
216 pub fn init(options: Options) Self {
217 _ = options;
218 return Self{
219 .state = State{ .data = [_]u32{0} ** (State.BLOCKBYTES / 4) },
220 .buf_off = 0,
221 };
222 }
223
224 /// Also known as 'absorb'
225 pub fn update(self: *Self, data: []const u8) void {
226 const buf = self.state.toSlice();
227 var in = data;
228 while (in.len > 0) {
229 const left = State.RATE - self.buf_off;
230 const ps = math.min(in.len, left);
231 for (buf[self.buf_off .. self.buf_off + ps], 0..) |*p, i| {
232 p.* ^= in[i];
233 }
234 self.buf_off += ps;
235 in = in[ps..];
236 if (self.buf_off == State.RATE) {
237 self.state.permute();
238 self.buf_off = 0;
239 }
240 }
241 }
242
243 /// Finish the current hashing operation, writing the hash to `out`
244 ///
245 /// From 4.9 "Application to hashing"
246 /// By default, Gimli-Hash provides a fixed-length output of 32 bytes
247 /// (the concatenation of two 16-byte blocks). However, Gimli-Hash can
248 /// be used as an “extendable one-way function” (XOF).
249 pub fn final(self: *Self, out: []u8) void {
250 const buf = self.state.toSlice();
251
252 // XOR 1 into the next byte of the state
253 buf[self.buf_off] ^= 1;
254 // XOR 1 into the last byte of the state, position 47.
255 buf[buf.len - 1] ^= 1;
256
257 self.state.squeeze(out);
258 }
259
260 pub const Error = error{};
261 pub const Writer = std.io.Writer(*Self, Error, write);
262
263 fn write(self: *Self, bytes: []const u8) Error!usize {
264 self.update(bytes);
265 return bytes.len;
266 }
267
268 pub fn writer(self: *Self) Writer {
269 return .{ .context = self };
270 }
271};
272
273pub fn hash(out: []u8, in: []const u8, options: Hash.Options) void {
274 var st = Hash.init(options);
275 st.update(in);
276 st.final(out);
277}
278
279test "hash" {
280 // a test vector (30) from NIST KAT submission.
281 var msg: [58 / 2]u8 = undefined;
282 _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C");
283 var md: [32]u8 = undefined;
284 hash(&md, &msg, .{});
285 try htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
286}
287
288test "hash test vector 17" {
289 var msg: [32 / 2]u8 = undefined;
290 _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F");
291 var md: [32]u8 = undefined;
292 hash(&md, &msg, .{});
293 try htest.assertEqual("404C130AF1B9023A7908200919F690FFBB756D5176E056FFDE320016A37C7282", &md);
294}
295
296test "hash test vector 33" {
297 var msg: [32]u8 = undefined;
298 _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
299 var md: [32]u8 = undefined;
300 hash(&md, &msg, .{});
301 try htest.assertEqual("A8F4FA28708BDA7EFB4C1914CA4AFA9E475B82D588D36504F87DBB0ED9AB3C4B", &md);
302}
303
304pub const Aead = struct {
305 pub const tag_length = State.RATE;
306 pub const nonce_length = 16;
307 pub const key_length = 32;
308
309 /// ad: Associated Data
310 /// npub: public nonce
311 /// k: private key
312 fn init(ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) State {
313 var state = State{
314 .data = undefined,
315 };
316 const buf = state.toSlice();
317
318 // Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
319 // followed by a 32-byte key.
320 assert(npub.len + k.len == State.BLOCKBYTES);
321 std.mem.copy(u8, buf[0..npub.len], &npub);
322 std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
323
324 // It then applies the Gimli permutation.
325 state.permute();
326
327 {
328 // Gimli-Cipher then handles each block of associated data, including
329 // exactly one final non-full block, in the same way as Gimli-Hash.
330 var data = ad;
331 while (data.len >= State.RATE) : (data = data[State.RATE..]) {
332 for (buf[0..State.RATE], 0..) |*p, i| {
333 p.* ^= data[i];
334 }
335 state.permute();
336 }
337 for (buf[0..data.len], 0..) |*p, i| {
338 p.* ^= data[i];
339 }
340
341 // XOR 1 into the next byte of the state
342 buf[data.len] ^= 1;
343 // XOR 1 into the last byte of the state, position 47.
344 buf[buf.len - 1] ^= 1;
345
346 state.permute();
347 }
348
349 return state;
350 }
351
352 /// c: ciphertext: output buffer should be of size m.len
353 /// tag: authentication tag: output MAC
354 /// m: message
355 /// ad: Associated Data
356 /// npub: public nonce
357 /// k: private key
358 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
359 assert(c.len == m.len);
360
361 var state = Aead.init(ad, npub, k);
362 const buf = state.toSlice();
363
364 // Gimli-Cipher then handles each block of plaintext, including
365 // exactly one final non-full block, in the same way as Gimli-Hash.
366 // Whenever a plaintext byte is XORed into a state byte, the new state
367 // byte is output as ciphertext.
368 var in = m;
369 var out = c;
370 while (in.len >= State.RATE) : ({
371 in = in[State.RATE..];
372 out = out[State.RATE..];
373 }) {
374 for (in[0..State.RATE], 0..) |v, i| {
375 buf[i] ^= v;
376 }
377 mem.copy(u8, out[0..State.RATE], buf[0..State.RATE]);
378 state.permute();
379 }
380 for (in[0..], 0..) |v, i| {
381 buf[i] ^= v;
382 out[i] = buf[i];
383 }
384
385 // XOR 1 into the next byte of the state
386 buf[in.len] ^= 1;
387 // XOR 1 into the last byte of the state, position 47.
388 buf[buf.len - 1] ^= 1;
389
390 state.permute();
391
392 // After the final non-full block of plaintext, the first 16 bytes
393 // of the state are output as an authentication tag.
394 std.mem.copy(u8, tag, buf[0..State.RATE]);
395 }
396
397 /// m: message: output buffer should be of size c.len
398 /// c: ciphertext
399 /// tag: authentication tag
400 /// ad: Associated Data
401 /// npub: public nonce
402 /// k: private key
403 /// NOTE: the check of the authentication tag is currently not done in constant time
404 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 {
405 assert(c.len == m.len);
406
407 var state = Aead.init(ad, npub, k);
408 const buf = state.toSlice();
409
410 var in = c;
411 var out = m;
412 while (in.len >= State.RATE) : ({
413 in = in[State.RATE..];
414 out = out[State.RATE..];
415 }) {
416 const d = in[0..State.RATE].*;
417 for (d, 0..) |v, i| {
418 out[i] = buf[i] ^ v;
419 }
420 mem.copy(u8, buf[0..State.RATE], d[0..State.RATE]);
421 state.permute();
422 }
423 for (buf[0..in.len], 0..) |*p, i| {
424 const d = in[i];
425 out[i] = p.* ^ d;
426 p.* = d;
427 }
428
429 // XOR 1 into the next byte of the state
430 buf[in.len] ^= 1;
431 // XOR 1 into the last byte of the state, position 47.
432 buf[buf.len - 1] ^= 1;
433
434 state.permute();
435
436 // After the final non-full block of plaintext, the first 16 bytes
437 // of the state are the authentication tag.
438 // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
439 if (!mem.eql(u8, buf[0..State.RATE], &tag)) {
440 @memset(m.ptr, undefined, m.len);
441 return error.AuthenticationFailed;
442 }
443 }
444};
445
446test "cipher" {
447 var key: [32]u8 = undefined;
448 _ = try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
449 var nonce: [16]u8 = undefined;
450 _ = try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
451 { // test vector (1) from NIST KAT submission.
452 const ad: [0]u8 = undefined;
453 const pt: [0]u8 = undefined;
454
455 var ct: [pt.len]u8 = undefined;
456 var tag: [16]u8 = undefined;
457 Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
458 try htest.assertEqual("", &ct);
459 try htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &tag);
460
461 var pt2: [pt.len]u8 = undefined;
462 try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
463 try testing.expectEqualSlices(u8, &pt, &pt2);
464 }
465 { // test vector (34) from NIST KAT submission.
466 const ad: [0]u8 = undefined;
467 var pt: [2 / 2]u8 = undefined;
468 _ = try std.fmt.hexToBytes(&pt, "00");
469
470 var ct: [pt.len]u8 = undefined;
471 var tag: [16]u8 = undefined;
472 Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
473 try htest.assertEqual("7F", &ct);
474 try htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &tag);
475
476 var pt2: [pt.len]u8 = undefined;
477 try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
478 try testing.expectEqualSlices(u8, &pt, &pt2);
479 }
480 { // test vector (106) from NIST KAT submission.
481 var ad: [12 / 2]u8 = undefined;
482 _ = try std.fmt.hexToBytes(&ad, "000102030405");
483 var pt: [6 / 2]u8 = undefined;
484 _ = try std.fmt.hexToBytes(&pt, "000102");
485
486 var ct: [pt.len]u8 = undefined;
487 var tag: [16]u8 = undefined;
488 Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
489 try htest.assertEqual("484D35", &ct);
490 try htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &tag);
491
492 var pt2: [pt.len]u8 = undefined;
493 try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
494 try testing.expectEqualSlices(u8, &pt, &pt2);
495 }
496 { // test vector (790) from NIST KAT submission.
497 var ad: [60 / 2]u8 = undefined;
498 _ = try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
499 var pt: [46 / 2]u8 = undefined;
500 _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
501
502 var ct: [pt.len]u8 = undefined;
503 var tag: [16]u8 = undefined;
504 Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
505 try htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
506 try htest.assertEqual("DFE23F1642508290D68245279558B2FB", &tag);
507
508 var pt2: [pt.len]u8 = undefined;
509 try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
510 try testing.expectEqualSlices(u8, &pt, &pt2);
511 }
512 { // test vector (1057) from NIST KAT submission.
513 const ad: [0]u8 = undefined;
514 var pt: [64 / 2]u8 = undefined;
515 _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
516
517 var ct: [pt.len]u8 = undefined;
518 var tag: [16]u8 = undefined;
519 Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
520 try htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
521 try htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &tag);
522
523 var pt2: [pt.len]u8 = undefined;
524 try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
525 try testing.expectEqualSlices(u8, &pt, &pt2);
526 }
527}
lib/std/crypto/tlcsprng.zig+8-10
......@@ -41,9 +41,11 @@ const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
4141}) orelse true;
4242const is_haiku = builtin.os.tag == .haiku;
4343
44const Rng = std.rand.DefaultCsprng;
45
4446const Context = struct {
4547 init_state: enum(u8) { uninitialized = 0, initialized, failed },
46 gimli: std.crypto.core.Gimli,
48 rng: Rng,
4749};
4850
4951var install_atfork_handler = std.once(struct {
......@@ -93,7 +95,7 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
9395 const S = struct {
9496 threadlocal var buf: Context align(mem.page_size) = .{
9597 .init_state = .uninitialized,
96 .gimli = undefined,
98 .rng = undefined,
9799 };
98100 };
99101 wipe_mem = mem.asBytes(&S.buf);
......@@ -156,12 +158,7 @@ fn childAtForkHandler() callconv(.C) void {
156158
157159fn fillWithCsprng(buffer: []u8) void {
158160 const ctx = @ptrCast(*Context, wipe_mem.ptr);
159 if (buffer.len != 0) {
160 ctx.gimli.squeeze(buffer);
161 } else {
162 ctx.gimli.permute();
163 }
164 mem.set(u8, ctx.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
161 return ctx.rng.fill(buffer);
165162}
166163
167164pub fn defaultRandomSeed(buffer: []u8) void {
......@@ -169,7 +166,7 @@ pub fn defaultRandomSeed(buffer: []u8) void {
169166}
170167
171168fn initAndFill(buffer: []u8) void {
172 var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
169 var seed: [Rng.secret_seed_length]u8 = undefined;
173170 // Because we panic on getrandom() failing, we provide the opportunity
174171 // to override the default seed function. This also makes
175172 // `std.crypto.random` available on freestanding targets, provided that
......@@ -177,7 +174,8 @@ fn initAndFill(buffer: []u8) void {
177174 std.options.cryptoRandomSeed(&seed);
178175
179176 const ctx = @ptrCast(*Context, wipe_mem.ptr);
180 ctx.gimli = std.crypto.core.Gimli.init(seed);
177 ctx.rng = Rng.init(seed);
178 std.crypto.utils.secureZero(u8, &seed);
181179
182180 // This is at the end so that accidental recursive dependencies result
183181 // in stack overflows instead of invalid random data.
lib/std/crypto/xoodoo.zig deleted-141
......@@ -1,141 +0,0 @@
1//! Xoodoo is a 384-bit permutation designed to achieve high security with high
2//! performance across a broad range of platforms, including 64-bit Intel/AMD
3//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
4//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
5//! side-channel protection, and ASICs with side-channel protection.
6//!
7//! Xoodoo is the core function of Xoodyak, a finalist of the NIST lightweight cryptography competition.
8//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/Xoodyak-spec.pdf
9//!
10//! It is not meant to be used directly, but as a building block for symmetric cryptography.
11
12const std = @import("../std.zig");
13const builtin = @import("builtin");
14const mem = std.mem;
15const math = std.math;
16const testing = std.testing;
17
18/// A Xoodoo state.
19pub const State = struct {
20 /// Number of bytes in the state.
21 pub const block_bytes = 48;
22
23 const rcs = [12]u32{ 0x058, 0x038, 0x3c0, 0x0d0, 0x120, 0x014, 0x060, 0x02c, 0x380, 0x0f0, 0x1a0, 0x012 };
24 const Lane = @Vector(4, u32);
25 st: [3]Lane,
26
27 /// Initialize a state from a slice of bytes.
28 pub fn init(initial_state: [block_bytes]u8) State {
29 var state = State{ .st = undefined };
30 mem.copy(u8, state.asBytes(), &initial_state);
31 state.endianSwap();
32 return state;
33 }
34
35 // A representation of the state as 32-bit words.
36 fn asWords(self: *State) *[12]u32 {
37 return @ptrCast(*[12]u32, &self.st);
38 }
39
40 /// A representation of the state as bytes. The byte order is architecture-dependent.
41 pub fn asBytes(self: *State) *[block_bytes]u8 {
42 return mem.asBytes(&self.st);
43 }
44
45 /// Byte-swap words storing the bytes of a given range if the architecture is not little-endian.
46 pub fn endianSwapPartial(self: *State, from: usize, to: usize) void {
47 for (self.asWords()[from / 4 .. (to + 3) / 4]) |*w| {
48 w.* = mem.littleToNative(u32, w.*);
49 }
50 }
51
52 /// Byte-swap the entire state if the architecture is not little-endian.
53 pub fn endianSwap(self: *State) void {
54 for (self.asWords()) |*w| {
55 w.* = mem.littleToNative(u32, w.*);
56 }
57 }
58
59 /// XOR a byte into the state at a given offset.
60 pub fn addByte(self: *State, byte: u8, offset: usize) void {
61 self.endianSwapPartial(offset, offset);
62 self.asBytes()[offset] ^= byte;
63 self.endianSwapPartial(offset, offset);
64 }
65
66 /// XOR bytes into the beginning of the state.
67 pub fn addBytes(self: *State, bytes: []const u8) void {
68 self.endianSwap();
69 for (self.asBytes()[0..bytes.len], 0..) |*byte, i| {
70 byte.* ^= bytes[i];
71 }
72 self.endianSwap();
73 }
74
75 /// Extract the first bytes of the state.
76 pub fn extract(self: *State, out: []u8) void {
77 self.endianSwap();
78 mem.copy(u8, out, self.asBytes()[0..out.len]);
79 self.endianSwap();
80 }
81
82 /// Set the words storing the bytes of a given range to zero.
83 pub fn clear(self: *State, from: usize, to: usize) void {
84 mem.set(u32, self.asWords()[from / 4 .. (to + 3) / 4], 0);
85 }
86
87 /// Apply the Xoodoo permutation.
88 pub fn permute(self: *State) void {
89 const rot8x32 = comptime if (builtin.target.cpu.arch.endian() == .Big)
90 [_]i32{ 9, 10, 11, 8, 13, 14, 15, 12, 1, 2, 3, 0, 5, 6, 7, 4 }
91 else
92 [_]i32{ 11, 8, 9, 10, 15, 12, 13, 14, 3, 0, 1, 2, 7, 4, 5, 6 };
93
94 var a = self.st[0];
95 var b = self.st[1];
96 var c = self.st[2];
97 inline for (rcs) |rc| {
98 var p = @shuffle(u32, a ^ b ^ c, undefined, [_]i32{ 3, 0, 1, 2 });
99 var e = math.rotl(Lane, p, 5);
100 p = math.rotl(Lane, p, 14);
101 e ^= p;
102 a ^= e;
103 b ^= e;
104 c ^= e;
105 b = @shuffle(u32, b, undefined, [_]i32{ 3, 0, 1, 2 });
106 c = math.rotl(Lane, c, 11);
107 a[0] ^= rc;
108 a ^= ~b & c;
109 b ^= ~c & a;
110 c ^= ~a & b;
111 b = math.rotl(Lane, b, 1);
112 c = @bitCast(Lane, @shuffle(u8, @bitCast(@Vector(16, u8), c), undefined, rot8x32));
113 }
114 self.st[0] = a;
115 self.st[1] = b;
116 self.st[2] = c;
117 }
118};
119
120test "xoodoo" {
121 const bytes = [_]u8{0x01} ** State.block_bytes;
122 var st = State.init(bytes);
123 var out: [State.block_bytes]u8 = undefined;
124 st.permute();
125 st.extract(&out);
126 const expected1 = [_]u8{ 51, 240, 163, 117, 43, 238, 62, 200, 114, 52, 79, 41, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 };
127 try testing.expectEqualSlices(u8, &expected1, &out);
128 st.clear(0, 10);
129 st.extract(&out);
130 const expected2 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 };
131 try testing.expectEqualSlices(u8, &expected2, &out);
132 st.addByte(1, 5);
133 st.addByte(2, 5);
134 st.extract(&out);
135 const expected3 = [_]u8{ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 };
136 try testing.expectEqualSlices(u8, &expected3, &out);
137 st.addBytes(&bytes);
138 st.extract(&out);
139 const expected4 = [_]u8{ 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 49, 109, 151, 180, 25, 4, 253, 184, 234, 178, 29, 2, 117, 171, 37, 14, 233, 34, 117, 60, 111, 5, 108, 226, 90, 204, 1, 181, 178, 147, 113, 234, 97, 213, 207, 204 };
140 try testing.expectEqualSlices(u8, &expected4, &out);
141}
lib/std/rand.zig+3-2
......@@ -18,11 +18,12 @@ const maxInt = std.math.maxInt;
1818pub const DefaultPrng = Xoshiro256;
1919
2020/// Cryptographically secure random numbers.
21pub const DefaultCsprng = Ascon;
21pub const DefaultCsprng = ChaCha;
2222
2323pub const Ascon = @import("rand/Ascon.zig");
24pub const ChaCha = @import("rand/ChaCha.zig");
25
2426pub const Isaac64 = @import("rand/Isaac64.zig");
25pub const Xoodoo = @import("rand/Xoodoo.zig");
2627pub const Pcg = @import("rand/Pcg.zig");
2728pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig");
2829pub const Xoshiro256 = @import("rand/Xoshiro256.zig");
lib/std/rand/Ascon.zig+29-14
......@@ -1,4 +1,12 @@
1//! CSPRNG based on the Ascon XOFa construction
1//! CSPRNG based on the Reverie construction, a permutation-based PRNG
2//! with forward security, instantiated with the Ascon(128,12,8) permutation.
3//!
4//! Compared to ChaCha, this PRNG is has a much smaller state, and can be
5//! a better choice for constrained environments.
6//!
7//! References:
8//! - A Robust and Sponge-Like PRNG with Improved Efficiency https://eprint.iacr.org/2016/886.pdf
9//! - Ascon https://ascon.iaik.tugraz.at/files/asconv12-nist.pdf
210
311const std = @import("std");
412const min = std.math.min;
......@@ -6,30 +14,38 @@ const mem = std.mem;
614const Random = std.rand.Random;
715const Self = @This();
816
9state: std.crypto.core.Ascon(.Little),
17const Ascon = std.crypto.core.Ascon(.Little);
1018
11const rate = 8;
19state: Ascon,
20
21const rate = 16;
1222pub const secret_seed_length = 32;
1323
1424/// The seed must be uniform, secret and `secret_seed_length` bytes long.
1525pub fn init(secret_seed: [secret_seed_length]u8) Self {
16 var state = std.crypto.core.Ascon(.Little).initXofA();
26 var self = Self{ .state = Ascon.initXof() };
27 self.addEntropy(&secret_seed);
28 return self;
29}
30
31/// Inserts entropy to refresh the internal state.
32pub fn addEntropy(self: *Self, bytes: []const u8) void {
33 comptime std.debug.assert(secret_seed_length % rate == 0);
1734 var i: usize = 0;
18 while (i + rate <= secret_seed.len) : (i += rate) {
19 state.addBytes(secret_seed[i..][0..rate]);
20 state.permuteR(8);
35 while (i + rate < bytes.len) : (i += rate) {
36 self.state.addBytes(bytes[i..][0..rate]);
37 self.state.permuteR(8);
2138 }
22 const left = secret_seed.len - i;
23 if (left > 0) state.addBytes(secret_seed[i..]);
24 state.addByte(0x80, left);
25 state.permute();
26 return Self{ .state = state };
39 if (i != bytes.len) self.state.addBytes(bytes[i..]);
40 self.state.permute();
2741}
2842
43/// Returns a `std.rand.Random` structure backed by the current RNG.
2944pub fn random(self: *Self) Random {
3045 return Random.init(self, fill);
3146}
3247
48/// Fills the buffer with random bytes.
3349pub fn fill(self: *Self, buf: []u8) void {
3450 var i: usize = 0;
3551 while (true) {
......@@ -40,6 +56,5 @@ pub fn fill(self: *Self, buf: []u8) void {
4056 self.state.permuteR(8);
4157 i += n;
4258 }
43 self.state.clear(0, rate);
44 self.state.permuteR(8);
59 self.state.permuteRatchet(6, rate);
4560}
lib/std/rand/ChaCha.zig created+97
......@@ -0,0 +1,97 @@
1//! CSPRNG based on the ChaCha8 stream cipher, with forward security.
2//!
3//! References:
4//! - Fast-key-erasure random-number generators https://blog.cr.yp.to/20170723-random.html
5
6const std = @import("std");
7const mem = std.mem;
8const Random = std.rand.Random;
9const Self = @This();
10
11const Cipher = std.crypto.stream.chacha.ChaCha8IETF;
12
13const State = [2 * Cipher.block_length]u8;
14
15state: State,
16offset: usize,
17
18const nonce = [_]u8{0} ** Cipher.nonce_length;
19
20pub const secret_seed_length = Cipher.key_length;
21
22/// The seed must be uniform, secret and `secret_seed_length` bytes long.
23pub fn init(secret_seed: [secret_seed_length]u8) Self {
24 var self = Self{ .state = undefined, .offset = 0 };
25 Cipher.stream(&self.state, 0, secret_seed, nonce);
26 return self;
27}
28
29/// Inserts entropy to refresh the internal state.
30pub fn addEntropy(self: *Self, bytes: []const u8) void {
31 var i: usize = 0;
32 while (i + Cipher.key_length <= bytes.len) : (i += Cipher.key_length) {
33 Cipher.xor(
34 self.state[0..Cipher.key_length],
35 self.state[0..Cipher.key_length],
36 0,
37 bytes[i..][0..Cipher.key_length].*,
38 nonce,
39 );
40 }
41 if (i < bytes.len) {
42 var k = [_]u8{0} ** Cipher.key_length;
43 mem.copy(u8, k[0..], bytes[i..]);
44 Cipher.xor(
45 self.state[0..Cipher.key_length],
46 self.state[0..Cipher.key_length],
47 0,
48 k,
49 nonce,
50 );
51 }
52 self.refill();
53}
54
55/// Returns a `std.rand.Random` structure backed by the current RNG.
56pub fn random(self: *Self) Random {
57 return Random.init(self, fill);
58}
59
60// Refills the buffer with random bytes, overwriting the previous key.
61fn refill(self: *Self) void {
62 Cipher.stream(&self.state, 0, self.state[0..Cipher.key_length].*, nonce);
63 self.offset = 0;
64}
65
66/// Fills the buffer with random bytes.
67pub fn fill(self: *Self, buf_: []u8) void {
68 const bytes = self.state[Cipher.key_length..];
69 var buf = buf_;
70
71 const avail = bytes.len - self.offset;
72 if (avail > 0) {
73 // Bytes from the current block
74 const n = @min(avail, buf.len);
75 mem.copy(u8, buf[0..n], bytes[self.offset..][0..n]);
76 mem.set(u8, bytes[self.offset..][0..n], 0);
77 buf = buf[n..];
78 self.offset += n;
79 }
80 if (buf.len == 0) return;
81
82 self.refill();
83
84 // Full blocks
85 while (buf.len >= bytes.len) {
86 mem.copy(u8, buf[0..bytes.len], bytes);
87 buf = buf[bytes.len..];
88 self.refill();
89 }
90
91 // Remaining bytes
92 if (buf.len > 0) {
93 mem.copy(u8, buf, bytes[0..buf.len]);
94 mem.set(u8, bytes[0..buf.len], 0);
95 self.offset = buf.len;
96 }
97}
lib/std/rand/Gimli.zig deleted-34
......@@ -1,34 +0,0 @@
1//! CSPRNG
2
3const std = @import("std");
4const Random = std.rand.Random;
5const mem = std.mem;
6const Gimli = @This();
7
8state: std.crypto.core.Gimli,
9
10pub const secret_seed_length = 32;
11
12/// The seed must be uniform, secret and `secret_seed_length` bytes long.
13pub fn init(secret_seed: [secret_seed_length]u8) Gimli {
14 var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
15 mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed);
16 mem.set(u8, initial_state[secret_seed_length..], 0);
17 var self = Gimli{
18 .state = std.crypto.core.Gimli.init(initial_state),
19 };
20 return self;
21}
22
23pub fn random(self: *Gimli) Random {
24 return Random.init(self, fill);
25}
26
27pub fn fill(self: *Gimli, buf: []u8) void {
28 if (buf.len != 0) {
29 self.state.squeeze(buf);
30 } else {
31 self.state.permute();
32 }
33 mem.set(u8, self.state.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
34}
lib/std/rand/Xoodoo.zig deleted-42
......@@ -1,42 +0,0 @@
1//! CSPRNG
2
3const std = @import("std");
4const Random = std.rand.Random;
5const min = std.math.min;
6const mem = std.mem;
7const Xoodoo = @This();
8
9const State = std.crypto.core.Xoodoo;
10
11state: State,
12
13const rate = 16;
14pub const secret_seed_length = 32;
15
16/// The seed must be uniform, secret and `secret_seed_length` bytes long.
17pub fn init(secret_seed: [secret_seed_length]u8) Xoodoo {
18 var initial_state: [State.block_bytes]u8 = undefined;
19 mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed);
20 mem.set(u8, initial_state[secret_seed_length..], 0);
21 var state = State.init(initial_state);
22 state.permute();
23 return Xoodoo{ .state = state };
24}
25
26pub fn random(self: *Xoodoo) Random {
27 return Random.init(self, fill);
28}
29
30pub fn fill(self: *Xoodoo, buf: []u8) void {
31 var i: usize = 0;
32 while (true) {
33 const left = buf.len - i;
34 const n = min(left, rate);
35 self.state.extract(buf[i..][0..n]);
36 if (left == 0) break;
37 self.state.permute();
38 i += n;
39 }
40 self.state.clear(0, rate);
41 self.state.permute();
42}
lib/std/rand/benchmark.zig created+217
......@@ -0,0 +1,217 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2
3const std = @import("std");
4const builtin = @import("builtin");
5const time = std.time;
6const Timer = time.Timer;
7const rand = std.rand;
8
9const KiB = 1024;
10const MiB = 1024 * KiB;
11const GiB = 1024 * MiB;
12
13const Rng = struct {
14 ty: type,
15 name: []const u8,
16 init_u8s: ?[]const u8 = null,
17 init_u64: ?u64 = null,
18};
19
20const prngs = [_]Rng{
21 Rng{
22 .ty = rand.Isaac64,
23 .name = "isaac64",
24 .init_u64 = 0,
25 },
26 Rng{
27 .ty = rand.Pcg,
28 .name = "pcg",
29 .init_u64 = 0,
30 },
31 Rng{
32 .ty = rand.RomuTrio,
33 .name = "romutrio",
34 .init_u64 = 0,
35 },
36 Rng{
37 .ty = std.rand.Sfc64,
38 .name = "sfc64",
39 .init_u64 = 0,
40 },
41 Rng{
42 .ty = std.rand.Xoroshiro128,
43 .name = "xoroshiro128",
44 .init_u64 = 0,
45 },
46 Rng{
47 .ty = std.rand.Xoshiro256,
48 .name = "xoshiro256",
49 .init_u64 = 0,
50 },
51};
52
53const csprngs = [_]Rng{
54 Rng{
55 .ty = rand.Ascon,
56 .name = "ascon",
57 .init_u8s = &[_]u8{0} ** 32,
58 },
59 Rng{
60 .ty = rand.ChaCha,
61 .name = "chacha",
62 .init_u8s = &[_]u8{0} ** 32,
63 },
64};
65
66const Result = struct {
67 throughput: u64,
68};
69
70const long_block_size: usize = 8 * 8192;
71const short_block_size: usize = 8;
72
73pub fn benchmark(comptime H: anytype, bytes: usize, comptime block_size: usize) !Result {
74 var rng = blk: {
75 if (H.init_u8s) |init| {
76 break :blk H.ty.init(init[0..].*);
77 }
78 if (H.init_u64) |init| {
79 break :blk H.ty.init(init);
80 }
81 break :blk H.ty.init();
82 };
83
84 var block: [block_size]u8 = undefined;
85
86 var offset: usize = 0;
87 var timer = try Timer.start();
88 const start = timer.lap();
89 while (offset < bytes) : (offset += block.len) {
90 rng.fill(block[0..]);
91 }
92 const end = timer.read();
93
94 const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
95 const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
96
97 std.debug.assert(rng.random().int(u64) != 0);
98
99 return Result{
100 .throughput = throughput,
101 };
102}
103
104fn usage() void {
105 std.debug.print(
106 \\throughput_test [options]
107 \\
108 \\Options:
109 \\ --filter [test-name]
110 \\ --count [int]
111 \\ --prngs-only
112 \\ --csprngs-only
113 \\ --short-only
114 \\ --long-only
115 \\ --help
116 \\
117 , .{});
118}
119
120fn mode(comptime x: comptime_int) comptime_int {
121 return if (builtin.mode == .Debug) x / 64 else x;
122}
123
124pub fn main() !void {
125 const stdout = std.io.getStdOut().writer();
126
127 var buffer: [1024]u8 = undefined;
128 var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
129 const args = try std.process.argsAlloc(fixed.allocator());
130
131 var filter: ?[]u8 = "";
132 var count: usize = mode(128 * MiB);
133 var bench_prngs = true;
134 var bench_csprngs = true;
135 var bench_long = true;
136 var bench_short = true;
137
138 var i: usize = 1;
139 while (i < args.len) : (i += 1) {
140 if (std.mem.eql(u8, args[i], "--mode")) {
141 try stdout.print("{}\n", .{builtin.mode});
142 return;
143 } else if (std.mem.eql(u8, args[i], "--filter")) {
144 i += 1;
145 if (i == args.len) {
146 usage();
147 std.os.exit(1);
148 }
149
150 filter = args[i];
151 } else if (std.mem.eql(u8, args[i], "--count")) {
152 i += 1;
153 if (i == args.len) {
154 usage();
155 std.os.exit(1);
156 }
157
158 const c = try std.fmt.parseUnsigned(usize, args[i], 10);
159 count = c * MiB;
160 } else if (std.mem.eql(u8, args[i], "--csprngs-only")) {
161 bench_prngs = false;
162 } else if (std.mem.eql(u8, args[i], "--prngs-only")) {
163 bench_csprngs = false;
164 } else if (std.mem.eql(u8, args[i], "--short-only")) {
165 bench_long = false;
166 } else if (std.mem.eql(u8, args[i], "--long-only")) {
167 bench_short = false;
168 } else if (std.mem.eql(u8, args[i], "--help")) {
169 usage();
170 return;
171 } else {
172 usage();
173 std.os.exit(1);
174 }
175 }
176
177 if (bench_prngs) {
178 if (bench_long) {
179 inline for (prngs) |R| {
180 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
181 try stdout.print("{s} (long outputs)\n", .{R.name});
182 const result_long = try benchmark(R, count, long_block_size);
183 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
184 }
185 }
186 }
187 if (bench_short) {
188 inline for (prngs) |R| {
189 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
190 try stdout.print("{s} (short outputs)\n", .{R.name});
191 const result_short = try benchmark(R, count, short_block_size);
192 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
193 }
194 }
195 }
196 }
197 if (bench_csprngs) {
198 if (bench_long) {
199 inline for (csprngs) |R| {
200 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
201 try stdout.print("{s} (cryptographic, long outputs)\n", .{R.name});
202 const result_long = try benchmark(R, count, long_block_size);
203 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
204 }
205 }
206 }
207 if (bench_short) {
208 inline for (csprngs) |R| {
209 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
210 try stdout.print("{s} (cryptographic, short outputs)\n", .{R.name});
211 const result_short = try benchmark(R, count, short_block_size);
212 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
213 }
214 }
215 }
216 }
217}