feat(loaders): add NIP-05 over Namecoin (.bit) identity loader#79
Draft
mstrofnone wants to merge 2 commits into
Draft
feat(loaders): add NIP-05 over Namecoin (.bit) identity loader#79mstrofnone wants to merge 2 commits into
mstrofnone wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: 9968f5a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Apex records bump against the 520-byte per-name limit on Namecoin and
commonly delegate their `nostr.names` block to a sibling name (e.g.
`dd/<name>`) via the ifa-0001 `"import"` directive. Without import-chain
handling, NIP-05 resolution sees no `nostr` field at the apex and
silently fails for these records (the canonical `testls.bit` demo
target is one).
This commit ports the import-chain resolver between the parsed apex
JSON and the existing `nostr` extractor:
- `expandImports(value, lookup, maxDepth = 4)` in
`helpers/namecoin-identity.ts` recursively merges imported names
(canonical array-of-arrays plus the three real-world shorthand
forms) into the importing object, with importer-wins precedence,
`null` semantic suppression, DNS-dotted subdomain selector walk
through the imported `map` tree (exact / `*` / `""` fallback),
visited-set cycle protection, and best-effort handling of failed
sub-lookups so transient ElectrumX hiccups never nuke an otherwise
resolvable record.
- `NamecoinIdentityLoader.fetchIdentity` calls `expandImports` once
between `JSON.parse` and `getIdentityFromNamecoinValue`, using the
consumer-provided `resolve` hook as the import lookup.
- Records without an `"import"` key pay zero extra I/O.
- A new `importDepth` knob (default 4, the spec minimum) lets
consumers disable or tune the recursion.
22 new tests across the helper (`expandImports` unit cases for each
shorthand, selector / wildcard, depth, cycle, malformed input,
lenient I/O) and the loader (apex + sibling fetched, named local-part
resolves through, exactly one query for non-import records,
importer-wins on the `nostr.names` map, local data survives a missing
import). `pnpm -F applesauce-loaders test`: 139/139 (was 117).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NIP-05 over Namecoin (
.bit) identity loaderThis PR adds a transport-free helper module and matching loader for resolving
NIP-05 identifiers rooted in the Namecoin blockchain
(the
.bitTLD), rather than DNS. It is the loaders-package companion to theexisting
DnsIdentityLoader/dns-identityhelper — same shape, differentroot of trust.
Spec draft: nostr-protocol/nips#2349.
Design siblings already in flight in other Nostr ecosystems:
nostr-tools: nbd-wtf/nostr-tools#533rust-nostr: rust-nostr/nostr#1367The parser shape (local-part priority exact →
_→ first valid,d/vs
id/namespace handling, leadingnostr:prefix tolerance, simplenostr: "<hex>"vs extendednostr: { names, relays, nip46 }form) isported from those references, themselves ports of the Kotlin implementation
in Amethyst and the Swift port
in Nostur.
What's added
packages/loaders/src/helpers/namecoin-identity.ts:parseNamecoinAddress(input)— accepts<x>.bit,alice@<x>.bit,d/<x>,id/<x>, with optional leadingnostr:prefixisNamecoinIdentifier/isDotBit— cheap front-door checksextractNostrFromValue(address, json)— extracts pubkey + optional relays +optional nip46 relays from a Namecoin name value, matching the reference
local-part priority rules
getIdentityFromNamecoinValue— builds anIdentity(the existingdns-identitytype) keyed by the Namecoin name indomainexpandImports(value, lookup, maxDepth = 4)— recursively resolvesifa-0001
"import"directives so apex records that delegate thenostr.namesblock to a sibling name (the canonicaltestls.bitworkaround for the 520-byte per-name limit) merge correctly before the
nostrfield is extracted. Supports the canonical array-of-arrays formplus the three real-world shorthand forms (bare string, single-element
array, name+selector pair),
nullsemantic suppression, the DNS-dottedsubdomain selector walk through the imported
maptree (exact →*→""fallback), the spec-mandated minimum recursion depth of four,visited-set cycle protection, and best-effort handling of failed
sub-lookups so transient ElectrumX hiccups never nuke an otherwise
resolvable record. Records without an
"import"key pay zero extra I/O.buildNameIndexScript/electrumScriptHash/parseNameUpdateScript—Namecoin script + ElectrumX scripthash helpers for callers that drive the
ElectrumX query themselves (handles direct push,
OP_PUSHDATA1,OP_PUSHDATA2, andOP_PUSHDATA4framing)DEFAULT_ELECTRUMX_SERVERS— the same three operator pairs the Kotlin /Swift / Rust references ship
packages/loaders/src/loaders/namecoin-identity-loader.ts:NamecoinIdentityLoadermirroringDnsIdentityLoader's observable surface— same
cache?: AsyncIdentityCache, same expiration / dedupe semantics,same
Identityreturn shapeloader.resolve = (namecoinName) => Promise<string>that the consumer wires up to an ElectrumX (WSS / TCP+TLS) client of their
choice. The default implementation throws a clear error so misconfiguration
is loud rather than silent.
importDepthknob (default 4, the ifa-0001 minimum) that controls theimport-chain recursion depth. Set to
0to disable import-followingentirely.
Why transport-free
applesauce-coreandapplesauce-loadersneed to stay isomorphic — noimport 'ws', nonode:imports, no pinned self-signed certificates. Thehelpers do all the parsing / verification and the loader stitches in
caching and dedupe, but the actual ElectrumX WSS / TCP+TLS connection is
left to the consumer (browser, Tauri, Node, Bun, etc. all have different
preferences). This matches how
nostr-tools/nip05namecoinis being shapedin the upstream JS sibling PR.
Runtime deps
One small addition to
applesauce-loaders/package.json:"dependencies": { + "@noble/hashes": "^1.7.1", "applesauce-core": "^6.1.0", ... }@noble/hashesis already a direct dep ofapplesauce-relayandapplesauce-wallet-connectat the same^1.7.1range, and alreadytransitively present via
nostr-tools. Used solely forsha256inelectrumScriptHash. No other new runtime deps.Tests
60 new tests across two files:
packages/loaders/src/helpers/__tests__/namecoin-identity.test.ts— 40tests covering parser positive/negative cases, the
nostr:prefix, thesimple vs extended JSON forms, the
_and first-valid fallbacks fordomain vs identity namespaces,
Identityconstruction, script layout,scripthash reversibility,
OP_PUSHDATA1framing, and the fullexpandImportsmatrix (all four"import"shapes, selector walk +wildcard, importer-wins,
nullsuppression, depth-4 happy path,depth-truncation, cycles, malformed-input + lookup-failure lenience)
packages/loaders/src/loaders/__tests__/namecoin-identity-loader.test.ts— 20 tests covering the default-resolver error, extended/simple JSON
parsing, error/missing branches, cache hit/miss/expired, the
single-identifier overload, concurrent-request dedupe via the
requestingmap, end-to-end import-chain resolution of bare and named.bitidentifiers, the zero-extra-cost guarantee for non-importrecords, importer-wins on the
nostr.namesmap, graceful degradationwhen the imported sibling is unreachable, and the
importDepth = 0disable knob
pnpm --filter applesauce-loaders test: 139/139 (was 79; 60 new).pnpm --filter applesauce-core test: 562/562.Snapshot updates
packages/loaders/src/helpers/__tests__/exports.test.tsandpackages/loaders/src/loaders/__tests__/exports.test.tsregenerated toinclude the new symbols (
DEFAULT_IMPORT_DEPTH,expandImports).Files touched
Out of scope
dns-identity.ts/dns-identity-loader.tsare untouched; this is purely additive
applesauce-corehelper barrel for Namecoin — the parser lives inapplesauce-loaders/helpersonly (the upstreamnostr-toolssiblingis still in PR; once that lands, a thin core re-export can follow)
Draft until the upstream JS sibling settles, but the API is intended to be
stable as-is.