UNPKG

js-randomness-predictor

Version:
96 lines (95 loc) 3.6 kB
import * as z3 from "z3-solver"; import { UnsatError } from "../errors.js"; export default class FirefoxRandomnessPredictor { #isInitialized = false; #mask = 0xffffffffffffffffn; #seState0; #seState1; #s0Ref; #s1Ref; #solver; #context; #concreteState0 = 0n; #concreteState1 = 0n; sequence = []; constructor(sequence) { this.sequence = sequence; } async #initialize() { if (this.#isInitialized) { return true; } try { const { Context } = await z3.init(); this.#context = Context("main"); this.#solver = new this.#context.Solver(); this.#seState0 = this.#context.BitVec.const("se_state0", 64); this.#seState1 = this.#context.BitVec.const("se_state1", 64); this.#s0Ref = this.#seState0; this.#s1Ref = this.#seState1; for (let i = 0; i < this.sequence.length; i++) { this.#xorShift128PlusSymbolic(); const mantissa = this.#recoverMantissa(this.sequence[i]); const state = this.#seState0.add(this.#seState1).and(this.#context.BitVec.val(0x1fffffffffffff, 64)); this.#solver.add(state.eq(this.#context.BitVec.val(mantissa, 64))); } const check = await this.#solver.check(); if (check !== "sat") { return Promise.reject(new UnsatError()); } const model = this.#solver.model(); this.#concreteState0 = model.get(this.#s0Ref).value(); this.#concreteState1 = model.get(this.#s1Ref).value(); // We have to get our concrete state up to the same point as our symbolic state, // therefore, we discard as many concrete XOR shift calls as we have `this.sequence.length` // Otherwise, we would return random numbers to the caller that they already have. // Now, when we return from predictNext() we get the actual next. for (let i = 0; i < this.sequence.length; i++) { this.#xorShift128PlusConcrete(); } this.#isInitialized = true; return true; } catch (e) { return Promise.reject(e); } } /** * Predict next random number. * @returns {Promise<number>} */ async predictNext() { await this.#initialize(); return this.#toDouble(this.#xorShift128PlusConcrete()); } #xorShift128PlusSymbolic() { if (this.#seState0 === undefined || this.#seState1 === undefined) { throw new Error("States are not defined!"); } let s1 = this.#seState0; let s0 = this.#seState1; s1 = s1.xor(s1.shl(23)); s1 = s1.xor(s1.lshr(17)); s1 = s1.xor(s0); s1 = s1.xor(s0.lshr(26)); this.#seState0 = this.#seState1; this.#seState1 = s1; } #xorShift128PlusConcrete() { let s1 = this.#concreteState0 & this.#mask; let s0 = this.#concreteState1 & this.#mask; s1 ^= (s1 << 23n) & this.#mask; s1 ^= (s1 >> 17n) & this.#mask; s1 ^= s0 & this.#mask; s1 ^= (s0 >> 26n) & this.#mask; this.#concreteState0 = s0 & this.#mask; this.#concreteState1 = s1 & this.#mask; return (this.#concreteState0 + this.#concreteState1) & this.#mask; } #recoverMantissa(double) { return BigInt(Math.floor(double * Math.pow(2, 53))); } #toDouble(n) { return Number(n & 0x1fffffffffffffn) / Number(1n << 53n); } }