"The trickiest part, which was the only point of failure, was in starting a game on the server at some exact second. Due to clock skew, the server could think it is several seconds (or even minutes) earlier or later than your clock does. The server doesn't broadcast what it thinks the current time is, either. One way to actually figure out the current time is through the random seed! Start a game on the server, noting the current time on your clock. Then generate NetHack games for that time, that time plus a second, that time minus a second, plus two seconds, and so on until you get the same starting map and stats. The offset you used is the number seconds between your machine and the server, so you can use it to know exactly when to start a server game. It's still not as exact as I would like, since you only get resolution of a second."
Reminds me of the oracle exploits the Matasano crypto challenge taught. I'm sure this is a very common type of tactic, I just haven't spent much time practicing exploits, despite my interest.
But correct me if I'm wrong, I would guess that with many attempts, possibly a binary search of time offsets, one can get sub-second timing resolution in the clock skew exploit above, no?
No, no, no. This is a horrible misconception.
It's easy to generate an effectively infinite stream of random numbers: use a block cipher in CTR mode (i.e., using the seed as the key, encrypt 0, then 1, then 2, then 3 and so on). All you need for this is a single truly-random seed, and from it you'll get 2^128 random numbers—far more than you will ever, ever need.
Now, how do you get that initial random seed? That's where the art comes in, but a basic version would simply hash all those truly-random input events (e.g. those temperature changes or internal timing variations) together. A more complex system, such as Schneier and Ferguson's superb Fortuna, uses a system of 32 pools, where the pools are used to periodically reseed the generator, in a clever fashion which can protect even against generator-state compromise.
And with modern chips, AES-256 in CTR mode is fast, really fast. Fortuna'd be perfectly usable in Nethack.
(As an aside, it'd also be perfectly usable as a replacement for the needlessly-complicated and insufficiently-grounded Linux CSPRNG underlying /dev/random and /dev/urandom).
The overwhelming majority of RNG disasters in the past decade have come from userland CSPRNGs written by people who were wary of urandom.
Sure. I just wish that /dev/urandom were well-thought-out rather than accidentally probably-good-enough.
I'm confused. Why can't they just read from /dev/urandom for all their random numbers?
EDIT: How is what I wrote wrong?