aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Random/ChaCha.zig
diff options
context:
space:
mode:
authore4m2 <git@e4m2.com>2024-02-08 14:43:20 +0100
committere4m2 <git@e4m2.com>2024-02-08 14:43:20 +0100
commit9af077d71eaf2c004d63405fc5c017e237cb9c6c (patch)
treee9a82339e59f49ae2b6fc57c893b3e16afaed9aa /lib/std/Random/ChaCha.zig
parent919a3bae1c5f2024b09e127a15c752d9dc0aa9a6 (diff)
downloadzig-9af077d71eaf2c004d63405fc5c017e237cb9c6c.tar.gz
zig-9af077d71eaf2c004d63405fc5c017e237cb9c6c.zip
std.rand: Move to std.Random
Diffstat (limited to 'lib/std/Random/ChaCha.zig')
-rw-r--r--lib/std/Random/ChaCha.zig98
1 files changed, 98 insertions, 0 deletions
diff --git a/lib/std/Random/ChaCha.zig b/lib/std/Random/ChaCha.zig
new file mode 100644
index 0000000000..75f62c9a47
--- /dev/null
+++ b/lib/std/Random/ChaCha.zig
@@ -0,0 +1,98 @@
+//! 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 = [8 * 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;
+ const src = bytes[i..];
+ @memcpy(k[0..src.len], src);
+ 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);
+ @memcpy(buf[0..n], bytes[self.offset..][0..n]);
+ @memset(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) {
+ @memcpy(buf[0..bytes.len], bytes);
+ buf = buf[bytes.len..];
+ self.refill();
+ }
+
+ // Remaining bytes
+ if (buf.len > 0) {
+ @memcpy(buf, bytes[0..buf.len]);
+ @memset(bytes[0..buf.len], 0);
+ self.offset = buf.len;
+ }
+}