portal-rng: add (random-seed-from-os!) across all three tiers

Ticket 0002 — reads 8 bytes from /dev/urandom (little-endian u64) and
seeds xoshiro256**. Opt-in kernel entropy for stochastic runs; the
default stays deterministic (k=0 at startup), so ticket 0001's
portal-reproducibility contract is unchanged.

Real-world flow now one call away:
  Machine A: (random-seed-from-os!) + run simulation + portal-save
  Machine B: portal-resume — same stream, bit-for-bit

All three impls fail loud on /dev/urandom trouble (LispErr in Python
and C, stderr + exit(1) in asm) — no silent fallback to a weak seed.

Tests:
- tests/functional.lsp: 2 new shared asserts (entropic + replay)
- asm/test.sh: 2 new asm-local checks (149 total, was 147)
- make test-all green across Python (205), C (205), asm (149)

Whitepaper §7.5 gains one sentence noting the OS-seed path.
unmoad: zero new findings in added code.
This commit is contained in:
russell@unturf.com 2026-04-20 15:50:26 -04:00
parent e84c5eede3
commit 4960381c67
13 changed files with 306 additions and 81 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -199,15 +199,16 @@
.equ BI_RANDOMINT, 111
.equ BI_RANDOMSTATE, 112
.equ BI_RANDOMSTATESET, 113
.equ BI_RANDOMSEEDFROMOS, 114
.ifdef GC_NAIVE
.equ BI_GC_COLLECT, 114
.equ BI_GC_STATS, 115
.equ BI_WITH_ARENA, 116
.equ BI_ARENA_STATS, 117
.equ BI_ARENA_SET_MODE, 118
.equ BI_COUNT, 119
.equ BI_GC_COLLECT, 115
.equ BI_GC_STATS, 116
.equ BI_WITH_ARENA, 117
.equ BI_ARENA_STATS, 118
.equ BI_ARENA_SET_MODE, 119
.equ BI_COUNT, 120
.else
.equ BI_COUNT, 114
.equ BI_COUNT, 115
.endif
# ============================================================
@ -309,6 +310,7 @@ bn_randomseed: .byte 12; .ascii "random-seed!"
bn_randomint: .byte 10; .ascii "random-int"
bn_randomstate: .byte 12; .ascii "random-state"
bn_randomstateset: .byte 13; .ascii "random-state!"
bn_randomseedfromos: .byte 20; .ascii "random-seed-from-os!"
bn_integerp: .byte 8; .ascii "integer?"
bn_portalsave: .byte 11; .ascii "portal-save"
bn_portalresume:.byte 13; .ascii "portal-resume"
@ -404,6 +406,7 @@ bi_names:
.quad bn_tcpsendfile
.quad bn_isqrt
.quad bn_randomseed, bn_randomint, bn_randomstate, bn_randomstateset
.quad bn_randomseedfromos
.ifdef GC_NAIVE
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
.endif
@ -421,6 +424,9 @@ err_rng_bad_n: .ascii "Error: random-int: n must be positive\n"
.equ err_rng_bad_n_len, . - err_rng_bad_n
err_rng_short: .ascii "Error: random-state!: expected list of 8 integers\n"
.equ err_rng_short_len, . - err_rng_short
err_rng_urandom: .ascii "Error: random-seed-from-os!: /dev/urandom unavailable\n"
.equ err_rng_urandom_len, . - err_rng_urandom
s_dev_urandom: .asciz "/dev/urandom"
# Print strings
s_true: .ascii "#t"
@ -3397,6 +3403,8 @@ eval_list:
je bi_random_state
cmpq $BI_RANDOMSTATESET, %rax
je bi_random_state_bang
cmpq $BI_RANDOMSEEDFROMOS, %rax
je bi_random_seed_from_os
cmpq $BI_INTEGERP, %rax
je bi_integerp
cmpq $BI_PORTALSAVE, %rax
@ -4953,6 +4961,53 @@ bi_random_state_bang:
movq $VAL_VOID, %rax
RET_VAL
# bi_random_seed_from_os: (random-seed-from-os!) -> void
# Reads 8 bytes from /dev/urandom, interprets as little-endian u64,
# seeds xoshiro256**. Opt-in entropy for stochastic runs; determinism
# remains the default. See docs/tickets/0002-os-entropy-seed.md.
bi_random_seed_from_os:
# open("/dev/urandom", O_RDONLY, 0)
movq $SYS_OPEN, %rax
leaq s_dev_urandom(%rip), %rdi
movq $O_RDONLY, %rsi
xorq %rdx, %rdx
syscall
testq %rax, %rax
js .brsfo_fail
movq %rax, %rbx # fd
# read 8 bytes into stack slot
subq $8, %rsp
movq $SYS_READ, %rax
movq %rbx, %rdi
movq %rsp, %rsi
movq $8, %rdx
syscall
cmpq $8, %rax
jne .brsfo_short
movq (%rsp), %rdi # seed = little-endian u64
addq $8, %rsp
pushq %rbx # save fd across rng_seed call
call rng_seed
popq %rbx
# close(fd)
movq $SYS_CLOSE, %rax
movq %rbx, %rdi
syscall
movq $VAL_VOID, %rax
RET_VAL
.brsfo_short:
addq $8, %rsp
# fall through to fail with same error (urandom unavailable / short)
.brsfo_fail:
movq $SYS_WRITE, %rax
movq $2, %rdi
leaq err_rng_urandom(%rip), %rsi
movq $err_rng_urandom_len, %rdx
syscall
movq $SYS_EXIT, %rax
movq $1, %rdi
syscall
bi_vector:
# (vector e1 e2 ...) build from remaining args in %r12
# Count args

View file

@ -62,6 +62,8 @@ check "random-int-2" "(begin (random-seed! 42) (random-int 1000000) (random-i
check "random-reseed" "(begin (random-seed! 42) (random-int 1000000) (random-seed! 42) (random-int 1000000))" "558742"
check "random-state-len" "(begin (random-seed! 42) (length (random-state)))" "8"
check "random-state-restore" "(begin (random-seed! 42) (define s (random-state)) (random-int 1000000) (random-state! s) (random-int 1000000))" "558742"
check "random-os-entropic" "(begin (random-seed-from-os!) (define a (random-state)) (random-seed-from-os!) (not (equal? a (random-state))))" "#t"
check "random-os-replay" "(begin (random-seed-from-os!) (define s (random-state)) (define v (random-int 1000000)) (random-state! s) (= v (random-int 1000000)))" "#t"
check "odd?" "(odd? 3)" "#t"
check "odd?-even" "(odd? 4)" "#f"
check "even?" "(even? 4)" "#t"