Commit dff4bbfd24
Changed files (13)
lib/std/crypto/ascon.zig
@@ -168,6 +168,17 @@ pub fn State(comptime endian: builtin.Endian) type {
state.permuteR(12);
}
+ /// Apply a permutation to the state and prevent backtracking.
+ /// The rate is expressed in bytes and must be a multiple of the word size (8).
+ pub inline fn permuteRatchet(state: *Self, comptime rounds: u4, comptime rate: u6) void {
+ const capacity = block_bytes - rate;
+ debug.assert(capacity > 0 and capacity % 8 == 0); // capacity must be a multiple of 64 bits
+ var mask: [capacity / 8]u64 = undefined;
+ inline for (&mask, state.st[state.st.len - mask.len ..]) |*m, x| m.* = x;
+ state.permuteR(rounds);
+ inline for (mask, state.st[state.st.len - mask.len ..]) |m, *x| x.* ^= m;
+ }
+
// Core Ascon permutation.
inline fn round(state: *Self, rk: u64) void {
const x = &state.st;
lib/std/crypto/benchmark.zig
@@ -29,7 +29,6 @@ const hashes = [_]Crypto{
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
Crypto{ .ty = crypto.hash.sha3.TurboShake128(null), .name = "turboshake-128" },
Crypto{ .ty = crypto.hash.sha3.TurboShake256(null), .name = "turboshake-256" },
- Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" },
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
Crypto{ .ty = crypto.hash.Blake3, .name = "blake3" },
@@ -274,7 +273,6 @@ const aeads = [_]Crypto{
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" },
Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
- Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" },
Crypto{ .ty = crypto.aead.aegis.Aegis256, .name = "aegis-256" },
Crypto{ .ty = crypto.aead.aes_gcm.Aes128Gcm, .name = "aes128-gcm" },
lib/std/crypto/chacha20.zig
@@ -195,6 +195,26 @@ fn ChaChaVecImpl(comptime rounds_nb: usize) type {
}
}
+ fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
+ var ctx = initContext(key, counter);
+ var x: BlockVec = undefined;
+ var i: usize = 0;
+ while (i + 64 <= out.len) : (i += 64) {
+ chacha20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+ hashToBytes(out[i..][0..64], x);
+ ctx[3][0] += 1;
+ }
+ if (i < out.len) {
+ chacha20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+
+ var buf: [64]u8 = undefined;
+ hashToBytes(buf[0..], x);
+ mem.copy(u8, out[i..], buf[0 .. out.len - i]);
+ }
+ }
+
fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
var c: [4]u32 = undefined;
for (c, 0..) |_, i| {
@@ -336,6 +356,26 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
}
}
+ fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
+ var ctx = initContext(key, counter);
+ var x: BlockVec = undefined;
+ var i: usize = 0;
+ while (i + 64 <= out.len) : (i += 64) {
+ chacha20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+ hashToBytes(out[i..][0..64], x);
+ ctx[12] += 1;
+ }
+ if (i < out.len) {
+ chacha20Core(x[0..], ctx);
+ contextFeedback(&x, ctx);
+
+ var buf: [64]u8 = undefined;
+ hashToBytes(buf[0..], x);
+ mem.copy(u8, out[i..], buf[0 .. out.len - i]);
+ }
+ }
+
fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 {
var c: [4]u32 = undefined;
for (c, 0..) |_, i| {
@@ -387,6 +427,8 @@ fn ChaChaIETF(comptime rounds_nb: usize) type {
pub const nonce_length = 12;
/// Key length in bytes.
pub const key_length = 32;
+ /// Block length in bytes.
+ pub const block_length = 64;
/// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
/// WARNING: This function doesn't provide authenticated encryption.
@@ -402,6 +444,18 @@ fn ChaChaIETF(comptime rounds_nb: usize) type {
d[3] = mem.readIntLittle(u32, nonce[8..12]);
ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
}
+
+ /// Write the output of the ChaCha20 stream cipher into `out`.
+ pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
+ assert(out.len / 64 <= (1 << 32 - 1) - counter);
+
+ var d: [4]u32 = undefined;
+ d[0] = counter;
+ d[1] = mem.readIntLittle(u32, nonce[0..4]);
+ d[2] = mem.readIntLittle(u32, nonce[4..8]);
+ d[3] = mem.readIntLittle(u32, nonce[8..12]);
+ ChaChaImpl(rounds_nb).chacha20Stream(out, keyToWords(key), d);
+ }
};
}
@@ -411,6 +465,8 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
pub const nonce_length = 8;
/// Key length in bytes.
pub const key_length = 32;
+ /// Block length in bytes.
+ pub const block_length = 64;
/// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`.
/// WARNING: This function doesn't provide authenticated encryption.
@@ -427,7 +483,6 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
c[2] = mem.readIntLittle(u32, nonce[0..4]);
c[3] = mem.readIntLittle(u32, nonce[4..8]);
- const block_length = (1 << 6);
// The full block size is greater than the address space on a 32bit machine
const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
@@ -448,6 +503,18 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
}
ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c);
}
+
+ /// Write the output of the ChaCha20 stream cipher into `out`.
+ pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
+ assert(out.len / 64 <= (1 << 32 - 1) - counter);
+ const k = keyToWords(key);
+ var c: [4]u32 = undefined;
+ c[0] = @truncate(u32, counter);
+ c[1] = @truncate(u32, counter >> 32);
+ c[2] = mem.readIntLittle(u32, nonce[0..4]);
+ c[3] = mem.readIntLittle(u32, nonce[4..8]);
+ ChaChaImpl(rounds_nb).chacha20Stream(out, k, c);
+ }
};
}
@@ -457,6 +524,8 @@ fn XChaChaIETF(comptime rounds_nb: usize) type {
pub const nonce_length = 24;
/// Key length in bytes.
pub const key_length = 32;
+ /// Block length in bytes.
+ pub const block_length = 64;
/// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`.
/// WARNING: This function doesn't provide authenticated encryption.
@@ -465,6 +534,12 @@ fn XChaChaIETF(comptime rounds_nb: usize) type {
const extended = extend(key, nonce, rounds_nb);
ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce);
}
+
+ /// Write the output of the XChaCha20 stream cipher into `out`.
+ pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
+ const extended = extend(key, nonce, rounds_nb);
+ ChaChaIETF(rounds_nb).xor(out, counter, extended.key, extended.nonce);
+ }
};
}
lib/std/crypto/gimli.zig
@@ -1,527 +0,0 @@
-//! Gimli is a 384-bit permutation designed to achieve high security with high
-//! performance across a broad range of platforms, including 64-bit Intel/AMD
-//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
-//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
-//! side-channel protection, and ASICs with side-channel protection.
-//!
-//! https://gimli.cr.yp.to/
-//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf
-
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const mem = std.mem;
-const math = std.math;
-const debug = std.debug;
-const assert = std.debug.assert;
-const testing = std.testing;
-const htest = @import("test.zig");
-const AuthenticationError = std.crypto.errors.AuthenticationError;
-
-pub const State = struct {
- pub const BLOCKBYTES = 48;
- pub const RATE = 16;
-
- data: [BLOCKBYTES / 4]u32 align(16),
-
- const Self = @This();
-
- pub fn init(initial_state: [State.BLOCKBYTES]u8) Self {
- var data: [BLOCKBYTES / 4]u32 = undefined;
- var i: usize = 0;
- while (i < State.BLOCKBYTES) : (i += 4) {
- data[i / 4] = mem.readIntNative(u32, initial_state[i..][0..4]);
- }
- return Self{ .data = data };
- }
-
- /// TODO follow the span() convention instead of having this and `toSliceConst`
- pub fn toSlice(self: *Self) *[BLOCKBYTES]u8 {
- return mem.asBytes(&self.data);
- }
-
- /// TODO follow the span() convention instead of having this and `toSlice`
- pub fn toSliceConst(self: *const Self) *const [BLOCKBYTES]u8 {
- return mem.asBytes(&self.data);
- }
-
- inline fn endianSwap(self: *Self) void {
- for (&self.data) |*w| {
- w.* = mem.littleToNative(u32, w.*);
- }
- }
-
- fn permute_unrolled(self: *Self) void {
- self.endianSwap();
- const state = &self.data;
- comptime var round = @as(u32, 24);
- inline while (round > 0) : (round -= 1) {
- var column = @as(usize, 0);
- while (column < 4) : (column += 1) {
- const x = math.rotl(u32, state[column], 24);
- const y = math.rotl(u32, state[4 + column], 9);
- const z = state[8 + column];
- state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
- state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
- state[column] = ((z ^ y) ^ ((x & y) << 3));
- }
- switch (round & 3) {
- 0 => {
- mem.swap(u32, &state[0], &state[1]);
- mem.swap(u32, &state[2], &state[3]);
- state[0] ^= round | 0x9e377900;
- },
- 2 => {
- mem.swap(u32, &state[0], &state[2]);
- mem.swap(u32, &state[1], &state[3]);
- },
- else => {},
- }
- }
- self.endianSwap();
- }
-
- fn permute_small(self: *Self) void {
- self.endianSwap();
- const state = &self.data;
- var round = @as(u32, 24);
- while (round > 0) : (round -= 1) {
- var column = @as(usize, 0);
- while (column < 4) : (column += 1) {
- const x = math.rotl(u32, state[column], 24);
- const y = math.rotl(u32, state[4 + column], 9);
- const z = state[8 + column];
- state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
- state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
- state[column] = ((z ^ y) ^ ((x & y) << 3));
- }
- switch (round & 3) {
- 0 => {
- mem.swap(u32, &state[0], &state[1]);
- mem.swap(u32, &state[2], &state[3]);
- state[0] ^= round | 0x9e377900;
- },
- 2 => {
- mem.swap(u32, &state[0], &state[2]);
- mem.swap(u32, &state[1], &state[3]);
- },
- else => {},
- }
- }
- self.endianSwap();
- }
-
- const Lane = @Vector(4, u32);
-
- inline fn shift(x: Lane, comptime n: comptime_int) Lane {
- return x << @splat(4, @as(u5, n));
- }
-
- fn permute_vectorized(self: *Self) void {
- self.endianSwap();
- const state = &self.data;
- var x = Lane{ state[0], state[1], state[2], state[3] };
- var y = Lane{ state[4], state[5], state[6], state[7] };
- var z = Lane{ state[8], state[9], state[10], state[11] };
- var round = @as(u32, 24);
- while (round > 0) : (round -= 1) {
- x = math.rotl(Lane, x, 24);
- y = math.rotl(Lane, y, 9);
- const newz = x ^ shift(z, 1) ^ shift(y & z, 2);
- const newy = y ^ x ^ shift(x | z, 1);
- const newx = z ^ y ^ shift(x & y, 3);
- x = newx;
- y = newy;
- z = newz;
- switch (round & 3) {
- 0 => {
- x = @shuffle(u32, x, undefined, [_]i32{ 1, 0, 3, 2 });
- x[0] ^= round | 0x9e377900;
- },
- 2 => {
- x = @shuffle(u32, x, undefined, [_]i32{ 2, 3, 0, 1 });
- },
- else => {},
- }
- }
- comptime var i: usize = 0;
- inline while (i < 4) : (i += 1) {
- state[0 + i] = x[i];
- state[4 + i] = y[i];
- state[8 + i] = z[i];
- }
- self.endianSwap();
- }
-
- pub const permute = if (builtin.cpu.arch == .x86_64) impl: {
- break :impl permute_vectorized;
- } else if (builtin.mode == .ReleaseSmall) impl: {
- break :impl permute_small;
- } else impl: {
- break :impl permute_unrolled;
- };
-
- pub fn squeeze(self: *Self, out: []u8) void {
- var i = @as(usize, 0);
- while (i + RATE <= out.len) : (i += RATE) {
- self.permute();
- mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]);
- }
- const leftover = out.len - i;
- if (leftover != 0) {
- self.permute();
- mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]);
- }
- }
-};
-
-test "permute" {
- // test vector from gimli-20170627
- const tv_input = [3][4]u32{
- [4]u32{ 0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46 },
- [4]u32{ 0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566 },
- [4]u32{ 0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026 },
- };
- var input: [48]u8 = undefined;
- var i: usize = 0;
- while (i < 12) : (i += 1) {
- mem.writeIntLittle(u32, input[i * 4 ..][0..4], tv_input[i / 4][i % 4]);
- }
-
- var state = State.init(input);
- state.permute();
-
- const tv_output = [3][4]u32{
- [4]u32{ 0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68 },
- [4]u32{ 0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8 },
- [4]u32{ 0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6 },
- };
- var expected_output: [48]u8 = undefined;
- i = 0;
- while (i < 12) : (i += 1) {
- mem.writeIntLittle(u32, expected_output[i * 4 ..][0..4], tv_output[i / 4][i % 4]);
- }
- try testing.expectEqualSlices(u8, state.toSliceConst(), expected_output[0..]);
-}
-
-pub const Hash = struct {
- state: State,
- buf_off: usize,
-
- pub const block_length = State.RATE;
- pub const digest_length = 32;
- pub const Options = struct {};
-
- const Self = @This();
-
- pub fn init(options: Options) Self {
- _ = options;
- return Self{
- .state = State{ .data = [_]u32{0} ** (State.BLOCKBYTES / 4) },
- .buf_off = 0,
- };
- }
-
- /// Also known as 'absorb'
- pub fn update(self: *Self, data: []const u8) void {
- const buf = self.state.toSlice();
- var in = data;
- while (in.len > 0) {
- const left = State.RATE - self.buf_off;
- const ps = math.min(in.len, left);
- for (buf[self.buf_off .. self.buf_off + ps], 0..) |*p, i| {
- p.* ^= in[i];
- }
- self.buf_off += ps;
- in = in[ps..];
- if (self.buf_off == State.RATE) {
- self.state.permute();
- self.buf_off = 0;
- }
- }
- }
-
- /// Finish the current hashing operation, writing the hash to `out`
- ///
- /// From 4.9 "Application to hashing"
- /// By default, Gimli-Hash provides a fixed-length output of 32 bytes
- /// (the concatenation of two 16-byte blocks). However, Gimli-Hash can
- /// be used as an โextendable one-way functionโ (XOF).
- pub fn final(self: *Self, out: []u8) void {
- const buf = self.state.toSlice();
-
- // XOR 1 into the next byte of the state
- buf[self.buf_off] ^= 1;
- // XOR 1 into the last byte of the state, position 47.
- buf[buf.len - 1] ^= 1;
-
- self.state.squeeze(out);
- }
-
- pub const Error = error{};
- pub const Writer = std.io.Writer(*Self, Error, write);
-
- fn write(self: *Self, bytes: []const u8) Error!usize {
- self.update(bytes);
- return bytes.len;
- }
-
- pub fn writer(self: *Self) Writer {
- return .{ .context = self };
- }
-};
-
-pub fn hash(out: []u8, in: []const u8, options: Hash.Options) void {
- var st = Hash.init(options);
- st.update(in);
- st.final(out);
-}
-
-test "hash" {
- // a test vector (30) from NIST KAT submission.
- var msg: [58 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C");
- var md: [32]u8 = undefined;
- hash(&md, &msg, .{});
- try htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
-}
-
-test "hash test vector 17" {
- var msg: [32 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F");
- var md: [32]u8 = undefined;
- hash(&md, &msg, .{});
- try htest.assertEqual("404C130AF1B9023A7908200919F690FFBB756D5176E056FFDE320016A37C7282", &md);
-}
-
-test "hash test vector 33" {
- var msg: [32]u8 = undefined;
- _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
- var md: [32]u8 = undefined;
- hash(&md, &msg, .{});
- try htest.assertEqual("A8F4FA28708BDA7EFB4C1914CA4AFA9E475B82D588D36504F87DBB0ED9AB3C4B", &md);
-}
-
-pub const Aead = struct {
- pub const tag_length = State.RATE;
- pub const nonce_length = 16;
- pub const key_length = 32;
-
- /// ad: Associated Data
- /// npub: public nonce
- /// k: private key
- fn init(ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) State {
- var state = State{
- .data = undefined,
- };
- const buf = state.toSlice();
-
- // Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
- // followed by a 32-byte key.
- assert(npub.len + k.len == State.BLOCKBYTES);
- std.mem.copy(u8, buf[0..npub.len], &npub);
- std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
-
- // It then applies the Gimli permutation.
- state.permute();
-
- {
- // Gimli-Cipher then handles each block of associated data, including
- // exactly one final non-full block, in the same way as Gimli-Hash.
- var data = ad;
- while (data.len >= State.RATE) : (data = data[State.RATE..]) {
- for (buf[0..State.RATE], 0..) |*p, i| {
- p.* ^= data[i];
- }
- state.permute();
- }
- for (buf[0..data.len], 0..) |*p, i| {
- p.* ^= data[i];
- }
-
- // XOR 1 into the next byte of the state
- buf[data.len] ^= 1;
- // XOR 1 into the last byte of the state, position 47.
- buf[buf.len - 1] ^= 1;
-
- state.permute();
- }
-
- return state;
- }
-
- /// c: ciphertext: output buffer should be of size m.len
- /// tag: authentication tag: output MAC
- /// m: message
- /// ad: Associated Data
- /// npub: public nonce
- /// k: private key
- pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
- assert(c.len == m.len);
-
- var state = Aead.init(ad, npub, k);
- const buf = state.toSlice();
-
- // Gimli-Cipher then handles each block of plaintext, including
- // exactly one final non-full block, in the same way as Gimli-Hash.
- // Whenever a plaintext byte is XORed into a state byte, the new state
- // byte is output as ciphertext.
- var in = m;
- var out = c;
- while (in.len >= State.RATE) : ({
- in = in[State.RATE..];
- out = out[State.RATE..];
- }) {
- for (in[0..State.RATE], 0..) |v, i| {
- buf[i] ^= v;
- }
- mem.copy(u8, out[0..State.RATE], buf[0..State.RATE]);
- state.permute();
- }
- for (in[0..], 0..) |v, i| {
- buf[i] ^= v;
- out[i] = buf[i];
- }
-
- // XOR 1 into the next byte of the state
- buf[in.len] ^= 1;
- // XOR 1 into the last byte of the state, position 47.
- buf[buf.len - 1] ^= 1;
-
- state.permute();
-
- // After the final non-full block of plaintext, the first 16 bytes
- // of the state are output as an authentication tag.
- std.mem.copy(u8, tag, buf[0..State.RATE]);
- }
-
- /// m: message: output buffer should be of size c.len
- /// c: ciphertext
- /// tag: authentication tag
- /// ad: Associated Data
- /// npub: public nonce
- /// k: private key
- /// NOTE: the check of the authentication tag is currently not done in constant time
- pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
- assert(c.len == m.len);
-
- var state = Aead.init(ad, npub, k);
- const buf = state.toSlice();
-
- var in = c;
- var out = m;
- while (in.len >= State.RATE) : ({
- in = in[State.RATE..];
- out = out[State.RATE..];
- }) {
- const d = in[0..State.RATE].*;
- for (d, 0..) |v, i| {
- out[i] = buf[i] ^ v;
- }
- mem.copy(u8, buf[0..State.RATE], d[0..State.RATE]);
- state.permute();
- }
- for (buf[0..in.len], 0..) |*p, i| {
- const d = in[i];
- out[i] = p.* ^ d;
- p.* = d;
- }
-
- // XOR 1 into the next byte of the state
- buf[in.len] ^= 1;
- // XOR 1 into the last byte of the state, position 47.
- buf[buf.len - 1] ^= 1;
-
- state.permute();
-
- // After the final non-full block of plaintext, the first 16 bytes
- // of the state are the authentication tag.
- // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
- if (!mem.eql(u8, buf[0..State.RATE], &tag)) {
- @memset(m.ptr, undefined, m.len);
- return error.AuthenticationFailed;
- }
- }
-};
-
-test "cipher" {
- var key: [32]u8 = undefined;
- _ = try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
- var nonce: [16]u8 = undefined;
- _ = try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
- { // test vector (1) from NIST KAT submission.
- const ad: [0]u8 = undefined;
- const pt: [0]u8 = undefined;
-
- var ct: [pt.len]u8 = undefined;
- var tag: [16]u8 = undefined;
- Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
- try htest.assertEqual("", &ct);
- try htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &tag);
-
- var pt2: [pt.len]u8 = undefined;
- try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
- try testing.expectEqualSlices(u8, &pt, &pt2);
- }
- { // test vector (34) from NIST KAT submission.
- const ad: [0]u8 = undefined;
- var pt: [2 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&pt, "00");
-
- var ct: [pt.len]u8 = undefined;
- var tag: [16]u8 = undefined;
- Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
- try htest.assertEqual("7F", &ct);
- try htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &tag);
-
- var pt2: [pt.len]u8 = undefined;
- try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
- try testing.expectEqualSlices(u8, &pt, &pt2);
- }
- { // test vector (106) from NIST KAT submission.
- var ad: [12 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&ad, "000102030405");
- var pt: [6 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&pt, "000102");
-
- var ct: [pt.len]u8 = undefined;
- var tag: [16]u8 = undefined;
- Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
- try htest.assertEqual("484D35", &ct);
- try htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &tag);
-
- var pt2: [pt.len]u8 = undefined;
- try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
- try testing.expectEqualSlices(u8, &pt, &pt2);
- }
- { // test vector (790) from NIST KAT submission.
- var ad: [60 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
- var pt: [46 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
-
- var ct: [pt.len]u8 = undefined;
- var tag: [16]u8 = undefined;
- Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
- try htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
- try htest.assertEqual("DFE23F1642508290D68245279558B2FB", &tag);
-
- var pt2: [pt.len]u8 = undefined;
- try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
- try testing.expectEqualSlices(u8, &pt, &pt2);
- }
- { // test vector (1057) from NIST KAT submission.
- const ad: [0]u8 = undefined;
- var pt: [64 / 2]u8 = undefined;
- _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
-
- var ct: [pt.len]u8 = undefined;
- var tag: [16]u8 = undefined;
- Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
- try htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
- try htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &tag);
-
- var pt2: [pt.len]u8 = undefined;
- try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
- try testing.expectEqualSlices(u8, &pt, &pt2);
- }
-}
lib/std/crypto/tlcsprng.zig
@@ -41,9 +41,11 @@ const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
}) orelse true;
const is_haiku = builtin.os.tag == .haiku;
+const Rng = std.rand.DefaultCsprng;
+
const Context = struct {
init_state: enum(u8) { uninitialized = 0, initialized, failed },
- gimli: std.crypto.core.Gimli,
+ rng: Rng,
};
var install_atfork_handler = std.once(struct {
@@ -93,7 +95,7 @@ fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
const S = struct {
threadlocal var buf: Context align(mem.page_size) = .{
.init_state = .uninitialized,
- .gimli = undefined,
+ .rng = undefined,
};
};
wipe_mem = mem.asBytes(&S.buf);
@@ -156,12 +158,7 @@ fn childAtForkHandler() callconv(.C) void {
fn fillWithCsprng(buffer: []u8) void {
const ctx = @ptrCast(*Context, wipe_mem.ptr);
- if (buffer.len != 0) {
- ctx.gimli.squeeze(buffer);
- } else {
- ctx.gimli.permute();
- }
- mem.set(u8, ctx.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
+ return ctx.rng.fill(buffer);
}
pub fn defaultRandomSeed(buffer: []u8) void {
@@ -169,7 +166,7 @@ pub fn defaultRandomSeed(buffer: []u8) void {
}
fn initAndFill(buffer: []u8) void {
- var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
+ var seed: [Rng.secret_seed_length]u8 = undefined;
// Because we panic on getrandom() failing, we provide the opportunity
// to override the default seed function. This also makes
// `std.crypto.random` available on freestanding targets, provided that
@@ -177,7 +174,8 @@ fn initAndFill(buffer: []u8) void {
std.options.cryptoRandomSeed(&seed);
const ctx = @ptrCast(*Context, wipe_mem.ptr);
- ctx.gimli = std.crypto.core.Gimli.init(seed);
+ ctx.rng = Rng.init(seed);
+ std.crypto.utils.secureZero(u8, &seed);
// This is at the end so that accidental recursive dependencies result
// in stack overflows instead of invalid random data.
lib/std/crypto/xoodoo.zig
@@ -1,141 +0,0 @@
-//! Xoodoo is a 384-bit permutation designed to achieve high security with high
-//! performance across a broad range of platforms, including 64-bit Intel/AMD
-//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
-//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
-//! side-channel protection, and ASICs with side-channel protection.
-//!
-//! Xoodoo is the core function of Xoodyak, a finalist of the NIST lightweight cryptography competition.
-//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/Xoodyak-spec.pdf
-//!
-//! It is not meant to be used directly, but as a building block for symmetric cryptography.
-
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const mem = std.mem;
-const math = std.math;
-const testing = std.testing;
-
-/// A Xoodoo state.
-pub const State = struct {
- /// Number of bytes in the state.
- pub const block_bytes = 48;
-
- const rcs = [12]u32{ 0x058, 0x038, 0x3c0, 0x0d0, 0x120, 0x014, 0x060, 0x02c, 0x380, 0x0f0, 0x1a0, 0x012 };
- const Lane = @Vector(4, u32);
- st: [3]Lane,
-
- /// Initialize a state from a slice of bytes.
- pub fn init(initial_state: [block_bytes]u8) State {
- var state = State{ .st = undefined };
- mem.copy(u8, state.asBytes(), &initial_state);
- state.endianSwap();
- return state;
- }
-
- // A representation of the state as 32-bit words.
- fn asWords(self: *State) *[12]u32 {
- return @ptrCast(*[12]u32, &self.st);
- }
-
- /// A representation of the state as bytes. The byte order is architecture-dependent.
- pub fn asBytes(self: *State) *[block_bytes]u8 {
- return mem.asBytes(&self.st);
- }
-
- /// Byte-swap words storing the bytes of a given range if the architecture is not little-endian.
- pub fn endianSwapPartial(self: *State, from: usize, to: usize) void {
- for (self.asWords()[from / 4 .. (to + 3) / 4]) |*w| {
- w.* = mem.littleToNative(u32, w.*);
- }
- }
-
- /// Byte-swap the entire state if the architecture is not little-endian.
- pub fn endianSwap(self: *State) void {
- for (self.asWords()) |*w| {
- w.* = mem.littleToNative(u32, w.*);
- }
- }
-
- /// XOR a byte into the state at a given offset.
- pub fn addByte(self: *State, byte: u8, offset: usize) void {
- self.endianSwapPartial(offset, offset);
- self.asBytes()[offset] ^= byte;
- self.endianSwapPartial(offset, offset);
- }
-
- /// XOR bytes into the beginning of the state.
- pub fn addBytes(self: *State, bytes: []const u8) void {
- self.endianSwap();
- for (self.asBytes()[0..bytes.len], 0..) |*byte, i| {
- byte.* ^= bytes[i];
- }
- self.endianSwap();
- }
-
- /// Extract the first bytes of the state.
- pub fn extract(self: *State, out: []u8) void {
- self.endianSwap();
- mem.copy(u8, out, self.asBytes()[0..out.len]);
- self.endianSwap();
- }
-
- /// Set the words storing the bytes of a given range to zero.
- pub fn clear(self: *State, from: usize, to: usize) void {
- mem.set(u32, self.asWords()[from / 4 .. (to + 3) / 4], 0);
- }
-
- /// Apply the Xoodoo permutation.
- pub fn permute(self: *State) void {
- const rot8x32 = comptime if (builtin.target.cpu.arch.endian() == .Big)
- [_]i32{ 9, 10, 11, 8, 13, 14, 15, 12, 1, 2, 3, 0, 5, 6, 7, 4 }
- else
- [_]i32{ 11, 8, 9, 10, 15, 12, 13, 14, 3, 0, 1, 2, 7, 4, 5, 6 };
-
- var a = self.st[0];
- var b = self.st[1];
- var c = self.st[2];
- inline for (rcs) |rc| {
- var p = @shuffle(u32, a ^ b ^ c, undefined, [_]i32{ 3, 0, 1, 2 });
- var e = math.rotl(Lane, p, 5);
- p = math.rotl(Lane, p, 14);
- e ^= p;
- a ^= e;
- b ^= e;
- c ^= e;
- b = @shuffle(u32, b, undefined, [_]i32{ 3, 0, 1, 2 });
- c = math.rotl(Lane, c, 11);
- a[0] ^= rc;
- a ^= ~b & c;
- b ^= ~c & a;
- c ^= ~a & b;
- b = math.rotl(Lane, b, 1);
- c = @bitCast(Lane, @shuffle(u8, @bitCast(@Vector(16, u8), c), undefined, rot8x32));
- }
- self.st[0] = a;
- self.st[1] = b;
- self.st[2] = c;
- }
-};
-
-test "xoodoo" {
- const bytes = [_]u8{0x01} ** State.block_bytes;
- var st = State.init(bytes);
- var out: [State.block_bytes]u8 = undefined;
- st.permute();
- st.extract(&out);
- 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 };
- try testing.expectEqualSlices(u8, &expected1, &out);
- st.clear(0, 10);
- st.extract(&out);
- 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 };
- try testing.expectEqualSlices(u8, &expected2, &out);
- st.addByte(1, 5);
- st.addByte(2, 5);
- st.extract(&out);
- 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 };
- try testing.expectEqualSlices(u8, &expected3, &out);
- st.addBytes(&bytes);
- st.extract(&out);
- 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 };
- try testing.expectEqualSlices(u8, &expected4, &out);
-}
lib/std/rand/Ascon.zig
@@ -1,4 +1,12 @@
-//! CSPRNG based on the Ascon XOFa construction
+//! CSPRNG based on the Reverie construction, a permutation-based PRNG
+//! with forward security, instantiated with the Ascon(128,12,8) permutation.
+//!
+//! Compared to ChaCha, this PRNG is has a much smaller state, and can be
+//! a better choice for constrained environments.
+//!
+//! References:
+//! - A Robust and Sponge-Like PRNG with Improved Efficiency https://eprint.iacr.org/2016/886.pdf
+//! - Ascon https://ascon.iaik.tugraz.at/files/asconv12-nist.pdf
const std = @import("std");
const min = std.math.min;
@@ -6,30 +14,38 @@ const mem = std.mem;
const Random = std.rand.Random;
const Self = @This();
-state: std.crypto.core.Ascon(.Little),
+const Ascon = std.crypto.core.Ascon(.Little);
-const rate = 8;
+state: Ascon,
+
+const rate = 16;
pub const secret_seed_length = 32;
/// The seed must be uniform, secret and `secret_seed_length` bytes long.
pub fn init(secret_seed: [secret_seed_length]u8) Self {
- var state = std.crypto.core.Ascon(.Little).initXofA();
+ var self = Self{ .state = Ascon.initXof() };
+ self.addEntropy(&secret_seed);
+ return self;
+}
+
+/// Inserts entropy to refresh the internal state.
+pub fn addEntropy(self: *Self, bytes: []const u8) void {
+ comptime std.debug.assert(secret_seed_length % rate == 0);
var i: usize = 0;
- while (i + rate <= secret_seed.len) : (i += rate) {
- state.addBytes(secret_seed[i..][0..rate]);
- state.permuteR(8);
+ while (i + rate < bytes.len) : (i += rate) {
+ self.state.addBytes(bytes[i..][0..rate]);
+ self.state.permuteR(8);
}
- const left = secret_seed.len - i;
- if (left > 0) state.addBytes(secret_seed[i..]);
- state.addByte(0x80, left);
- state.permute();
- return Self{ .state = state };
+ if (i != bytes.len) self.state.addBytes(bytes[i..]);
+ self.state.permute();
}
+/// Returns a `std.rand.Random` structure backed by the current RNG.
pub fn random(self: *Self) Random {
return Random.init(self, fill);
}
+/// Fills the buffer with random bytes.
pub fn fill(self: *Self, buf: []u8) void {
var i: usize = 0;
while (true) {
@@ -40,6 +56,5 @@ pub fn fill(self: *Self, buf: []u8) void {
self.state.permuteR(8);
i += n;
}
- self.state.clear(0, rate);
- self.state.permuteR(8);
+ self.state.permuteRatchet(6, rate);
}
lib/std/rand/benchmark.zig
@@ -0,0 +1,217 @@
+// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
+
+const std = @import("std");
+const builtin = @import("builtin");
+const time = std.time;
+const Timer = time.Timer;
+const rand = std.rand;
+
+const KiB = 1024;
+const MiB = 1024 * KiB;
+const GiB = 1024 * MiB;
+
+const Rng = struct {
+ ty: type,
+ name: []const u8,
+ init_u8s: ?[]const u8 = null,
+ init_u64: ?u64 = null,
+};
+
+const prngs = [_]Rng{
+ Rng{
+ .ty = rand.Isaac64,
+ .name = "isaac64",
+ .init_u64 = 0,
+ },
+ Rng{
+ .ty = rand.Pcg,
+ .name = "pcg",
+ .init_u64 = 0,
+ },
+ Rng{
+ .ty = rand.RomuTrio,
+ .name = "romutrio",
+ .init_u64 = 0,
+ },
+ Rng{
+ .ty = std.rand.Sfc64,
+ .name = "sfc64",
+ .init_u64 = 0,
+ },
+ Rng{
+ .ty = std.rand.Xoroshiro128,
+ .name = "xoroshiro128",
+ .init_u64 = 0,
+ },
+ Rng{
+ .ty = std.rand.Xoshiro256,
+ .name = "xoshiro256",
+ .init_u64 = 0,
+ },
+};
+
+const csprngs = [_]Rng{
+ Rng{
+ .ty = rand.Ascon,
+ .name = "ascon",
+ .init_u8s = &[_]u8{0} ** 32,
+ },
+ Rng{
+ .ty = rand.ChaCha,
+ .name = "chacha",
+ .init_u8s = &[_]u8{0} ** 32,
+ },
+};
+
+const Result = struct {
+ throughput: u64,
+};
+
+const long_block_size: usize = 8 * 8192;
+const short_block_size: usize = 8;
+
+pub fn benchmark(comptime H: anytype, bytes: usize, comptime block_size: usize) !Result {
+ var rng = blk: {
+ if (H.init_u8s) |init| {
+ break :blk H.ty.init(init[0..].*);
+ }
+ if (H.init_u64) |init| {
+ break :blk H.ty.init(init);
+ }
+ break :blk H.ty.init();
+ };
+
+ var block: [block_size]u8 = undefined;
+
+ var offset: usize = 0;
+ var timer = try Timer.start();
+ const start = timer.lap();
+ while (offset < bytes) : (offset += block.len) {
+ rng.fill(block[0..]);
+ }
+ const end = timer.read();
+
+ const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
+ const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
+
+ std.debug.assert(rng.random().int(u64) != 0);
+
+ return Result{
+ .throughput = throughput,
+ };
+}
+
+fn usage() void {
+ std.debug.print(
+ \\throughput_test [options]
+ \\
+ \\Options:
+ \\ --filter [test-name]
+ \\ --count [int]
+ \\ --prngs-only
+ \\ --csprngs-only
+ \\ --short-only
+ \\ --long-only
+ \\ --help
+ \\
+ , .{});
+}
+
+fn mode(comptime x: comptime_int) comptime_int {
+ return if (builtin.mode == .Debug) x / 64 else x;
+}
+
+pub fn main() !void {
+ const stdout = std.io.getStdOut().writer();
+
+ var buffer: [1024]u8 = undefined;
+ var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
+ const args = try std.process.argsAlloc(fixed.allocator());
+
+ var filter: ?[]u8 = "";
+ var count: usize = mode(128 * MiB);
+ var bench_prngs = true;
+ var bench_csprngs = true;
+ var bench_long = true;
+ var bench_short = true;
+
+ var i: usize = 1;
+ while (i < args.len) : (i += 1) {
+ if (std.mem.eql(u8, args[i], "--mode")) {
+ try stdout.print("{}\n", .{builtin.mode});
+ return;
+ } else if (std.mem.eql(u8, args[i], "--filter")) {
+ i += 1;
+ if (i == args.len) {
+ usage();
+ std.os.exit(1);
+ }
+
+ filter = args[i];
+ } else if (std.mem.eql(u8, args[i], "--count")) {
+ i += 1;
+ if (i == args.len) {
+ usage();
+ std.os.exit(1);
+ }
+
+ const c = try std.fmt.parseUnsigned(usize, args[i], 10);
+ count = c * MiB;
+ } else if (std.mem.eql(u8, args[i], "--csprngs-only")) {
+ bench_prngs = false;
+ } else if (std.mem.eql(u8, args[i], "--prngs-only")) {
+ bench_csprngs = false;
+ } else if (std.mem.eql(u8, args[i], "--short-only")) {
+ bench_long = false;
+ } else if (std.mem.eql(u8, args[i], "--long-only")) {
+ bench_short = false;
+ } else if (std.mem.eql(u8, args[i], "--help")) {
+ usage();
+ return;
+ } else {
+ usage();
+ std.os.exit(1);
+ }
+ }
+
+ if (bench_prngs) {
+ if (bench_long) {
+ inline for (prngs) |R| {
+ if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
+ try stdout.print("{s} (long outputs)\n", .{R.name});
+ const result_long = try benchmark(R, count, long_block_size);
+ try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
+ }
+ }
+ }
+ if (bench_short) {
+ inline for (prngs) |R| {
+ if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
+ try stdout.print("{s} (short outputs)\n", .{R.name});
+ const result_short = try benchmark(R, count, short_block_size);
+ try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
+ }
+ }
+ }
+ }
+ if (bench_csprngs) {
+ if (bench_long) {
+ inline for (csprngs) |R| {
+ if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
+ try stdout.print("{s} (cryptographic, long outputs)\n", .{R.name});
+ const result_long = try benchmark(R, count, long_block_size);
+ try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
+ }
+ }
+ }
+ if (bench_short) {
+ inline for (csprngs) |R| {
+ if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
+ try stdout.print("{s} (cryptographic, short outputs)\n", .{R.name});
+ const result_short = try benchmark(R, count, short_block_size);
+ try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
+ }
+ }
+ }
+ }
+}
lib/std/rand/ChaCha.zig
@@ -0,0 +1,97 @@
+//! CSPRNG based on the ChaCha8 stream cipher, with forward security.
+//!
+//! References:
+//! - Fast-key-erasure random-number generators https://blog.cr.yp.to/20170723-random.html
+
+const std = @import("std");
+const mem = std.mem;
+const Random = std.rand.Random;
+const Self = @This();
+
+const Cipher = std.crypto.stream.chacha.ChaCha8IETF;
+
+const State = [2 * Cipher.block_length]u8;
+
+state: State,
+offset: usize,
+
+const nonce = [_]u8{0} ** Cipher.nonce_length;
+
+pub const secret_seed_length = Cipher.key_length;
+
+/// The seed must be uniform, secret and `secret_seed_length` bytes long.
+pub fn init(secret_seed: [secret_seed_length]u8) Self {
+ var self = Self{ .state = undefined, .offset = 0 };
+ Cipher.stream(&self.state, 0, secret_seed, nonce);
+ return self;
+}
+
+/// Inserts entropy to refresh the internal state.
+pub fn addEntropy(self: *Self, bytes: []const u8) void {
+ var i: usize = 0;
+ while (i + Cipher.key_length <= bytes.len) : (i += Cipher.key_length) {
+ Cipher.xor(
+ self.state[0..Cipher.key_length],
+ self.state[0..Cipher.key_length],
+ 0,
+ bytes[i..][0..Cipher.key_length].*,
+ nonce,
+ );
+ }
+ if (i < bytes.len) {
+ var k = [_]u8{0} ** Cipher.key_length;
+ mem.copy(u8, k[0..], bytes[i..]);
+ Cipher.xor(
+ self.state[0..Cipher.key_length],
+ self.state[0..Cipher.key_length],
+ 0,
+ k,
+ nonce,
+ );
+ }
+ self.refill();
+}
+
+/// Returns a `std.rand.Random` structure backed by the current RNG.
+pub fn random(self: *Self) Random {
+ return Random.init(self, fill);
+}
+
+// Refills the buffer with random bytes, overwriting the previous key.
+fn refill(self: *Self) void {
+ Cipher.stream(&self.state, 0, self.state[0..Cipher.key_length].*, nonce);
+ self.offset = 0;
+}
+
+/// Fills the buffer with random bytes.
+pub fn fill(self: *Self, buf_: []u8) void {
+ const bytes = self.state[Cipher.key_length..];
+ var buf = buf_;
+
+ const avail = bytes.len - self.offset;
+ if (avail > 0) {
+ // Bytes from the current block
+ const n = @min(avail, buf.len);
+ mem.copy(u8, buf[0..n], bytes[self.offset..][0..n]);
+ mem.set(u8, bytes[self.offset..][0..n], 0);
+ buf = buf[n..];
+ self.offset += n;
+ }
+ if (buf.len == 0) return;
+
+ self.refill();
+
+ // Full blocks
+ while (buf.len >= bytes.len) {
+ mem.copy(u8, buf[0..bytes.len], bytes);
+ buf = buf[bytes.len..];
+ self.refill();
+ }
+
+ // Remaining bytes
+ if (buf.len > 0) {
+ mem.copy(u8, buf, bytes[0..buf.len]);
+ mem.set(u8, bytes[0..buf.len], 0);
+ self.offset = buf.len;
+ }
+}
lib/std/rand/Gimli.zig
@@ -1,34 +0,0 @@
-//! CSPRNG
-
-const std = @import("std");
-const Random = std.rand.Random;
-const mem = std.mem;
-const Gimli = @This();
-
-state: std.crypto.core.Gimli,
-
-pub const secret_seed_length = 32;
-
-/// The seed must be uniform, secret and `secret_seed_length` bytes long.
-pub fn init(secret_seed: [secret_seed_length]u8) Gimli {
- var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
- mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed);
- mem.set(u8, initial_state[secret_seed_length..], 0);
- var self = Gimli{
- .state = std.crypto.core.Gimli.init(initial_state),
- };
- return self;
-}
-
-pub fn random(self: *Gimli) Random {
- return Random.init(self, fill);
-}
-
-pub fn fill(self: *Gimli, buf: []u8) void {
- if (buf.len != 0) {
- self.state.squeeze(buf);
- } else {
- self.state.permute();
- }
- mem.set(u8, self.state.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
-}
lib/std/rand/Xoodoo.zig
@@ -1,42 +0,0 @@
-//! CSPRNG
-
-const std = @import("std");
-const Random = std.rand.Random;
-const min = std.math.min;
-const mem = std.mem;
-const Xoodoo = @This();
-
-const State = std.crypto.core.Xoodoo;
-
-state: State,
-
-const rate = 16;
-pub const secret_seed_length = 32;
-
-/// The seed must be uniform, secret and `secret_seed_length` bytes long.
-pub fn init(secret_seed: [secret_seed_length]u8) Xoodoo {
- var initial_state: [State.block_bytes]u8 = undefined;
- mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed);
- mem.set(u8, initial_state[secret_seed_length..], 0);
- var state = State.init(initial_state);
- state.permute();
- return Xoodoo{ .state = state };
-}
-
-pub fn random(self: *Xoodoo) Random {
- return Random.init(self, fill);
-}
-
-pub fn fill(self: *Xoodoo, buf: []u8) void {
- var i: usize = 0;
- while (true) {
- const left = buf.len - i;
- const n = min(left, rate);
- self.state.extract(buf[i..][0..n]);
- if (left == 0) break;
- self.state.permute();
- i += n;
- }
- self.state.clear(0, rate);
- self.state.permute();
-}
lib/std/crypto.zig
@@ -17,8 +17,6 @@ pub const aead = struct {
pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb;
};
- pub const Gimli = @import("crypto/gimli.zig").Aead;
-
pub const chacha_poly = struct {
pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305;
pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305;
@@ -52,8 +50,6 @@ pub const core = struct {
pub const keccak = @import("crypto/keccak_p.zig");
pub const Ascon = @import("crypto/ascon.zig").State;
- pub const Gimli = @import("crypto/gimli.zig").State;
- pub const Xoodoo = @import("crypto/xoodoo.zig").State;
/// Modes are generic compositions to construct encryption/decryption functions from block ciphers and permutations.
///
@@ -87,7 +83,6 @@ pub const ecc = struct {
pub const hash = struct {
pub const blake2 = @import("crypto/blake2.zig");
pub const Blake3 = @import("crypto/blake3.zig").Blake3;
- pub const Gimli = @import("crypto/gimli.zig").Hash;
pub const Md5 = @import("crypto/md5.zig").Md5;
pub const Sha1 = @import("crypto/sha1.zig").Sha1;
pub const sha2 = @import("crypto/sha2.zig");
@@ -221,8 +216,6 @@ test {
_ = aead.aes_ocb.Aes128Ocb;
_ = aead.aes_ocb.Aes256Ocb;
- _ = aead.Gimli;
-
_ = aead.chacha_poly.ChaCha20Poly1305;
_ = aead.chacha_poly.ChaCha12Poly1305;
_ = aead.chacha_poly.ChaCha8Poly1305;
@@ -239,8 +232,6 @@ test {
_ = core.aes;
_ = core.Ascon;
- _ = core.Gimli;
- _ = core.Xoodoo;
_ = core.modes;
_ = dh.X25519;
@@ -256,7 +247,6 @@ test {
_ = hash.blake2;
_ = hash.Blake3;
- _ = hash.Gimli;
_ = hash.Md5;
_ = hash.Sha1;
_ = hash.sha2;
@@ -334,7 +324,6 @@ test "issue #4532: no index out of bounds" {
hash.blake2.Blake2b256,
hash.blake2.Blake2b384,
hash.blake2.Blake2b512,
- hash.Gimli,
};
inline for (types) |Hasher| {
lib/std/rand.zig
@@ -18,11 +18,12 @@ const maxInt = std.math.maxInt;
pub const DefaultPrng = Xoshiro256;
/// Cryptographically secure random numbers.
-pub const DefaultCsprng = Ascon;
+pub const DefaultCsprng = ChaCha;
pub const Ascon = @import("rand/Ascon.zig");
+pub const ChaCha = @import("rand/ChaCha.zig");
+
pub const Isaac64 = @import("rand/Isaac64.zig");
-pub const Xoodoo = @import("rand/Xoodoo.zig");
pub const Pcg = @import("rand/Pcg.zig");
pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig");
pub const Xoshiro256 = @import("rand/Xoshiro256.zig");