code wiki / (root) / nx_csprng.nx

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}