| 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 | |
| 6 | const std = @import("std"); |
| 7 | const mem = std.mem; |
| 8 | const Self = @This(); |
| 9 | |
| 10 | const Cipher = std.crypto.stream.chacha.ChaCha8IETF; |
| 11 | |
| 12 | const State = [8 * Cipher.block_length]u8; |
| 13 | |
| 14 | state: State, |
| 15 | offset: usize, |
| 16 | |
| 17 | const nonce: [Cipher.nonce_length]u8 = @splat(0); |
| 18 | |
| 19 | pub const secret_seed_length = Cipher.key_length; |
| 20 | |
| 21 | /// The seed must be uniform, secret and `secret_seed_length` bytes long. |
| 22 | pub fn init(secret_seed: [secret_seed_length]u8) Self { |
| 23 | var self: Self = .{ .state = undefined, .offset = 0 }; |
| 24 | Cipher.stream(&self.state, 0, secret_seed, nonce); |
| 25 | return self; |
| 26 | } |
| 27 | |
| 28 | /// Inserts entropy to refresh the internal state. |
| 29 | pub fn addEntropy(self: *Self, bytes: []const u8) void { |
| 30 | var i: usize = 0; |
| 31 | while (i + Cipher.key_length <= bytes.len) : (i += Cipher.key_length) { |
| 32 | Cipher.xor( |
| 33 | self.state[0..Cipher.key_length], |
| 34 | self.state[0..Cipher.key_length], |
| 35 | 0, |
| 36 | bytes[i..][0..Cipher.key_length].*, |
| 37 | nonce, |
| 38 | ); |
| 39 | } |
| 40 | if (i < bytes.len) { |
| 41 | var k: [Cipher.key_length]u8 = @splat(0); |
| 42 | const src = bytes[i..]; |
| 43 | @memcpy(k[0..src.len], src); |
| 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.Random` structure backed by the current RNG. |
| 56 | pub fn random(self: *Self) std.Random { |
| 57 | return std.Random.init(self, fill); |
| 58 | } |
| 59 | |
| 60 | // Refills the buffer with random bytes, overwriting the previous key. |
| 61 | fn 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. |
| 67 | pub 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 | @memcpy(buf[0..n], bytes[self.offset..][0..n]); |
| 76 | @memset(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 | @memcpy(buf[0..bytes.len], bytes); |
| 87 | buf = buf[bytes.len..]; |
| 88 | self.refill(); |
| 89 | } |
| 90 | |
| 91 | // Remaining bytes |
| 92 | if (buf.len > 0) { |
| 93 | @memcpy(buf, bytes[0..buf.len]); |
| 94 | @memset(bytes[0..buf.len], 0); |
| 95 | self.offset = buf.len; |
| 96 | } |
| 97 | } |