Created
June 19, 2023 16:32
-
-
Save rfl890/17418c7a28ff9903adc3ce555fcf6e02 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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