mirror of https://github.com/status-im/js-waku.git
chore!: extract decoder code
Separation of concerns by moving decoding logic in new class.
This commit is contained in:
parent
8fd1455122
commit
130c49b636
|
@ -1,4 +1,4 @@
|
||||||
import { ENR } from "@waku/enr";
|
import { ENR, EnrDecoder } from "@waku/enr";
|
||||||
import type { IEnr } from "@waku/interfaces";
|
import type { IEnr } from "@waku/interfaces";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ export class DnsNodeDiscovery {
|
||||||
next = selectRandomPath(branches, context);
|
next = selectRandomPath(branches, context);
|
||||||
return await this._search(next, context);
|
return await this._search(next, context);
|
||||||
case ENRTree.RECORD_PREFIX:
|
case ENRTree.RECORD_PREFIX:
|
||||||
return ENR.decodeTxt(entry);
|
return EnrDecoder.fromString(entry);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<ENR> {
|
||||||
|
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<ENR> {
|
||||||
|
const decoded = RLP.decode(encoded).map(hexToBytes);
|
||||||
|
return EnrDecoder.fromValues(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async fromValues(decoded: Uint8Array[]): Promise<ENR> {
|
||||||
|
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<ENRKey, ENRValue> = {};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import { equals } from "uint8arrays/equals";
|
||||||
|
|
||||||
import { ERR_INVALID_ID } from "./constants.js";
|
import { ERR_INVALID_ID } from "./constants.js";
|
||||||
import { EnrCreator } from "./creator.js";
|
import { EnrCreator } from "./creator.js";
|
||||||
|
import { EnrDecoder } from "./decoder.js";
|
||||||
import { ENR } from "./enr.js";
|
import { ENR } from "./enr.js";
|
||||||
import { getPrivateKeyFromPeerId } from "./peer_id.js";
|
import { getPrivateKeyFromPeerId } from "./peer_id.js";
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ describe("ENR", function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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 (!enr.signature) throw "enr.signature is undefined";
|
||||||
if (!enr2.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 () => {
|
it("should decode valid enr successfully", async () => {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg";
|
"enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg";
|
||||||
const enr = await ENR.decodeTxt(txt);
|
const enr = await EnrDecoder.fromString(txt);
|
||||||
const eth2 = enr.get("eth2");
|
const eth2 = enr.get("eth2");
|
||||||
if (!eth2) throw "eth2 is undefined";
|
if (!eth2) throw "eth2 is undefined";
|
||||||
expect(bytesToHex(eth2)).to.be.equal("f6775d0700000113ffffffffffff1f00");
|
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 () => {
|
it("should decode valid ENR with multiaddrs successfully [shared test vector]", async () => {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA";
|
"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).to.not.be.undefined;
|
||||||
expect(enr.multiaddrs!.length).to.be.equal(3);
|
expect(enr.multiaddrs!.length).to.be.equal(3);
|
||||||
|
@ -93,7 +94,7 @@ describe("ENR", function () {
|
||||||
it("should decode valid enr with tcp successfully", async () => {
|
it("should decode valid enr with tcp successfully", async () => {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-IS4QAmC_o1PMi5DbR4Bh4oHVyQunZblg4bTaottPtBodAhJZvxVlWW-4rXITPNg4mwJ8cW__D9FBDc9N4mdhyMqB-EBgmlkgnY0gmlwhIbRi9KJc2VjcDI1NmsxoQOevTdO6jvv3fRruxguKR-3Ge4bcFsLeAIWEDjrfaigNoN0Y3CCdl8";
|
"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.not.be.undefined;
|
||||||
expect(enr.tcp).to.be.equal(30303);
|
expect(enr.tcp).to.be.equal(30303);
|
||||||
expect(enr.ip).to.not.be.undefined;
|
expect(enr.ip).to.not.be.undefined;
|
||||||
|
@ -114,7 +115,7 @@ describe("ENR", function () {
|
||||||
enr.set("id", new Uint8Array([0]));
|
enr.set("id", new Uint8Array([0]));
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
const txt = await enr.encodeTxt(privateKey);
|
||||||
|
|
||||||
await ENR.decodeTxt(txt);
|
await EnrDecoder.fromString(txt);
|
||||||
assert.fail("Expect error here");
|
assert.fail("Expect error here");
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const e = err as Error;
|
const e = err as Error;
|
||||||
|
@ -126,7 +127,7 @@ describe("ENR", function () {
|
||||||
try {
|
try {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-IS4QJ2d11eu6dC7E7LoXeLMgMP3kom1u3SE8esFSWvaHoo0dP1jg8O3-nx9ht-EO3CmG7L6OkHcMmoIh00IYWB92QABgmlkgnY0gmlwhH8AAAGJc2d11eu6dCsxoQIB_c-jQMOXsbjWkbN-kj99H57gfId5pfb4wa1qxwV4CIN1ZHCCIyk";
|
"enr:-IS4QJ2d11eu6dC7E7LoXeLMgMP3kom1u3SE8esFSWvaHoo0dP1jg8O3-nx9ht-EO3CmG7L6OkHcMmoIh00IYWB92QABgmlkgnY0gmlwhH8AAAGJc2d11eu6dCsxoQIB_c-jQMOXsbjWkbN-kj99H57gfId5pfb4wa1qxwV4CIN1ZHCCIyk";
|
||||||
ENR.decodeTxt(txt);
|
EnrDecoder.fromString(txt);
|
||||||
assert.fail("Expect error here");
|
assert.fail("Expect error here");
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const e = err as Error;
|
const e = err as Error;
|
||||||
|
@ -180,7 +181,7 @@ describe("ENR", function () {
|
||||||
it("should return false", async () => {
|
it("should return false", async () => {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg";
|
"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
|
// should have id and public key inside ENR
|
||||||
expect(enr.verify(new Uint8Array(32), new Uint8Array(64))).to.be.false;
|
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 () {
|
it("should encode/decode to RLP encoding", async function () {
|
||||||
const encoded = await record.encode(privateKey);
|
const encoded = await record.encode(privateKey);
|
||||||
const decoded = await ENR.decode(encoded);
|
const decoded = await EnrDecoder.fromRLP(encoded);
|
||||||
|
|
||||||
record.forEach((value, key) => {
|
record.forEach((value, key) => {
|
||||||
expect(equals(decoded.get(key)!, value)).to.be.true;
|
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
|
// spec enr https://eips.ethereum.org/EIPS/eip-778
|
||||||
const testTxt =
|
const testTxt =
|
||||||
"enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
|
"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
|
// Note: Signatures are different due to the extra entropy added
|
||||||
// by @noble/secp256k1:
|
// by @noble/secp256k1:
|
||||||
// https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
|
// https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
|
||||||
|
@ -403,7 +404,7 @@ describe("ENR", function () {
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
|
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(false);
|
||||||
expect(decoded.store).to.equal(false);
|
expect(decoded.store).to.equal(false);
|
||||||
|
@ -419,7 +420,7 @@ describe("ENR", function () {
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(true);
|
||||||
expect(decoded.store).to.equal(true);
|
expect(decoded.store).to.equal(true);
|
||||||
|
@ -432,7 +433,7 @@ describe("ENR", function () {
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(true);
|
||||||
expect(decoded.store).to.equal(false);
|
expect(decoded.store).to.equal(false);
|
||||||
|
@ -445,7 +446,7 @@ describe("ENR", function () {
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(false);
|
||||||
expect(decoded.store).to.equal(true);
|
expect(decoded.store).to.equal(true);
|
||||||
|
@ -458,7 +459,7 @@ describe("ENR", function () {
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(false);
|
||||||
expect(decoded.store).to.equal(false);
|
expect(decoded.store).to.equal(false);
|
||||||
|
@ -471,7 +472,7 @@ describe("ENR", function () {
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(privateKey);
|
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.relay).to.equal(false);
|
||||||
expect(decoded.store).to.equal(false);
|
expect(decoded.store).to.equal(false);
|
||||||
|
@ -485,7 +486,7 @@ describe("ENR", function () {
|
||||||
const txt =
|
const txt =
|
||||||
"enr:-Iu4QADPfXNCM6iYyte0pIdbMirIw_AsKR7J1DeJBysXDWz4DZvyjgIwpMt-sXTVUzLJdE9FaStVy2ZKtHUVQAH61-KAgmlkgnY0gmlwhMCosvuJc2VjcDI1NmsxoQI0OCNtPJtBayNgvFvKp-0YyCozcvE1rqm_V1W51nHVv4N0Y3CC6mCFd2FrdTIH";
|
"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.relay).to.equal(true);
|
||||||
expect(decoded.store).to.equal(true);
|
expect(decoded.store).to.equal(true);
|
||||||
|
|
|
@ -13,9 +13,8 @@ import type {
|
||||||
SequenceNumber,
|
SequenceNumber,
|
||||||
Waku2,
|
Waku2,
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { bytesToHex, bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils";
|
import { bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import { fromString } from "uint8arrays/from-string";
|
|
||||||
import { toString } from "uint8arrays/to-string";
|
import { toString } from "uint8arrays/to-string";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -65,57 +64,6 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
|
|
||||||
return enr;
|
return enr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async decodeFromValues(decoded: Uint8Array[]): Promise<ENR> {
|
|
||||||
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<ENRKey, ENRValue> = {};
|
|
||||||
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<ENR> {
|
|
||||||
const decoded = RLP.decode(encoded).map(hexToBytes);
|
|
||||||
return ENR.decodeFromValues(decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
static decodeTxt(encoded: string): Promise<ENR> {
|
|
||||||
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 {
|
set(k: ENRKey, v: ENRValue): this {
|
||||||
this.signature = undefined;
|
this.signature = undefined;
|
||||||
this.seq++;
|
this.seq++;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from "./constants.js";
|
export * from "./constants.js";
|
||||||
export * from "./creator.js";
|
export * from "./creator.js";
|
||||||
|
export * from "./decoder.js";
|
||||||
export * from "./enr.js";
|
export * from "./enr.js";
|
||||||
export * from "./peer_id.js";
|
export * from "./peer_id.js";
|
||||||
export * from "./waku2_codec.js";
|
export * from "./waku2_codec.js";
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
||||||
Registrar,
|
Registrar,
|
||||||
} from "@libp2p/interface-registrar";
|
} from "@libp2p/interface-registrar";
|
||||||
import { BaseProtocol } from "@waku/core/lib/base_protocol";
|
import { BaseProtocol } from "@waku/core/lib/base_protocol";
|
||||||
import { ENR } from "@waku/enr";
|
import { EnrDecoder } from "@waku/enr";
|
||||||
import type {
|
import type {
|
||||||
IPeerExchange,
|
IPeerExchange,
|
||||||
PeerExchangeQueryParams,
|
PeerExchangeQueryParams,
|
||||||
|
@ -95,7 +95,7 @@ export class WakuPeerExchange extends BaseProtocol implements IPeerExchange {
|
||||||
|
|
||||||
const enrs = await Promise.all(
|
const enrs = await Promise.all(
|
||||||
decoded.peerInfos.map(
|
decoded.peerInfos.map(
|
||||||
(peerInfo) => peerInfo.enr && ENR.decode(peerInfo.enr)
|
(peerInfo) => peerInfo.enr && EnrDecoder.fromRLP(peerInfo.enr)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { waitForRemotePeer } from "@waku/core";
|
import { waitForRemotePeer } from "@waku/core";
|
||||||
import { createRelayNode } from "@waku/create";
|
import { createRelayNode } from "@waku/create";
|
||||||
import { ENR } from "@waku/enr";
|
import { EnrDecoder } from "@waku/enr";
|
||||||
import type { RelayNode } from "@waku/interfaces";
|
import type { RelayNode } from "@waku/interfaces";
|
||||||
import { Protocols } from "@waku/interfaces";
|
import { Protocols } from "@waku/interfaces";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
@ -38,7 +38,7 @@ describe("ENR Interop: nwaku", function () {
|
||||||
const nimPeerId = await nwaku.getPeerId();
|
const nimPeerId = await nwaku.getPeerId();
|
||||||
|
|
||||||
expect(nwakuInfo.enrUri).to.not.be.undefined;
|
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.peerId?.toString()).to.eq(nimPeerId.toString());
|
||||||
expect(dec.waku2).to.deep.eq({
|
expect(dec.waku2).to.deep.eq({
|
||||||
relay: true,
|
relay: true,
|
||||||
|
@ -70,7 +70,7 @@ describe("ENR Interop: nwaku", function () {
|
||||||
const nimPeerId = await nwaku.getPeerId();
|
const nimPeerId = await nwaku.getPeerId();
|
||||||
|
|
||||||
expect(nwakuInfo.enrUri).to.not.be.undefined;
|
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.peerId?.toString()).to.eq(nimPeerId.toString());
|
||||||
expect(dec.waku2).to.deep.eq({
|
expect(dec.waku2).to.deep.eq({
|
||||||
relay: true,
|
relay: true,
|
||||||
|
@ -102,7 +102,7 @@ describe("ENR Interop: nwaku", function () {
|
||||||
const nimPeerId = await nwaku.getPeerId();
|
const nimPeerId = await nwaku.getPeerId();
|
||||||
|
|
||||||
expect(nwakuInfo.enrUri).to.not.be.undefined;
|
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.peerId?.toString()).to.eq(nimPeerId.toString());
|
||||||
expect(dec.waku2).to.deep.eq({
|
expect(dec.waku2).to.deep.eq({
|
||||||
relay: true,
|
relay: true,
|
||||||
|
|
Loading…
Reference in New Issue