mirror of
https://github.com/waku-org/js-waku.git
synced 2025-02-01 15:17:33 +00:00
87717981eb
* chore: update noise * update: package.lock * update: @chainsafe/libp2p-gossipsub * rm unwanted libp2p interface deps & bump up libp2p * refactor code for new deps * update: new package.lock * setup prettier, refactor eslint and rm trailing commas * update package.lock * fix build * import type for interface * fix imports for merge * update typedoc exports * add: CustomEvent import * use new libp2p interface * add aegir as dev dep for tests
124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { ENR } from "@waku/enr";
|
|
import { keccak256, verifySignature } from "@waku/enr";
|
|
import { utf8ToBytes } from "@waku/utils/bytes";
|
|
import base32 from "hi-base32";
|
|
import { fromString } from "uint8arrays/from-string";
|
|
|
|
export type ENRRootValues = {
|
|
eRoot: string;
|
|
lRoot: string;
|
|
seq: number;
|
|
signature: string;
|
|
};
|
|
|
|
export type ENRTreeValues = {
|
|
publicKey: string;
|
|
domain: string;
|
|
};
|
|
|
|
export class ENRTree {
|
|
public static readonly RECORD_PREFIX = ENR.RECORD_PREFIX;
|
|
public static readonly TREE_PREFIX = "enrtree:";
|
|
public static readonly BRANCH_PREFIX = "enrtree-branch:";
|
|
public static readonly ROOT_PREFIX = "enrtree-root:";
|
|
|
|
/**
|
|
* Extracts the branch subdomain referenced by a DNS tree root string after verifying
|
|
* the root record signature with its base32 compressed public key.
|
|
*/
|
|
static parseAndVerifyRoot(root: string, publicKey: string): string {
|
|
if (!root.startsWith(this.ROOT_PREFIX))
|
|
throw new Error(
|
|
`ENRTree root entry must start with '${this.ROOT_PREFIX}'`
|
|
);
|
|
|
|
const rootValues = ENRTree.parseRootValues(root);
|
|
const decodedPublicKey = base32.decode.asBytes(publicKey);
|
|
|
|
// The signature is a 65-byte secp256k1 over the keccak256 hash
|
|
// of the record content, excluding the `sig=` part, encoded as URL-safe base64 string
|
|
// (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method)
|
|
const signedComponent = root.split(" sig")[0];
|
|
|
|
const signedComponentBuffer = utf8ToBytes(signedComponent);
|
|
const signatureBuffer = fromString(rootValues.signature, "base64url").slice(
|
|
0,
|
|
64
|
|
);
|
|
|
|
const isVerified = verifySignature(
|
|
signatureBuffer,
|
|
keccak256(signedComponentBuffer),
|
|
new Uint8Array(decodedPublicKey)
|
|
);
|
|
|
|
if (!isVerified) throw new Error("Unable to verify ENRTree root signature");
|
|
|
|
return rootValues.eRoot;
|
|
}
|
|
|
|
static parseRootValues(txt: string): ENRRootValues {
|
|
const matches = txt.match(
|
|
/^enrtree-root:v1 e=([^ ]+) l=([^ ]+) seq=(\d+) sig=([^ ]+)$/
|
|
);
|
|
|
|
if (!Array.isArray(matches))
|
|
throw new Error("Could not parse ENRTree root entry");
|
|
|
|
matches.shift(); // The first entry is the full match
|
|
const [eRoot, lRoot, seq, signature] = matches;
|
|
|
|
if (!eRoot)
|
|
throw new Error("Could not parse 'e' value from ENRTree root entry");
|
|
if (!lRoot)
|
|
throw new Error("Could not parse 'l' value from ENRTree root entry");
|
|
|
|
if (!seq)
|
|
throw new Error("Could not parse 'seq' value from ENRTree root entry");
|
|
if (!signature)
|
|
throw new Error("Could not parse 'sig' value from ENRTree root entry");
|
|
|
|
return { eRoot, lRoot, seq: Number(seq), signature };
|
|
}
|
|
|
|
/**
|
|
* Returns the public key and top level domain of an ENR tree entry.
|
|
* The domain is the starting point for traversing a set of linked DNS TXT records
|
|
* and the public key is used to verify the root entry record
|
|
*/
|
|
static parseTree(tree: string): ENRTreeValues {
|
|
if (!tree.startsWith(this.TREE_PREFIX))
|
|
throw new Error(
|
|
`ENRTree tree entry must start with '${this.TREE_PREFIX}'`
|
|
);
|
|
|
|
const matches = tree.match(/^enrtree:\/\/([^@]+)@(.+)$/);
|
|
|
|
if (!Array.isArray(matches))
|
|
throw new Error("Could not parse ENRTree tree entry");
|
|
|
|
matches.shift(); // The first entry is the full match
|
|
const [publicKey, domain] = matches;
|
|
|
|
if (!publicKey)
|
|
throw new Error("Could not parse public key from ENRTree tree entry");
|
|
if (!domain)
|
|
throw new Error("Could not parse domain from ENRTree tree entry");
|
|
|
|
return { publicKey, domain };
|
|
}
|
|
|
|
/**
|
|
* Returns subdomains listed in an ENR branch entry. These in turn lead to
|
|
* either further branch entries or ENR records.
|
|
*/
|
|
static parseBranch(branch: string): string[] {
|
|
if (!branch.startsWith(this.BRANCH_PREFIX))
|
|
throw new Error(
|
|
`ENRTree branch entry must start with '${this.BRANCH_PREFIX}'`
|
|
);
|
|
|
|
return branch.split(this.BRANCH_PREFIX)[1].split(",");
|
|
}
|
|
}
|