Create a gist now

Instantly share code, notes, and snippets.

Demonstrates how to replicate a stream of Math.random() given two past observations for **Chrome**. Code was only tried on Chrome 43 (07/2015). Answers the question from this issue: https://github.com/fta2012/ReplicatedRandom/issues/2.
var rngstate;
function MathRandom() {
// Our own implementation of Math.random().
// Source code was copied from https://github.com/v8/v8/blob/4.6.85/src/math.js#L131
// You need to initialize rngstate with `solve` before this can be used.
// If using node.js, you have to s/18030/18273/g here and in `solve` since they implement it slightly differently: https://github.com/nodejs/node-v0.x-archive/blob/d13d7f74d794340ac5e126cfb4ce507fe0f803d5/deps/v8/src/math.js#L146
console.assert(rngstate, "You need to set the global variable `rngstate` first. For example: `rngstate = solve(Math.random(), Math.random());`");
if (!rngstate) return;
var r0 = (Math.imul(18030, rngstate[0] & 0xFFFF) + (rngstate[0] >>> 16)) | 0;
rngstate[0] = r0;
var r1 = (Math.imul(36969, rngstate[1] & 0xFFFF) + (rngstate[1] >>> 16)) | 0;
rngstate[1] = r1;
var x = ((r0 << 16) + (r1 & 0xFFFF)) | 0;
// Division by 0x100000000 through multiplication by reciprocal.
return (x < 0 ? (x + 0x100000000) : x) * 2.3283064365386962890625e-10;
}
function solve(first, second, gap) {
// Given two values of Math.random() and number of other calls to Math.random() in between, solve for the rngstate.
var first_x = first / 2.3283064365386962890625e-10;
var first_r0_lower = first_x >>> 16;
var first_r1_lower = first_x & 0xFFFF;
var second_x = second / 2.3283064365386962890625e-10;
var second_r0_lower = second_x >>> 16;
var second_r1_lower = second_x & 0xFFFF;
var first_r0_upper, first_r1_upper, second_r0, second_r1;
var found = false;
for (first_r0_upper = 0; first_r0_upper <= 0xFFFF; first_r0_upper++) {
second_r0 = (first_r0_upper << 16) | first_r0_lower;
for (var i = 0; i <= gap; i++) {
second_r0 = (Math.imul(18030, second_r0 & 0xFFFF) + (second_r0 >>> 16)) | 0;
}
if ((second_r0 & 0xFFFF) == second_r0_lower) {
found = true;
break;
}
}
if (!found) {
return null;
}
found = false;
for (first_r1_upper = 0; first_r1_upper <= 0xFFFF; first_r1_upper++) {
second_r1 = (first_r1_upper << 16) | first_r1_lower;
for (var i = 0; i <= gap; i++) {
second_r1 = (Math.imul(36969, second_r1 & 0xFFFF) + (second_r1 >>> 16)) | 0;
}
if ((second_r1 & 0xFFFF) == second_r1_lower) {
found = true;
break;
}
}
if (!found) {
return null;
}
return [second_r0, second_r1];
}
// Example usage. Needs two prior known values (`observation1` and `observation2`) to pass to `solve`
var num_unknown_values = 0; // Change this to the number of calls to Math.random() between our two observations
var observation1 = Math.random();
for (var i = 0; i < num_unknown_values; i++) {
Math.random();
}
var observation2 = Math.random();
rngstate = solve(observation1, observation2, num_unknown_values);
// Test
for (var i = 0; i < 10; i++) {
var predicted = MathRandom(); // Our own implementation of Math.random() with the solved rngstate from above
console.log('Predicted:', predicted);
var observation = Math.random();
console.log('Actual:', observation);
console.assert(observation == predicted);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment