diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index df31b7e..374f78b 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -1,3 +1,4 @@ +import { randomInt } from 'node:crypto'; import { SimplePool } from 'nostr-tools/pool'; import { getQueryTimeoutMs, @@ -134,7 +135,13 @@ function isTransientPublishCategory(category) { ].includes(category); } -function calculateBackoffDelayMs(attemptNumber, baseMs, capMs, randomFn = Math.random) { +const MAX_RANDOM = 281474976710655; // 2**48 - 1 + +function secureRandom() { + return randomInt(0, MAX_RANDOM) / MAX_RANDOM; +} + +function calculateBackoffDelayMs(attemptNumber, baseMs, capMs, randomFn = secureRandom) { const maxDelay = Math.min(capMs, baseMs * (2 ** Math.max(0, attemptNumber - 1))); return Math.floor(randomFn() * maxDelay); } @@ -409,7 +416,7 @@ export async function publishLock(relays, event, deps = {}) { retryBaseDelayMs = 500, retryCapDelayMs = 8_000, sleepFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), - randomFn = Math.random, + randomFn = secureRandom, telemetryLogger = console.error, healthLogger = console.error, diagnostics = {}, @@ -590,3 +597,5 @@ export function _resetRelayHealthState() { relayHealthState.metricsByRelay.clear(); relayHealthState.lastSnapshotAt = 0; } + +export const _secureRandom = secureRandom; diff --git a/test/secure-random.test.mjs b/test/secure-random.test.mjs new file mode 100644 index 0000000..af64c3b --- /dev/null +++ b/test/secure-random.test.mjs @@ -0,0 +1,21 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { _secureRandom } from '../src/lock-ops.mjs'; + +describe('secureRandom', () => { + it('returns values in [0, 1)', () => { + for (let i = 0; i < 1000; i++) { + const val = _secureRandom(); + assert.ok(val >= 0, `Value ${val} should be >= 0`); + assert.ok(val < 1, `Value ${val} should be < 1`); + } + }); + + it('returns varied values', () => { + const values = new Set(); + for (let i = 0; i < 100; i++) { + values.add(_secureRandom()); + } + assert.ok(values.size > 90, 'Values should be reasonably unique'); + }); +});