chore: allow passing of the source of randomness for arrayShuffle

This commit is contained in:
Peter Sanderson
2024-10-15 14:45:51 +02:00
committed by Peter Sanderson
parent 4f66613553
commit e1fe4b85d4
16 changed files with 80 additions and 16 deletions

View File

@@ -1,4 +1,4 @@
import { scheduleAction, arrayShuffle, urlToOnion } from '@trezor/utils';
import { scheduleAction, arrayShuffle, urlToOnion, getWeakRandomInt } from '@trezor/utils';
import { TypedEmitter } from '@trezor/utils';
import type { BlockbookAPI } from '@trezor/blockchain-link/src/workers/blockbook/websocket';
@@ -38,7 +38,7 @@ export class CoinjoinBackendClient {
constructor(settings: CoinjoinBackendClientSettings) {
this.logger = settings.logger;
this.blockbookUrls = arrayShuffle(settings.blockbookUrls);
this.blockbookUrls = arrayShuffle(settings.blockbookUrls, { randomInt: getWeakRandomInt });
this.onionDomains = settings.onionDomains ?? {};
this.blockbookRequestId = Math.floor(Math.random() * settings.blockbookUrls.length);
this.websockets = new CoinjoinWebsocketController(settings);

View File

@@ -1,4 +1,4 @@
import { getWeakRandomId, arrayShuffle } from '@trezor/utils';
import { getWeakRandomId, arrayShuffle, getWeakRandomInt } from '@trezor/utils';
import * as coordinator from '../coordinator';
import * as middleware from '../middleware';
@@ -160,7 +160,7 @@ export const outputRegistration = async (
const assignedAddresses: AccountAddress[] = [];
return Promise.all(
arrayShuffle(outputs).map(output =>
arrayShuffle(outputs, { randomInt: getWeakRandomInt }).map(output =>
registerOutput(round, account, output, assignedAddresses, options),
),
);
@@ -170,7 +170,9 @@ export const outputRegistration = async (
round.setSessionPhase(SessionPhase.AwaitingOthersOutputs);
// inform coordinator that each registered input is ready to sign
await Promise.all(
arrayShuffle(round.inputs).map(input => readyToSign(round, input, options)),
arrayShuffle(round.inputs, { randomInt: getWeakRandomInt }).map(input =>
readyToSign(round, input, options),
),
);
logger.info(`Ready to sign ~~${round.id}~~`);
} catch (error) {

View File

@@ -1,4 +1,4 @@
import { arrayShuffle } from '@trezor/utils';
import { arrayShuffle, getWeakRandomInt } from '@trezor/utils';
import * as coordinator from '../coordinator';
import * as middleware from '../middleware';
@@ -234,7 +234,7 @@ export const transactionSigning = async (
round.setSessionPhase(SessionPhase.SendingSignature);
await Promise.all(
arrayShuffle(round.inputs).map(input =>
arrayShuffle(round.inputs, { randomInt: getWeakRandomInt }).map(input =>
sendTxSignature(round, resolvedTime, input, options),
),
);

View File

@@ -12,7 +12,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: () => 0,
getWeakRandomNumberInRange: () => 0,
};
});

View File

@@ -14,7 +14,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: () => 0,
getWeakRandomNumberInRange: () => 0,
};
});

View File

@@ -10,7 +10,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: () => 0,
getWeakRandomNumberInRange: () => 0,
};
});

View File

@@ -11,7 +11,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: () => 0,
getWeakRandomNumberInRange: () => 0,
};
});

View File

@@ -13,7 +13,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: jest.fn(() => 0),
getWeakRandomNumberInRange: jest.fn(() => 0),
};
});

View File

@@ -19,7 +19,7 @@ jest.mock('@trezor/utils', () => {
return {
__esModule: true,
...originalModule,
getRandomNumberInRange: jest.fn(originalModule.getRandomNumberInRange),
getWeakRandomNumberInRange: jest.fn(originalModule.getWeakRandomNumberInRange),
};
});

View File

@@ -5,10 +5,14 @@
*
* This method does not mutate the original array.
*/
export const arrayShuffle = <T>(array: readonly T[]): T[] => {
export const arrayShuffle = <T>(
array: readonly T[],
{ randomInt }: { randomInt: (min: number, max: number) => number },
): T[] => {
const shuffled = array.slice();
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const j = randomInt(0, i + 1);
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}

View File

@@ -0,0 +1,13 @@
/**
* @param min Inclusive
* @param max Exclusive
*/
export const getWeakRandomInt = (min: number, max: number) => {
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}`,
);
}
return Math.floor(Math.random() * (max - min) + min);
};

View File

@@ -1,2 +1,8 @@
/**
* @deprecated Use `getWeakRandomInt` instead.
*
* @param min Inclusive
* @param max Inclusive
*/
export const getWeakRandomNumberInRange = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1)) + min;

View File

@@ -22,6 +22,7 @@ export * from './getNumberFromPixelString';
export * from './getWeakRandomNumberInRange';
export * from './getSynchronize';
export * from './getWeakRandomId';
export * from './getWeakRandomInt';
export * from './hasUppercaseLetter';
export * from './isAscii';
export * from './isHex';

View File

@@ -1,4 +1,5 @@
import { arrayShuffle } from '../src/arrayShuffle';
import { getWeakRandomInt } from '../src/getWeakRandomInt';
const KEYS = ['a', 'b', 'c', 'd', 'e'];
const SAMPLES = 10000;
@@ -13,7 +14,7 @@ describe(arrayShuffle.name, () => {
const samples = Object.fromEntries(KEYS.map(key => [key, new Array(KEYS.length).fill(0)]));
for (let sample = 0; sample < SAMPLES; ++sample) {
const shuffled = arrayShuffle(KEYS);
const shuffled = arrayShuffle(KEYS, { randomInt: getWeakRandomInt });
for (let i = 0; i < shuffled.length; ++i) {
samples[shuffled[i]][i]++;
}

View File

@@ -0,0 +1,26 @@
import { getWeakRandomInt } from '../src';
describe(getWeakRandomInt.name, () => {
it('raises same error as randomInt from crypto when max <= min', () => {
const EXPECTED_ERROR = new RangeError(
'The value of "max" is out of range. It must be greater than the value of "min" (0). Received -1',
);
expect(() => getWeakRandomInt(0, -1)).toThrowError(EXPECTED_ERROR);
});
it('returns same value when range is trivial', () => {
expect(getWeakRandomInt(0, 1)).toEqual(0);
expect(getWeakRandomInt(100, 101)).toEqual(100);
});
it('returns same value when range is trivial', () => {
for (let i = 0; i < 10_000; i++) {
const result = getWeakRandomInt(0, 100);
expect(Number.isInteger(result)).toBe(true);
expect(result).toBeGreaterThanOrEqual(0);
expect(result).toBeLessThan(100);
}
});
});

View File

@@ -0,0 +1,11 @@
import { getWeakRandomNumberInRange } from '../src';
describe(getWeakRandomNumberInRange.name, () => {
it('returns value in range', () => {
for (let i = 0; i < 10_000; i++) {
const result = getWeakRandomNumberInRange(0, 100);
expect(result).toBeGreaterThanOrEqual(0);
expect(result).toBeLessThanOrEqual(100);
}
});
});