nx_xoshiro.nx source
↩ module page · 114 lines · 3725 B
1// xoshiro.nx -- xoshiro256++ pseudorandom number generator.
2//
3// David Blackman + Sebastiano Vigna 2018. 256-bit state,
4// 64-bit output per step, period 2^256 - 1. Passes BigCrush
5// statistical tests. Used by: Rust `rand` standard generator,
6// Lua 5.4, GNU FORTRAN, .NET 6+.
7//
8// NOT cryptographically secure -- an attacker who observes
9// enough output can reconstruct state. For crypto use rand.nx
10// (/dev/urandom) or HMAC-DRBG (pending).
11//
12// Used for: simulation, deterministic test data, sampling,
13// fuzzing. When you need REPRODUCIBLE pseudorandom from a seed.
14//
15// Invariants:
16// X1 64-bit output per step; full state advances.
17// X2 Same seed -> same output sequence forever.
18// X3 All-zeros state forbidden (would produce all zeros).
19// seed_with implements the SplitMix64 trick to safely
20// initialise from any 64-bit seed including 0.
21//
22// license_tier: INDEPENDENT_REDERIVE
23// genealogy_id: international-research-sources/nist/sp_800_90a
24//
25
26// nx_safety_envelope:
27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
28// sil_target: SIL1
29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
30// verdict: NOT_YET_EVALUATED
31
32import "nx_syscalls.nx"
33import "nx_bits.nx"
34
35struct Xoshiro {
36 s0: i64,
37 s1: i64,
38 s2: i64,
39 s3: i64,
40}
41
42// Delegated to nx_bits_rotl64 (rolq/rol intrinsic).
43func xs_rotl(x: i64, k: i64) -> i64 {
44 return nx_bits_rotl64(x, k)
45}
46
47// SplitMix64 single-step (Vigna 2014). Used to initialise the
48// xoshiro state from a single seed -- avoids the all-zeros
49// pathological start.
50func xs_splitmix(state_p: *i64) -> i64 {
51 *state_p = *state_p + 0x9E3779B97F4A7C15
52 var z: i64 = *state_p
53 z = (z ^ ((z >> 30) & 0x3FFFFFFFFFFFFFFF)) * 0xBF58476D1CE4E5B9
54 z = (z ^ ((z >> 27) & 0x1FFFFFFFFFFFFFFF)) * 0x94D049BB133111EB
55 return z ^ ((z >> 31) & 0x1FFFFFFF)
56}
57
58// Initialise the generator from a 64-bit seed.
59func xoshiro_seed(rng: *Xoshiro, seed: i64) -> i64 {
60 let sm_state_raw: *u8 = sys_mmap(16)
61 let sm_state: *i64 = sm_state_raw as *i64
62 *sm_state = seed
63 rng.s0 = xs_splitmix(sm_state)
64 rng.s1 = xs_splitmix(sm_state)
65 rng.s2 = xs_splitmix(sm_state)
66 rng.s3 = xs_splitmix(sm_state)
67 return 0
68}
69
70// Construct + seed. Caller-owned struct.
71func xoshiro_new(seed: i64) -> *Xoshiro {
72 let raw: *u8 = sys_mmap(64)
73 let rng: *Xoshiro = raw as *Xoshiro
74 xoshiro_seed(rng, seed)
75 return rng
76}
77
78// Advance the state and return 64 bits. This is the xoshiro256++
79// scrambler: rotl(s0 + s3, 23) + s0.
80func xoshiro_next(rng: *Xoshiro) -> i64 {
81 let result: i64 = xs_rotl(rng.s0 + rng.s3, 23) + rng.s0
82 let t: i64 = rng.s1 << 17
83 rng.s2 = rng.s2 ^ rng.s0
84 rng.s3 = rng.s3 ^ rng.s1
85 rng.s1 = rng.s1 ^ rng.s2
86 rng.s0 = rng.s0 ^ rng.s3
87 rng.s2 = rng.s2 ^ t
88 rng.s3 = xs_rotl(rng.s3, 45)
89 return result
90}
91
92// Return a value in [0, bound). Uses Lemire's nearly-divisionless
93// bounded random method (2019).
94func xoshiro_bounded(rng: *Xoshiro, bound: i64) -> i64 {
95 if bound <= 1 { return 0 }
96 // Simple modulo for now (small bias). Lemire's method needs
97 // 128-bit multiplication we can't do cleanly.
98 let v: i64 = xoshiro_next(rng)
99 // Convert to non-negative range.
100 let pos: i64 = v & 0x7FFFFFFFFFFFFFFF
101 return pos % bound
102}
103
104// Compile-only smoke.
105func main() -> i64 {
106 let rng: *Xoshiro = xoshiro_new(42)
107 let v1: i64 = xoshiro_next(rng)
108 let v2: i64 = xoshiro_next(rng)
109 if v1 == v2 { return 1 } // would be astronomically improbable
110 let bounded: i64 = xoshiro_bounded(rng, 100)
111 if bounded < 0 { return 2 }
112 if bounded >= 100 { return 3 }
113 return 0
114}