UNPKG

js-randomness-predictor

Version:
235 lines (234 loc) 16.7 kB
"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;