Skip to content

Instantly share code, notes, and snippets.

@rfl890
Created June 19, 2023 16:32
Show Gist options
  • Save rfl890/17418c7a28ff9903adc3ce555fcf6e02 to your computer and use it in GitHub Desktop.
Save rfl890/17418c7a28ff9903adc3ce555fcf6e02 to your computer and use it in GitHub Desktop.
/*
This is a "rewrite" of isaacCSPRNG by macmcmeans (https://github.com/macmcmeans/isaacCSPRNG)
It has been adapted to use newer JavaScript features. I've
removed some features that I deemed unnecessary,
but you can write them yourselves easily by using
the random() function as you would with Math.random().
*/
class isaacCSPRNG {
#memory;
#accumulator;
#lastResult;
#counter;
#resultArray;
#generationCounter;
#textEncoder;
#BIG_ENDIAN;
constructor(specifiedSeed) {
/* intialize values */
this.#memory = new Uint32Array(256);
this.#accumulator = 0;
this.#lastResult = 0;
this.#counter = 0;
this.#resultArray = new Uint32Array(256);
this.#generationCounter = 0;
this.#textEncoder = new TextEncoder();
this.#BIG_ENDIAN =
new Uint16Array(new Uint8Array([0x45, 0xfe]).buffer)[0] === 0x45fe;
this._version = "1.1";
let internalSeed = new Uint32Array(2);
window.crypto.getRandomValues(internalSeed);
const defaultInternalSeed = internalSeed[0] + internalSeed[1];
this.seed(specifiedSeed || defaultInternalSeed);
}
#makeDouble(hi, lo) {
if (this.#BIG_ENDIAN) {
return (
new Float64Array(
new Uint32Array([
((hi & 0x1fffff) | 0x3ff00000) >>> 0,
lo,
]).buffer
)[0] - 1
);
} else {
return (
new Float64Array(
new Uint32Array([
lo,
((hi & 0x1fffff) | 0x3ff00000) >>> 0,
]).buffer
)[0] - 1
);
}
}
// 32-bit integer safe add.
// currently relies on the fact that
// js bitwise ops are 32 bit but will
// change later
#add(x, y) {
return (x + y) >> 0;
}
#hexEncode(data) {
let outputString = "";
for (let i = 0; i < data.length; i++) {
outputString += data[i].toString(16).padStart(2, 0);
}
return outputString;
}
#hexDecode(data) {
const output = new Uint8Array(data.length / 2);
let _i = 0;
for (let i = 0; i < data.length; i += 2) {
output[_i] = parseInt(data.charAt(i) + data.charAt(i + 1), 16);
_i++;
}
return output;
}
#reset() {
this.#accumulator = 0;
this.#lastResult = 0;
this.#counter = 0;
this.#memory.fill(0);
this.#resultArray.fill(0);
this.#generationCounter = 0;
}
#strToi32(str) {
return new Uint32Array(
new TextEncoder().encode(
str + "\x00".repeat(4 - (str.length % 4))
).buffer
);
}
seed(theSeed) {
let a, b, c, d, e, f, g, h, i;
a = b = c = d = e = f = g = h = 0x9e3779b9;
if (theSeed && typeof theSeed === "string") {
theSeed = this.#strToi32(theSeed);
}
if (theSeed && typeof theSeed === "number") {
theSeed = new Uint32Array([theSeed]);
}
if (
theSeed &&
(theSeed instanceof Array || theSeed instanceof Uint32Array)
) {
this.#reset();
for (let i = 0; i < theSeed.length; i++) {
this.#resultArray[i & 0xff] +=
typeof theSeed[i] === "number" ? theSeed[i] : 0;
}
}
function _seed_mix() {
a ^= b << 11;
d = this.#add(d, a);
b = this.#add(b, c);
b ^= c >>> 2;
e = this.#add(e, b);
c = this.#add(c, d);
c ^= d << 8;
f = this.#add(f, c);
d = this.#add(d, e);
d ^= e >>> 16;
g = this.#add(g, d);
e = this.#add(e, f);
e ^= f << 10;
h = this.#add(h, e);
f = this.#add(f, g);
f ^= g >>> 4;
a = this.#add(a, f);
g = this.#add(g, h);
g ^= h << 8;
b = this.#add(b, g);
h = this.#add(h, a);
h ^= a >>> 9;
c = this.#add(c, h);
a = this.#add(a, b);
}
for (let i = 0; i < 4; i++) _seed_mix.call(this);
for (i = 0; i < 256; i += 8) {
if (theSeed) {
a = this.#add(a, this.#resultArray[i + 0]);
b = this.#add(b, this.#resultArray[i + 1]);
c = this.#add(c, this.#resultArray[i + 2]);
d = this.#add(d, this.#resultArray[i + 3]);
e = this.#add(e, this.#resultArray[i + 4]);
f = this.#add(f, this.#resultArray[i + 5]);
g = this.#add(g, this.#resultArray[i + 6]);
h = this.#add(h, this.#resultArray[i + 7]);
}
_seed_mix.call(this);
this.#memory[i + 0] = a;
this.#memory[i + 1] = b;
this.#memory[i + 2] = c;
this.#memory[i + 3] = d;
this.#memory[i + 4] = e;
this.#memory[i + 5] = f;
this.#memory[i + 6] = g;
this.#memory[i + 7] = h;
}
if (theSeed) {
for (i = 0; i < 256; i += 8) {
a = this.#add(a, this.#memory[i + 0]);
b = this.#add(b, this.#memory[i + 1]);
c = this.#add(c, this.#memory[i + 2]);
d = this.#add(d, this.#memory[i + 3]);
e = this.#add(e, this.#memory[i + 4]);
f = this.#add(f, this.#memory[i + 5]);
g = this.#add(g, this.#memory[i + 6]);
h = this.#add(h, this.#memory[i + 7]);
_seed_mix.call(this);
this.#memory[i + 0] = a;
this.#memory[i + 1] = b;
this.#memory[i + 2] = c;
this.#memory[i + 3] = d;
this.#memory[i + 4] = e;
this.#memory[i + 5] = f;
this.#memory[i + 6] = g;
this.#memory[i + 7] = h;
}
}
this.#prng();
this.#generationCounter = 256;
}
#prng(n) {
let i, x, y;
n = n && typeof n === "number" ? Math.floor(Math.abs(n)) : 1;
while (n--) {
this.#counter = this.#add(this.#counter, 1);
this.#lastResult = this.#add(this.#lastResult, this.#counter);
for (let i = 0; i < 256; i++) {
switch (i & 3) {
case 0:
this.#accumulator ^= this.#accumulator << 13;
break;
case 1:
this.#accumulator ^= this.#accumulator >>> 6;
break;
case 2:
this.#accumulator ^= this.#accumulator << 2;
break;
case 3:
this.#accumulator ^= this.#accumulator >>> 16;
break;
}
this.#accumulator = this.#add(
this.#memory[(i + 128) & 0xff],
this.#accumulator
);
x = this.#memory[i];
this.#memory[i] = y = this.#add(
this.#memory[(x >>> 2) & 0xff],
this.#add(this.#accumulator, this.#lastResult)
);
this.#resultArray[i] = this.#lastResult = this.#add(
this.#memory[(y >>> 10) & 0xff],
x
);
}
}
}
// Returns a random 32-bit unsigned integer.
rand32u() {
if (!this.#generationCounter--) {
this.#prng();
this.#generationCounter = 255;
}
return this.#resultArray[this.#generationCounter];
}
// Returns a random 32-bit signed integer.
rand32s() {
return this.rand32u() >> 0;
}
// Returns a double fraction on the interval [0, 1). Drop in replacement
// for Math.random
random() {
const upperHalf = this.rand32u();
const lowerHalf = this.rand32u();
return this.#makeDouble(upperHalf, lowerHalf);
}
// Returns n random bytes.
bytes(n) {
if (typeof n !== "number" || !(n >= 0 && n <= Number.MAX_SAFE_INTEGER))
throw new Error("n must be a number >= 0 and <= (2^53)-1");
if (n <= 4) {
const randomValue = new Uint32Array([this.rand32u()]);
const result = new Uint8Array(randomValue.buffer);
return result.subarray(0, n);
}
const remainder = n % 4;
const blocks = (n - remainder) / 4;
const finalLength = blocks + (remainder > 0 ? 1 : 0);
const result = new Uint32Array(finalLength);
for (let i = 0; i < finalLength; i++) {
result[i] = this.rand32u();
}
const view = new Uint8Array(result.buffer);
return view.subarray(0, n);
}
internals() {
return {
memory: Array.from(this.#memory),
accumulator: this.#accumulator,
lastResult: this.#lastResult,
counter: this.#counter,
resultArray: Array.from(this.#resultArray),
generationCounter: this.#generationCounter,
};
}
exportState() {
return JSON.stringify(this.internals());
}
importState(str) {
const _state = JSON.parse(str);
this.#memory = new Uint32Array(_state.memory);
this.#accumulator = _state.accumulator;
this.#lastResult = _state.lastResult;
this.#counter = _state.counter;
this.#resultArray = new Uint32Array(_state.resultArray);
this.#generationCounter = _state.generationCounter;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment