js-randomness-predictor
Version:
Predict Math.random output in Node, Chrome, and Firefox
235 lines (234 loc) • 16.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _NodeRandomnessPredictor_instances, _NodeRandomnessPredictor_MAX_SEQUENCE_LENGTH, _NodeRandomnessPredictor_DEFAULT_SEQUENCE_LENGTH, _NodeRandomnessPredictor_nodeVersion, _NodeRandomnessPredictor_isInitialized, _NodeRandomnessPredictor_concreteState0, _NodeRandomnessPredictor_concreteState1, _NodeRandomnessPredictor_mask, _NodeRandomnessPredictor_internalSequence, _NodeRandomnessPredictor_seState0, _NodeRandomnessPredictor_seState1, _NodeRandomnessPredictor_s0Ref, _NodeRandomnessPredictor_s1Ref, _NodeRandomnessPredictor_solver, _NodeRandomnessPredictor_context, _NodeRandomnessPredictor_getNodeVersion, _NodeRandomnessPredictor_initialize, _NodeRandomnessPredictor_xorShift128PlusSymbolic, _NodeRandomnessPredictor_xorShift128PlusConcrete, _NodeRandomnessPredictor_recoverMantissaAndAddToSolver, _NodeRandomnessPredictor_toDouble;
Object.defineProperty(exports, "__esModule", { value: true });
const z3 = __importStar(require("z3-solver"));
const errors_js_1 = require("../errors.js");
/**
*
* In Node versions <= 11 the ToDouble method was different : https://github.com/nodejs/node/blob/v10.0.0/deps/v8/src/base/utils/random-number-generator.h#L114-L120
* ```
* static inline double ToDouble(uint64_t state0, uint64_t state1) {
* // Exponent for double values for [1.0 .. 2.0)
* static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000};
* static const uint64_t kMantissaMask = uint64_t{0x000FFFFFFFFFFFFF};
* uint64_t random = ((state0 + state1) & kMantissaMask) | kExponentBits;
* return bit_cast<double>(random) - 1;
* }
* ```
*
* In Node v24.x.x (commit was in Feb2025), V8 updated their impl of the `ToDouble` method. The old method was in use since 2022.
* This caused breaking changes to this predictor, so we now have to detect node version so we can choose which ToDouble to implement.
* - Old Impl: https://github.com/v8/v8/blob/e99218a1cca470ddec1931547b36a256f3450078/src/base/utils/random-number-generator.h#L111
* ```
* // Static and exposed for external use.
* static inline double ToDouble(uint64_t state0) {
* // Exponent for double values for [1.0 .. 2.0)
* static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000};
* uint64_t random = (state0 >> 12) | kExponentBits;
* return base::bit_cast<double>(random) - 1;
* }
* ```
* - New Impl: https://github.com/v8/v8/blob/1c3a9c08e932e87b04c7bf9ecc648e1f50d418fd/src/base/utils/random-number-generator.h#L111
* ```
* // Static and exposed for external use.
* static inline double ToDouble(uint64_t state0) {
* // Get a random [0,2**53) integer value (up to MAX_SAFE_INTEGER) by dropping
* // 11 bits of the state.
* double random_0_to_2_53 = static_cast<double>(state0 >> 11);
* // Map this to [0,1) by division with 2**53.
* constexpr double k2_53{static_cast<uint64_t>(1) << 53};
* return random_0_to_2_53 / k2_53;
* }
* ```
*
*/
class NodeRandomnessPredictor {
constructor(sequence) {
_NodeRandomnessPredictor_instances.add(this);
// See here for why MAX_SEQUENCE_LENGTH is needed: https://github.com/matthewoestreich/js-randomness-predictor/blob/main/.github/KNOWN_ISSUES.md#random-number-pool-exhaustion
_NodeRandomnessPredictor_MAX_SEQUENCE_LENGTH.set(this, 64);
_NodeRandomnessPredictor_DEFAULT_SEQUENCE_LENGTH.set(this, 4);
_NodeRandomnessPredictor_nodeVersion.set(this, __classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_getNodeVersion).call(this));
_NodeRandomnessPredictor_isInitialized.set(this, false);
_NodeRandomnessPredictor_concreteState0.set(this, 0n);
_NodeRandomnessPredictor_concreteState1.set(this, 0n);
_NodeRandomnessPredictor_mask.set(this, 0xffffffffffffffffn);
_NodeRandomnessPredictor_internalSequence.set(this, []);
_NodeRandomnessPredictor_seState0.set(this, void 0);
_NodeRandomnessPredictor_seState1.set(this, void 0);
_NodeRandomnessPredictor_s0Ref.set(this, void 0);
_NodeRandomnessPredictor_s1Ref.set(this, void 0);
_NodeRandomnessPredictor_solver.set(this, void 0);
_NodeRandomnessPredictor_context.set(this, void 0);
if (sequence && sequence.length >= __classPrivateFieldGet(this, _NodeRandomnessPredictor_MAX_SEQUENCE_LENGTH, "f")) {
throw new Error(`sequence.length must be less than '${__classPrivateFieldGet(this, _NodeRandomnessPredictor_MAX_SEQUENCE_LENGTH, "f")}', got '${sequence.length}'`);
}
if (!sequence) {
sequence = Array.from({ length: __classPrivateFieldGet(this, _NodeRandomnessPredictor_DEFAULT_SEQUENCE_LENGTH, "f") }, Math.random);
}
this.sequence = sequence;
__classPrivateFieldSet(this, _NodeRandomnessPredictor_internalSequence, [...sequence.reverse()], "f");
}
async predictNext() {
await __classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_initialize).call(this);
return __classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_toDouble).call(this, __classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_xorShift128PlusConcrete).call(this));
}
// For testing - DO NOT USE IF YOU DON'T WANT TO BREAK THINGS.
setNodeVersion(version) {
__classPrivateFieldSet(this, _NodeRandomnessPredictor_nodeVersion, version, "f");
}
}
_NodeRandomnessPredictor_MAX_SEQUENCE_LENGTH = new WeakMap(), _NodeRandomnessPredictor_DEFAULT_SEQUENCE_LENGTH = new WeakMap(), _NodeRandomnessPredictor_nodeVersion = new WeakMap(), _NodeRandomnessPredictor_isInitialized = new WeakMap(), _NodeRandomnessPredictor_concreteState0 = new WeakMap(), _NodeRandomnessPredictor_concreteState1 = new WeakMap(), _NodeRandomnessPredictor_mask = new WeakMap(), _NodeRandomnessPredictor_internalSequence = new WeakMap(), _NodeRandomnessPredictor_seState0 = new WeakMap(), _NodeRandomnessPredictor_seState1 = new WeakMap(), _NodeRandomnessPredictor_s0Ref = new WeakMap(), _NodeRandomnessPredictor_s1Ref = new WeakMap(), _NodeRandomnessPredictor_solver = new WeakMap(), _NodeRandomnessPredictor_context = new WeakMap(), _NodeRandomnessPredictor_instances = new WeakSet(), _NodeRandomnessPredictor_getNodeVersion = function _NodeRandomnessPredictor_getNodeVersion() {
const [major, minor, patch] = process.versions.node.split(".").map(Number);
return { major, minor, patch };
}, _NodeRandomnessPredictor_initialize =
// `#initialize()` essentially solves symbolic state so we can move forward using
// concrete state (which is way faster than having to recompute symbolic state
// for every prediction).
async function _NodeRandomnessPredictor_initialize() {
if (__classPrivateFieldGet(this, _NodeRandomnessPredictor_isInitialized, "f")) {
return true;
}
try {
const { Context } = await z3.init();
__classPrivateFieldSet(this, _NodeRandomnessPredictor_context, Context("main"), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_solver, new (__classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").Solver)(), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_seState0, __classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.const("se_state0", 64), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_seState1, __classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.const("se_state1", 64), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_s0Ref, __classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f"), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_s1Ref, __classPrivateFieldGet(this, _NodeRandomnessPredictor_seState1, "f"), "f");
for (let i = 0; i < __classPrivateFieldGet(this, _NodeRandomnessPredictor_internalSequence, "f").length; i++) {
__classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_xorShift128PlusSymbolic).call(this);
__classPrivateFieldGet(this, _NodeRandomnessPredictor_instances, "m", _NodeRandomnessPredictor_recoverMantissaAndAddToSolver).call(this, __classPrivateFieldGet(this, _NodeRandomnessPredictor_internalSequence, "f")[i]);
}
const check = await __classPrivateFieldGet(this, _NodeRandomnessPredictor_solver, "f").check();
if (check !== "sat") {
return Promise.reject(new errors_js_1.UnsatError());
}
const model = __classPrivateFieldGet(this, _NodeRandomnessPredictor_solver, "f").model();
__classPrivateFieldSet(this, _NodeRandomnessPredictor_concreteState0, model.get(__classPrivateFieldGet(this, _NodeRandomnessPredictor_s0Ref, "f")).value(), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_concreteState1, model.get(__classPrivateFieldGet(this, _NodeRandomnessPredictor_s1Ref, "f")).value(), "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_isInitialized, true, "f");
return true;
}
catch (e) {
return Promise.reject(e);
}
}, _NodeRandomnessPredictor_xorShift128PlusSymbolic = function _NodeRandomnessPredictor_xorShift128PlusSymbolic() {
if (!__classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f") || !__classPrivateFieldGet(this, _NodeRandomnessPredictor_seState1, "f")) {
throw new errors_js_1.SymbolicStateEmpty();
}
let s1 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f");
let s0 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_seState1, "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_seState0, s0, "f");
s1 = s1.xor(s1.shl(23));
s1 = s1.xor(s1.lshr(17));
s1 = s1.xor(s0);
s1 = s1.xor(s0.lshr(26));
__classPrivateFieldSet(this, _NodeRandomnessPredictor_seState1, s1, "f");
}, _NodeRandomnessPredictor_xorShift128PlusConcrete = function _NodeRandomnessPredictor_xorShift128PlusConcrete() {
const ogConcreteState0 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_concreteState0, "f");
const ogConcreteState1 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_concreteState1, "f");
let ps1 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_concreteState0, "f");
let ps0 = __classPrivateFieldGet(this, _NodeRandomnessPredictor_concreteState1, "f") ^ (ps1 >> 26n);
ps0 ^= ps1;
ps0 = (ps0 ^ (ps0 >> 17n) ^ (ps0 >> 34n) ^ (ps0 >> 51n)) & __classPrivateFieldGet(this, _NodeRandomnessPredictor_mask, "f");
ps0 = (ps0 ^ (ps0 << 23n) ^ (ps0 << 46n)) & __classPrivateFieldGet(this, _NodeRandomnessPredictor_mask, "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_concreteState0, ps0, "f");
__classPrivateFieldSet(this, _NodeRandomnessPredictor_concreteState1, ps1, "f");
// Very old logic that goes back to at least v10
if (__classPrivateFieldGet(this, _NodeRandomnessPredictor_nodeVersion, "f").major <= 11) {
return ogConcreteState0 + ogConcreteState1;
}
// Newer logic
return ogConcreteState0;
}, _NodeRandomnessPredictor_recoverMantissaAndAddToSolver = function _NodeRandomnessPredictor_recoverMantissaAndAddToSolver(n) {
const majorVersion = __classPrivateFieldGet(this, _NodeRandomnessPredictor_nodeVersion, "f").major;
// Very old logic that goes back to at least v10
if (majorVersion <= 11) {
const buffer = Buffer.alloc(8);
buffer.writeDoubleLE(n + 1, 0);
const rawBits = (BigInt(buffer.readUInt32LE(4)) << 32n) | BigInt(buffer.readUInt32LE(0));
const mantissaMask = 0x000fffffffffffffn;
const mantissa = rawBits & mantissaMask;
const sum = __classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f").add(__classPrivateFieldGet(this, _NodeRandomnessPredictor_seState1, "f")).and(__classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.val(mantissaMask, 64));
__classPrivateFieldGet(this, _NodeRandomnessPredictor_solver, "f").add(sum.eq(__classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.val(mantissa, 64)));
return;
}
// Old-ish `ToDouble` logic (in use from ~2022 - Feb 2025)
if (majorVersion <= 23) {
const buffer = Buffer.alloc(8);
buffer.writeDoubleLE(n + 1, 0);
const uint64 = (BigInt(buffer.readUInt32LE(4)) << 32n) | BigInt(buffer.readUInt32LE(0));
const mantissa = uint64 & ((1n << 52n) - 1n);
__classPrivateFieldGet(this, _NodeRandomnessPredictor_solver, "f").add(__classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f").lshr(12).eq(__classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.val(mantissa, 64)));
return;
}
// New `ToDouble` logic (Feb 2025) introduced to V8.
const mantissa = Math.floor(n * Math.pow(2, 53));
__classPrivateFieldGet(this, _NodeRandomnessPredictor_solver, "f").add(__classPrivateFieldGet(this, _NodeRandomnessPredictor_seState0, "f").lshr(11).eq(__classPrivateFieldGet(this, _NodeRandomnessPredictor_context, "f").BitVec.val(BigInt(mantissa), 64)));
}, _NodeRandomnessPredictor_toDouble = function _NodeRandomnessPredictor_toDouble(n) {
const majorVersion = __classPrivateFieldGet(this, _NodeRandomnessPredictor_nodeVersion, "f").major;
// Very old logic that goes back to at least v10
if (majorVersion <= 11) {
const kExponentBits = 0x3ff0000000000000n;
const kMantissaMask = 0x000fffffffffffffn;
const random = (n & kMantissaMask) | kExponentBits;
const buffer = Buffer.alloc(8);
buffer.writeBigUInt64LE(random, 0);
return buffer.readDoubleLE(0) - 1;
}
/* Old-ish logic (pre-Feb 2025) */
if (majorVersion <= 23) {
const buffer = Buffer.allocUnsafe(8);
buffer.writeBigUInt64LE((n >> 12n) | 0x3ff0000000000000n, 0);
return buffer.readDoubleLE(0) - 1;
}
/* New ToDouble logic (Feb 2025+) */
return Number(n >> 11n) / Math.pow(2, 53);
};
exports.default = NodeRandomnessPredictor;