Files
trezor-suite/packages/utils/src/getRandomInt.ts

59 lines
2.0 KiB
TypeScript

import { getRandomValues as cryptoGetRandomValues } from 'crypto';
/**
* Before changing anything here, see the Modulo Bias problem!
* @see https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
*
* @param min Inclusive
* @param max Exclusive (to match the crypto.randomInt() function API
*/
export const getRandomInt = (min: number, max: number) => {
if (!Number.isSafeInteger(min)) {
throw new RangeError(
`The "min" argument must be a safe integer. Received type ${typeof min} (${min})`,
);
}
if (!Number.isSafeInteger(max)) {
throw new RangeError(
`The "max" argument must be a safe integer. Received type ${typeof max} (${max})`,
);
}
if (min >= max) {
throw new RangeError(
`The value of "max" is out of range. It must be greater than the value of "min" (${min}). Received ${max}`,
);
}
const MAX_RANGE_32_BITS = 0xffffffff + 1;
const range = max - min;
if (range > MAX_RANGE_32_BITS) {
throw new RangeError(
`This function only provide 32 bits of entropy, therefore range cannot be more then 2^32.`,
);
}
const getRandomValues =
typeof window !== 'undefined'
? (array: Uint32Array) => window.crypto.getRandomValues(array)
: (array: Uint32Array) => cryptoGetRandomValues(array);
const array = new Uint32Array(1); // This provides 32 bits of entropy.
// It is crucial to avoid modulo bias.
// See: https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
// We calculate the maximum possible value that can be evenly distributed across the desired range.
const maxRange = MAX_RANGE_32_BITS - (MAX_RANGE_32_BITS % range);
let randomValue: number;
do {
getRandomValues(array);
randomValue = array[0];
} while (randomValue >= maxRange);
return min + (randomValue % range);
};