diff options
| author | e4m2 <git@e4m2.com> | 2024-02-08 14:43:20 +0100 |
|---|---|---|
| committer | e4m2 <git@e4m2.com> | 2024-02-08 14:43:20 +0100 |
| commit | 9af077d71eaf2c004d63405fc5c017e237cb9c6c (patch) | |
| tree | e9a82339e59f49ae2b6fc57c893b3e16afaed9aa /lib/std/Random/ChaCha.zig | |
| parent | 919a3bae1c5f2024b09e127a15c752d9dc0aa9a6 (diff) | |
| download | zig-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.zig | 98 |
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; + } +} |
