nx_csprng.nx source
↩ module page · 121 lines · 3730 B
1// nx_csprng.nx -- cryptographically secure random via getrandom(2).
2//
3// license_tier: INDEPENDENT_REDERIVE
4// genealogy_id: international-research-sources/nist/sp_800_90a
5//
6// Companion to nx_random (xoshiro256++): when caller needs entropy
7// suitable for cryptographic keys, session tokens, AT_RANDOM seed
8// material -- not deterministic test fixtures.
9//
10// Backed by the Linux getrandom() syscall (number 278 on RV64).
11// Falls back to reading /dev/urandom if getrandom is unavailable
12// (early boot or older kernel).
13//
14// API:
15// nx_csprng_fill(dst, n) -> 0 on success, negative errno on fail
16// nx_csprng_u64() -> i64 (full 64 bits of entropy)
17// nx_csprng_seed_rng(rng) -> reseed an nx_random.NxRng with crypto entropy
18//
19// Pairs with nx_random (deterministic) + nx_uuid (entropy consumer).
20
21// nx_safety_envelope:
22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
23// sil_target: SIL1
24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
25// verdict: NOT_YET_EVALUATED
26
27import "nx_syscalls.nx"
28import "nx_random.nx"
29
30// getrandom flags
31const NX_GRND_NONBLOCK: i64 = 1
32const NX_GRND_RANDOM: i64 = 2
33const NX_GRND_INSECURE: i64 = 4
34
35// Path string for /dev/urandom fallback.
36func nx_csprng_urandom_path(out: *u8) -> i64 {
37 out[0] = 0x2F; out[1] = 0x64; out[2] = 0x65; out[3] = 0x76
38 out[4] = 0x2F; out[5] = 0x75; out[6] = 0x72; out[7] = 0x61
39 out[8] = 0x6E; out[9] = 0x64; out[10] = 0x6F; out[11] = 0x6D
40 out[12] = 0
41 return 12
42}
43
44// Try getrandom() then fall back to /dev/urandom.
45func nx_csprng_fill(dst: *u8, n: i64) -> i64 {
46 if n <= 0 { return 0 }
47
48 // First try getrandom(buf, len, 0).
49 let r: i64 = __syscall(278, dst as i64, n, 0, 0, 0, 0)
50 if r == n { return 0 }
51
52 // Fallback: open /dev/urandom and read.
53 let path: *u8 = sys_mmap(16)
54 nx_csprng_urandom_path(path)
55 let fd: i64 = sys_openat_rd(path)
56 if fd < 0 { return fd }
57
58 var got: i64 = 0
59 while got < n {
60 let tail: *u8 = (((dst as i64) + got) as *u8)
61 let m: i64 = sys_read(fd, tail, n - got)
62 if m <= 0 {
63 sys_close(fd)
64 return 0 - 1
65 }
66 got = got + m
67 }
68 sys_close(fd)
69 return 0
70}
71
72// Convenience: pull 64 bits as one i64.
73func nx_csprng_u64() -> i64 {
74 let buf_raw: *u8 = sys_mmap(16)
75 let buf: *i64 = buf_raw as *i64
76 *buf = 0
77 nx_csprng_fill(buf_raw, 8)
78 return *buf
79}
80
81// Reseed an nx_random.NxRng with crypto entropy. Useful for
82// long-running services that started with a deterministic seed
83// for warm-up but want unpredictable streams in steady state.
84func nx_csprng_seed_rng(rng: *NxRng) -> i64 {
85 let buf_raw: *u8 = sys_mmap(64)
86 let r: i64 = nx_csprng_fill(buf_raw, 32)
87 if r < 0 { return r }
88 let words: *i64 = buf_raw as *i64
89 rng.s0 = words[0]
90 rng.s1 = words[1]
91 rng.s2 = words[2]
92 rng.s3 = words[3]
93 return 0
94}
95
96// ---- self-test ---------------------------------------------------
97
98func main() -> i64 {
99 let buf: *u8 = sys_mmap(64)
100 var i: i64 = 0
101 while i < 64 { buf[i] = 0; i = i + 1 }
102
103 let r: i64 = nx_csprng_fill(buf, 32)
104 if r < 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
105
106 // Statistical sanity: not all zero (would happen with prob ~2^-256).
107 var nonzero: i64 = 0
108 var k: i64 = 0
109 while k < 32 {
110 if buf[k] != 0 { nonzero = 1 }
111 k = k + 1
112 }
113 if nonzero == 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
114
115 // Two consecutive u64 calls should diverge with overwhelming prob.
116 let v1: i64 = nx_csprng_u64()
117 let v2: i64 = nx_csprng_u64()
118 if v1 == v2 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
119
120 return 0
121}