Skip to content

Commit

Permalink
Add restrictions for new UTF-8 specification ENS names (#42, ethers-i…
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo authored and Woodpile37 committed Jan 14, 2024
1 parent 575221b commit 5f04b99
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/hash/src.ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { id } from "./id";
import { dnsEncode, isValidName, namehash } from "./namehash";
import { hashMessage, messagePrefix } from "./message";

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

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

Expand Down
78 changes: 60 additions & 18 deletions packages/hash/src.ts/namehash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { concat, hexlify } from "@ethersproject/bytes";
import { toUtf8Bytes } from "@ethersproject/strings";
import { toUtf8Bytes, toUtf8String } from "@ethersproject/strings";
import { keccak256 } from "@ethersproject/keccak256";

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

const Partition = new RegExp("^((.*)\\.)?([^.]+)$");
function checkComponent(comp: Uint8Array): Uint8Array {
if (comp.length === 0) { throw new Error("invalid ENS name; empty component"); }
let nonUnder = false;
let last = -1;
for (let i = 0; i < comp.length; i++) {
const c = comp[i];

// An underscore (i.e. "_"); only allows at the beginning
if (c === 0x5f) {
if (nonUnder) { throw new Error("invalid ENS name; non-prefix underscore"); }
} else {
// A hyphen (i.e. "-"); only allows a single in a row
if (c === 0x2d && last === c) {
throw new Error("invalid ENS name; double-hyphen");
}
nonUnder = true;
}
last = c;
}
return comp;
}

function ensNameSplit(name: string): Array<Uint8Array> {
const bytes = toUtf8Bytes(ens_normalize(name));
const comps: Array<Uint8Array> = [ ];

if (name.length === 0) { return comps; }

let last = 0;
for (let i = 0; i < bytes.length; i++) {
const d = bytes[i];

// A separator (i.e. "."); copy this component
if (d === 0x2e) {
comps.push(checkComponent(bytes.slice(last, i)));
last = i + 1;
}
}

// There was a stray separator at the end of the name
if (last >= bytes.length) { throw new Error("invalid ENS name; empty component"); }

comps.push(checkComponent(bytes.slice(last)));
return comps;
}

export function ensNormalize(name: string): string {
return ensNameSplit(name).map((comp) => toUtf8String(comp)).join(".");
}

export function isValidName(name: string): boolean {
try {
return ens_normalize(name).length !== 0;
return (ensNameSplit(name).length !== 0);
} catch (error) { }
return false;
}
Expand All @@ -26,34 +74,28 @@ export function namehash(name: string): string {
logger.throwArgumentError("invalid ENS name; not a string", "name", name);
}

let current = ens_normalize(name);
let result: string | Uint8Array = Zeros;
while (current.length) {
const partition = current.match(Partition);
if (partition == null || partition[2] === "") {
logger.throwArgumentError("invalid ENS address; missing component", "name", name);
}
const label = toUtf8Bytes(partition[3]);
result = keccak256(concat([result, keccak256(label)]));

current = partition[2] || "";
const comps = ensNameSplit(name);
while (comps.length) {
result = keccak256(concat([result, keccak256(comps.pop())]));
}

return hexlify(result);
}

export function dnsEncode(name: string): string {
name = ens_normalize(name)
return hexlify(concat(name.split(".").map((comp) => {

return hexlify(concat(ensNameSplit(name).map((comp) => {
// DNS does not allow components over 63 bytes in length
if (toUtf8Bytes(comp).length > 63) {
if (comp.length > 63) {
throw new Error("invalid DNS encoded entry; length exceeds 63 bytes");
}

// We jam in an _ prefix to fill in with the length later
const bytes = toUtf8Bytes("_" + comp);
const bytes = new Uint8Array(comp.length + 1);
bytes.set(comp, 1);
bytes[0] = bytes.length - 1;
return bytes;

}))) + "00";
}

0 comments on commit 5f04b99

Please sign in to comment.