70 lines
1.7 KiB
TypeScript
70 lines
1.7 KiB
TypeScript
import {bytesToHex, hexToNumber} from '@noble/curves/abstract/utils'
|
|
|
|
const useless = new Uint8Array(0)
|
|
|
|
export default class Random {
|
|
private readonly state: Uint8Array;
|
|
|
|
public constructor(
|
|
state?: Uint8Array
|
|
) {
|
|
if (state === undefined) {
|
|
this.state = crypto.getRandomValues(new Uint8Array(64))
|
|
} else {
|
|
if (state.length !== 64) throw "State must have a length of 64"
|
|
this.state = state
|
|
}
|
|
}
|
|
|
|
public async random(max: bigint) : Promise<bigint> {
|
|
if (max <= 0) throw "Only works for range [0, max[, with max > 0"
|
|
|
|
const size = log2(max)
|
|
const bitsize = ((size % 8) === 0) ? size : (size - (size % 8) + 8)
|
|
|
|
while (true) {
|
|
const bytes = await this.random_bytes(bitsize)
|
|
const n = hexToNumber(bytesToHex(bytes)) >> BigInt(bitsize - size)
|
|
if (n < max) return n
|
|
}
|
|
}
|
|
|
|
async random_bytes(bitsize: number) : Promise<Uint8Array> {
|
|
const hash_size = Math.max(512, bitsize)
|
|
|
|
const state = await crypto.subtle.importKey(
|
|
"raw",
|
|
this.state,
|
|
{name: "HKDF"},
|
|
false,
|
|
["deriveBits"],
|
|
)
|
|
const buffer = await crypto.subtle.deriveBits(
|
|
{
|
|
name: "HKDF",
|
|
hash: "SHA-512",
|
|
info: useless,
|
|
salt: useless,
|
|
},
|
|
state,
|
|
hash_size,
|
|
)
|
|
const w = new Uint8Array(buffer)
|
|
|
|
for (let i = 0;i < 64;++i) this.state[i] ^= w[i]
|
|
|
|
return w.slice(0, Math.max(bitsize >> 3, 1))
|
|
}
|
|
}
|
|
|
|
function log2(n: bigint) : number {
|
|
let l = 0
|
|
|
|
while (n > 0) {
|
|
n = n >> 1n
|
|
l += 1
|
|
}
|
|
|
|
return l
|
|
}
|