js-randomness-predictor
Version:
Predict Math.random output in Node, Deno, Bun, Chrome, Firefox, and Safari
12 lines (11 loc) • 16 kB
JavaScript
import*as w from"node:fs";import*as g from"node:path";import{fileURLToPath as Q}from"node:url";import{spawnSync as z}from"node:child_process";import Z from"yargs";import{hideBin as ee}from"yargs/helpers";import*as N from"z3-solver-jsrp";function te(t){return`\x1B[31m${t}\x1B[0m`}function ne(t){return`\x1B[33m${t}\x1B[0m`}function oe(t){return`\x1B[34m${t}\x1B[0m`}class M{static info(...e){console.info(oe(`[INFO] ${e.join(" ")}`))}static log(...e){console.log(`[LOG] ${e.join(" ")}`)}static warn(...e){console.warn(ne(`[WARN] ${e.join(" ")}`))}static error(...e){console.error(te(`[ERROR] ${e.join(" ")}`))}}function I(t=1){const e=[];for(let n=0;n<t;n++)e.push(Math.random());return e}class m extends Error{constructor(e){e=e===void 0?"Cannot solve state. Unable to make accurate predictions.":e,super(e),this.name="UnsatError",Object.setPrototypeOf(this,new.target.prototype)}}class q extends Error{constructor(e){super(e),this.name="InsufficientSequenceLengthError",Object.setPrototypeOf(this,new.target.prototype)}}class re extends Error{constructor(e){super(e),this.name="SequenceNotFoundError",Object.setPrototypeOf(this,new.target.prototype)}}class E extends Error{constructor(e){super(e),this.name="UnexpectedRuntimeError",Object.setPrototypeOf(this,new.target.prototype)}}class a{constructor(){}static isDeno(){return typeof globalThis.Deno<"u"&&typeof Deno.version<"u"&&typeof Deno.version.deno=="string"}static isBun(){return typeof globalThis.Bun<"u"&&typeof Bun.version<"u"&&typeof process.versions.bun=="string"}static isNode(){return!this.isDeno()&&!this.isBun()&&typeof process.versions.node<"u"&&typeof process.versions.node=="string"}static isFirefox(){return typeof window<"u"&&navigator.userAgent.indexOf("Firefox")>-1}static isChrome(){return typeof window<"u"&&navigator.userAgent.indexOf("Chrome")>-1}static isSafari(){return typeof window<"u"&&navigator.userAgent.indexOf("Safari")>-1}static type(){if(this.isNode())return"node";if(this.isDeno())return"deno";if(this.isBun())return"bun";if(this.isChrome())return"chrome";if(this.isSafari())return"safari";if(this.isFirefox())return"firefox";throw new E}}function c(t){return t&0xffffffffffffffffn}class s{constructor(){}static symbolic(e){let n=e[0];n=n.xor(n.shl(23)),n=n.xor(n.lshr(17)),n=n.xor(e[1]),n=n.xor(e[1].lshr(26)),e[0]=e[1],e[1]=n}static symbolicArithmeticShiftRight(e){let n=e[0];n=n.xor(n.shl(23)),n=n.xor(n.shr(17)),n=n.xor(e[1]),n=n.xor(e[1].shr(26)),e[0]=e[1],e[1]=n}static concreteBackwards(e){let n=e[1]^e[0]>>26n^e[0];n=c(n^n>>17n^n>>34n^n>>51n),n=c(n^n<<23n^n<<46n),e[1]=e[0],e[0]=n}static concrete(e){let n=e[0];n^=c(n<<23n),n^=c(n>>17n),n^=e[1],n^=c(e[1]>>26n),e[0]=e[1],e[1]=n}static concreteArithmeticShiftRight(e){let n=c(e[0]);n^=c(n<<23n),n^=this.#t(n,17n),n^=e[1],n^=this.#t(e[1],26n),e[0]=e[1],e[1]=c(n)}static#t(e,n){const o=c(e>>n);if((e&1n<<63n)===0n)return o;const i=(1n<<n)-1n<<64n-n;return o|i}}class B{sequence;#t=[0n,0n];#n;#e;constructor(e,n){if(n.length<=0)throw new Error("V8EnginePredictor requires at least one solvingStrategy in solvingStrategies");this.sequence=e,this.#n=n,this.#e=this.#n[0]}async predictNext(){this.#t[0]===0n&&this.#t[1]===0n&&await this.#r();const e=this.#e.toDouble(this.#t);return this.#e.concreteXorShift(this.#t),e}async#o(){try{const{Context:e}=await N.init(),n=e("main"),o=new n.Solver,r=n.BitVec.const("ss0",64),i=n.BitVec.const("ss1",64),f=[r,i],h=[...this.sequence].reverse();for(const d of h){this.#e.symbolicXorShift(f);const p=this.#e.recoverMantissa(d);this.#e.constrainMantissa(p,f,o,n)}if(await o.check()!=="sat")throw new m;const u=o.model();this.#t=[u.get(r).value(),u.get(i).value()]}catch(e){return Promise.reject(e)}}async#r(){let e;for(const n of this.#n)try{return this.#e=n,await this.#o()}catch(o){if(!(o instanceof m))throw o;e=o}throw e??new Error("V8EnginePredictor : no strategies attempted")}}const S=Math.pow(2,53),se=[{recoverMantissa:t=>{const e=Math.floor(t*S);return BigInt(e)},toDouble:t=>Number(t[0]>>11n)/S,constrainMantissa:(t,e,n,o)=>{n.add(e[0].lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)},{recoverMantissa:t=>{const e=Math.floor(t*S);return BigInt(e)},toDouble:t=>{const e=c(t[0]+t[1]);return Number(e>>11n)/S},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]);n.add(r.lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)}];class ie extends B{constructor(e){super(e,se)}}class ce{sequence;#t=[0n,0n];#n;#e;constructor(e,n){if(n.length<=0)throw new Error("SpiderMonkeyPredictor requires at least one solvingStrategy in solvingStrategies");this.sequence=e,this.#n=n,this.#e=this.#n[0]}async predictNext(){return this.#t[0]===0n&&this.#t[1]===0n&&await this.#r(),this.#e.concreteXorShift(this.#t),this.#e.toDouble(this.#t)}async#o(){try{const{Context:e}=await N.init(),n=e("main"),o=new n.Solver,r=n.BitVec.const("ss0",64),i=n.BitVec.const("ss1",64),f=[r,i];for(const d of this.sequence){this.#e.symbolicXorShift(f);const p=this.#e.recoverMantissa(d);this.#e.constrainMantissa(p,f,o,n)}if(await o.check()!=="sat")throw new m;const h=o.model(),u=[h.get(r).value(),h.get(i).value()];for(const d of this.sequence)this.#e.concreteXorShift(u);this.#t=u}catch(e){return Promise.reject(e)}}async#r(){let e;for(const n of this.#n)try{return this.#e=n,await this.#o()}catch(o){if(!(o instanceof m))throw o;e=o}throw e??new Error("SpiderMonkeyPredictor : no strategies attempted")}}const C=0x1fffffffffffffn,O=Math.pow(2,53),ae=[{recoverMantissa:t=>BigInt(Math.floor(t*O)),toDouble:t=>{const e=c(t[0]+t[1]);return Number(e&C)/O},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]).and(o.BitVec.val(C,64));n.add(r.eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concrete(t)}];class fe extends ce{constructor(e){super(e,ae)}}const $="JSRP_RUNTIME",ue=10,b=64,R=["node","bun","deno","chrome","firefox","safari"],X=["node","bun","deno"],he=["chrome","safari","firefox"];Object.fromEntries(R.map(t=>[t,X.includes(t)])),Object.fromEntries(R.map(t=>[t,he.includes(t)]));const D={node:"v8",bun:"javascriptcore",deno:"v8",chrome:"v8",firefox:"spidermonkey",safari:"javascriptcore"};Object.entries(D).reduce((t,[e,n])=>((t[n]||=[]).push(e),t),{});const y={bun:6,safari:6,node:5,chrome:4,deno:4,firefox:4},U=64,x=0x000fffffffffffffn,V=0x3ff0000000000000n,v=Math.pow(2,53),de=[{recoverMantissa:t=>{const e=Math.floor(t*v);return BigInt(e)},toDouble:t=>{const e=c(t[0]+t[1]);return Number(e>>11n)/v},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]);n.add(r.lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)},{recoverMantissa:t=>{const e=Math.floor(t*v);return BigInt(e)},toDouble:t=>Number(t[0]>>11n)/v,constrainMantissa:(t,e,n,o)=>{n.add(e[0].lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)},{recoverMantissa:t=>{const e=Buffer.alloc(8);return e.writeDoubleLE(t+1,0),e.readBigUInt64LE(0)&x},toDouble:t=>{const e=Buffer.alloc(8);return e.writeBigUInt64LE(t[0]>>12n|V,0),e.readDoubleLE(0)-1},constrainMantissa:(t,e,n,o)=>{n.add(e[0].lshr(12).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)},{recoverMantissa:t=>{const e=Buffer.alloc(8);return e.writeDoubleLE(t+1,0),e.readBigUInt64LE(0)&x},toDouble:t=>{const e=t[0]+t[1],n=Buffer.alloc(8);return n.writeBigUInt64LE(e&x|V,0),n.readDoubleLE(0)-1},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]).and(o.BitVec.val(x,64));n.add(r.eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)}];class le extends B{constructor(e){if(e){if(e.length>=U)throw new Error(`sequence.length must be less than '${U}', got '${e.length}'`);if(e.length<y.node)throw new Error(`sequence.length must be at least '${y.node}', got '${e.length}'`)}if(!e){if(!a.isNode())throw new E("Expected NodeJS runtime! Unable to auto-generate sequence, please provide one.");e=Array.from({length:y.node},Math.random)}super(e,de)}}class L{sequence;#t=[0n,0n];#n;#e;constructor(e,n){if(n.length<=0)throw new Error("JavaScriptCorePredictor requires at least one solvingStrategy in solvingStrategies");this.sequence=e,this.#n=n,this.#e=this.#n[0]}async predictNext(){return this.#t[0]===0n&&this.#t[1]===0n&&await this.#r(),this.#e.concreteXorShift(this.#t),this.#e.toDouble(this.#t)}async#o(){try{const{Context:e}=await N.init(),n=e("main"),o=new n.Solver,r=n.BitVec.const("ss0",64),i=n.BitVec.const("ss1",64),f=[r,i];for(const d of this.sequence){this.#e.symbolicXorShift(f);const p=this.#e.recoverMantissa(d);this.#e.constrainMantissa(p,f,o,n)}if(await o.check()!=="sat")throw new m;const h=o.model(),u=[h.get(r).value(),h.get(i).value()];for(const d of this.sequence)this.#e.concreteXorShift(u);this.#t=u}catch(e){return Promise.reject(e)}}async#r(){let e;for(const n of this.#n)try{return this.#e=n,await this.#o()}catch(o){if(!(o instanceof m))throw o;e=o}throw e??new Error("JavaScriptCorePredictor : no strategies attempted")}}const j=6,F=0x1fffffffffffffn,k=Math.pow(2,53),G={recoverMantissa:t=>BigInt(Math.floor(t*k)),toDouble:t=>{const e=c(t[0]+t[1]);return Number(e&F)/k},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]).and(o.BitVec.val(F,64));n.add(r.eq(o.BitVec.val(t,64)))}},me=[{...G,symbolicXorShift:t=>s.symbolicArithmeticShiftRight(t),concreteXorShift:t=>s.concreteArithmeticShiftRight(t)},{...G,symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concrete(t)}];class pe extends L{constructor(e){if(e.length<j)throw new q(`sequence length must be >= ${j} : got ${e.length}`);super(e,me)}}const T=6,J=0x1fffffffffffffn,K=Math.pow(2,53),W={recoverMantissa:t=>BigInt(Math.floor(t*K)),toDouble:t=>{const e=c(t[0]+t[1]);return Number(e&J)/K},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]).and(o.BitVec.val(J,64));n.add(r.eq(o.BitVec.val(t,64)))}},we=[{...W,symbolicXorShift:t=>s.symbolicArithmeticShiftRight(t),concreteXorShift:t=>s.concreteArithmeticShiftRight(t)},{...W,symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concrete(t)}];class ge extends L{constructor(e){if(e&&e.length<T)throw new q(`sequence length must be >= ${T} : got ${e.length}`);if(!e){if(!a.isBun())throw new E("Expected Bun runtime! Unable to auto-generate sequence, please provide one.");e=I(T)}super(e,we)}}const _=Math.pow(2,53),Ee=[{recoverMantissa:t=>{const e=Math.floor(t*_);return BigInt(e)},toDouble:t=>Number(t[0]>>11n)/_,constrainMantissa:(t,e,n,o)=>{n.add(e[0].lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)},{recoverMantissa:t=>{const e=Math.floor(t*_);return BigInt(e)},toDouble:t=>{const e=c(t[0]+t[1]);return Number(e>>11n)/_},constrainMantissa:(t,e,n,o)=>{const r=e[0].add(e[1]);n.add(r.lshr(11).eq(o.BitVec.val(t,64)))},symbolicXorShift:t=>s.symbolic(t),concreteXorShift:t=>s.concreteBackwards(t)}];class Se extends B{constructor(e){if(!e){if(!a.isDeno())throw new E("Expected Deno runtime! Unable to auto-generate sequence, please provide one.");e=Array.from({length:4},Math.random)}super(e,Ee)}}const l={node:t=>new le(t),firefox:t=>new fe(t),chrome:t=>new ie(t),safari:t=>new pe(t),bun:t=>new ge(t),deno:t=>new Se(t)};l.node,l.firefox,l.chrome,l.safari,l.bun,l.deno;const A=xe(process.env[$]);if(A==="node"||process.env.JSRP_CHILD==="1")try{await Y().parseAsync(),process.exit(0)}catch(t){console.error(t.message),process.exit(1)}const H=[Q(import.meta.url),...process.argv.slice(2)],P={stdio:"inherit",env:{...process.env},shell:!0};if(P.env.JSRP_CHILD="1",A==="deno"){const t=g.resolve(import.meta.dirname,"./deno_import_map.json");H.unshift("--node-modules-dir=auto","--allow-env","--allow-read",`--import-map=${t}`),P.env.DENO_COMPAT="1"}const be=z(`${A.toString()} ${H.join(" ")}`,P);process.exit(be.status??1);function Y(){return Z(ee(process.argv)).scriptName("js-randomness-predictor").command(ye()).fail((t,e)=>{throw e||new Error(t)}).help()}function ye(){return{command:"*",describe:"Predict future Math.random() values",builder:t=>t.option("environment",{alias:"e",describe:"Predictor environment",choices:R,demandOption:!0,type:"string"}).option("sequence",{alias:"s",describe:"Observed sequence",type:"array",coerce:e=>e.map(n=>{const o=Number(n);if(isNaN(o))throw new Error(`Invalid number in sequence: ${n}`);return o})}).option("predictions",{alias:"p",describe:"Number of predictions",type:"number",default:10,coerce:e=>{if(e<=0)throw new Error(`--predictions must be greater than 0! Got ${e}`);return e}}).option("export",{alias:"x",describe:"File path to export results. Must be to a .json file. Path relative to current working directory!",type:"string"}).option("force",{alias:"f",describe:"If exporting, overwrite existing file or create needed directories in path",type:"boolean"}),handler:async t=>{await ve(t)}}}function xe(t){const e=t?.trim();return X.includes(e??"")?e:"node"}async function ve(t){try{_e(t);const e={actual:"You'll need to get this yourself via the same way you generated the sequence",sequence:t.sequence?t.sequence:I(y[t.environment]),isCorrect:void 0,predictions:[],runtime:a.type(),_warnings:[],_info:[]},n=Ne(t,e);process.env.JSRP_DRY_RUN==="1"&&(console.log(JSON.stringify(e,null,2)),process.exit(0));const o=l[t.environment](e.sequence);await Ie(o,e,n),Me(t,e,n),Be(e),t.export&&Re(t,e);const r={sequence:e.sequence,predictions:e.predictions,actual:e.actual,runtime:e.runtime};e?.isCorrect!==void 0&&(r.isCorrect=e.isCorrect),console.log(JSON.stringify(r,null,2)),e._info&&e._info.length&&e._info.forEach(i=>M.info(`
`,i,`
`)),e._warnings&&e._warnings.length&&e._warnings.forEach(i=>M.warn(`
`,i,`
`))}catch(e){M.error(`
Something went wrong!`,e?.message,`
`),process.exit(1)}}function _e(t){if(!t.sequence&&a.type()!==t.environment)throw new re(`'--sequence' is required when '--environment' is '${t.environment}' and '${$}' is '${a.type()}'`)}function Ne(t,e){const n=t.predictions!==void 0?t.predictions:ue;if(D[t.environment]!=="v8")return n;if(e.sequence.length>=b)throw new Error(`Sequence too large! Sequence length must be less than '${b}', got '${e.sequence.length}'`);if(n+e.sequence.length<=b)return n;const o=b-e.sequence.length;return(e._warnings??=[]).push(`Exceeded max predictions!
- For a sequence length of '${e.sequence.length}', max number of predictions allowed is '${o}'.
- Truncated number of predictions to '${o}'.
- Why? See here : https://github.com/matthewoestreich/js-randomness-predictor/blob/main/.github/KNOWN_ISSUES.md#random-number-pool-exhaustion`),o}function Me(t,e,n){t.sequence||(t.environment==="node"&&a.isNode()||t.environment==="deno"&&a.isDeno()||t.environment==="bun"&&a.isBun())&&(e.actual=I(n))}async function Ie(t,e,n){for(let o=0;o<n;o++)e.predictions.push(await t.predictNext())}function Be(t){Array.isArray(t.actual)&&(t.isCorrect=t.actual.every((e,n)=>e===t.predictions[n]))}function Re(t,e){const n=g.resolve(process.cwd(),t.export.toString()),o=g.dirname(n),r=w.existsSync(n),i=w.existsSync(o);if(r&&!w.statSync(n).isFile()){e._warnings?.push(`Export path must be to a file! ${n}`);return}if(g.extname(n)!==".json"){e._warnings?.push(`Export path must be to a .json file! ${n}`);return}if(r&&!t.force){e._warnings?.push("Export path already exists and '--force' was not used! Use '--force' to overwrite existing files.");return}if(!i&&!t.force){e._warnings?.push("One or more directories does not exist in export path and '--force' was not used! Use '--force' to create full path if it does not exist.");return}!i&&t.force&&w.mkdirSync(o,{recursive:!0}),Te(n,e)}function Te(t,e){try{const n={sequence:e.sequence,predictions:e.predictions,actual:e.actual,runtime:e.runtime};e.isCorrect!==void 0&&(n.isCorrect=e.isCorrect),w.writeFileSync(t,JSON.stringify(n,null,2),{encoding:"utf-8"}),(e._info??=[]).push(`Exported results to '${t}'`)}catch(n){(e._warnings??=[]).push(`Unable to export results! ${n.message}`)}}export{Y as default};