diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index d777e52b1e..60d949277f 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -1,28 +1,21 @@ import type { PeerId } from "@libp2p/interface-peer-id"; import type { PeerInfo } from "@libp2p/interface-peer-info"; import type { Multiaddr } from "@multiformats/multiaddr"; -import { - convertToBytes, - convertToString, -} from "@multiformats/multiaddr/convert"; import type { ENRKey, ENRValue, IEnr, NodeId, SequenceNumber, - Waku2, } from "@waku/interfaces"; -import { bytesToUtf8 } from "@waku/utils"; import debug from "debug"; import { ERR_INVALID_ID } from "./constants.js"; import { keccak256, verifySignature } from "./crypto.js"; import { locationMultiaddrFromEnrFields } from "./get_multiaddr.js"; -import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; import { createPeerIdFromPublicKey } from "./peer_id.js"; +import { RawEnr } from "./raw_enr.js"; import * as v4 from "./v4.js"; -import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js"; const log = debug("waku:enr"); @@ -37,22 +30,10 @@ export enum TransportProtocolPerIpVersion { UDP6 = "udp6", } -export class ENR extends Map implements IEnr { +export class ENR extends RawEnr implements IEnr { public static readonly RECORD_PREFIX = "enr:"; - public seq: SequenceNumber; - public signature?: Uint8Array; public peerId?: PeerId; - private constructor( - kvs: Record = {}, - seq: SequenceNumber = BigInt(1), - signature?: Uint8Array - ) { - super(Object.entries(kvs)); - this.seq = seq; - this.signature = signature; - } - static async create( kvs: Record = {}, seq: SequenceNumber = BigInt(1), @@ -70,26 +51,6 @@ export class ENR extends Map implements IEnr { return enr; } - set(k: ENRKey, v: ENRValue): this { - this.signature = undefined; - this.seq++; - return super.set(k, v); - } - - get id(): string { - const id = this.get("id"); - if (!id) throw new Error("id not found."); - return bytesToUtf8(id); - } - - get publicKey(): Uint8Array | undefined { - switch (this.id) { - case "v4": - return this.get("secp256k1"); - default: - throw new Error(ERR_INVALID_ID); - } - } get nodeId(): NodeId | undefined { switch (this.id) { @@ -99,150 +60,6 @@ export class ENR extends Map implements IEnr { throw new Error(ERR_INVALID_ID); } } - - get ip(): string | undefined { - const raw = this.get("ip"); - if (raw) { - return convertToString("ip4", raw) as string; - } else { - return undefined; - } - } - - set ip(ip: string | undefined) { - if (ip) { - this.set("ip", convertToBytes("ip4", ip)); - } else { - this.delete("ip"); - } - } - - get tcp(): number | undefined { - const raw = this.get("tcp"); - if (raw) { - return Number(convertToString("tcp", raw)); - } else { - return undefined; - } - } - - set tcp(port: number | undefined) { - if (port === undefined) { - this.delete("tcp"); - } else { - this.set("tcp", convertToBytes("tcp", port.toString(10))); - } - } - - get udp(): number | undefined { - const raw = this.get("udp"); - if (raw) { - return Number(convertToString("udp", raw)); - } else { - return undefined; - } - } - - set udp(port: number | undefined) { - if (port === undefined) { - this.delete("udp"); - } else { - this.set("udp", convertToBytes("udp", port.toString(10))); - } - } - - get ip6(): string | undefined { - const raw = this.get("ip6"); - if (raw) { - return convertToString("ip6", raw) as string; - } else { - return undefined; - } - } - - set ip6(ip: string | undefined) { - if (ip) { - this.set("ip6", convertToBytes("ip6", ip)); - } else { - this.delete("ip6"); - } - } - - get tcp6(): number | undefined { - const raw = this.get("tcp6"); - if (raw) { - return Number(convertToString("tcp", raw)); - } else { - return undefined; - } - } - - set tcp6(port: number | undefined) { - if (port === undefined) { - this.delete("tcp6"); - } else { - this.set("tcp6", convertToBytes("tcp", port.toString(10))); - } - } - - get udp6(): number | undefined { - const raw = this.get("udp6"); - if (raw) { - return Number(convertToString("udp", raw)); - } else { - return undefined; - } - } - - set udp6(port: number | undefined) { - if (port === undefined) { - this.delete("udp6"); - } else { - this.set("udp6", convertToBytes("udp", port.toString(10))); - } - } - - /** - * Get the `multiaddrs` field from ENR. - * - * This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys. - * These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host - * address (e.g. `dns4`, `dnsaddr`, etc).. - * - * If the peer information only contains information that can be represented with the ENR pre-defined keys - * (ip, tcp, etc) then the usage of { @link getLocationMultiaddr } should be preferred. - * - * The multiaddresses stored in this field are expected to be location multiaddresses, ie, peer id less. - */ - get multiaddrs(): Multiaddr[] | undefined { - const raw = this.get("multiaddrs"); - - if (raw) return decodeMultiaddrs(raw); - - return; - } - - /** - * Set the `multiaddrs` field on the ENR. - * - * This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys. - * These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host - * address (e.g. `dns4`, `dnsaddr`, etc).. - * - * If the peer information only contains information that can be represented with the ENR pre-defined keys - * (ip, tcp, etc) then the usage of { @link setLocationMultiaddr } should be preferred. - * The multiaddresses stored in this field must be location multiaddresses, - * ie, without a peer id. - */ - set multiaddrs(multiaddrs: Multiaddr[] | undefined) { - if (multiaddrs === undefined) { - this.delete("multiaddrs"); - } else { - const multiaddrsBuf = encodeMultiaddrs(multiaddrs); - this.set("multiaddrs", multiaddrsBuf); - } - } - getLocationMultiaddr: ( protocol: TransportProtocol | TransportProtocolPerIpVersion ) => Multiaddr | undefined = locationMultiaddrFromEnrFields.bind({}, this); @@ -330,28 +147,6 @@ export class ENR extends Map implements IEnr { return []; } - /** - * Get the `waku2` field from ENR. - */ - get waku2(): Waku2 | undefined { - const raw = this.get("waku2"); - if (raw) return decodeWaku2(raw[0]); - - return; - } - - /** - * Set the `waku2` field on the ENR. - */ - set waku2(waku2: Waku2 | undefined) { - if (waku2 === undefined) { - this.delete("waku2"); - } else { - const byte = encodeWaku2(waku2); - this.set("waku2", new Uint8Array([byte])); - } - } - verify(data: Uint8Array, signature: Uint8Array): boolean { if (!this.get("id") || this.id !== "v4") { throw new Error(ERR_INVALID_ID); diff --git a/packages/enr/src/raw_enr.ts b/packages/enr/src/raw_enr.ts new file mode 100644 index 0000000000..5a15f48649 --- /dev/null +++ b/packages/enr/src/raw_enr.ts @@ -0,0 +1,212 @@ +import type { Multiaddr } from "@multiformats/multiaddr"; +import { + convertToBytes, + convertToString, +} from "@multiformats/multiaddr/convert"; +import type { ENRKey, ENRValue, SequenceNumber, Waku2 } from "@waku/interfaces"; +import { bytesToUtf8 } from "@waku/utils"; + +import { ERR_INVALID_ID } from "./constants.js"; +import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; +import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js"; + +export class RawEnr extends Map { + public seq: SequenceNumber; + public signature?: Uint8Array; + + protected constructor( + kvs: Record = {}, + seq: SequenceNumber = BigInt(1), + signature?: Uint8Array + ) { + super(Object.entries(kvs)); + this.seq = seq; + this.signature = signature; + } + + set(k: ENRKey, v: ENRValue): this { + this.signature = undefined; + this.seq++; + return super.set(k, v); + } + + get id(): string { + const id = this.get("id"); + if (!id) throw new Error("id not found."); + return bytesToUtf8(id); + } + + get publicKey(): Uint8Array | undefined { + switch (this.id) { + case "v4": + return this.get("secp256k1"); + default: + throw new Error(ERR_INVALID_ID); + } + } + + get ip(): string | undefined { + const raw = this.get("ip"); + if (raw) { + return convertToString("ip4", raw) as string; + } else { + return undefined; + } + } + + set ip(ip: string | undefined) { + if (ip) { + this.set("ip", convertToBytes("ip4", ip)); + } else { + this.delete("ip"); + } + } + + get tcp(): number | undefined { + const raw = this.get("tcp"); + if (raw) { + return Number(convertToString("tcp", raw)); + } else { + return undefined; + } + } + + set tcp(port: number | undefined) { + if (port === undefined) { + this.delete("tcp"); + } else { + this.set("tcp", convertToBytes("tcp", port.toString(10))); + } + } + + get udp(): number | undefined { + const raw = this.get("udp"); + if (raw) { + return Number(convertToString("udp", raw)); + } else { + return undefined; + } + } + + set udp(port: number | undefined) { + if (port === undefined) { + this.delete("udp"); + } else { + this.set("udp", convertToBytes("udp", port.toString(10))); + } + } + + get ip6(): string | undefined { + const raw = this.get("ip6"); + if (raw) { + return convertToString("ip6", raw) as string; + } else { + return undefined; + } + } + + set ip6(ip: string | undefined) { + if (ip) { + this.set("ip6", convertToBytes("ip6", ip)); + } else { + this.delete("ip6"); + } + } + + get tcp6(): number | undefined { + const raw = this.get("tcp6"); + if (raw) { + return Number(convertToString("tcp", raw)); + } else { + return undefined; + } + } + + set tcp6(port: number | undefined) { + if (port === undefined) { + this.delete("tcp6"); + } else { + this.set("tcp6", convertToBytes("tcp", port.toString(10))); + } + } + + get udp6(): number | undefined { + const raw = this.get("udp6"); + if (raw) { + return Number(convertToString("udp", raw)); + } else { + return undefined; + } + } + + set udp6(port: number | undefined) { + if (port === undefined) { + this.delete("udp6"); + } else { + this.set("udp6", convertToBytes("udp", port.toString(10))); + } + } + + /** + * Get the `multiaddrs` field from ENR. + * + * This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys. + * These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host + * address (e.g. `dns4`, `dnsaddr`, etc).. + * + * If the peer information only contains information that can be represented with the ENR pre-defined keys + * (ip, tcp, etc) then the usage of { @link getLocationMultiaddr } should be preferred. + * + * The multiaddresses stored in this field are expected to be location multiaddresses, ie, peer id less. + */ + get multiaddrs(): Multiaddr[] | undefined { + const raw = this.get("multiaddrs"); + + if (raw) return decodeMultiaddrs(raw); + + return; + } + + /** + * Set the `multiaddrs` field on the ENR. + * + * This field is used to store multiaddresses that cannot be stored with the current ENR pre-defined keys. + * These can be a multiaddresses that include encapsulation (e.g. wss) or do not use `ip4` nor `ip6` for the host + * address (e.g. `dns4`, `dnsaddr`, etc).. + * + * If the peer information only contains information that can be represented with the ENR pre-defined keys + * (ip, tcp, etc) then the usage of { @link setLocationMultiaddr } should be preferred. + * The multiaddresses stored in this field must be location multiaddresses, + * ie, without a peer id. + */ + set multiaddrs(multiaddrs: Multiaddr[] | undefined) { + if (multiaddrs === undefined) { + this.delete("multiaddrs"); + } else { + const multiaddrsBuf = encodeMultiaddrs(multiaddrs); + this.set("multiaddrs", multiaddrsBuf); + } + } + + /** + * Get the `waku2` field from ENR. + */ + get waku2(): Waku2 | undefined { + const raw = this.get("waku2"); + if (raw) return decodeWaku2(raw[0]); + + return; + } + + /** + * Set the `waku2` field on the ENR. + */ + set waku2(waku2: Waku2 | undefined) { + if (waku2 === undefined) { + this.delete("waku2"); + } else { + const byte = encodeWaku2(waku2); + this.set("waku2", new Uint8Array([byte])); + } + } +}