tutanota/packages/tutanota-crypto/lib/random/Randomizer.ts
2025-12-05 09:35:58 +01:00

81 lines
2.4 KiB
TypeScript

// @ts-ignore[untyped-import]
import sjcl from "../internal/sjcl.js"
import type { EntropySource } from "../misc/Constants.js"
import { CryptoError } from "../misc/CryptoError.js"
/**
* This Interface provides an abstraction of the random number generator implementation.
*/
export class Randomizer {
random: any
constructor() {
this.random = new sjcl.prng(6)
}
/**
* Adds entropy to the random number generator algorithm
* @param entropyCache with: number Any number value, entropy The amount of entropy in the number in bit,
* source The source of the number.
*/
addEntropy(
entropyCache: Array<{
source: EntropySource
entropy: number
data: number | Array<number>
}>,
): Promise<void> {
for (const entry of entropyCache) {
this.random.addEntropy(entry.data, entry.entropy, entry.source)
}
return Promise.resolve()
}
addStaticEntropy(bytes: Uint8Array) {
for (const byte of bytes) {
this.random.addEntropy(byte, 8, "static")
}
}
/**
* Not used currently because we always have enough entropy using getRandomValues()
*/
isReady(): boolean {
return this.random.isReady() !== 0
}
/**
* Generates random data. The function initRandomDataGenerator must have been called prior to the first call to this function.
* @param nbrOfBytes The number of bytes the random data shall have.
* @return A hex coded string of random data.
* @throws {CryptoError} if the randomizer is not seeded (isReady == false)
*/
generateRandomData(nbrOfBytes: number): Uint8Array<ArrayBuffer> {
try {
// read the minimal number of words to get nbrOfBytes
let nbrOfWords = Math.floor((nbrOfBytes + 3) / 4)
let words = this.random.randomWords(nbrOfWords)
let arrayBuffer = sjcl.codec.arrayBuffer.fromBits(words, false)
// simply cut off the exceeding bytes
return new Uint8Array(arrayBuffer, 0, nbrOfBytes) // truncate the arraybuffer as precaution
} catch (e) {
throw new CryptoError("error during random number generation", e as Error)
}
}
/**
* Generate a number that fits in the range of an n-byte integer
*/
generateRandomNumber(nbrOfBytes: number): number {
const bytes = this.generateRandomData(nbrOfBytes)
let result = 0
for (let i = 0; i < bytes.length; ++i) {
result += bytes[i] << (i * 8)
}
return result
}
}
// TODO singleton should be created in the app?
// the randomizer instance (singleton) that should be used throughout the app
export const random: Randomizer = new Randomizer()