mags-banko/seedrandom.js
2025-04-12 13:41:28 +02:00

237 lines
6.8 KiB
JavaScript

(function (global, pool, math, width, chunks, digits, module, define, rngname) {
//
// The following constants are related to IEEE 754 limits.
//
var startdenom = math.pow(width, chunks),
significance = math.pow(2, digits),
overflow = significance * 2,
mask = width - 1,
nodecrypto;
//
// seedrandom()
// This is the seedrandom function described above.
//
var impl = (math["seed" + rngname] = function (seed, options, callback) {
var key = [];
options = options == true ? { entropy: true } : options || {};
// Flatten the seed string or build one from local entropy if needed.
var shortseed = mixkey(
flatten(
options.entropy
? [seed, tostring(pool)]
: seed == null
? autoseed()
: seed,
3
),
key
);
// Use the seed to initialize an ARC4 generator.
var arc4 = new ARC4(key);
// Mix the randomness into accumulated entropy.
mixkey(tostring(arc4.S), pool);
// Calling convention: what to return as a function of prng, seed, is_math.
return (
options.pass ||
callback ||
// If called as a method of Math (Math.seedrandom()), mutate Math.random
// because that is how seedrandom.js has worked since v1.0. Otherwise,
// it is a newer calling convention, so return the prng directly.
function (prng, seed, is_math_call) {
if (is_math_call) {
math[rngname] = prng;
return seed;
} else return prng;
}
)(
// This function returns a random double in [0, 1) that contains
// randomness in every bit of the mantissa of the IEEE 754 value.
function () {
var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48
d = startdenom, // and denominator d = 2 ^ 48.
x = 0; // and no 'extra last byte'.
while (n < significance) {
// Fill up all significant digits by
n = (n + x) * width; // shifting numerator and
d *= width; // denominator and generating a
x = arc4.g(1); // new least-significant-byte.
}
while (n >= overflow) {
// To avoid rounding up, before adding
n /= 2; // last byte, shift everything
d /= 2; // right using integer math until
x >>>= 1; // we have exactly the desired bits.
}
return (n + x) / d; // Form the number within [0, 1).
},
shortseed,
"global" in options ? options.global : this == math
);
});
//
// ARC4
//
// An ARC4 implementation. The constructor takes a key in the form of
// an array of at most (width) integers that should be 0 <= x < (width).
//
// The g(count) method returns a pseudorandom integer that concatenates
// the next (count) outputs from ARC4. Its return value is a number x
// that is in the range 0 <= x < (width ^ count).
//
/** @constructor */
function ARC4(key) {
var t,
keylen = key.length,
me = this,
i = 0,
j = (me.i = me.j = 0),
s = (me.S = []);
// The empty key [] is treated as [0].
if (!keylen) {
key = [keylen++];
}
// Set up S using the standard key scheduling algorithm.
while (i < width) {
s[i] = i++;
}
for (i = 0; i < width; i++) {
s[i] = s[(j = mask & (j + key[i % keylen] + (t = s[i])))];
s[j] = t;
}
// The "g" method returns the next (count) outputs as one number.
(me.g = function (count) {
// Using instance members instead of closure state nearly doubles speed.
var t,
r = 0,
i = me.i,
j = me.j,
s = me.S;
while (count--) {
t = s[(i = mask & (i + 1))];
r =
r * width + s[mask & ((s[i] = s[(j = mask & (j + t))]) + (s[j] = t))];
}
me.i = i;
me.j = j;
return r;
// For robust unpredictability, the function call below automatically
// discards an initial batch of values. This is called RC4-drop[256].
// See http://google.com/search?q=rsa+fluhrer+response&btnI
})(width);
}
//
// flatten()
// Converts an object tree to nested arrays of strings.
//
function flatten(obj, depth) {
var result = [],
typ = typeof obj,
prop;
if (depth && typ == "object") {
for (prop in obj) {
try {
result.push(flatten(obj[prop], depth - 1));
} catch (e) {}
}
}
return result.length ? result : typ == "string" ? obj : obj + "\0";
}
//
// mixkey()
// Mixes a string seed into a key that is an array of integers, and
// returns a shortened string seed that is equivalent to the result key.
//
function mixkey(seed, key) {
var stringseed = seed + "",
smear,
j = 0;
while (j < stringseed.length) {
key[mask & j] =
mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++));
}
return tostring(key);
}
//
// autoseed()
// Returns an object for autoseeding, using window.crypto if available.
//
/** @param {Uint8Array|Navigator=} seed */
function autoseed(seed) {
try {
if (nodecrypto) return tostring(nodecrypto.randomBytes(width));
global.crypto.getRandomValues((seed = new Uint8Array(width)));
return tostring(seed);
} catch (e) {
return [
+new Date(),
global,
(seed = global.navigator) && seed.plugins,
global.screen,
tostring(pool),
];
}
}
//
// tostring()
// Converts an array of charcodes to a string
//
function tostring(a) {
return String.fromCharCode.apply(0, a);
}
//
// When seedrandom.js is loaded, we immediately mix a few bits
// from the built-in RNG into the entropy pool. Because we do
// not want to interfere with deterministic PRNG state later,
// seedrandom will not call math.random on its own again after
// initialization.
//
mixkey(math[rngname](), pool);
//
// Nodejs and AMD support: export the implementation as a module using
// either convention.
//
if (module && module.exports) {
module.exports = impl;
try {
// When in node.js, try using crypto package for autoseeding.
nodecrypto = require("crypto");
} catch (ex) {}
} else if (define && define.amd) {
define(function () {
return impl;
});
}
//
// Node.js native crypto support.
//
// End anonymous scope, and pass initial values.
})(
this, // global window object
[], // pool: entropy pool starts empty
Math, // math: package containing random, pow, and seedrandom
256, // width: each RC4 output is 0 <= x < 256
6, // chunks: at least six RC4 outputs for each double
52, // digits: there are 52 significant digits in a double
typeof module == "object" && module, // present in node.js
typeof define == "function" && define, // present with an AMD loader
"random" // rngname: name for Math.random and Math.seedrandom
);