Implement random

This commit is contained in:
2024-06-10 09:45:25 +02:00
parent 5baff97de9
commit aea465d113
3 changed files with 108 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
import {expect, test} from 'bun:test'
import Random from '.'
test('same result', async () => {
const random1 = new Random(new Uint8Array(64))
const a = await random1.random(128n)
const a2 = await random1.random(128n)
expect(a2).not.toBe(a)
const random2 = new Random(new Uint8Array(64))
const b = await random2.random(128n)
expect(a).toBe(b)
})
test('Different state, different number', async () => {
const random1 = new Random(new Uint8Array(64))
const a = await random1.random(128n)
const state = new Uint8Array(64)
state[0] = 1
const random2= new Random(state)
const b = await random2.random(128n)
expect(a).not.toBe(b)
})
test('New random is random', async () => {
const r1 = new Random()
const r2 = new Random()
const a = await r1.random(100n)
const b = await r2.random(100n)
expect(b).not.toBe(a)
})
+69 -1
View File
@@ -1 +1,69 @@
export default class Random {}
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
}
+3
View File
@@ -13,6 +13,9 @@
"test": "bun test"
},
"dependencies": {
"@noble/curves": "^1.4.0"
},
"devDependencies": {
"@types/bun": "^1.1.2"
},