Skip to content

Commit e52fbfb

Browse files
committed
Add restrictions for new UTF-8 specification ENS names (#42, #2376, #2754).
1 parent 00114d7 commit e52fbfb

File tree

2 files changed

+61
-19
lines changed

2 files changed

+61
-19
lines changed

packages/hash/src.ts/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { id } from "./id";
44
import { dnsEncode, isValidName, namehash } from "./namehash";
55
import { hashMessage, messagePrefix } from "./message";
66

7-
import { ens_normalize as ensNormalize } from "./ens-normalize/lib";
7+
import { ensNormalize } from "./namehash";
88

99
import { TypedDataEncoder as _TypedDataEncoder } from "./typed-data";
1010

packages/hash/src.ts/namehash.ts

+60-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { concat, hexlify } from "@ethersproject/bytes";
2-
import { toUtf8Bytes } from "@ethersproject/strings";
2+
import { toUtf8Bytes, toUtf8String } from "@ethersproject/strings";
33
import { keccak256 } from "@ethersproject/keccak256";
44

55
import { Logger } from "@ethersproject/logger";
@@ -11,11 +11,59 @@ import { ens_normalize } from "./ens-normalize/lib";
1111
const Zeros = new Uint8Array(32);
1212
Zeros.fill(0);
1313

14-
const Partition = new RegExp("^((.*)\\.)?([^.]+)$");
14+
function checkComponent(comp: Uint8Array): Uint8Array {
15+
if (comp.length === 0) { throw new Error("invalid ENS name; empty component"); }
16+
let nonUnder = false;
17+
let last = -1;
18+
for (let i = 0; i < comp.length; i++) {
19+
const c = comp[i];
20+
21+
// An underscore (i.e. "_"); only allows at the beginning
22+
if (c === 0x5f) {
23+
if (nonUnder) { throw new Error("invalid ENS name; non-prefix underscore"); }
24+
} else {
25+
// A hyphen (i.e. "-"); only allows a single in a row
26+
if (c === 0x2d && last === c) {
27+
throw new Error("invalid ENS name; double-hyphen");
28+
}
29+
nonUnder = true;
30+
}
31+
last = c;
32+
}
33+
return comp;
34+
}
35+
36+
function ensNameSplit(name: string): Array<Uint8Array> {
37+
const bytes = toUtf8Bytes(ens_normalize(name));
38+
const comps: Array<Uint8Array> = [ ];
39+
40+
if (name.length === 0) { return comps; }
41+
42+
let last = 0;
43+
for (let i = 0; i < bytes.length; i++) {
44+
const d = bytes[i];
45+
46+
// A separator (i.e. "."); copy this component
47+
if (d === 0x2e) {
48+
comps.push(checkComponent(bytes.slice(last, i)));
49+
last = i + 1;
50+
}
51+
}
52+
53+
// There was a stray separator at the end of the name
54+
if (last >= bytes.length) { throw new Error("invalid ENS name; empty component"); }
55+
56+
comps.push(checkComponent(bytes.slice(last)));
57+
return comps;
58+
}
59+
60+
export function ensNormalize(name: string): string {
61+
return ensNameSplit(name).map((comp) => toUtf8String(comp)).join(".");
62+
}
1563

1664
export function isValidName(name: string): boolean {
1765
try {
18-
return ens_normalize(name).length !== 0;
66+
return (ensNameSplit(name).length !== 0);
1967
} catch (error) { }
2068
return false;
2169
}
@@ -26,34 +74,28 @@ export function namehash(name: string): string {
2674
logger.throwArgumentError("invalid ENS name; not a string", "name", name);
2775
}
2876

29-
let current = ens_normalize(name);
3077
let result: string | Uint8Array = Zeros;
31-
while (current.length) {
32-
const partition = current.match(Partition);
33-
if (partition == null || partition[2] === "") {
34-
logger.throwArgumentError("invalid ENS address; missing component", "name", name);
35-
}
36-
const label = toUtf8Bytes(partition[3]);
37-
result = keccak256(concat([result, keccak256(label)]));
3878

39-
current = partition[2] || "";
79+
const comps = ensNameSplit(name);
80+
while (comps.length) {
81+
result = keccak256(concat([result, keccak256(comps.pop())]));
4082
}
4183

4284
return hexlify(result);
4385
}
4486

4587
export function dnsEncode(name: string): string {
46-
name = ens_normalize(name)
47-
return hexlify(concat(name.split(".").map((comp) => {
48-
88+
return hexlify(concat(ensNameSplit(name).map((comp) => {
4989
// DNS does not allow components over 63 bytes in length
50-
if (toUtf8Bytes(comp).length > 63) {
90+
if (comp.length > 63) {
5191
throw new Error("invalid DNS encoded entry; length exceeds 63 bytes");
5292
}
5393

54-
// We jam in an _ prefix to fill in with the length later
55-
const bytes = toUtf8Bytes("_" + comp);
94+
const bytes = new Uint8Array(comp.length + 1);
95+
bytes.set(comp, 1);
5696
bytes[0] = bytes.length - 1;
5797
return bytes;
98+
5899
}))) + "00";
59100
}
101+

0 commit comments

Comments
 (0)