zig/lib/std/rand/Xoroshiro128.zig
Andrew Kelley 013efaf139 std: introduce a thread-local CSPRNG for general use
std.crypto.random

* cross platform, even freestanding
* can't fail. on initialization for some systems requires calling
  os.getrandom(), in which case there are rare but theoretically
  possible errors. The code panics in these cases, however the
  application may choose to override the default seed function and then
  handle the failure another way.
* thread-safe
* supports the full Random interface
* cryptographically secure
* no syscall required to initialize on Linux (AT_RANDOM)
* calls arc4random on systems that support it

`std.crypto.randomBytes` is removed in favor of `std.crypto.random.bytes`.

I moved some of the Random implementations into their own files in the
interest of organization.

stage2 no longer requires passing a RNG; instead it uses this API.

Closes #6704
2020-12-18 12:22:46 -07:00

134 lines
2.9 KiB
Zig

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
//! Xoroshiro128+ - http://xoroshiro.di.unimi.it/
//!
//! PRNG
const std = @import("std");
const Random = std.rand.Random;
const math = std.math;
const Xoroshiro128 = @This();
random: Random,
s: [2]u64,
pub fn init(init_s: u64) Xoroshiro128 {
var x = Xoroshiro128{
.random = Random{ .fillFn = fill },
.s = undefined,
};
x.seed(init_s);
return x;
}
fn next(self: *Xoroshiro128) u64 {
const s0 = self.s[0];
var s1 = self.s[1];
const r = s0 +% s1;
s1 ^= s0;
self.s[0] = math.rotl(u64, s0, @as(u8, 55)) ^ s1 ^ (s1 << 14);
self.s[1] = math.rotl(u64, s1, @as(u8, 36));
return r;
}
// Skip 2^64 places ahead in the sequence
fn jump(self: *Xoroshiro128) void {
var s0: u64 = 0;
var s1: u64 = 0;
const table = [_]u64{
0xbeac0467eba5facb,
0xd86b048b86aa9922,
};
inline for (table) |entry| {
var b: usize = 0;
while (b < 64) : (b += 1) {
if ((entry & (@as(u64, 1) << @intCast(u6, b))) != 0) {
s0 ^= self.s[0];
s1 ^= self.s[1];
}
_ = self.next();
}
}
self.s[0] = s0;
self.s[1] = s1;
}
pub fn seed(self: *Xoroshiro128, init_s: u64) void {
// Xoroshiro requires 128-bits of seed.
var gen = std.rand.SplitMix64.init(init_s);
self.s[0] = gen.next();
self.s[1] = gen.next();
}
fn fill(r: *Random, buf: []u8) void {
const self = @fieldParentPtr(Xoroshiro128, "random", r);
var i: usize = 0;
const aligned_len = buf.len - (buf.len & 7);
// Complete 8 byte segments.
while (i < aligned_len) : (i += 8) {
var n = self.next();
comptime var j: usize = 0;
inline while (j < 8) : (j += 1) {
buf[i + j] = @truncate(u8, n);
n >>= 8;
}
}
// Remaining. (cuts the stream)
if (i != buf.len) {
var n = self.next();
while (i < buf.len) : (i += 1) {
buf[i] = @truncate(u8, n);
n >>= 8;
}
}
}
test "xoroshiro sequence" {
var r = Xoroshiro128.init(0);
r.s[0] = 0xaeecf86f7878dd75;
r.s[1] = 0x01cd153642e72622;
const seq1 = [_]u64{
0xb0ba0da5bb600397,
0x18a08afde614dccc,
0xa2635b956a31b929,
0xabe633c971efa045,
0x9ac19f9706ca3cac,
0xf62b426578c1e3fb,
};
for (seq1) |s| {
std.testing.expect(s == r.next());
}
r.jump();
const seq2 = [_]u64{
0x95344a13556d3e22,
0xb4fb32dafa4d00df,
0xb2011d9ccdcfe2dd,
0x05679a9b2119b908,
0xa860a1da7c9cd8a0,
0x658a96efe3f86550,
};
for (seq2) |s| {
std.testing.expect(s == r.next());
}
}