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 { 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 { 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 }