From 05b122e646132c5dece536cecb8b1350e8d7318a Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 12:51:56 +1100 Subject: [PATCH 01/14] chore: remove unnecessary functions --- packages/enr/src/enr.ts | 5 ++--- packages/enr/src/keypair/index.ts | 27 +++++++-------------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 337bb06a43..8b690e66da 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -27,7 +27,7 @@ import { compressPublicKey, keccak256, verifySignature } from "./crypto.js"; import { createKeypair, createKeypairFromPeerId, - createPeerIdFromKeypair, + createPeerIdFromPublicKey, IKeypair, KeypairType, } from "./keypair/index.js"; @@ -63,8 +63,7 @@ export class ENR extends Map implements IEnr { try { const publicKey = enr.publicKey; if (publicKey) { - const keypair = createKeypair(enr.keypairType, undefined, publicKey); - enr.peerId = await createPeerIdFromKeypair(keypair); + enr.peerId = await createPeerIdFromPublicKey(publicKey); } } catch (e) { log("Could not calculate peer id for ENR", e); diff --git a/packages/enr/src/keypair/index.ts b/packages/enr/src/keypair/index.ts index addd1dad34..7f76c0465f 100644 --- a/packages/enr/src/keypair/index.ts +++ b/packages/enr/src/keypair/index.ts @@ -10,6 +10,13 @@ export const ERR_TYPE_NOT_IMPLEMENTED = "Keypair type not implemented"; export * from "./types.js"; export * from "./secp256k1.js"; +export function createPeerIdFromPublicKey( + publicKey: Uint8Array +): Promise { + const _publicKey = new supportedKeys.secp256k1.Secp256k1PublicKey(publicKey); + return peerIdFromKeys(_publicKey.bytes, undefined); +} + export function createKeypair( type: KeypairType, privateKey?: Uint8Array, @@ -23,26 +30,6 @@ export function createKeypair( } } -export async function createPeerIdFromKeypair( - keypair: IKeypair -): Promise { - switch (keypair.type) { - case KeypairType.secp256k1: { - const publicKey = new supportedKeys.secp256k1.Secp256k1PublicKey( - keypair.publicKey - ); - - const privateKey = keypair.hasPrivateKey() - ? new supportedKeys.secp256k1.Secp256k1PrivateKey(keypair.privateKey) - : undefined; - - return peerIdFromKeys(publicKey.bytes, privateKey?.bytes); - } - default: - throw new Error(ERR_TYPE_NOT_IMPLEMENTED); - } -} - export async function createKeypairFromPeerId( peerId: PeerId ): Promise { From 46a020c6b495785a945b733fa6d8351bd248f3f2 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:06:24 +1100 Subject: [PATCH 02/14] chore: split function as one part was only used in test --- packages/enr/src/enr.spec.ts | 26 ++++++++--------- packages/enr/src/enr.ts | 9 +++--- packages/enr/src/keypair/index.ts | 47 +++++++++++++------------------ 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index c45a1eb2b2..6a61d9a4d2 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -8,14 +8,14 @@ import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; import { getPublicKey } from "./crypto.js"; import { ENR } from "./enr.js"; -import { createKeypairFromPeerId, IKeypair } from "./keypair/index.js"; +import { getPrivateKeyFromPeerId } from "./keypair/index.js"; describe("ENR", function () { describe("Txt codec", () => { it("should encodeTxt and decodeTxt", async () => { const peerId = await createSecp256k1PeerId(); const enr = await ENR.createFromPeerId(peerId); - const keypair = await createKeypairFromPeerId(peerId); + const privateKey = await getPrivateKeyFromPeerId(peerId); enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); enr.multiaddrs = [ multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"), @@ -32,7 +32,7 @@ describe("ENR", function () { lightPush: false, }; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const enr2 = await ENR.decodeTxt(txt); if (!enr.signature) throw "enr.signature is undefined"; @@ -107,11 +107,11 @@ describe("ENR", function () { try { const peerId = await createSecp256k1PeerId(); const enr = await ENR.createFromPeerId(peerId); - const keypair = await createKeypairFromPeerId(peerId); + const privateKey = await getPrivateKeyFromPeerId(peerId); enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); enr.set("id", new Uint8Array([0])); - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); await ENR.decodeTxt(txt); assert.fail("Expect error here"); @@ -384,12 +384,12 @@ describe("ENR", function () { let peerId; let enr: ENR; let waku2Protocols: Waku2; - let keypair: IKeypair; + let privateKey: Uint8Array; beforeEach(async function () { peerId = await createSecp256k1PeerId(); enr = await ENR.createFromPeerId(peerId); - keypair = await createKeypairFromPeerId(peerId); + privateKey = await getPrivateKeyFromPeerId(peerId); waku2Protocols = { relay: false, store: false, @@ -401,7 +401,7 @@ describe("ENR", function () { it("should set field with all protocols disabled", async () => { enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -417,7 +417,7 @@ describe("ENR", function () { waku2Protocols.lightPush = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(true); @@ -430,7 +430,7 @@ describe("ENR", function () { waku2Protocols.relay = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(true); @@ -443,7 +443,7 @@ describe("ENR", function () { waku2Protocols.store = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -456,7 +456,7 @@ describe("ENR", function () { waku2Protocols.filter = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -469,7 +469,7 @@ describe("ENR", function () { waku2Protocols.lightPush = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(keypair.privateKey); + const txt = await enr.encodeTxt(privateKey); const decoded = (await ENR.decodeTxt(txt)).waku2!; expect(decoded.relay).to.equal(false); diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 8b690e66da..682f7db603 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -26,8 +26,8 @@ import { import { compressPublicKey, keccak256, verifySignature } from "./crypto.js"; import { createKeypair, - createKeypairFromPeerId, createPeerIdFromPublicKey, + getPublicKeyFromPeerId, IKeypair, KeypairType, } from "./keypair/index.js"; @@ -91,10 +91,9 @@ export class ENR extends Map implements IEnr { peerId: PeerId, kvs: Record = {} ): Promise { - const keypair = await createKeypairFromPeerId(peerId); - switch (keypair.type) { - case KeypairType.secp256k1: - return ENR.createV4(keypair.publicKey, kvs); + switch (peerId.type) { + case "secp256k1": + return ENR.createV4(getPublicKeyFromPeerId(peerId), kvs); default: throw new Error(); } diff --git a/packages/enr/src/keypair/index.ts b/packages/enr/src/keypair/index.ts index 7f76c0465f..b656d65788 100644 --- a/packages/enr/src/keypair/index.ts +++ b/packages/enr/src/keypair/index.ts @@ -30,34 +30,25 @@ export function createKeypair( } } -export async function createKeypairFromPeerId( - peerId: PeerId -): Promise { - let keypairType; - switch (peerId.type) { - case "RSA": - keypairType = KeypairType.rsa; - break; - case "Ed25519": - keypairType = KeypairType.ed25519; - break; - case "secp256k1": - keypairType = KeypairType.secp256k1; - break; - default: - throw new Error("Unsupported peer id type"); +export function getPublicKeyFromPeerId(peerId: PeerId): Uint8Array { + if (peerId.type !== "secp256k1") { + throw new Error("Unsupported peer id type"); } - const publicKey = peerId.publicKey - ? unmarshalPublicKey(peerId.publicKey) - : undefined; - const privateKey = peerId.privateKey - ? await unmarshalPrivateKey(peerId.privateKey) - : undefined; - - return createKeypair( - keypairType, - privateKey?.marshal(), - publicKey?.marshal() - ); + return unmarshalPublicKey(peerId.publicKey).marshal(); +} + +// Only used in tests +export async function getPrivateKeyFromPeerId( + peerId: PeerId +): Promise { + if (peerId.type !== "secp256k1") { + throw new Error("Unsupported peer id type"); + } + if (!peerId.privateKey) { + throw new Error("Private key not present on peer id"); + } + + const privateKey = await unmarshalPrivateKey(peerId.privateKey); + return privateKey.marshal(); } From 93ba160791ce4c7997a9b9112f7e4138914c4813 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:13:48 +1100 Subject: [PATCH 03/14] chore: remove unused keypair api --- packages/enr/src/enr.spec.ts | 2 +- packages/enr/src/enr.ts | 28 ++------ packages/enr/src/index.ts | 2 +- packages/enr/src/keypair/secp256k1.ts | 69 ------------------- packages/enr/src/keypair/types.ts | 14 ---- .../enr/src/{keypair/index.ts => peer_id.ts} | 20 ------ 6 files changed, 6 insertions(+), 129 deletions(-) delete mode 100644 packages/enr/src/keypair/secp256k1.ts delete mode 100644 packages/enr/src/keypair/types.ts rename packages/enr/src/{keypair/index.ts => peer_id.ts} (66%) diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index 6a61d9a4d2..af9c3737fb 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -8,7 +8,7 @@ import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; import { getPublicKey } from "./crypto.js"; import { ENR } from "./enr.js"; -import { getPrivateKeyFromPeerId } from "./keypair/index.js"; +import { getPrivateKeyFromPeerId } from "./peer_id.js"; describe("ENR", function () { describe("Txt codec", () => { diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 682f7db603..5b3a71312e 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -24,15 +24,12 @@ import { MAX_RECORD_SIZE, } from "./constants.js"; import { compressPublicKey, keccak256, verifySignature } from "./crypto.js"; -import { - createKeypair, - createPeerIdFromPublicKey, - getPublicKeyFromPeerId, - IKeypair, - KeypairType, -} from "./keypair/index.js"; import { multiaddrFromFields } from "./multiaddr_from_fields.js"; import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; +import { + createPeerIdFromPublicKey, + getPublicKeyFromPeerId, +} from "./peer_id.js"; import * as v4 from "./v4.js"; import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js"; @@ -161,15 +158,6 @@ export class ENR extends Map implements IEnr { return bytesToUtf8(id); } - get keypairType(): KeypairType { - switch (this.id) { - case "v4": - return KeypairType.secp256k1; - default: - throw new Error(ERR_INVALID_ID); - } - } - get publicKey(): Uint8Array | undefined { switch (this.id) { case "v4": @@ -179,14 +167,6 @@ export class ENR extends Map implements IEnr { } } - get keypair(): IKeypair | undefined { - if (this.publicKey) { - const publicKey = this.publicKey; - return createKeypair(this.keypairType, undefined, publicKey); - } - return; - } - get nodeId(): NodeId | undefined { switch (this.id) { case "v4": diff --git a/packages/enr/src/index.ts b/packages/enr/src/index.ts index fc8d4c6203..45e2a3bb4e 100644 --- a/packages/enr/src/index.ts +++ b/packages/enr/src/index.ts @@ -1,5 +1,5 @@ export * from "./constants.js"; export * from "./enr.js"; -export * from "./keypair/index.js"; +export * from "./peer_id.js"; export * from "./waku2_codec.js"; export * from "./crypto.js"; diff --git a/packages/enr/src/keypair/secp256k1.ts b/packages/enr/src/keypair/secp256k1.ts deleted file mode 100644 index b658cb5421..0000000000 --- a/packages/enr/src/keypair/secp256k1.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as secp from "@noble/secp256k1"; - -import { compressPublicKey, randomBytes } from "../crypto.js"; - -import { IKeypair, KeypairType } from "./types.js"; - -export class Secp256k1Keypair implements IKeypair { - readonly type: KeypairType; - _privateKey?: Uint8Array; - readonly _publicKey?: Uint8Array; - - constructor(privateKey?: Uint8Array, publicKey?: Uint8Array) { - let pub = publicKey; - if (pub) { - pub = compressPublicKey(pub); - } - if ((this._privateKey = privateKey) && !this.privateKeyVerify()) { - throw new Error("Invalid private key"); - } - if ((this._publicKey = pub) && !this.publicKeyVerify()) { - throw new Error("Invalid public key"); - } - - this.type = KeypairType.secp256k1; - } - - static async generate(): Promise { - const privateKey = randomBytes(32); - const publicKey = secp.getPublicKey(privateKey); - return new Secp256k1Keypair(privateKey, publicKey); - } - - privateKeyVerify(key = this._privateKey): boolean { - if (key) { - return secp.utils.isValidPrivateKey(key); - } - return true; - } - - publicKeyVerify(key = this._publicKey): boolean { - if (key) { - try { - secp.Point.fromHex(key); - return true; - } catch { - return false; - } - } - return true; - } - - get privateKey(): Uint8Array { - if (!this._privateKey) { - throw new Error(); - } - return this._privateKey; - } - - get publicKey(): Uint8Array { - if (!this._publicKey) { - throw new Error(); - } - return this._publicKey; - } - - hasPrivateKey(): boolean { - return !!this._privateKey; - } -} diff --git a/packages/enr/src/keypair/types.ts b/packages/enr/src/keypair/types.ts deleted file mode 100644 index a24618dadf..0000000000 --- a/packages/enr/src/keypair/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum KeypairType { - rsa = 0, - ed25519 = 1, - secp256k1 = 2, -} - -export interface IKeypair { - type: KeypairType; - privateKey: Uint8Array; - publicKey: Uint8Array; - privateKeyVerify(): boolean; - publicKeyVerify(): boolean; - hasPrivateKey(): boolean; -} diff --git a/packages/enr/src/keypair/index.ts b/packages/enr/src/peer_id.ts similarity index 66% rename from packages/enr/src/keypair/index.ts rename to packages/enr/src/peer_id.ts index b656d65788..db65f14d2a 100644 --- a/packages/enr/src/keypair/index.ts +++ b/packages/enr/src/peer_id.ts @@ -3,13 +3,6 @@ import { supportedKeys } from "@libp2p/crypto/keys"; import type { PeerId } from "@libp2p/interface-peer-id"; import { peerIdFromKeys } from "@libp2p/peer-id"; -import { Secp256k1Keypair } from "./secp256k1.js"; -import { IKeypair, KeypairType } from "./types.js"; - -export const ERR_TYPE_NOT_IMPLEMENTED = "Keypair type not implemented"; -export * from "./types.js"; -export * from "./secp256k1.js"; - export function createPeerIdFromPublicKey( publicKey: Uint8Array ): Promise { @@ -17,19 +10,6 @@ export function createPeerIdFromPublicKey( return peerIdFromKeys(_publicKey.bytes, undefined); } -export function createKeypair( - type: KeypairType, - privateKey?: Uint8Array, - publicKey?: Uint8Array -): IKeypair { - switch (type) { - case KeypairType.secp256k1: - return new Secp256k1Keypair(privateKey, publicKey); - default: - throw new Error(ERR_TYPE_NOT_IMPLEMENTED); - } -} - export function getPublicKeyFromPeerId(peerId: PeerId): Uint8Array { if (peerId.type !== "secp256k1") { throw new Error("Unsupported peer id type"); From a51308763734a7ddfca504448c60ea9f230cdaf3 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:18:16 +1100 Subject: [PATCH 04/14] chore: remove functions not used in prod --- packages/enr/src/crypto.ts | 8 -------- packages/enr/src/enr.spec.ts | 6 +++--- packages/enr/src/v4.ts | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/enr/src/crypto.ts b/packages/enr/src/crypto.ts index a265710a2c..4d9f8729b9 100644 --- a/packages/enr/src/crypto.ts +++ b/packages/enr/src/crypto.ts @@ -2,14 +2,6 @@ import * as secp from "@noble/secp256k1"; import { concat } from "@waku/utils"; import sha3 from "js-sha3"; -export const randomBytes = secp.utils.randomBytes; - -/** - * Return the public key for the given private key, to be used for asymmetric - * encryption. - */ -export const getPublicKey = secp.getPublicKey; - /** * ECDSA Sign a message with the given private key. * diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index af9c3737fb..874f754043 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -1,12 +1,12 @@ import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { multiaddr } from "@multiformats/multiaddr"; +import * as secp from "@noble/secp256k1"; import type { Waku2 } from "@waku/interfaces"; import { bytesToHex, hexToBytes, utf8ToBytes } from "@waku/utils"; import { assert, expect } from "chai"; import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; -import { getPublicKey } from "./crypto.js"; import { ENR } from "./enr.js"; import { getPrivateKeyFromPeerId } from "./peer_id.js"; @@ -194,7 +194,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createV4(getPublicKey(privateKey)); + record = await ENR.createV4(secp.getPublicKey(privateKey)); record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303")); record.seq = seq; await record.encodeTxt(privateKey); @@ -239,7 +239,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createV4(getPublicKey(privateKey)); + record = await ENR.createV4(secp.getPublicKey(privateKey)); }); it("should get / set UDP multiaddr", () => { diff --git a/packages/enr/src/v4.ts b/packages/enr/src/v4.ts index 7964a787ca..e1d3b1666f 100644 --- a/packages/enr/src/v4.ts +++ b/packages/enr/src/v4.ts @@ -3,7 +3,6 @@ import type { NodeId } from "@waku/interfaces"; import { bytesToHex } from "@waku/utils"; import { keccak256 } from "./crypto.js"; - export async function sign( privKey: Uint8Array, msg: Uint8Array From 031f4c0c21a81758f54897dc7e87e535fb543778 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:30:17 +1100 Subject: [PATCH 05/14] chore: improve function name By describe why one would use it. --- packages/enr/src/enr.spec.ts | 4 ++-- packages/enr/src/enr.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index 874f754043..c23e596e11 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -194,7 +194,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createV4(secp.getPublicKey(privateKey)); + record = await ENR.createFromPublicKey(secp.getPublicKey(privateKey)); record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303")); record.seq = seq; await record.encodeTxt(privateKey); @@ -239,7 +239,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createV4(secp.getPublicKey(privateKey)); + record = await ENR.createFromPublicKey(secp.getPublicKey(privateKey)); }); it("should get / set UDP multiaddr", () => { diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 5b3a71312e..73a12b5cbc 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -69,7 +69,7 @@ export class ENR extends Map implements IEnr { return enr; } - static createV4( + static createFromPublicKey( publicKey: Uint8Array, kvs: Record = {} ): Promise { @@ -90,7 +90,7 @@ export class ENR extends Map implements IEnr { ): Promise { switch (peerId.type) { case "secp256k1": - return ENR.createV4(getPublicKeyFromPeerId(peerId), kvs); + return ENR.createFromPublicKey(getPublicKeyFromPeerId(peerId), kvs); default: throw new Error(); } From 8fd1455122257a7e41a4966ced7f830e07ea3633 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:34:04 +1100 Subject: [PATCH 06/14] chore: extract test code createFrom* functions are only used in test. By extracting them in different class it should help bundlers. --- .../dns-discovery/src/fetch_nodes.spec.ts | 3 +- packages/enr/src/creator.ts | 36 +++++++++++++++++++ packages/enr/src/enr.spec.ts | 13 +++---- packages/enr/src/enr.ts | 34 ++---------------- packages/enr/src/index.ts | 1 + 5 files changed, 48 insertions(+), 39 deletions(-) create mode 100644 packages/enr/src/creator.ts diff --git a/packages/dns-discovery/src/fetch_nodes.spec.ts b/packages/dns-discovery/src/fetch_nodes.spec.ts index 93eaffbbd0..efb085c7aa 100644 --- a/packages/dns-discovery/src/fetch_nodes.spec.ts +++ b/packages/dns-discovery/src/fetch_nodes.spec.ts @@ -1,6 +1,7 @@ import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { multiaddr } from "@multiformats/multiaddr"; import { ENR } from "@waku/enr"; +import { EnrCreator } from "@waku/enr"; import type { Waku2 } from "@waku/interfaces"; import { expect } from "chai"; @@ -8,7 +9,7 @@ import { fetchNodesUntilCapabilitiesFulfilled } from "./fetch_nodes.js"; async function createEnr(waku2: Waku2): Promise { const peerId = await createSecp256k1PeerId(); - const enr = await ENR.createFromPeerId(peerId); + const enr = await EnrCreator.fromPeerId(peerId); enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); enr.multiaddrs = [ multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"), diff --git a/packages/enr/src/creator.ts b/packages/enr/src/creator.ts new file mode 100644 index 0000000000..b310789184 --- /dev/null +++ b/packages/enr/src/creator.ts @@ -0,0 +1,36 @@ +import { PeerId } from "@libp2p/interface-peer-id"; +import type { ENRKey, ENRValue } from "@waku/interfaces"; +import { utf8ToBytes } from "@waku/utils"; + +import { compressPublicKey } from "./crypto.js"; +import { ENR } from "./enr.js"; +import { getPublicKeyFromPeerId } from "./peer_id.js"; + +export class EnrCreator { + static fromPublicKey( + publicKey: Uint8Array, + kvs: Record = {} + ): Promise { + // EIP-778 specifies that the key must be in compressed format, 33 bytes + if (publicKey.length !== 33) { + publicKey = compressPublicKey(publicKey); + } + return ENR.create({ + ...kvs, + id: utf8ToBytes("v4"), + secp256k1: publicKey, + }); + } + + static async fromPeerId( + peerId: PeerId, + kvs: Record = {} + ): Promise { + switch (peerId.type) { + case "secp256k1": + return EnrCreator.fromPublicKey(getPublicKeyFromPeerId(peerId), kvs); + default: + throw new Error(); + } + } +} diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index c23e596e11..65e60dd6ea 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -7,6 +7,7 @@ import { assert, expect } from "chai"; import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; +import { EnrCreator } from "./creator.js"; import { ENR } from "./enr.js"; import { getPrivateKeyFromPeerId } from "./peer_id.js"; @@ -14,7 +15,7 @@ describe("ENR", function () { describe("Txt codec", () => { it("should encodeTxt and decodeTxt", async () => { const peerId = await createSecp256k1PeerId(); - const enr = await ENR.createFromPeerId(peerId); + const enr = await EnrCreator.fromPeerId(peerId); const privateKey = await getPrivateKeyFromPeerId(peerId); enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); enr.multiaddrs = [ @@ -106,7 +107,7 @@ describe("ENR", function () { it("should throw error - no id", async () => { try { const peerId = await createSecp256k1PeerId(); - const enr = await ENR.createFromPeerId(peerId); + const enr = await EnrCreator.fromPeerId(peerId); const privateKey = await getPrivateKeyFromPeerId(peerId); enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); @@ -194,7 +195,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createFromPublicKey(secp.getPublicKey(privateKey)); + record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey)); record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303")); record.seq = seq; await record.encodeTxt(privateKey); @@ -239,7 +240,7 @@ describe("ENR", function () { privateKey = hexToBytes( "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ); - record = await ENR.createFromPublicKey(secp.getPublicKey(privateKey)); + record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey)); }); it("should get / set UDP multiaddr", () => { @@ -308,7 +309,7 @@ describe("ENR", function () { before(async function () { peerId = await createSecp256k1PeerId(); - enr = await ENR.createFromPeerId(peerId); + enr = await EnrCreator.fromPeerId(peerId); enr.ip = ip4; enr.ip6 = ip6; enr.tcp = tcp; @@ -388,7 +389,7 @@ describe("ENR", function () { beforeEach(async function () { peerId = await createSecp256k1PeerId(); - enr = await ENR.createFromPeerId(peerId); + enr = await EnrCreator.fromPeerId(peerId); privateKey = await getPrivateKeyFromPeerId(peerId); waku2Protocols = { relay: false, diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 73a12b5cbc..8ee793ab8f 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -23,13 +23,10 @@ import { ERR_NO_SIGNATURE, MAX_RECORD_SIZE, } from "./constants.js"; -import { compressPublicKey, keccak256, verifySignature } from "./crypto.js"; +import { keccak256, verifySignature } from "./crypto.js"; import { multiaddrFromFields } from "./multiaddr_from_fields.js"; import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; -import { - createPeerIdFromPublicKey, - getPublicKeyFromPeerId, -} from "./peer_id.js"; +import { createPeerIdFromPublicKey } from "./peer_id.js"; import * as v4 from "./v4.js"; import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js"; @@ -69,33 +66,6 @@ export class ENR extends Map implements IEnr { return enr; } - static createFromPublicKey( - publicKey: Uint8Array, - kvs: Record = {} - ): Promise { - // EIP-778 specifies that the key must be in compressed format, 33 bytes - if (publicKey.length !== 33) { - publicKey = compressPublicKey(publicKey); - } - return ENR.create({ - ...kvs, - id: utf8ToBytes("v4"), - secp256k1: publicKey, - }); - } - - static async createFromPeerId( - peerId: PeerId, - kvs: Record = {} - ): Promise { - switch (peerId.type) { - case "secp256k1": - return ENR.createFromPublicKey(getPublicKeyFromPeerId(peerId), kvs); - default: - throw new Error(); - } - } - static async decodeFromValues(decoded: Uint8Array[]): Promise { if (!Array.isArray(decoded)) { throw new Error("Decoded ENR must be an array"); diff --git a/packages/enr/src/index.ts b/packages/enr/src/index.ts index 45e2a3bb4e..496c7e82c2 100644 --- a/packages/enr/src/index.ts +++ b/packages/enr/src/index.ts @@ -1,4 +1,5 @@ export * from "./constants.js"; +export * from "./creator.js"; export * from "./enr.js"; export * from "./peer_id.js"; export * from "./waku2_codec.js"; From 130c49b636807063364f309da0da2a24a68f2178 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 13:49:32 +1100 Subject: [PATCH 07/14] chore!: extract decoder code Separation of concerns by moving decoding logic in new class. --- packages/dns-discovery/src/dns.ts | 4 +- packages/enr/src/decoder.ts | 59 +++++++++++++++++++ packages/enr/src/enr.spec.ts | 33 ++++++----- packages/enr/src/enr.ts | 54 +---------------- packages/enr/src/index.ts | 1 + .../peer-exchange/src/waku_peer_exchange.ts | 4 +- packages/tests/tests/enr.node.spec.ts | 8 +-- 7 files changed, 86 insertions(+), 77 deletions(-) create mode 100644 packages/enr/src/decoder.ts diff --git a/packages/dns-discovery/src/dns.ts b/packages/dns-discovery/src/dns.ts index 6052be6df8..41693b3204 100644 --- a/packages/dns-discovery/src/dns.ts +++ b/packages/dns-discovery/src/dns.ts @@ -1,4 +1,4 @@ -import { ENR } from "@waku/enr"; +import { ENR, EnrDecoder } from "@waku/enr"; import type { IEnr } from "@waku/interfaces"; import debug from "debug"; @@ -131,7 +131,7 @@ export class DnsNodeDiscovery { next = selectRandomPath(branches, context); return await this._search(next, context); case ENRTree.RECORD_PREFIX: - return ENR.decodeTxt(entry); + return EnrDecoder.fromString(entry); default: return null; } diff --git a/packages/enr/src/decoder.ts b/packages/enr/src/decoder.ts new file mode 100644 index 0000000000..c2a2370f2d --- /dev/null +++ b/packages/enr/src/decoder.ts @@ -0,0 +1,59 @@ +import * as RLP from "@ethersproject/rlp"; +import type { ENRKey, ENRValue } from "@waku/interfaces"; +import { bytesToHex, bytesToUtf8, hexToBytes } from "@waku/utils"; +import { log } from "debug"; +import { fromString } from "uint8arrays/from-string"; + +import { ENR } from "./enr.js"; + +export class EnrDecoder { + static fromString(encoded: string): Promise { + if (!encoded.startsWith(ENR.RECORD_PREFIX)) { + throw new Error( + `"string encoded ENR must start with '${ENR.RECORD_PREFIX}'` + ); + } + return EnrDecoder.fromRLP(fromString(encoded.slice(4), "base64url")); + } + + static fromRLP(encoded: Uint8Array): Promise { + const decoded = RLP.decode(encoded).map(hexToBytes); + return EnrDecoder.fromValues(decoded); + } + + private static async fromValues(decoded: Uint8Array[]): Promise { + if (!Array.isArray(decoded)) { + throw new Error("Decoded ENR must be an array"); + } + if (decoded.length % 2 !== 0) { + throw new Error("Decoded ENR must have an even number of elements"); + } + const [signature, seq, ...kvs] = decoded; + if (!signature || Array.isArray(signature)) { + throw new Error("Decoded ENR invalid signature: must be a byte array"); + } + if (!seq || Array.isArray(seq)) { + throw new Error( + "Decoded ENR invalid sequence number: must be a byte array" + ); + } + const obj: Record = {}; + for (let i = 0; i < kvs.length; i += 2) { + try { + obj[bytesToUtf8(kvs[i])] = kvs[i + 1]; + } catch (e) { + log("Failed to decode ENR key to UTF-8, skipping it", kvs[i], e); + } + } + // If seq is an empty array, translate as value 0 + const hexSeq = "0x" + (seq.length ? bytesToHex(seq) : "00"); + + const enr = await ENR.create(obj, BigInt(hexSeq), signature); + + const rlpEncodedBytes = hexToBytes(RLP.encode([seq, ...kvs])); + if (!enr.verify(rlpEncodedBytes, signature)) { + throw new Error("Unable to verify ENR signature"); + } + return enr; + } +} diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index 65e60dd6ea..8b1277aaf3 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -8,6 +8,7 @@ import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; import { EnrCreator } from "./creator.js"; +import { EnrDecoder } from "./decoder.js"; import { ENR } from "./enr.js"; import { getPrivateKeyFromPeerId } from "./peer_id.js"; @@ -34,7 +35,7 @@ describe("ENR", function () { }; const txt = await enr.encodeTxt(privateKey); - const enr2 = await ENR.decodeTxt(txt); + const enr2 = await EnrDecoder.fromString(txt); if (!enr.signature) throw "enr.signature is undefined"; if (!enr2.signature) throw "enr.signature is undefined"; @@ -65,7 +66,7 @@ describe("ENR", function () { it("should decode valid enr successfully", async () => { const txt = "enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg"; - const enr = await ENR.decodeTxt(txt); + const enr = await EnrDecoder.fromString(txt); const eth2 = enr.get("eth2"); if (!eth2) throw "eth2 is undefined"; expect(bytesToHex(eth2)).to.be.equal("f6775d0700000113ffffffffffff1f00"); @@ -74,7 +75,7 @@ describe("ENR", function () { it("should decode valid ENR with multiaddrs successfully [shared test vector]", async () => { const txt = "enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA"; - const enr = await ENR.decodeTxt(txt); + const enr = await EnrDecoder.fromString(txt); expect(enr.multiaddrs).to.not.be.undefined; expect(enr.multiaddrs!.length).to.be.equal(3); @@ -93,7 +94,7 @@ describe("ENR", function () { it("should decode valid enr with tcp successfully", async () => { const txt = "enr:-IS4QAmC_o1PMi5DbR4Bh4oHVyQunZblg4bTaottPtBodAhJZvxVlWW-4rXITPNg4mwJ8cW__D9FBDc9N4mdhyMqB-EBgmlkgnY0gmlwhIbRi9KJc2VjcDI1NmsxoQOevTdO6jvv3fRruxguKR-3Ge4bcFsLeAIWEDjrfaigNoN0Y3CCdl8"; - const enr = await ENR.decodeTxt(txt); + const enr = await EnrDecoder.fromString(txt); expect(enr.tcp).to.not.be.undefined; expect(enr.tcp).to.be.equal(30303); expect(enr.ip).to.not.be.undefined; @@ -114,7 +115,7 @@ describe("ENR", function () { enr.set("id", new Uint8Array([0])); const txt = await enr.encodeTxt(privateKey); - await ENR.decodeTxt(txt); + await EnrDecoder.fromString(txt); assert.fail("Expect error here"); } catch (err: unknown) { const e = err as Error; @@ -126,7 +127,7 @@ describe("ENR", function () { try { const txt = "enr:-IS4QJ2d11eu6dC7E7LoXeLMgMP3kom1u3SE8esFSWvaHoo0dP1jg8O3-nx9ht-EO3CmG7L6OkHcMmoIh00IYWB92QABgmlkgnY0gmlwhH8AAAGJc2d11eu6dCsxoQIB_c-jQMOXsbjWkbN-kj99H57gfId5pfb4wa1qxwV4CIN1ZHCCIyk"; - ENR.decodeTxt(txt); + EnrDecoder.fromString(txt); assert.fail("Expect error here"); } catch (err: unknown) { const e = err as Error; @@ -180,7 +181,7 @@ describe("ENR", function () { it("should return false", async () => { const txt = "enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg"; - const enr = await ENR.decodeTxt(txt); + const enr = await EnrDecoder.fromString(txt); // should have id and public key inside ENR expect(enr.verify(new Uint8Array(32), new Uint8Array(64))).to.be.false; }); @@ -209,7 +210,7 @@ describe("ENR", function () { it("should encode/decode to RLP encoding", async function () { const encoded = await record.encode(privateKey); - const decoded = await ENR.decode(encoded); + const decoded = await EnrDecoder.fromRLP(encoded); record.forEach((value, key) => { expect(equals(decoded.get(key)!, value)).to.be.true; @@ -220,7 +221,7 @@ describe("ENR", function () { // spec enr https://eips.ethereum.org/EIPS/eip-778 const testTxt = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"; - const decoded = await ENR.decodeTxt(testTxt); + const decoded = await EnrDecoder.fromString(testTxt); // Note: Signatures are different due to the extra entropy added // by @noble/secp256k1: // https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey @@ -403,7 +404,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); expect(decoded.store).to.equal(false); @@ -419,7 +420,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(true); expect(decoded.store).to.equal(true); @@ -432,7 +433,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(true); expect(decoded.store).to.equal(false); @@ -445,7 +446,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); expect(decoded.store).to.equal(true); @@ -458,7 +459,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); expect(decoded.store).to.equal(false); @@ -471,7 +472,7 @@ describe("ENR", function () { enr.waku2 = waku2Protocols; const txt = await enr.encodeTxt(privateKey); - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); expect(decoded.store).to.equal(false); @@ -485,7 +486,7 @@ describe("ENR", function () { const txt = "enr:-Iu4QADPfXNCM6iYyte0pIdbMirIw_AsKR7J1DeJBysXDWz4DZvyjgIwpMt-sXTVUzLJdE9FaStVy2ZKtHUVQAH61-KAgmlkgnY0gmlwhMCosvuJc2VjcDI1NmsxoQI0OCNtPJtBayNgvFvKp-0YyCozcvE1rqm_V1W51nHVv4N0Y3CC6mCFd2FrdTIH"; - const decoded = (await ENR.decodeTxt(txt)).waku2!; + const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(true); expect(decoded.store).to.equal(true); diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 8ee793ab8f..5066fe6c4a 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -13,9 +13,8 @@ import type { SequenceNumber, Waku2, } from "@waku/interfaces"; -import { bytesToHex, bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils"; +import { bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils"; import debug from "debug"; -import { fromString } from "uint8arrays/from-string"; import { toString } from "uint8arrays/to-string"; import { @@ -65,57 +64,6 @@ export class ENR extends Map implements IEnr { return enr; } - - static async decodeFromValues(decoded: Uint8Array[]): Promise { - if (!Array.isArray(decoded)) { - throw new Error("Decoded ENR must be an array"); - } - if (decoded.length % 2 !== 0) { - throw new Error("Decoded ENR must have an even number of elements"); - } - const [signature, seq, ...kvs] = decoded; - if (!signature || Array.isArray(signature)) { - throw new Error("Decoded ENR invalid signature: must be a byte array"); - } - if (!seq || Array.isArray(seq)) { - throw new Error( - "Decoded ENR invalid sequence number: must be a byte array" - ); - } - const obj: Record = {}; - for (let i = 0; i < kvs.length; i += 2) { - try { - obj[bytesToUtf8(kvs[i])] = kvs[i + 1]; - } catch (e) { - log("Failed to decode ENR key to UTF-8, skipping it", kvs[i], e); - } - } - // If seq is an empty array, translate as value 0 - const hexSeq = "0x" + (seq.length ? bytesToHex(seq) : "00"); - - const enr = await ENR.create(obj, BigInt(hexSeq), signature); - - const rlpEncodedBytes = hexToBytes(RLP.encode([seq, ...kvs])); - if (!enr.verify(rlpEncodedBytes, signature)) { - throw new Error("Unable to verify ENR signature"); - } - return enr; - } - - static decode(encoded: Uint8Array): Promise { - const decoded = RLP.decode(encoded).map(hexToBytes); - return ENR.decodeFromValues(decoded); - } - - static decodeTxt(encoded: string): Promise { - if (!encoded.startsWith(this.RECORD_PREFIX)) { - throw new Error( - `"string encoded ENR must start with '${this.RECORD_PREFIX}'` - ); - } - return ENR.decode(fromString(encoded.slice(4), "base64url")); - } - set(k: ENRKey, v: ENRValue): this { this.signature = undefined; this.seq++; diff --git a/packages/enr/src/index.ts b/packages/enr/src/index.ts index 496c7e82c2..d8b6fb5481 100644 --- a/packages/enr/src/index.ts +++ b/packages/enr/src/index.ts @@ -1,5 +1,6 @@ export * from "./constants.js"; export * from "./creator.js"; +export * from "./decoder.js"; export * from "./enr.js"; export * from "./peer_id.js"; export * from "./waku2_codec.js"; diff --git a/packages/peer-exchange/src/waku_peer_exchange.ts b/packages/peer-exchange/src/waku_peer_exchange.ts index 9fedc0d96c..afe07dba1f 100644 --- a/packages/peer-exchange/src/waku_peer_exchange.ts +++ b/packages/peer-exchange/src/waku_peer_exchange.ts @@ -5,7 +5,7 @@ import type { Registrar, } from "@libp2p/interface-registrar"; import { BaseProtocol } from "@waku/core/lib/base_protocol"; -import { ENR } from "@waku/enr"; +import { EnrDecoder } from "@waku/enr"; import type { IPeerExchange, PeerExchangeQueryParams, @@ -95,7 +95,7 @@ export class WakuPeerExchange extends BaseProtocol implements IPeerExchange { const enrs = await Promise.all( decoded.peerInfos.map( - (peerInfo) => peerInfo.enr && ENR.decode(peerInfo.enr) + (peerInfo) => peerInfo.enr && EnrDecoder.fromRLP(peerInfo.enr) ) ); diff --git a/packages/tests/tests/enr.node.spec.ts b/packages/tests/tests/enr.node.spec.ts index 415e91aaa5..5e5fe8c111 100644 --- a/packages/tests/tests/enr.node.spec.ts +++ b/packages/tests/tests/enr.node.spec.ts @@ -1,6 +1,6 @@ import { waitForRemotePeer } from "@waku/core"; import { createRelayNode } from "@waku/create"; -import { ENR } from "@waku/enr"; +import { EnrDecoder } from "@waku/enr"; import type { RelayNode } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { expect } from "chai"; @@ -38,7 +38,7 @@ describe("ENR Interop: nwaku", function () { const nimPeerId = await nwaku.getPeerId(); expect(nwakuInfo.enrUri).to.not.be.undefined; - const dec = await ENR.decodeTxt(nwakuInfo.enrUri ?? ""); + const dec = await EnrDecoder.fromString(nwakuInfo.enrUri ?? ""); expect(dec.peerId?.toString()).to.eq(nimPeerId.toString()); expect(dec.waku2).to.deep.eq({ relay: true, @@ -70,7 +70,7 @@ describe("ENR Interop: nwaku", function () { const nimPeerId = await nwaku.getPeerId(); expect(nwakuInfo.enrUri).to.not.be.undefined; - const dec = await ENR.decodeTxt(nwakuInfo.enrUri ?? ""); + const dec = await EnrDecoder.fromString(nwakuInfo.enrUri ?? ""); expect(dec.peerId?.toString()).to.eq(nimPeerId.toString()); expect(dec.waku2).to.deep.eq({ relay: true, @@ -102,7 +102,7 @@ describe("ENR Interop: nwaku", function () { const nimPeerId = await nwaku.getPeerId(); expect(nwakuInfo.enrUri).to.not.be.undefined; - const dec = await ENR.decodeTxt(nwakuInfo.enrUri ?? ""); + const dec = await EnrDecoder.fromString(nwakuInfo.enrUri ?? ""); expect(dec.peerId?.toString()).to.eq(nimPeerId.toString()); expect(dec.waku2).to.deep.eq({ relay: true, From 22ffcf571aa3998267f0f3b59576abc38f3f4281 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 14:00:54 +1100 Subject: [PATCH 08/14] chore!: extract encoder code Separation of concerns by moving encoding logic in new class. --- packages/enr/src/encoder.ts | 50 ++++++++++++++++++++++++++++++++++ packages/enr/src/enr.spec.ts | 21 +++++++------- packages/enr/src/enr.ts | 49 ++------------------------------- packages/interfaces/src/enr.ts | 1 - 4 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 packages/enr/src/encoder.ts diff --git a/packages/enr/src/encoder.ts b/packages/enr/src/encoder.ts new file mode 100644 index 0000000000..9076ab8fdf --- /dev/null +++ b/packages/enr/src/encoder.ts @@ -0,0 +1,50 @@ +import * as RLP from "@ethersproject/rlp"; +import type { ENRKey, ENRValue } from "@waku/interfaces"; +import { hexToBytes, utf8ToBytes } from "@waku/utils"; +import { toString } from "uint8arrays/to-string"; + +import { ERR_NO_SIGNATURE, MAX_RECORD_SIZE } from "./constants.js"; +import { ENR } from "./enr.js"; + +export class EnrEncoder { + static async toValues( + enr: ENR, + privateKey?: Uint8Array + ): Promise<(ENRKey | ENRValue | number[])[]> { + // sort keys and flatten into [k, v, k, v, ...] + const content: Array = Array.from(enr.keys()) + .sort((a, b) => a.localeCompare(b)) + .map((k) => [k, enr.get(k)] as [ENRKey, ENRValue]) + .map(([k, v]) => [utf8ToBytes(k), v]) + .flat(); + content.unshift(new Uint8Array([Number(enr.seq)])); + if (privateKey) { + content.unshift( + await enr.sign(hexToBytes(RLP.encode(content)), privateKey) + ); + } else { + if (!enr.signature) { + throw new Error(ERR_NO_SIGNATURE); + } + content.unshift(enr.signature); + } + return content; + } + + static async toBytes(enr: ENR, privateKey?: Uint8Array): Promise { + const encoded = hexToBytes( + RLP.encode(await EnrEncoder.toValues(enr, privateKey)) + ); + if (encoded.length >= MAX_RECORD_SIZE) { + throw new Error("ENR must be less than 300 bytes"); + } + return encoded; + } + + static async toString(enr: ENR, privateKey?: Uint8Array): Promise { + return ( + ENR.RECORD_PREFIX + + toString(await EnrEncoder.toBytes(enr, privateKey), "base64url") + ); + } +} diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index 8b1277aaf3..6ad814e67c 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -9,6 +9,7 @@ import { equals } from "uint8arrays/equals"; import { ERR_INVALID_ID } from "./constants.js"; import { EnrCreator } from "./creator.js"; import { EnrDecoder } from "./decoder.js"; +import { EnrEncoder } from "./encoder.js"; import { ENR } from "./enr.js"; import { getPrivateKeyFromPeerId } from "./peer_id.js"; @@ -34,7 +35,7 @@ describe("ENR", function () { lightPush: false, }; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const enr2 = await EnrDecoder.fromString(txt); if (!enr.signature) throw "enr.signature is undefined"; @@ -113,7 +114,7 @@ describe("ENR", function () { enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000")); enr.set("id", new Uint8Array([0])); - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); await EnrDecoder.fromString(txt); assert.fail("Expect error here"); @@ -199,7 +200,7 @@ describe("ENR", function () { record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey)); record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303")); record.seq = seq; - await record.encodeTxt(privateKey); + await EnrEncoder.toString(record, privateKey); }); it("should properly compute the node id", () => { @@ -209,7 +210,7 @@ describe("ENR", function () { }); it("should encode/decode to RLP encoding", async function () { - const encoded = await record.encode(privateKey); + const encoded = await EnrEncoder.toBytes(record, privateKey); const decoded = await EnrDecoder.fromRLP(encoded); record.forEach((value, key) => { @@ -403,7 +404,7 @@ describe("ENR", function () { it("should set field with all protocols disabled", async () => { enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -419,7 +420,7 @@ describe("ENR", function () { waku2Protocols.lightPush = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(true); @@ -432,7 +433,7 @@ describe("ENR", function () { waku2Protocols.relay = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(true); @@ -445,7 +446,7 @@ describe("ENR", function () { waku2Protocols.store = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -458,7 +459,7 @@ describe("ENR", function () { waku2Protocols.filter = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); @@ -471,7 +472,7 @@ describe("ENR", function () { waku2Protocols.lightPush = true; enr.waku2 = waku2Protocols; - const txt = await enr.encodeTxt(privateKey); + const txt = await EnrEncoder.toString(enr, privateKey); const decoded = (await EnrDecoder.fromString(txt)).waku2!; expect(decoded.relay).to.equal(false); diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 5066fe6c4a..7e5e6160bc 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -1,4 +1,3 @@ -import * as RLP from "@ethersproject/rlp"; import type { PeerId } from "@libp2p/interface-peer-id"; import type { Multiaddr } from "@multiformats/multiaddr"; import { @@ -13,15 +12,10 @@ import type { SequenceNumber, Waku2, } from "@waku/interfaces"; -import { bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils"; +import { bytesToUtf8 } from "@waku/utils"; import debug from "debug"; -import { toString } from "uint8arrays/to-string"; -import { - ERR_INVALID_ID, - ERR_NO_SIGNATURE, - MAX_RECORD_SIZE, -} from "./constants.js"; +import { ERR_INVALID_ID } from "./constants.js"; import { keccak256, verifySignature } from "./crypto.js"; import { multiaddrFromFields } from "./multiaddr_from_fields.js"; import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; @@ -380,43 +374,4 @@ export class ENR extends Map implements IEnr { } return this.signature; } - - async encodeToValues( - privateKey?: Uint8Array - ): Promise<(ENRKey | ENRValue | number[])[]> { - // sort keys and flatten into [k, v, k, v, ...] - const content: Array = Array.from(this.keys()) - .sort((a, b) => a.localeCompare(b)) - .map((k) => [k, this.get(k)] as [ENRKey, ENRValue]) - .map(([k, v]) => [utf8ToBytes(k), v]) - .flat(); - content.unshift(new Uint8Array([Number(this.seq)])); - if (privateKey) { - content.unshift( - await this.sign(hexToBytes(RLP.encode(content)), privateKey) - ); - } else { - if (!this.signature) { - throw new Error(ERR_NO_SIGNATURE); - } - content.unshift(this.signature); - } - return content; - } - - async encode(privateKey?: Uint8Array): Promise { - const encoded = hexToBytes( - RLP.encode(await this.encodeToValues(privateKey)) - ); - if (encoded.length >= MAX_RECORD_SIZE) { - throw new Error("ENR must be less than 300 bytes"); - } - return encoded; - } - - async encodeTxt(privateKey?: Uint8Array): Promise { - return ( - ENR.RECORD_PREFIX + toString(await this.encode(privateKey), "base64url") - ); - } } diff --git a/packages/interfaces/src/enr.ts b/packages/interfaces/src/enr.ts index 33dcbe28e2..c14cfe0a83 100644 --- a/packages/interfaces/src/enr.ts +++ b/packages/interfaces/src/enr.ts @@ -33,6 +33,5 @@ export interface IEnr extends Map { multiaddrs?: Multiaddr[]; waku2?: Waku2; - encode(privateKey?: Uint8Array): Promise; getFullMultiaddrs(): Multiaddr[]; } From af5c5733a545af0bc3d8feae9aadfb5f4ad5ff1b Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 14:17:26 +1100 Subject: [PATCH 09/14] chore: split functions --- packages/enr/src/decoder.ts | 99 +++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/packages/enr/src/decoder.ts b/packages/enr/src/decoder.ts index c2a2370f2d..12f1dfc241 100644 --- a/packages/enr/src/decoder.ts +++ b/packages/enr/src/decoder.ts @@ -18,42 +18,67 @@ export class EnrDecoder { static fromRLP(encoded: Uint8Array): Promise { const decoded = RLP.decode(encoded).map(hexToBytes); - return EnrDecoder.fromValues(decoded); - } - - private static async fromValues(decoded: Uint8Array[]): Promise { - if (!Array.isArray(decoded)) { - throw new Error("Decoded ENR must be an array"); - } - if (decoded.length % 2 !== 0) { - throw new Error("Decoded ENR must have an even number of elements"); - } - const [signature, seq, ...kvs] = decoded; - if (!signature || Array.isArray(signature)) { - throw new Error("Decoded ENR invalid signature: must be a byte array"); - } - if (!seq || Array.isArray(seq)) { - throw new Error( - "Decoded ENR invalid sequence number: must be a byte array" - ); - } - const obj: Record = {}; - for (let i = 0; i < kvs.length; i += 2) { - try { - obj[bytesToUtf8(kvs[i])] = kvs[i + 1]; - } catch (e) { - log("Failed to decode ENR key to UTF-8, skipping it", kvs[i], e); - } - } - // If seq is an empty array, translate as value 0 - const hexSeq = "0x" + (seq.length ? bytesToHex(seq) : "00"); - - const enr = await ENR.create(obj, BigInt(hexSeq), signature); - - const rlpEncodedBytes = hexToBytes(RLP.encode([seq, ...kvs])); - if (!enr.verify(rlpEncodedBytes, signature)) { - throw new Error("Unable to verify ENR signature"); - } - return enr; + return fromValues(decoded); + } +} + +async function fromValues(values: Uint8Array[]): Promise { + const { signature, seq, kvs } = checkValues(values); + + const obj: Record = {}; + for (let i = 0; i < kvs.length; i += 2) { + try { + obj[bytesToUtf8(kvs[i])] = kvs[i + 1]; + } catch (e) { + log("Failed to decode ENR key to UTF-8, skipping it", kvs[i], e); + } + } + const _seq = decodeSeq(seq); + + const enr = await ENR.create(obj, _seq, signature); + checkSignature(seq, kvs, enr, signature); + return enr; +} + +function decodeSeq(seq: Uint8Array): bigint { + // If seq is an empty array, translate as value 0 + if (!seq.length) return BigInt(0); + + return BigInt("0x" + bytesToHex(seq)); +} + +function checkValues(values: Uint8Array[]): { + signature: Uint8Array; + seq: Uint8Array; + kvs: Uint8Array[]; +} { + if (!Array.isArray(values)) { + throw new Error("Decoded ENR must be an array"); + } + if (values.length % 2 !== 0) { + throw new Error("Decoded ENR must have an even number of elements"); + } + const [signature, seq, ...kvs] = values; + if (!signature || Array.isArray(signature)) { + throw new Error("Decoded ENR invalid signature: must be a byte array"); + } + if (!seq || Array.isArray(seq)) { + throw new Error( + "Decoded ENR invalid sequence number: must be a byte array" + ); + } + + return { signature, seq, kvs }; +} + +function checkSignature( + seq: Uint8Array, + kvs: Uint8Array[], + enr: ENR, + signature: Uint8Array +): void { + const rlpEncodedBytes = hexToBytes(RLP.encode([seq, ...kvs])); + if (!enr.verify(rlpEncodedBytes, signature)) { + throw new Error("Unable to verify ENR signature"); } } From 84f114bfa439d9e4a287edbee294fe420ee4a454 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 3 Mar 2023 15:20:32 +1100 Subject: [PATCH 10/14] chore: extract and simplify `getLocationMultiaddr` Using switch/case when appropriate. --- packages/enr/src/enr.ts | 46 ++---------------------------- packages/enr/src/get_multiaddr.ts | 47 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 43 deletions(-) create mode 100644 packages/enr/src/get_multiaddr.ts diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 7e5e6160bc..4ae36289f1 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -17,7 +17,7 @@ import debug from "debug"; import { ERR_INVALID_ID } from "./constants.js"; import { keccak256, verifySignature } from "./crypto.js"; -import { multiaddrFromFields } from "./multiaddr_from_fields.js"; +import { locationMultiaddrFromEnrFields } from "./get_multiaddr.js"; import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; import { createPeerIdFromPublicKey } from "./peer_id.js"; import * as v4 from "./v4.js"; @@ -231,49 +231,9 @@ export class ENR extends Map implements IEnr { } } - getLocationMultiaddr( + getLocationMultiaddr: ( protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6" - ): Multiaddr | undefined { - if (protocol === "udp") { - return ( - this.getLocationMultiaddr("udp4") || this.getLocationMultiaddr("udp6") - ); - } - if (protocol === "tcp") { - return ( - this.getLocationMultiaddr("tcp4") || this.getLocationMultiaddr("tcp6") - ); - } - const isIpv6 = protocol.endsWith("6"); - const ipVal = this.get(isIpv6 ? "ip6" : "ip"); - if (!ipVal) { - return; - } - - const isUdp = protocol.startsWith("udp"); - const isTcp = protocol.startsWith("tcp"); - let protoName, protoVal; - if (isUdp) { - protoName = "udp"; - protoVal = isIpv6 ? this.get("udp6") : this.get("udp"); - } else if (isTcp) { - protoName = "tcp"; - protoVal = isIpv6 ? this.get("tcp6") : this.get("tcp"); - } else { - return; - } - - if (!protoVal) { - return; - } - - return multiaddrFromFields( - isIpv6 ? "ip6" : "ip4", - protoName, - ipVal, - protoVal - ); - } + ) => Multiaddr | undefined = locationMultiaddrFromEnrFields.bind({}, this); setLocationMultiaddr(multiaddr: Multiaddr): void { const protoNames = multiaddr.protoNames(); diff --git a/packages/enr/src/get_multiaddr.ts b/packages/enr/src/get_multiaddr.ts new file mode 100644 index 0000000000..1f28fac2ac --- /dev/null +++ b/packages/enr/src/get_multiaddr.ts @@ -0,0 +1,47 @@ +import { Multiaddr } from "@multiformats/multiaddr"; +import type { IEnr } from "@waku/interfaces"; + +import { multiaddrFromFields } from "./multiaddr_from_fields.js"; + +export function locationMultiaddrFromEnrFields( + enr: IEnr, + protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6" +): Multiaddr | undefined { + switch (protocol) { + case "udp": + return ( + locationMultiaddrFromEnrFields(enr, "udp4") || + locationMultiaddrFromEnrFields(enr, "udp6") + ); + case "tcp": + return ( + locationMultiaddrFromEnrFields(enr, "tcp4") || + locationMultiaddrFromEnrFields(enr, "tcp6") + ); + } + const isIpv6 = protocol.endsWith("6"); + const ipVal = enr.get(isIpv6 ? "ip6" : "ip"); + if (!ipVal) return; + + const protoName = protocol.slice(0, 3); + let protoVal; + switch (protoName) { + case "udp": + protoVal = isIpv6 ? enr.get("udp6") : enr.get("udp"); + break; + case "tcp": + protoVal = isIpv6 ? enr.get("tcp6") : enr.get("tcp"); + break; + default: + return; + } + + if (!protoVal) return; + + return multiaddrFromFields( + isIpv6 ? "ip6" : "ip4", + protoName, + ipVal, + protoVal + ); +} From 6dbcde041ab8fa8c2df75cc25319a0eccf6b0454 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Tue, 7 Mar 2023 14:36:21 +1100 Subject: [PATCH 11/14] chore!: directly convert from ENR to `PeerInfo`, remove unneeded utility --- package-lock.json | 10 +-- packages/dns-discovery/src/index.ts | 42 ++++----- packages/enr/package.json | 1 + packages/enr/src/enr.spec.ts | 90 ++++++++++++------- packages/enr/src/enr.ts | 42 ++++++++- packages/interfaces/package.json | 1 + packages/interfaces/src/enr.ts | 5 ++ .../src/waku_peer_exchange_discovery.ts | 23 ++--- packages/utils/package.json | 3 - packages/utils/src/libp2p/index.ts | 17 ---- 10 files changed, 141 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 042dd99ee1..043556cef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28228,6 +28228,7 @@ }, "devDependencies": { "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/peer-id-factory": "^2.0.1", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", @@ -28274,6 +28275,7 @@ "@libp2p/interface-connection-manager": "^1.3.7", "@libp2p/interface-libp2p": "^1.1.1", "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", "@libp2p/interface-registrar": "^2.0.8", "@multiformats/multiaddr": "^11.4.0", @@ -28520,16 +28522,13 @@ "version": "0.0.1", "license": "MIT OR Apache-2.0", "dependencies": { - "@libp2p/peer-id": "^2.0.2", "debug": "^4.3.4", "uint8arrays": "^4.0.3" }, "devDependencies": { "@libp2p/interface-connection": "^3.0.8", "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", - "@multiformats/multiaddr": "^11.4.0", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", @@ -32902,6 +32901,7 @@ "@ethersproject/rlp": "^5.7.0", "@libp2p/crypto": "^1.0.12", "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/peer-id": "^2.0.2", "@libp2p/peer-id-factory": "^2.0.1", "@multiformats/multiaddr": "^11.4.0", @@ -32949,6 +32949,7 @@ "@libp2p/interface-connection-manager": "^1.3.7", "@libp2p/interface-libp2p": "^1.1.1", "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", "@libp2p/interface-registrar": "^2.0.8", "@multiformats/multiaddr": "^11.4.0", @@ -33151,10 +33152,7 @@ "requires": { "@libp2p/interface-connection": "^3.0.8", "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", - "@libp2p/peer-id": "^2.0.2", - "@multiformats/multiaddr": "^11.4.0", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", diff --git a/packages/dns-discovery/src/index.ts b/packages/dns-discovery/src/index.ts index d768ff2647..0151714c68 100644 --- a/packages/dns-discovery/src/index.ts +++ b/packages/dns-discovery/src/index.ts @@ -7,7 +7,6 @@ import type { PeerInfo } from "@libp2p/interface-peer-info"; import type { PeerStore } from "@libp2p/interface-peer-store"; import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events"; import type { IEnr } from "@waku/interfaces"; -import { multiaddrsToPeerInfo } from "@waku/utils"; import debug from "debug"; import { DnsNodeDiscovery, NodeCapabilityCount } from "./dns.js"; @@ -94,27 +93,28 @@ export class PeerDiscoveryDns this._started = true; for await (const peer of this.nextPeer()) { if (!this._started) return; - const peerInfos = multiaddrsToPeerInfo(peer.getFullMultiaddrs()); - peerInfos.forEach(async (peerInfo) => { - if ( - (await this._components.peerStore.getTags(peerInfo.id)).find( - ({ name }) => name === DEFAULT_BOOTSTRAP_TAG_NAME - ) - ) - return; - await this._components.peerStore.tagPeer( - peerInfo.id, - DEFAULT_BOOTSTRAP_TAG_NAME, - { - value: this._options.tagValue ?? DEFAULT_BOOTSTRAP_TAG_VALUE, - ttl: this._options.tagTTL ?? DEFAULT_BOOTSTRAP_TAG_TTL, - } - ); - this.dispatchEvent( - new CustomEvent("peer", { detail: peerInfo }) - ); - }); + const peerInfo = peer.peerInfo; + if (!peerInfo) continue; + + if ( + (await this._components.peerStore.getTags(peerInfo.id)).find( + ({ name }) => name === DEFAULT_BOOTSTRAP_TAG_NAME + ) + ) + continue; + + await this._components.peerStore.tagPeer( + peerInfo.id, + DEFAULT_BOOTSTRAP_TAG_NAME, + { + value: this._options.tagValue ?? DEFAULT_BOOTSTRAP_TAG_VALUE, + ttl: this._options.tagTTL ?? DEFAULT_BOOTSTRAP_TAG_TTL, + } + ); + this.dispatchEvent( + new CustomEvent("peer", { detail: peerInfo }) + ); } } diff --git a/packages/enr/package.json b/packages/enr/package.json index fcd81666e0..c8306c4c9e 100644 --- a/packages/enr/package.json +++ b/packages/enr/package.json @@ -64,6 +64,7 @@ }, "devDependencies": { "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/peer-id-factory": "^2.0.1", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", diff --git a/packages/enr/src/enr.spec.ts b/packages/enr/src/enr.spec.ts index 6ad814e67c..f498089f4d 100644 --- a/packages/enr/src/enr.spec.ts +++ b/packages/enr/src/enr.spec.ts @@ -1,3 +1,4 @@ +import type { PeerId } from "@libp2p/interface-peer-id"; import { createSecp256k1PeerId } from "@libp2p/peer-id-factory"; import { multiaddr } from "@multiformats/multiaddr"; import * as secp from "@noble/secp256k1"; @@ -10,7 +11,11 @@ import { ERR_INVALID_ID } from "./constants.js"; import { EnrCreator } from "./creator.js"; import { EnrDecoder } from "./decoder.js"; import { EnrEncoder } from "./encoder.js"; -import { ENR } from "./enr.js"; +import { + ENR, + TransportProtocol, + TransportProtocolPerIpVersion, +} from "./enr.js"; import { getPrivateKeyFromPeerId } from "./peer_id.js"; describe("ENR", function () { @@ -42,7 +47,7 @@ describe("ENR", function () { if (!enr2.signature) throw "enr.signature is undefined"; expect(bytesToHex(enr2.signature)).to.be.equal(bytesToHex(enr.signature)); - const ma = enr2.getLocationMultiaddr("udp")!; + const ma = enr2.getLocationMultiaddr(TransportProtocol.UDP)!; expect(ma.toString()).to.be.equal("/ip4/18.223.219.100/udp/9000"); expect(enr2.multiaddrs).to.not.be.undefined; expect(enr2.multiaddrs!.length).to.be.equal(3); @@ -256,16 +261,16 @@ describe("ENR", function () { record.set("ip", tuples0[0][1]); record.set("udp", tuples0[1][1]); // and get the multiaddr - expect(record.getLocationMultiaddr("udp")!.toString()).to.equal( - multi0.toString() - ); + expect( + record.getLocationMultiaddr(TransportProtocol.UDP)!.toString() + ).to.equal(multi0.toString()); // set the multiaddr const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300"); record.setLocationMultiaddr(multi1); // and get the multiaddr - expect(record.getLocationMultiaddr("udp")!.toString()).to.equal( - multi1.toString() - ); + expect( + record.getLocationMultiaddr(TransportProtocol.UDP)!.toString() + ).to.equal(multi1.toString()); // and get the underlying records const tuples1 = multi1.tuples(); expect(record.get("ip")).to.deep.equal(tuples1[0][1]); @@ -284,16 +289,16 @@ describe("ENR", function () { record.set("ip", tuples0[0][1]); record.set("tcp", tuples0[1][1]); // and get the multiaddr - expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal( - multi0.toString() - ); + expect( + record.getLocationMultiaddr(TransportProtocol.TCP)!.toString() + ).to.equal(multi0.toString()); // set the multiaddr const multi1 = multiaddr("/ip4/0.0.0.0/tcp/30300"); record.setLocationMultiaddr(multi1); // and get the multiaddr - expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal( - multi1.toString() - ); + expect( + record.getLocationMultiaddr(TransportProtocol.TCP)!.toString() + ).to.equal(multi1.toString()); // and get the underlying records const tuples1 = multi1.tuples(); expect(record.get("ip")).to.deep.equal(tuples1[0][1]); @@ -306,7 +311,7 @@ describe("ENR", function () { const ip6 = "::1"; const tcp = 8080; const udp = 8080; - let peerId; + let peerId: PeerId; let enr: ENR; before(async function () { @@ -321,43 +326,43 @@ describe("ENR", function () { }); it("should properly create location multiaddrs - udp4", () => { - expect(enr.getLocationMultiaddr("udp4")).to.deep.equal( - multiaddr(`/ip4/${ip4}/udp/${udp}`) - ); + expect( + enr.getLocationMultiaddr(TransportProtocolPerIpVersion.UDP4) + ).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`)); }); it("should properly create location multiaddrs - tcp4", () => { - expect(enr.getLocationMultiaddr("tcp4")).to.deep.equal( - multiaddr(`/ip4/${ip4}/tcp/${tcp}`) - ); + expect( + enr.getLocationMultiaddr(TransportProtocolPerIpVersion.TCP4) + ).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`)); }); it("should properly create location multiaddrs - udp6", () => { - expect(enr.getLocationMultiaddr("udp6")).to.deep.equal( - multiaddr(`/ip6/${ip6}/udp/${udp}`) - ); + expect( + enr.getLocationMultiaddr(TransportProtocolPerIpVersion.UDP6) + ).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${udp}`)); }); it("should properly create location multiaddrs - tcp6", () => { - expect(enr.getLocationMultiaddr("tcp6")).to.deep.equal( - multiaddr(`/ip6/${ip6}/tcp/${tcp}`) - ); + expect( + enr.getLocationMultiaddr(TransportProtocolPerIpVersion.TCP6) + ).to.deep.equal(multiaddr(`/ip6/${ip6}/tcp/${tcp}`)); }); it("should properly create location multiaddrs - udp", () => { // default to ip4 - expect(enr.getLocationMultiaddr("udp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal( multiaddr(`/ip4/${ip4}/udp/${udp}`) ); // if ip6 is set, use it enr.ip = undefined; - expect(enr.getLocationMultiaddr("udp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal( multiaddr(`/ip6/${ip6}/udp/${udp}`) ); // if ip6 does not exist, use ip4 enr.ip6 = undefined; enr.ip = ip4; - expect(enr.getLocationMultiaddr("udp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal( multiaddr(`/ip4/${ip4}/udp/${udp}`) ); enr.ip6 = ip6; @@ -365,22 +370,41 @@ describe("ENR", function () { it("should properly create location multiaddrs - tcp", () => { // default to ip4 - expect(enr.getLocationMultiaddr("tcp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal( multiaddr(`/ip4/${ip4}/tcp/${tcp}`) ); // if ip6 is set, use it enr.ip = undefined; - expect(enr.getLocationMultiaddr("tcp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal( multiaddr(`/ip6/${ip6}/tcp/${tcp}`) ); // if ip6 does not exist, use ip4 enr.ip6 = undefined; enr.ip = ip4; - expect(enr.getLocationMultiaddr("tcp")).to.deep.equal( + expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal( multiaddr(`/ip4/${ip4}/tcp/${tcp}`) ); enr.ip6 = ip6; }); + + it("should properly create peer info with all multiaddrs", () => { + const peerInfo = enr.peerInfo!; + console.log(peerInfo); + expect(peerInfo.id.toString()).to.equal(peerId.toString()); + expect(peerInfo.multiaddrs.length).to.equal(4); + expect(peerInfo.multiaddrs.map((ma) => ma.toString())).to.contain( + multiaddr(`/ip4/${ip4}/tcp/${tcp}`).toString() + ); + expect(peerInfo.multiaddrs.map((ma) => ma.toString())).to.contain( + multiaddr(`/ip6/${ip6}/tcp/${tcp}`).toString() + ); + expect(peerInfo.multiaddrs.map((ma) => ma.toString())).to.contain( + multiaddr(`/ip4/${ip4}/udp/${udp}`).toString() + ); + expect(peerInfo.multiaddrs.map((ma) => ma.toString())).to.contain( + multiaddr(`/ip6/${ip6}/udp/${udp}`).toString() + ); + }); }); describe("waku2 key round trip", async () => { diff --git a/packages/enr/src/enr.ts b/packages/enr/src/enr.ts index 4ae36289f1..d777e52b1e 100644 --- a/packages/enr/src/enr.ts +++ b/packages/enr/src/enr.ts @@ -1,4 +1,5 @@ import type { PeerId } from "@libp2p/interface-peer-id"; +import type { PeerInfo } from "@libp2p/interface-peer-info"; import type { Multiaddr } from "@multiformats/multiaddr"; import { convertToBytes, @@ -25,6 +26,17 @@ import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js"; const log = debug("waku:enr"); +export enum TransportProtocol { + TCP = "tcp", + UDP = "udp", +} +export enum TransportProtocolPerIpVersion { + TCP4 = "tcp4", + UDP4 = "udp4", + TCP6 = "tcp6", + UDP6 = "udp6", +} + export class ENR extends Map implements IEnr { public static readonly RECORD_PREFIX = "enr:"; public seq: SequenceNumber; @@ -232,7 +244,7 @@ export class ENR extends Map implements IEnr { } getLocationMultiaddr: ( - protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6" + protocol: TransportProtocol | TransportProtocolPerIpVersion ) => Multiaddr | undefined = locationMultiaddrFromEnrFields.bind({}, this); setLocationMultiaddr(multiaddr: Multiaddr): void { @@ -259,6 +271,32 @@ export class ENR extends Map implements IEnr { } } + getAllLocationMultiaddrs(): Multiaddr[] { + const multiaddrs = []; + + for (const protocol of Object.values(TransportProtocolPerIpVersion)) { + const ma = this.getLocationMultiaddr( + protocol as TransportProtocolPerIpVersion + ); + if (ma) multiaddrs.push(ma); + } + + const _multiaddrs = this.multiaddrs ?? []; + multiaddrs.concat(_multiaddrs); + + return multiaddrs; + } + + get peerInfo(): PeerInfo | undefined { + const id = this.peerId; + if (!id) return; + return { + id, + multiaddrs: this.getAllLocationMultiaddrs(), + protocols: [], + }; + } + /** * Returns the full multiaddr from the ENR fields matching the provided * `protocol` parameter. @@ -268,7 +306,7 @@ export class ENR extends Map implements IEnr { * @param protocol */ getFullMultiaddr( - protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6" + protocol: TransportProtocol | TransportProtocolPerIpVersion ): Multiaddr | undefined { if (this.peerId) { const locationMultiaddr = this.getLocationMultiaddr(protocol); diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index dc1c4b1221..b8f4e101b0 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -54,6 +54,7 @@ "@libp2p/interface-connection-manager": "^1.3.7", "@libp2p/interface-libp2p": "^1.1.1", "@libp2p/interface-peer-id": "^2.0.1", + "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", "@libp2p/interface-registrar": "^2.0.8", "@multiformats/multiaddr": "^11.4.0", diff --git a/packages/interfaces/src/enr.ts b/packages/interfaces/src/enr.ts index c14cfe0a83..d15f87af37 100644 --- a/packages/interfaces/src/enr.ts +++ b/packages/interfaces/src/enr.ts @@ -1,4 +1,5 @@ import type { PeerId } from "@libp2p/interface-peer-id"; +import type { PeerInfo } from "@libp2p/interface-peer-info"; import type { Multiaddr } from "@multiformats/multiaddr"; export type ENRKey = string; @@ -32,6 +33,10 @@ export interface IEnr extends Map { udp6?: number; multiaddrs?: Multiaddr[]; waku2?: Waku2; + peerInfo: PeerInfo | undefined; + /** + * @deprecated: use { @link IEnr.peerInfo } instead. + */ getFullMultiaddrs(): Multiaddr[]; } diff --git a/packages/peer-exchange/src/waku_peer_exchange_discovery.ts b/packages/peer-exchange/src/waku_peer_exchange_discovery.ts index 72b1a3a241..65707d86ef 100644 --- a/packages/peer-exchange/src/waku_peer_exchange_discovery.ts +++ b/packages/peer-exchange/src/waku_peer_exchange_discovery.ts @@ -165,20 +165,25 @@ export class PeerExchangeDiscovery continue; } - const { peerId } = ENR; - const multiaddrs = ENR.getFullMultiaddrs(); - - if (!peerId || !multiaddrs || multiaddrs.length === 0) continue; + const peerInfo = ENR.peerInfo; if ( - (await this.components.peerStore.getTags(peerId)).find( + !peerInfo || + !peerInfo.id || + !peerInfo.multiaddrs || + !peerInfo.multiaddrs.length + ) + continue; + + if ( + (await this.components.peerStore.getTags(peerInfo.id)).find( ({ name }) => name === DEFAULT_PEER_EXCHANGE_TAG_NAME ) ) continue; await this.components.peerStore.tagPeer( - peerId, + peerInfo.id, DEFAULT_PEER_EXCHANGE_TAG_NAME, { value: @@ -191,11 +196,7 @@ export class PeerExchangeDiscovery this.dispatchEvent( new CustomEvent("peer", { - detail: { - id: peerId, - multiaddrs, - protocols: [], - }, + detail: peerInfo, }) ); } diff --git a/packages/utils/package.json b/packages/utils/package.json index 8515bf3b8e..b50133b30c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -50,16 +50,13 @@ "node": ">=16" }, "dependencies": { - "@libp2p/peer-id": "^2.0.2", "debug": "^4.3.4", "uint8arrays": "^4.0.3" }, "devDependencies": { "@libp2p/interface-connection": "^3.0.8", "@libp2p/interface-peer-id": "^2.0.1", - "@libp2p/interface-peer-info": "^1.0.8", "@libp2p/interface-peer-store": "^1.2.8", - "@multiformats/multiaddr": "^11.4.0", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", diff --git a/packages/utils/src/libp2p/index.ts b/packages/utils/src/libp2p/index.ts index 13536c6ba5..a0ffefc974 100644 --- a/packages/utils/src/libp2p/index.ts +++ b/packages/utils/src/libp2p/index.ts @@ -1,9 +1,6 @@ import type { Connection } from "@libp2p/interface-connection"; import type { PeerId } from "@libp2p/interface-peer-id"; -import type { PeerInfo } from "@libp2p/interface-peer-info"; import type { Peer, PeerStore } from "@libp2p/interface-peer-store"; -import { peerIdFromString } from "@libp2p/peer-id"; -import type { Multiaddr } from "@multiformats/multiaddr"; import debug from "debug"; const log = debug("waku:libp2p-utils"); @@ -78,20 +75,6 @@ export async function selectPeerForProtocol( return { peer, protocol }; } -export function multiaddrsToPeerInfo(mas: Multiaddr[]): PeerInfo[] { - return mas - .map((ma) => { - const peerIdStr = ma.getPeerId(); - const protocols: string[] = []; - return { - id: peerIdStr ? peerIdFromString(peerIdStr) : null, - multiaddrs: [ma.decapsulateCode(421)], - protocols, - }; - }) - .filter((peerInfo): peerInfo is PeerInfo => peerInfo.id !== null); -} - export function selectConnection( connections: Connection[] ): Connection | undefined { From 81ad14cad371a0134afb4aac3e3a7c121e1c106e Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Tue, 7 Mar 2023 14:53:07 +1100 Subject: [PATCH 12/14] chore: segregate basic access to fields and more advanced logic Move basic access to ENR fields (with conversion) to `RawEnr` `ENR` inherits from `RawEnr` and add a layer of logic. --- packages/enr/src/enr.ts | 209 +---------------------------------- packages/enr/src/raw_enr.ts | 212 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 207 deletions(-) create mode 100644 packages/enr/src/raw_enr.ts 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])); + } + } +} From 1181eadcf82d46014f7d8d1c289fd415fa1c7114 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Tue, 7 Mar 2023 15:09:27 +1100 Subject: [PATCH 13/14] doc: fix references in comments --- packages/enr/src/raw_enr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/enr/src/raw_enr.ts b/packages/enr/src/raw_enr.ts index 5a15f48649..73c00bca30 100644 --- a/packages/enr/src/raw_enr.ts +++ b/packages/enr/src/raw_enr.ts @@ -155,7 +155,7 @@ export class RawEnr extends Map { * 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. + * (ip, tcp, etc) then the usage of { @link ENR.getLocationMultiaddr } should be preferred. * * The multiaddresses stored in this field are expected to be location multiaddresses, ie, peer id less. */ @@ -175,7 +175,7 @@ export class RawEnr extends Map { * 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. + * (ip, tcp, etc) then the usage of { @link ENR.setLocationMultiaddr } should be preferred. * The multiaddresses stored in this field must be location multiaddresses, * ie, without a peer id. */ From 5a42efcd8d9eb6b000437605411553a273daeec6 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 10 Mar 2023 10:22:54 +1100 Subject: [PATCH 14/14] chore: rename: peer exchange peers are not bootstrap peers --- .../peer-exchange/src/waku_peer_exchange_discovery.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/peer-exchange/src/waku_peer_exchange_discovery.ts b/packages/peer-exchange/src/waku_peer_exchange_discovery.ts index 65707d86ef..c0c46ccf45 100644 --- a/packages/peer-exchange/src/waku_peer_exchange_discovery.ts +++ b/packages/peer-exchange/src/waku_peer_exchange_discovery.ts @@ -49,8 +49,8 @@ export interface Options { } export const DEFAULT_PEER_EXCHANGE_TAG_NAME = "peer-exchange"; -const DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_VALUE = 50; -const DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_TTL = 120000; +const DEFAULT_PEER_EXCHANGE_TAG_VALUE = 50; +const DEFAULT_PEER_EXCHANGE_TAG_TTL = 120000; export class PeerExchangeDiscovery extends EventEmitter @@ -186,11 +186,8 @@ export class PeerExchangeDiscovery peerInfo.id, DEFAULT_PEER_EXCHANGE_TAG_NAME, { - value: - this.options.tagValue ?? - DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_VALUE, - ttl: - this.options.tagTTL ?? DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_TTL, + value: this.options.tagValue ?? DEFAULT_PEER_EXCHANGE_TAG_VALUE, + ttl: this.options.tagTTL ?? DEFAULT_PEER_EXCHANGE_TAG_TTL, } );