Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ROT.RNG.setSeed unexpected behavior #184

Open
nluqo opened this issue Jul 29, 2020 · 9 comments
Open

ROT.RNG.setSeed unexpected behavior #184

nluqo opened this issue Jul 29, 2020 · 9 comments

Comments

@nluqo
Copy link
Contributor

nluqo commented Jul 29, 2020

I'm using setSeed for seeded runs in a game and was really surprised by the behavior. Two main issues:

  • The only valid input seems to be positive numbers. Passing a string would be really useful, but for both strings and negative numbers, the state is always the same. The first uniform is: 2.3283064365386963e-10
  • There is no avalanche effect. Similar seeds produce similar random numbers at least for the first one. 123 produces 0.0599007117561996 and 124 produces 0.060387709410861135. The numbers do diverge after that first uniform value, so it's probably easy enough to just throw away the first value?

Perhaps these are known behaviors, but from the developer perspective I found them really confusing... and it's quite easy to write some broken randomness by not knowing it. Even some notes on the docs would help out a lot.

@ondras
Copy link
Owner

ondras commented Jul 30, 2020

Hi @nluqo,

good finding! Thanks for the report.

* The only valid input seems to be positive numbers.

Right, the number type is also mentioned in the autogenerated documentation: https://ondras.github.io/rot.js/doc/classes/_rng_.rng.html#setseed

* Similar seeds produce similar random numbers at least for the first one.

This is a problem indeed. I am open to fixing it, but we are facing a backwards compatibility issue - folks might be expecting a particular behavior for their already-generated seeds.

* Passing a string would be really useful,

So, how about we add a seed-by-string feature that uses a better seeding mechanism and produces the desired avalanche effect? This would be a compatible change - and the documentation can mention that seeding with a string produces generally more robust results.

I am, however, not sure how to properly implement the seeding with a string. The current RNG implementation (Alea) is based on the work at https://github.com/nquinlan/better-random-numbers-for-javascript-mirror.

@blinkdog
Copy link
Contributor

I am, however, not sure how to properly implement the seeding with a string. The current RNG implementation (Alea) is based on the work at https://github.com/nquinlan/better-random-numbers-for-javascript-mirror.

I'd put (pre-salt + the string + post-salt) through SHA-256 and then pull the least significant bytes to turn into a seed value.

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest

The salt strings can be anything and don't need to be kept secret.
Running dd if=/dev/urandom bs=1 count=24 | base64 gave me these:

PRE_SALT = "j2wAXcEUrFnZGpt9WsZE1RV4oNAOBcBO"
POST_SALT = "c5/0KcU6ad912gQ88c/53Ng+magf2x7P"

@ondras
Copy link
Owner

ondras commented Jul 30, 2020

I'd put (pre-salt + the string + post-salt) through SHA-256 and then pull the least significant bytes to turn into a seed value.

The RNG uses four state values, though I am not sure how exactly is the _c supposed to work - (current) seeding sets it always to 1. So you suggest using the last 8*3 bytes to set _s0, _s1 and _s2 ?

@ondras
Copy link
Owner

ondras commented Jul 30, 2020

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest

Also, I would prefer the seeding process to be synchronous :-)

@blinkdog
Copy link
Contributor

I'd put (pre-salt + the string + post-salt) through SHA-256 and then pull the least significant bytes to turn into a seed value.

The RNG uses four state values, though I am not sure how exactly is the _c supposed to work - (current) seeding sets it always to 1. So you suggest using the last 8*3 bytes to set _s0, _s1 and _s2 ?

Yeah, that'd be a good way to do it.

@blinkdog
Copy link
Contributor

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
Also, I would prefer the seeding process to be synchronous :-)

Hmmm, in Node.js I'd use:
https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options

And the browser I'd borrow Browserify's browser implementation of Node's crypto:
https://github.com/crypto-browserify/crypto-browserify
https://github.com/crypto-browserify/createHash
https://github.com/crypto-browserify/sha.js

These would give compatible synchronous strong hash functions.

@ondras
Copy link
Owner

ondras commented Jul 30, 2020

Sounds a bit like an overkill to me. We just need a way to convert a string to three 64bit values, without any particular crypto/security properties... how about the original Baagoe's Mash function? I would say this should have been used from the very beginning.

@blinkdog
Copy link
Contributor

Sounds a bit like an overkill to me. We just need a way to convert a string to three 64bit values, without any particular crypto/security properties... how about the original Baagoe's Mash function? I would say this should have been used from the very beginning.

Yeah, that's pretty cool. I'll bookmark this one.

@nluqo
Copy link
Contributor Author

nluqo commented Jul 30, 2020

Right, the number type is also mentioned in the autogenerated documentation:

Ah yes, I forgot this even existed. Woops. Though I still think it should mention the sign of the number. I was using a library to generate SHA256 digests and getting an int out of it and it only returned signed integers... and thought I was good for a while.

It's easy to test a couple times (positive input, negative input, positive input) and think "these all look different enough" but not realize all your negative inputs will degenerate to the same state.

So, how about we add a seed-by-string feature

Sounds good to me. It will change from the current behavior if you were misusing the API and passing a string, but I assume this is unlikely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants