resolver: reduce comments

This commit is contained in:
sh committed 2026-09-04 14:49:07 +00:00
1 parent e670d9fa37
commit ad4077bf14
4 files changed
+30 -62

No files matched your search

+3 -4
View File
@@ -306,8 +306,8 @@ jobs:
# Resolver test job
# =============================
# The SNRC resolver is Python and stdlib-only apart from keccak, so it needs
# none of the Haskell toolchain above and runs independently of it.
# The SNRC resolver is Python, so this job needs none of the Haskell toolchain
# above and runs independently of it.
resolver-test:
name: "resolver (python)"
@@ -323,8 +323,7 @@ jobs:
python-version: "3.13"
- name: Install resolver dependencies
# The only runtime dependency; declared in
# scripts/resolver/service/pyproject.toml.
# Must match scripts/resolver/service/pyproject.toml.
run: python -m pip install "eth-hash[pycryptodome]>=0.7"
- name: Test
+15 -21
View File
@@ -131,36 +131,30 @@ identically (`bar.foobar.testing`).
### Querying by labelhash
A client that asks whether a name is free is usually about to register it.
Whoever runs the resolver sees that question and could register the name first.
To avoid that, send the keccak hash of the label instead of the label itself,
written in ENS's `[<64 hex>]` form. The answer is the same:
A client asking whether a name is free is usually about to register it, and
whoever runs the resolver could register it first. To avoid that, send the
keccak hash of the label in ENS's `[<64 hex>]` form instead of the label:
```sh
# instead of /resolve/acme.testing
curl -s "http://127.0.0.1:8000/resolve/[$(printf acme | keccak-256sum | cut -d' ' -f1)].testing"
```
This works because namehash is `keccak(parent || keccak(label))`. Passing
`keccak(label)` gives the same node, so the resolver reads the same record. It
learns which name you meant only if it guesses the label and hashes it.
namehash is `keccak(parent || keccak(label))`, so this reaches the same node and
returns the same record. The resolver learns the name only by guessing the label
and hashing it.
Brackets keep the two forms from colliding. `[` and `]` are not valid in a
normalised ENS name, and the dApp normalises before it registers, so no name
registered through it can look like this. Nothing on chain checks the character
set, but a `[<64 hex>]` label is 66 bytes and the registrar's `maxLabelLength`
is 63, so it cannot be registered directly either. ENS uses this same encoding
for a label whose preimage it does not know. A plain `0x…` label would not work
here, because that is an ordinary name anyone can register.
Brackets cannot collide with a real name: they are invalid in a normalised ENS
name, and a `[<64 hex>]` label is 66 bytes against the registrar's
`maxLabelLength` of 63. A plain `0x…` label is not treated as a hash, since that
is an ordinary, registrable name.
Only 2LDs can be queried by hash. A 2LD is what a registration buys, so it is
the only name worth hiding. Subnames are left out because nobody can race you
for one: the owner of the 2LD creates them. In a subname the resolver hashes a
`[<64 hex>]` label as written instead of decoding it, so such a query points at
a node nobody can own.
Only 2LDs can be queried this way, as only a 2LD can be raced for: subnames are
created by the 2LD's owner. A bracket label in a subname is hashed as written,
so it points at a node nobody can own.
This hides your interest in a name, and nothing more. The registration itself
is public, and the controller's commit-reveal protects that step.
This hides interest in a name and nothing else: the registration itself is
public, and commit-reveal covers that step.
### Status codes
+7 -19
View File
@@ -124,14 +124,8 @@ def namehash(name: str) -> bytes:
return node
# ENS writes a label whose preimage it does not know as `[<64 hex>]`, and that
# is the form reused here for a label the caller deliberately withholds. The
# brackets are what keep the two forms apart: `[` and `]` are outside the
# normalised character set, so no registrable name can take this shape, and the
# ecosystem already reads it back as a hash (ensjs `isEncodedLabelhash`; the
# subgraph refuses any real label containing a bracket). A bare `0x…` label
# would not be safe this way - that is an ordinary, registrable name, kept from
# clashing only by a registrar length cap that its owner can raise.
# ENS's encoding for a label whose preimage is unknown. Brackets are outside
# the normalised character set, so it cannot collide with a registrable name.
ENCODED_LABELHASH_LEN = 66 # "[" + 64 hex + "]"
@@ -147,17 +141,11 @@ def is_encoded_labelhash(label: str) -> bool:
def node_of(name: str) -> bytes:
"""namehash, accepting an encoded labelhash in place of a 2LD's label.
A client checking whether a name is free is usually about to register it,
so the question itself is worth front-running. namehash is defined as
keccak(parent || keccak(label)), so a caller who supplies keccak(label)
reaches the same node having never sent the label.
Only 2LDs may be queried this way: that is the name a registration is
bought for, so the only one worth hiding. Subnames of any depth are
excluded - a subname is created by the 2LD's owner, nobody can race a
caller for one, so there is nothing to front-run. A label in `[<64 hex>]`
form there is hashed literally, not decoded; as brackets cannot occur in a
real registration, such a query names a node nobody can own.
keccak(parent || keccak(label)) reaches the same node without the label,
so a caller can check a 2LD without disclosing which one they are about to
register. Subnames are excluded - only the 2LD's owner creates them, so
there is nothing to front-run - and a bracket label there is hashed as
written.
"""
labels = name.split(".")
if len(labels) == 2 and is_encoded_labelhash(labels[0]):
+5 -18
View File
@@ -83,18 +83,11 @@ class SplitLinksTests(unittest.TestCase):
class EncodedLabelhashTests(unittest.TestCase):
"""Querying by labelhash instead of by label.
"""`node_of` accepts a 2LD's label as an encoded labelhash `[<64 hex>]`,
reaching the same node as the label itself."""
A client asking whether a name is free is usually about to register it, so
the question itself is worth front-running by whoever runs the resolver.
namehash is keccak(parent || keccak(label)), so supplying keccak(label)
yields the same node and the same answer, having never sent the label.
The encoding is ENS's own `[<64 hex>]`, which cannot collide with a real
name: brackets are outside the normalised character set."""
# keccak-256("alice") = 9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501
# - written out in full wherever a test needs a real labelhash.
# keccak-256("alice"), written out in full wherever a test needs it.
# 9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501
def test_the_encoded_form_is_recognised(self):
self.assertTrue(
@@ -110,9 +103,8 @@ class EncodedLabelhashTests(unittest.TestCase):
def test_non_hex_between_the_brackets_is_not(self):
self.assertFalse(snrc.is_encoded_labelhash("[" + "z" * 64 + "]"))
# uppercase hex is not it either: the handler lowercases the whole name
# uppercase is rejected because the handler lowercases the whole name
self.assertFalse(snrc.is_encoded_labelhash("[" + "A" * 64 + "]"))
# explicitly disallowed prefix
self.assertFalse(snrc.is_encoded_labelhash("[0x9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501]"))
def test_the_wrong_length_is_not(self):
@@ -132,9 +124,6 @@ class EncodedLabelhashTests(unittest.TestCase):
self.assertEqual(snrc.node_of("alice.testing"), snrc.namehash("alice.testing"))
def test_an_encoded_subname_is_not_the_name_it_would_decode_to(self):
"""Only 2LDs are queried by hash. If a label in `[<64 hex>]` form were
decoded in a subname, that subname would silently be the name the hash
stands for - here `alice.alice.testing`."""
self.assertNotEqual(
snrc.node_of(
"[9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501]"
@@ -152,8 +141,6 @@ class EncodedLabelhashTests(unittest.TestCase):
)
def test_a_0x_prefixed_label_is_taken_literally(self):
"""`0x<64 hex>` is a registrable name, not a hash - the brackets are
what make the hashed form unambiguous."""
name = "0x9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501.testing"
self.assertEqual(snrc.node_of(name), snrc.namehash(name))
self.assertNotEqual(snrc.node_of(name), snrc.node_of("alice.testing"))