Skip to content

feat(nip05): resolve Namecoin .bit identifiers + inline search indicator#281

Open
mstrofnone wants to merge 2 commits into
dergigi:masterfrom
mstrofnone:feat/namecoin-nip05-and-search-indicator
Open

feat(nip05): resolve Namecoin .bit identifiers + inline search indicator#281
mstrofnone wants to merge 2 commits into
dergigi:masterfrom
mstrofnone:feat/namecoin-nip05-and-search-indicator

Conversation

@mstrofnone
Copy link
Copy Markdown

@mstrofnone mstrofnone commented May 19, 2026

Summary

Adds Namecoin .bit resolution to the existing NIP-05 path, plus an inline resolution indicator under the search bar so users can paste a .bit identifier and see who it resolves to without leaving the page.

Supported identifier shapes:

  • alice@example.bit
  • example.bit (uses the _ root entry)
  • d/example (Namecoin domain namespace)
  • id/alice (Namecoin identity namespace)
  • nostr: NIP-21 prefix tolerated on all of the above

Why

NIP-05 only ever defined the wire format, never bound the lookup transport to HTTPS — most clients just default there because that is the path the spec sketches. Namecoin .bit names ship the exact same nostr.names[<localpart>] / nostr.pubkey JSON shape, just stored on-chain instead of in /.well-known/nostr.json. A .bit identifier survives DNS revocation and gives the user a self-custodial identifier with the same UX as a NIP-05.

ants is a search client first — being able to type m@testls.bit into the search bar and see it resolve to a real npub (with a clear failure mode when it does not) is the cheapest possible demonstration that the two transports can coexist.

What it does

File What it adds
src/lib/namecoin/nip05.ts TypeScript port of the .bit resolver: WSS to an ElectrumX server, scripthash path via blockchain.scripthash.get_history, NAME_UPDATE vout parsing, name-expiry check (~36 000 blocks), JSON extraction for both d/ (names map + relays) and id/ (identity object) shapes. Pluggable WebSocket impl via useWebSocketImplementation.
src/lib/namecoin/cache.ts Session-level pos/neg cache + in-flight dedup. No localStorage, no TTL — entries live for the runtime.
src/lib/namecoin/index.ts Public-facing resolveNamecoinNip05 and isValidNamecoinNip05.
src/lib/profile/nip05.ts Routes .bit identifiers through the chain resolver from both resolveNip05ToPubkey and verifyNip05, so any existing call site that already handles a NIP-05 value picks .bit up for free.
src/app/api/nip05/verify/route.ts Short-circuits .bit identifiers through isValidNamecoinNip05 on the server side so the browser never has to deal with the self-signed ElectrumX TLS certs the public servers ship today.
src/components/NamecoinResolutionIndicator.tsx Inline indicator: spinner → resolved npub short-form + Namecoin badge (click into /p/<npub>) → explicit failure message on lookup error or missing nostr field.
src/components/SearchView.tsx Mounts the indicator between <SearchInput> and <QueryTranslation>. Renders nothing when the query is not a .bit identifier.

Wire-format references

This is a TypeScript port of the Namecoin NIP-05 wire format that already ships in multiple other clients. All five implementations interop on the same on-chain JSON shape:

Amethyst PR #2957 is the direct precedent for the search-bar indicator: a small inline row mounted directly under the search input, showing spinner → Namecoin badge + resolved user, or an explicit failure variant. This PR ports that pattern to ants' web search bar.

The on-chain format itself is documented in the draft Namecoin NIP-05 track (https://github.com/mstrofnone/nipsN1.md), which has been cross-posted to nostr-protocol/nips for discussion.

Why not depend on nostr-tools directly

nbd-wtf/nostr-tools PR #533 adds a nip05namecoin export with the exact same API surface, but is still open. Rather than gate this PR on it landing, the resolver is shipped inline in src/lib/namecoin/ — mirroring the path Jumble PR #774 took. If/when #533 ships, switching this PR's internals over is a ~50-line swap.

Default ElectrumX server set

DEFAULT_ELECTRUMX_SERVERS mirrors amethyst's quartz/.../namecoin/ElectrumXServer.kt DEFAULT_ELECTRUMX_SERVERS (4-of-6 subset). Amethyst additionally ships two bare-IP entries (23.158.233.10, 46.229.238.187) for the JVM TLS path, but browsers refuse WSS to bare IPs without an IP-SAN cert, so we intentionally omit them here:

export const DEFAULT_ELECTRUMX_SERVERS: ElectrumXServer[] = [
  { host: 'electrumx.testls.space',       port: 50004 }, // amethyst primary
  { host: 'nmc2.bitcoins.sk',             port: 57004 },
  { host: 'relay.testls.bit',             port: 50004 }, // .bit-addressed, dogfooded
  { host: 'electrum.nmc.ethicnology.com', port: 50004 }, // Let's Encrypt cert
];

The last entry ships a Let's Encrypt cert, which makes it the easiest one to reach from a browser today. The first three serve self-signed certs (see TLSA scope below).

TLSA / browser TLS scope

The public Namecoin ElectrumX operators currently serve self-signed certificates, so direct browser WSS dials will fail TLS. The verification flow goes through /api/nip05/verify on the Next.js server side, where Node's TLS stack can accept the self-signed CA via the pinned-cert dance (we leave that to the deployer's environment, no extra dependency required for the lookup itself).

TLSA pinning of the ElectrumX dial is intentionally out of scope for this PR; it is tracked separately in the Namecoin NIP-05 track as N3.

Verification

npx tsc --noEmit          clean
npm run lint              clean (next lint + tsc --noEmit)
npx jest                  50/50 passing (19 new + 31 existing)
npm run build             production build green

The 19 new tests cover identifier parsing (.bit / d/ / id/, nostr: prefix, user@domain.bit), JSON extraction (simple form, extended names map, id/ identity object, malformed input), relay surfacing, and the import-walking helper.

Out of scope

  • TLSA pinning (N3) — separate PR.
  • Relay-URL resolution for wss://example.bit (N2) — separate PR.
  • Cross-resolving .bit payment pointers / CLINK extension (N5) — separate PR.

Adds a TypeScript port of the Namecoin NIP-05 wire format (ifa-0001
Domain Name Object, ElectrumX WSS transport) so that ants can verify
and look up nostr pubkeys whose identifier lives on the Namecoin
blockchain instead of DNS.

Supported identifier shapes:
  - alice@example.bit
  - example.bit  (uses the _ root entry)
  - d/example    (Namecoin domain namespace)
  - id/alice     (Namecoin identity namespace)
  - nostr: NIP-21 prefix tolerated on all of the above

What the change touches:

  - src/lib/namecoin/nip05.ts        new resolver + parser. WSS to an
                                     ElectrumX server, scripthash path
                                     for blockchain.scripthash.get_history,
                                     NAME_UPDATE vout parsing, name
                                     expiry check (~36000 blocks).
                                     Pluggable WebSocket impl via
                                     useWebSocketImplementation, BYO
                                     fallback server list.
  - src/lib/namecoin/cache.ts        session-level pos/neg cache +
                                     in-flight dedup. No localStorage,
                                     no TTL.
  - src/lib/namecoin/index.ts        public-facing resolveNamecoinNip05
                                     and isValidNamecoinNip05 helpers.
  - src/lib/profile/nip05.ts         route .bit identifiers through the
                                     chain resolver (both resolveNip05ToPubkey
                                     and verifyNip05).
  - src/app/api/nip05/verify/route.ts
                                     short-circuit .bit identifiers
                                     through isValidNamecoinNip05 on the
                                     server side so browsers do not have
                                     to deal with the self-signed
                                     ElectrumX TLS certs themselves.
  - src/components/NamecoinResolutionIndicator.tsx
                                     inline indicator mounted under the
                                     global search bar. Renders a
                                     spinner, then either the resolved
                                     npub + Namecoin badge (clickable
                                     into /p/<npub>) or an explicit
                                     failure message.
  - src/components/SearchView.tsx    mount the indicator between
                                     <SearchInput> and <QueryTranslation>.

Tests:
  src/lib/namecoin/__tests__/nip05.test.ts adds 19 cases covering
  identifier parsing (.bit / d/ / id/, nostr: prefix, user@domain.bit),
  value-JSON extraction (simple form, extended names map, id/ identity
  object, malformed input), relay surfacing, and import-walking helper.

Verification:
  npx tsc --noEmit         clean
  npm run lint             clean (next lint + tsc --noEmit)
  npx jest                 50/50 passing (19 new + 31 existing)
  npm run build            production build green

Wire format reference:
  - NIP-2349 (Namecoin NIP-05) draft.
  - amethyst PR #2956 / #2957 (Kotlin inline search resolution indicator).
  - nbd-wtf/nostr-tools PR #533 (Namecoin NIP-05 in nostr-tools, open).
  - CodyTseng/jumble PR #774 (parallel TS port, open).

The resolver hits one of three public Namecoin ElectrumX endpoints
over WSS, falls through to the next on transport error, returns null
on a definitive name-not-found. TLSA pinning is intentionally out of
scope for this PR; the server-side API route is the recommended entry
point because the public ElectrumX operators currently serve
self-signed certs that browsers refuse.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 19, 2026

@mstrofnone is attempting to deploy a commit to the dergigi's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

This PR introduces Namecoin .bit identifier support by implementing a complete ElectrumX-based NIP-05 resolver with session caching, integrating it into the server-side profile and API layers, and adding a client-side search indicator component with comprehensive tests.

Changes

Namecoin NIP-05 Resolution and Integration

Layer / File(s) Summary
Core Namecoin NIP-05 Resolver
src/lib/namecoin/nip05.ts
Implements Namecoin resolution via ElectrumX WebSocket servers, supporting identifier parsing (.bit, d/<name>, id/<name> forms), script-hash-based lookups, OP_NAME_UPDATE decoding, and multi-format nostr field extraction with relay parsing and import directive support.
Session Caching and Public API
src/lib/namecoin/cache.ts, src/lib/namecoin/index.ts
Adds in-memory positive/negative caching with identifier normalization and inflight promise deduplication; wraps the core resolver in resolveNamecoinNip05 and isValidNamecoinNip05 public functions.
Server-side Integration
src/lib/profile/nip05.ts, src/app/api/nip05/verify/route.ts
Updates resolveNip05ToPubkey to detect and resolve .bit identifiers; updates verifyNip05 to skip normalization and fallback for Namecoin inputs; extends the verify API endpoint to detect .bit and return a source: 'namecoin' response.
Client-side UI Component
src/components/NamecoinResolutionIndicator.tsx, src/components/SearchView.tsx
Adds a React component that renders inline .bit resolution status with debouncing (~400ms), state machine-driven rendering (loading/resolved/error), optional npub encoding, and profile navigation; integrates the component into the search view.
NIP-05 Function Tests
src/lib/namecoin/__tests__/nip05.test.ts
Comprehensive Jest suite testing identifier validation, parsing of multiple .bit forms, Nostr value extraction from simple and complex JSON shapes, localpart fallback, identity-object support, relay parsing, and import directive extraction.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant QueryProfile
  participant nameShow
  participant ElectrumX as ElectrumX WSS
  participant ParseOps as Script Parser
  Caller->>QueryProfile: identifier
  QueryProfile->>nameShow: identifier, servers
  nameShow->>ElectrumX: get_history(script_hash)
  ElectrumX-->>nameShow: tx history
  nameShow->>ParseOps: parse OP_NAME_UPDATE
  ParseOps-->>nameShow: decoded value JSON
  nameShow->>QueryProfile: { pubkey, relays }
  QueryProfile-->>Caller: NamecoinResolveResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A rabbit hops through Namecoin's lanes,
Resolving names on blockchain chains,
With ElectrumX and caches bright,
The search bar glows with .bit delight!
From server deep to UI shown—
A fuzzy friend now claims this zone. 🌰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the two main features added: Namecoin .bit identifier resolution for NIP-05 and an inline search indicator component.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Bring DEFAULT_ELECTRUMX_SERVERS to parity with amethyst's
quartz/.../namecoin/ElectrumXServer.kt DEFAULT_ELECTRUMX_SERVERS,
keeping only the 4 entries that publish a WSS endpoint reachable
from a browser. The two bare-IP entries from amethyst's list
(23.158.233.10, 46.229.238.187) are intentionally omitted: browsers
refuse WSS to bare IPs without an IP-SAN cert, so they would just
fail the handshake.

Order matches amethyst:
  1. electrumx.testls.space      (primary)
  2. nmc2.bitcoins.sk
  3. relay.testls.bit            (the only .bit-addressed entry)
  4. electrum.nmc.ethicnology.com (Let's Encrypt; easiest from browser)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/namecoin/nip05.ts (1)

1-593: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this module to satisfy the max file-length rule.

This new file exceeds the repository limit and should be split (e.g., transport, script parsing, and value extraction into separate modules).
As per coding guidelines, "Keep files below 420 lines".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/namecoin/nip05.ts` around lines 1 - 593, The file is too long — split
it into focused modules: transport (WebSocket/RPC, buildWSSUrl,
useWebSocketImplementation, ElectrumXServer, DEFAULT_ELECTRUMX_SERVERS,
nameShow/nameShowWithFallback and nameShow’s dependency on RPC), script parsing
(buildNameIndexScript, pushData, electrumScriptHash, extractNameValue,
parseNameScript, readPushData, hexToBytes), and JSON/value extraction
(extractNostrFromValue, extractFromDomainNamesObject, extractFromIdentityObject,
extractRelays, extractImports, + HEX_PUBKEY_RE and
NamecoinResolveResult/ParsedIdentifier types if needed). Move the related
symbols into those files and replace local references with imports/exports
(e.g., queryProfile -> imports nameShowWithFallback from transport and
extractNostrFromValue from value-extraction module; nameShow imports
buildNameIndexScript and electrumScriptHash from script-parsing module). Ensure
types (ParsedIdentifier, ElectrumXServer, NamecoinResolveResult) are exported
where shared, and preserve function/class names (RPC, buildNameIndexScript,
parseNameScript, extractNostrFromValue, extractRelays, nameShowWithFallback) so
callers can find them after splitting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/NamecoinResolutionIndicator.tsx`:
- Around line 81-83: In NamecoinResolutionIndicator, the effect cleanup only
sets cancelled but leaves the pending debounce timer to fire; update the
returned cleanup to also clear and null out debounceRef.current (and call
clearTimeout on it) so the timer won't run after unmount and you won't trigger
stale lookups — e.g., in the effect's return use
clearTimeout(debounceRef.current), set debounceRef.current = null, and keep
cancelled = true to fully cancel pending work.

In `@src/components/SearchView.tsx`:
- Line 43: The SearchView component is growing too large—extract the Namecoin
indicator wiring and the adjacent search-header UI into a new child component to
reduce size; create a new component (e.g., NamecoinHeader or
SearchHeaderWithNamecoin) that imports NamecoinResolutionIndicator and accepts
the necessary props or callbacks currently used in SearchView (state setters,
search term, resolution status, etc.), move the JSX and related handlers from
SearchView into that component, update SearchView to render the new child (pass
down props/functions), and remove the now-duplicated imports/logic so SearchView
shrinks below the 420-line guideline.

In `@src/lib/namecoin/index.ts`:
- Around line 75-79: The fallback path currently calls rawIsValid(pubkeyHex,
identifier) which ignores the provided servers; update the fallback to pass the
callers' servers (e.g., rawIsValid(pubkeyHex, identifier, servers)) and modify
rawIsValid's signature/implementation to accept and use the servers parameter
(propagating it to any internal resolver or HTTP client) so the same server list
is used on both resolveNamecoinNip05 and the raw verification fallback.
- Around line 42-46: The cache/inflight lookups for namecoin resolution (calls
to getCachedNamecoinResolve and getInflightNamecoinResolve using identifier)
ignore the `servers` option and can return results from a different server set;
update the keying logic so the cache/inflight keys incorporate a stable
fingerprint of the servers (e.g., JSON.stringify or a short hash of the
`servers` array) or skip cache/inflight when a custom `servers` value is
provided; specifically change the calls around
getCachedNamecoinResolve(identifier) and getInflightNamecoinResolve(identifier)
(and the analogous checks at the other occurrence around lines 60-61) to use a
composite key like `${identifier}|${serversKey}` or to bypass caching when
`servers` is explicitly passed.

In `@src/lib/namecoin/nip05.ts`:
- Around line 435-441: The hexToBytes function currently allows partial parses
because parseInt silently accepts invalid chars; update hexToBytes to validate
each 2-char hexPair before parsing (e.g., ensure each hexPair matches
/^[0-9a-fA-F]{2}$/) and only then call parseInt, throwing a clear Error('hex:
invalid byte') for any non-matching pair; also factor out related utilities from
the large src/lib/namecoin/nip05.ts into a new module (e.g., nip05HexUtils or
hex.ts) to reduce file length below the 420-line limit.

---

Outside diff comments:
In `@src/lib/namecoin/nip05.ts`:
- Around line 1-593: The file is too long — split it into focused modules:
transport (WebSocket/RPC, buildWSSUrl, useWebSocketImplementation,
ElectrumXServer, DEFAULT_ELECTRUMX_SERVERS, nameShow/nameShowWithFallback and
nameShow’s dependency on RPC), script parsing (buildNameIndexScript, pushData,
electrumScriptHash, extractNameValue, parseNameScript, readPushData,
hexToBytes), and JSON/value extraction (extractNostrFromValue,
extractFromDomainNamesObject, extractFromIdentityObject, extractRelays,
extractImports, + HEX_PUBKEY_RE and NamecoinResolveResult/ParsedIdentifier types
if needed). Move the related symbols into those files and replace local
references with imports/exports (e.g., queryProfile -> imports
nameShowWithFallback from transport and extractNostrFromValue from
value-extraction module; nameShow imports buildNameIndexScript and
electrumScriptHash from script-parsing module). Ensure types (ParsedIdentifier,
ElectrumXServer, NamecoinResolveResult) are exported where shared, and preserve
function/class names (RPC, buildNameIndexScript, parseNameScript,
extractNostrFromValue, extractRelays, nameShowWithFallback) so callers can find
them after splitting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 000a084d-659c-4237-ba72-faa32b44b577

📥 Commits

Reviewing files that changed from the base of the PR and between 765cc56 and 9c9b3f9.

📒 Files selected for processing (8)
  • src/app/api/nip05/verify/route.ts
  • src/components/NamecoinResolutionIndicator.tsx
  • src/components/SearchView.tsx
  • src/lib/namecoin/__tests__/nip05.test.ts
  • src/lib/namecoin/cache.ts
  • src/lib/namecoin/index.ts
  • src/lib/namecoin/nip05.ts
  • src/lib/profile/nip05.ts

Comment on lines +81 to +83
return () => {
cancelled = true;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear debounce timer in effect cleanup to avoid stale post-unmount lookups.

Cleanup sets cancelled, but the timer still executes and can start a needless request. Also clear debounceRef.current in the returned cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/NamecoinResolutionIndicator.tsx` around lines 81 - 83, In
NamecoinResolutionIndicator, the effect cleanup only sets cancelled but leaves
the pending debounce timer to fire; update the returned cleanup to also clear
and null out debounceRef.current (and call clearTimeout on it) so the timer
won't run after unmount and you won't trigger stale lookups — e.g., in the
effect's return use clearTimeout(debounceRef.current), set debounceRef.current =
null, and keep cancelled = true to fully cancel pending work.

import VideoWithBlurhash from '@/components/VideoWithBlurhash';
import SearchInput from '@/components/SearchInput';
import QueryTranslation from '@/components/QueryTranslation';
import NamecoinResolutionIndicator from '@/components/NamecoinResolutionIndicator';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Avoid adding new UI responsibilities to an already oversized file.

Please extract the Namecoin indicator wiring (and nearby search-header UI) into a smaller child module to move this file back toward the size constraint.
As per coding guidelines, "Keep files below 420 lines".

Also applies to: 1657-1657

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/SearchView.tsx` at line 43, The SearchView component is
growing too large—extract the Namecoin indicator wiring and the adjacent
search-header UI into a new child component to reduce size; create a new
component (e.g., NamecoinHeader or SearchHeaderWithNamecoin) that imports
NamecoinResolutionIndicator and accepts the necessary props or callbacks
currently used in SearchView (state setters, search term, resolution status,
etc.), move the JSX and related handlers from SearchView into that component,
update SearchView to render the new child (pass down props/functions), and
remove the now-duplicated imports/logic so SearchView shrinks below the 420-line
guideline.

Comment thread src/lib/namecoin/index.ts
Comment on lines +42 to +46
const cached = getCachedNamecoinResolve(identifier);
if (cached !== undefined) return cached;

const inflight = getInflightNamecoinResolve(identifier);
if (inflight) return inflight;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache/inflight keys ignore servers, causing cross-server result contamination.

Lookups with custom servers can return cached/inflight results produced by a different server set. Include a server fingerprint in keys (or bypass cache when servers is explicitly passed).

Also applies to: 60-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/namecoin/index.ts` around lines 42 - 46, The cache/inflight lookups
for namecoin resolution (calls to getCachedNamecoinResolve and
getInflightNamecoinResolve using identifier) ignore the `servers` option and can
return results from a different server set; update the keying logic so the
cache/inflight keys incorporate a stable fingerprint of the servers (e.g.,
JSON.stringify or a short hash of the `servers` array) or skip cache/inflight
when a custom `servers` value is provided; specifically change the calls around
getCachedNamecoinResolve(identifier) and getInflightNamecoinResolve(identifier)
(and the analogous checks at the other occurrence around lines 60-61) to use a
composite key like `${identifier}|${serversKey}` or to bypass caching when
`servers` is explicitly passed.

Comment thread src/lib/namecoin/index.ts
Comment on lines +75 to +79
const res = await resolveNamecoinNip05(identifier, servers);
if (res) return res.pubkey.toLowerCase() === pubkeyHex.toLowerCase();
// Fall back to the raw path so callers can still verify with custom servers.
return rawIsValid(pubkeyHex, identifier);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fallback verification ignores the caller’s servers argument.

When resolution misses, rawIsValid(pubkeyHex, identifier) uses default servers, not the provided servers, so behavior diverges from function inputs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/namecoin/index.ts` around lines 75 - 79, The fallback path currently
calls rawIsValid(pubkeyHex, identifier) which ignores the provided servers;
update the fallback to pass the callers' servers (e.g., rawIsValid(pubkeyHex,
identifier, servers)) and modify rawIsValid's signature/implementation to accept
and use the servers parameter (propagating it to any internal resolver or HTTP
client) so the same server list is used on both resolveNamecoinNip05 and the raw
verification fallback.

Comment thread src/lib/namecoin/nip05.ts
Comment on lines +435 to +441
function hexToBytes(hex: string): Uint8Array {
if (hex.length % 2 !== 0) throw new Error('hex: odd length');
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) {
const b = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
if (Number.isNaN(b)) throw new Error('hex: invalid byte');
out[i] = b;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In JavaScript, what does parseInt('0g', 16)return, and doesparseInt allow partial parsing for invalid trailing characters?

💡 Result:

parseInt('0g', 16) returns 0. Explanation: In radix 16, the valid digits are 0-9 and A-F (case-insensitive). When parseInt encounters a character that is not a valid numeral in the specified radix, it ignores that character and all succeeding characters, returning the integer value parsed up to that point [1][2]. For the string '0g' at radix 16, '0' is valid and 'g' is not; so it parses only the leading '0' and stops, yielding 0 [1][2]. Does parseInt allow partial parsing for invalid trailing characters? Yes. parseInt performs “prefix” parsing: it parses the initial sequence of valid digits for the given radix, and when it hits the first invalid trailing character, it stops parsing there rather than requiring the entire string to be valid [1][2]. If the first character cannot be converted to a number in the specified radix, then parseInt returns NaN instead [1][2].

Citations:


🏁 Script executed:

wc -l src/lib/namecoin/nip05.ts

Repository: dergigi/ants

Length of output: 86


🏁 Script executed:

sed -n '435,441p' src/lib/namecoin/nip05.ts

Repository: dergigi/ants

Length of output: 285


🏁 Script executed:

wc -l src/lib/namecoin/nip05.ts && rg "function hexToBytes" src/lib/namecoin/nip05.ts -A 10

Repository: dergigi/ants

Length of output: 441


Harden hex-byte validation to reject partial parses.

parseInt(hexPair, 16) performs prefix parsing and silently accepts partial input (e.g., '0g' parses to 0 instead of throwing). This allows malformed hex to be accepted instead of rejected.

Suggested fix
 function hexToBytes(hex: string): Uint8Array {
   if (hex.length % 2 !== 0) throw new Error('hex: odd length');
   const out = new Uint8Array(hex.length / 2);
   for (let i = 0; i < out.length; i++) {
-    const b = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
-    if (Number.isNaN(b)) throw new Error('hex: invalid byte');
+    const pair = hex.slice(i * 2, i * 2 + 2);
+    if (!/^[0-9a-fA-F]{2}$/.test(pair)) throw new Error('hex: invalid byte');
+    const b = Number.parseInt(pair, 16);
     out[i] = b;
   }
   return out;
 }

Additionally, the file exceeds the 420-line limit (currently 601 lines). Consider splitting functionality into separate modules.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function hexToBytes(hex: string): Uint8Array {
if (hex.length % 2 !== 0) throw new Error('hex: odd length');
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) {
const b = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
if (Number.isNaN(b)) throw new Error('hex: invalid byte');
out[i] = b;
function hexToBytes(hex: string): Uint8Array {
if (hex.length % 2 !== 0) throw new Error('hex: odd length');
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) {
const pair = hex.slice(i * 2, i * 2 + 2);
if (!/^[0-9a-fA-F]{2}$/.test(pair)) throw new Error('hex: invalid byte');
const b = Number.parseInt(pair, 16);
out[i] = b;
}
return out;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/namecoin/nip05.ts` around lines 435 - 441, The hexToBytes function
currently allows partial parses because parseInt silently accepts invalid chars;
update hexToBytes to validate each 2-char hexPair before parsing (e.g., ensure
each hexPair matches /^[0-9a-fA-F]{2}$/) and only then call parseInt, throwing a
clear Error('hex: invalid byte') for any non-matching pair; also factor out
related utilities from the large src/lib/namecoin/nip05.ts into a new module
(e.g., nip05HexUtils or hex.ts) to reduce file length below the 420-line 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.

1 participant