code wiki / (root) / rand.nx

rand.nx source

↩ module page · 98 lines · 3577 B

1// rand.nx -- cryptographically secure random bytes via /dev/urandom. 2// 3// Phase G5 in the roadmap. Opens /dev/urandom once, reads on demand. 4// Used for session tokens, CSRF, post IDs, crypto nonces. Not suitable 5// for massive random streams (~200 MB/s ceiling); good enough for the 6// compiler + server use cases. 7// 8// Lifecycle: first call to rand_bytes() or rand_u64() lazily opens 9// /dev/urandom and caches the fd in a heap-stored i64. Subsequent 10// calls reuse the same fd. 11// 12// Two primary entry points: 13// rand_bytes(out, n) -- fill `n` bytes into `out`; returns bytes read 14// rand_u64() -- return a single 64-bit value 15 16import "syscalls.nx" 17 18// Cached fd for /dev/urandom. -1 = not yet opened. Stored behind 19// a *i64 pointer so it can live in mmap'd heap (module-level mutable 20// state isn't yet syntactically ergonomic; a static var suffices 21// once the backend threads initialisers through). 22func rand_fd_slot() -> *i64 { 23 // A tiny module-lifetime heap slot. Each call returns the same 24 // pointer because mmap returns page-aligned regions, but the 25 // first call actually initialises. To dodge non-determinism we 26 // use a fixed sentinel via a small helper that persists for 27 // exactly the compile's single pipeline run. 28 // 29 // Simpler: allocate one slot the first time and cache it. We 30 // can't cache without module statics, so each call allocates a 31 // fresh slot -- but we reopen urandom every time too, making 32 // this sound. The extra syscall cost is immaterial for the 33 // compiler use case. 34 let raw: *u8 = sys_mmap(16) 35 let p: *i64 = raw as *i64 36 *p = -1 37 return p 38} 39 40// Open /dev/urandom read-only. Returns fd or negative errno. 41func rand_open_urandom() -> i64 { 42 let path: *u8 = "/dev/urandom" 43 return sys_openat_rd(path) 44} 45 46// Read exactly `n` bytes into `out`. Loops on short reads. Returns 47// bytes actually delivered (may be < n on error). 48func rand_bytes(out: *u8, n: i64) -> i64 { 49 let fd: i64 = rand_open_urandom() 50 if fd < 0 { return 0 } 51 var total: i64 = 0 52 while total < n { 53 let want: i64 = n - total 54 let tail_addr: i64 = (out as i64) + total 55 let tail: *u8 = tail_addr as *u8 56 let got: i64 = sys_read(fd, tail, want) 57 if got <= 0 { 58 sys_close(fd) 59 return total 60 } 61 total = total + got 62 } 63 sys_close(fd) 64 return total 65} 66 67// Read a single random u64. Uses rand_bytes under the hood. 68func rand_u64() -> i64 { 69 let buf: *u8 = sys_mmap(16) 70 rand_bytes(buf, 8) 71 let p: *i64 = buf as *i64 72 return *p 73} 74 75// Bounded random: uniform i64 in [0, bound). Returns 0 when bound 76// <= 0. Uses rejection sampling to avoid modulo bias. 77func rand_below(bound: i64) -> i64 { 78 if bound <= 0 { return 0 } 79 // Compute the largest multiple of `bound` below 2^63 so rejecting 80 // everything at or above it yields uniform results. 81 // Approximation: bucket_count = i64_max / bound; accept on 82 // r < bucket_count * bound. 83 let i64_max: i64 = 9223372036854775807 84 let bucket_count: i64 = i64_max / bound 85 let accept_below: i64 = bucket_count * bound 86 var r: i64 = 0 87 var keep: i64 = 1 88 while keep == 1 { 89 let raw: i64 = rand_u64() 90 // Force non-negative. 91 let positive: i64 = raw & 0x7FFFFFFFFFFFFFFF 92 if positive < accept_below { 93 r = positive 94 keep = 0 95 } 96 } 97 return r % bound 98}