Skip to content

fix(core): prevent signature verification from silently disabling due to NaN ratio - #397

Open
aykoooo wants to merge 2 commits into
nostr-dev-kit:masterfrom
aykoooo:fix/validation-ratio-nan
Open

fix(core): prevent signature verification from silently disabling due to NaN ratio#397
aykoooo wants to merge 2 commits into
nostr-dev-kit:masterfrom
aykoooo:fix/validation-ratio-nan

Conversation

@aykoooo

@aykoooo aykoooo commented Jul 30, 2026

Copy link
Copy Markdown

Problem

defaultValidationRatioFn reads this.initialValidationRatio (declared on NDK) but was bound to NDKRelay, which lacks that field. Once validatedCount >= 10, the function returned NaN, causing targetValidationRatio to become NaN on the next 30s timer tick. shouldValidateEvent() then returned false forever for that relay—verification dropped to 0% instead of the documented 10% floor.

Fix

  1. Bind defaultValidationRatioFn to NDK at assignment (ndk/index.ts:501)
  2. Add !Number.isFinite() guard in shouldValidateEvent() as a fail-safe (relay/index.ts:296)

Tests

Three new regression tests in validation-ratio.test.ts:

  • Rebound function returns finite ratios across all count thresholds (0, 9, 10, 50, 100, 1000)
  • 30s timer maintains finite ratio; verification doesn't get stuck off
  • Non-finite ratio fails safe (verifies rather than skips)

All three new tests fail on master and pass with this fix.

Security Note

This bug silently disables signature verification per-relay after just 10 validated events. Disclosed privately to maintainer prior to submission.

aykoooo added 2 commits July 27, 2026 15:37
defaultValidationRatioFn reads this.initialValidationRatio and
this.lowestValidationRatio, which are declared on NDK. It was assigned
unbound and then re-bound to the NDKRelay in the relay constructor,
making 'this' an NDKRelay (which has no initialValidationRatio).

Result: for validatedCount < 10 the fn returned undefined; for >= 10 it
returned NaN (undefined arithmetic + Math.max(NaN, 0.1)). The NaN flowed
into targetValidationRatio on the 30s tick, and shouldValidateEvent()
returned false forever for that relay — verification dropped to 0%, not
the intended 10% floor.

Bind at assignment so the relay's rebind is a no-op for the default fn.
User-supplied validationRatioFn remains unbound (relay binds it, as today).
…ssion test

shouldValidateEvent previously checked only targetValidationRatio === undefined,
which left NaN (produced by the bound-to-relay defaultValidationRatioFn reading
a missing initialValidationRatio field) falling through to NaN>=1.0 (false)
and Math.random()<NaN (false), permanently disabling verification for that
relay.

Replace the undefined check with !Number.isFinite, which covers undefined,
NaN, and +/-Infinity, and fails safe (verify) instead of unsafe (skip).

Also extend validation-ratio.test.ts with regression coverage that the
existing suite lacked at validatedCount>=10:
  - rebound validationRatioFn returns finite ratios in [0.1,1.0] at
    counts {0,9,10,50,100,1000}, with exact values at the decay thresholds;
  - the 30s updateValidationRatio timer keeps targetValidationRatio finite
    and shouldValidateEvent is not permanently false;
  - non-finite targetValidationRatio fails safe (verifies).

All three new tests fail on master (4b86acd) and pass with this fix.
Copilot AI review requested due to automatic review settings July 30, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants