Skip to content

test(nostr-cid-vm): make JWK-y corruption provably off-curve (closes #438)#504

Merged
melvincarvalho merged 1 commit into
gh-pagesfrom
issue-438-fix-flaky-jwk-y-corruption
May 19, 2026
Merged

test(nostr-cid-vm): make JWK-y corruption provably off-curve (closes #438)#504
melvincarvalho merged 1 commit into
gh-pagesfrom
issue-438-fix-flaky-jwk-y-corruption

Conversation

@melvincarvalho
Copy link
Copy Markdown
Contributor

Summary

  • Replaces the single-char base64url flip on jwk.y with 'A'.repeat(43) (32 zero bytes) so the "right x, wrong y" JWK is provably off-curve for any practical random keypair.
  • Same shape, same parsing path — only the corruption is deterministic now.

Root cause

The old corruption was:

y: goodJwk.y.slice(0, -1) + (goodJwk.y.endsWith('A') ? 'B' : 'A')

The last base64url char of a 32-byte field carries only 4 high bits — the bottom 2 are padding zeroes. 'A' = 000000 and 'B' = 000001 differ only in those padding bits, so they decode to the same bytes. When y's base64url happened to end in 'A' (~1/16 of randomly generated keys), the "corruption" was a no-op and the JWK passed curve validation, so the WebID upgrade succeeded and the test failed.

A much rarer second path (the one the issue analysis flagged): the flip happened to land on -y mod p, which is the other valid y for the same x. Either way, the fix below kills both.

Fix

Set y to 32 zero bytes. (x, 0) is on secp256k1 iff x³ ≡ -7 mod p, which has exactly 3 solutions in ~2²⁵⁶ — vanishingly improbable for any randomly generated x. Keeps option 2 from the issue's fix menu, preserving "random keypair" coverage.

Test plan

  • node --test test/nostr-cid-vm.test.js — all 25 tests pass.
  • Ran the suite 30 consecutive times — zero failures (previously flaked ~1 in 16 runs).
  • Targeted test still validates the intended path: profile carries a JWK with valid x but invalid y, the curve check rejects it, and auth falls back to did:nostr:.

Closes #438.

The "rejects a JWK with the right x but wrong y" test flipped the last
base64url char of y from 'A'→'B' (or x→'A'). Because the final base64url
char of a 32-byte field carries only 4 high bits — the bottom 2 are
padding — 'A' (000000) and 'B' (000001) decode to the SAME bytes
whenever y ended in 'A' (≈1/16 of randomly generated keys), turning the
"corruption" into a no-op so the test occasionally accepted the JWK as
valid. (A second, far rarer path: the flip landed on -y mod p.)

Set y to 32 zero bytes instead. (x, 0) is on secp256k1 iff x³ ≡ -7
mod p, which has exactly 3 solutions in ~2²⁵⁶ — provably off-curve for
any practical random x. Verified across 30 consecutive runs.
@melvincarvalho melvincarvalho merged commit fdc219f into gh-pages May 19, 2026
1 check passed
@melvincarvalho melvincarvalho deleted the issue-438-fix-flaky-jwk-y-corruption branch May 19, 2026 06:36
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.

Flaky test: nostr-cid-vm.test.js JWK-y corruption can yield a valid curve point

1 participant