Skip to content

docs(design): how libID would integrate with ENS - #7

Open
SupremaLex wants to merge 6 commits into
mainfrom
design/ens-integration
Open

docs(design): how libID would integrate with ENS#7
SupremaLex wants to merge 6 commits into
mainfrom
design/ens-integration

Conversation

@SupremaLex

Copy link
Copy Markdown
Member

A design proposal for projecting IdentityNames bindings into ENS, so a name in
an X bio resolves in a wallet that has never heard of libID. Nothing here is
built, and it defines no ASM-*/SP-*/REQ-* identifiers — the parts carrying
trust assumptions would graduate into specs/ if this is approved.

The load-bearing finding

We would not be issuing subnames. We would be projecting state that already
exists: resolveHandle(platformId, handle) already answers the question an ENS
resolver asks. So a name needs no registry entry, no NFT, no mint and no storage
of its own — it is a view.

The cost is one small resolver contract on mainnet, one stateless read-only
gateway, and nothing on the write path: IdentityNames is untouched. A rename
or a wallet move then follows the binding with no ENS transaction at all.

Coinbase runs the same shape for eleven million *.cb.id names.

What the document decides

  • Namespacehandles.link, the DNS domain we already own, imported
    on-chain via DNSRegistrar. .eth is rejected: an annual renewal whose expiry
    would kill every name beneath it. Gasless DNSSEC (ENSIP-17) cannot serve
    subnames at all.
  • Name shape<handle>.<platform>[.<chain>].handles.link. The chain is a
    label of the hierarchy; omitting it means "answer by the caller's coin type".
  • Cross-chain — ENSIP-11 coin types say which chain's contract to read, and
    answering only for chains that hold a binding makes wrong-chain delivery
    impossible rather than unlikely.
  • Handle mapping — specified, not described. ENSIP-15 is not UTS-46; on our
    lowercase ASCII exactly two of its rules can fire. GitHub passes unchanged, X
    goes through an injective _- substitution that loses only a doubled
    underscore at the third and fourth characters, Gmail splits at its dots, and an
    _id-marked name derived from idNode covers everything that refuses.
  • Trust — the gateway signs, and that is the model rather than a stage toward
    something else. The cost is stated: a compromised signing key can answer with
    any address. Three keys are kept apart — the DNS credentials that own the
    namespace, the owner key that sets the resolver, and the signer on the gateway
    host.

Two things worth reading closely

Resolution is three steps and the address comes from the third. The revert
carries the endpoint, the gateway returns a signed blob, and a second eth_call
into the resolver's callback turns it into an address. Both calls are eth_call;
no transaction, no gas.

The ENS name follows the DNS domain. A later valid DNSSEC proof overwrites
the owner, so whoever holds the registrar account and the DNSSEC keys holds the
namespace. Section 0 writes out the import: the _ens TXT record and every
argument of proveAndClaimWithResolver.

Still open

Gateway policy when chains disagree; whether _- is surfaced in our own UI;
and name depth, which is the first thing to measure after the import because
depth is decided by the client rather than by the specification.

A proposal, not a spec: it defines no identifiers and nothing here is built.

The finding that shapes it is that we would not be issuing subnames, we would
be projecting state that already exists. `resolveHandle` answers the question
an ENS resolver asks, so a name needs no registry entry, no NFT and no storage
-- it is a view. That costs one resolver contract on mainnet, one stateless
read-only gateway, and nothing at all on the write path, and it means a rename
or a wallet move follows the binding with no ENS transaction.

The namespace is the DNS domain we already own, imported on-chain. Gasless
DNSSEC cannot serve subnames, and a `.eth` name would add an annual renewal
whose expiry takes every name below it. The import is written out: DNSSEC as a
precondition, the `_ens` TXT record, every argument of
`proveAndClaimWithResolver`, and the dependency that follows -- the ENS name
follows the DNS domain, so a later valid proof overwrites the owner.

Resolution is three steps, and the address comes from the third: the revert
carries the endpoint, the gateway returns a signed blob, and a second `eth_call`
turns it into an address. Supporting ENS is not enough for a wallet; it needs
ENSIP-10, ERC-3668 and ENSIP-11 together.

Cross-chain needs no invention: ENSIP-11 coin types say which chain's
`IdentityNames` to read, and answering only for chains that hold a binding makes
wrong-chain delivery impossible rather than unlikely. The chain is a label of
the hierarchy; it may be omitted, and then the coin type decides. What several
chains do cost is freshness, which is ordered within a chain and not across them
-- with handle recycling that can put two people under one name, and the gateway
is the only component positioned to notice.

Handle mapping is specified rather than described. ENSIP-15 is not UTS-46, and
on our lowercase ASCII exactly two of its rules can fire: an underscore only at
the start of a label, and no hyphen at both the third and fourth characters. The
transform follows from them -- GitHub unchanged, X through an injective `_` to
`-` substitution which loses only a doubled underscore at those two positions,
Gmail split at its dots once the platform label implies the domain, and an
id-derived name behind an `_id` marker for everything that refuses. Workspace is
expressible and deliberately not exposed, because a resolving name would publish
where someone works.

The gateway signs, and that is the model rather than a stage toward something
else. The cost is accepted and stated: a compromised signing key can answer with
any address. Three keys are kept apart -- the DNS credentials that own the
namespace, the owner key that sets the resolver, and the signer that lives on
the gateway.

Also records where the boundary runs -- everything before the gateway call
belongs to the client, everything after belongs to us -- and what is
deliberately not proposed, namely minting subnames as NFTs, which would create a
second source of truth able to disagree with the binding.

Assisted-by: Claude Opus 5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…t encode

The draft promised totality through an id-derived name,
`<idNode as 64 hex characters>._id.handles.link`. Sixty-four characters is
one past the DNS label ceiling of RFC 1035, so no client can encode it —
ethers' `dnsEncode` refuses above 63. The property it was there to hold
never held, and the accounts it was meant to rescue had no name at all.

Those accounts are Google addresses outside gmail.com, which is not a
corner: one of the five bindings on eden today is `green.baneling@fuel.sh`.
So the Workspace form this document already described is enabled rather
than reserved. Six labels with a marker in the middle is closer to a
machine string than to a name, and it discloses an employer — both were
reasons to withhold it, and neither survives the alternative being no name.

The privacy consequence is kept, moved to where it belongs: whether a
Workspace name is minted by default is a decision for the binding flow, and
it is recorded as open.

What still has no name is stated plainly instead of being covered by a
fallback: an address holding `_` or `+`. Both are legal as proved, neither
is legal in a label, and no substitution is reversible — unlike X, where
`_` maps to `-` because X forbids `-`, a Google address may hold both.

Assisted-by: Claude Opus 5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The gateway signs for the resolver address it is configured to serve, not
the {sender} in the path: through the batch gateway the two agree, but a
resolver announcing ERC-7996 is called directly and the universal resolver
raises the lookup again under its own address. CORS goes on every response
because viem runs the batch gateway in the page and fetches the gateway
cross-origin. ENSv2's DNS TLD resolver consults the v1 registry first, so
the import and the resolver survive v2 unchanged.

Assisted-by: Claude Fable 5.1
Signed-off-by: xgreenx <xgreenx9999@gmail.com>
… only method

The gateway in usernames-indexer#5 compares {sender} to its configured
resolver and answers a terminal 400 on mismatch; the digest never uses it.
Say so, instead of "logged and not parsed strictly". The resolver's urls
carry {data} and the deployment workflow refuses a template without it, so
ERC-3668's POST form never occurs; drop the OPTIONS clause that promised it.

Assisted-by: Claude Fable 5.1
Signed-off-by: xgreenx <xgreenx9999@gmail.com>
… no RPC

Aligns the backend section with usernames-indexer#7: answers come from the
indexed model, chains are whatever indexers have written into the store,
and the gateway takes no per-chain configuration.

Assisted-by: Claude Fable 5.1
Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…d, and never decodes them

The backend block said "no database" and decoded the coin type, which is
not what usernames-indexer#7 does: the gateway reads the Postgres mirror
the indexer keeps, matches a coin type forward against the chains the store
holds, and answers null for a label that names another chain.

Assisted-by: Claude Fable 5.1
Signed-off-by: xgreenx <xgreenx9999@gmail.com>
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