chore!: extract encoder code

Separation of concerns by moving encoding logic in new class.
This commit is contained in:
fryorcraken.eth 2023-03-03 14:00:54 +11:00
parent 130c49b636
commit 22ffcf571a
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
4 changed files with 63 additions and 58 deletions

View File

@ -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<ENRKey | ENRValue | number[]> = 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<Uint8Array> {
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<string> {
return (
ENR.RECORD_PREFIX +
toString(await EnrEncoder.toBytes(enr, privateKey), "base64url")
);
}
}

View File

@ -9,6 +9,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 { EnrDecoder } from "./decoder.js";
import { EnrEncoder } from "./encoder.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 () {
lightPush: false, lightPush: false,
}; };
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const enr2 = await EnrDecoder.fromString(txt); const enr2 = await EnrDecoder.fromString(txt);
if (!enr.signature) throw "enr.signature is undefined"; 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.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
enr.set("id", new Uint8Array([0])); enr.set("id", new Uint8Array([0]));
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
await EnrDecoder.fromString(txt); await EnrDecoder.fromString(txt);
assert.fail("Expect error here"); assert.fail("Expect error here");
@ -199,7 +200,7 @@ describe("ENR", function () {
record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey)); record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey));
record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303")); record.setLocationMultiaddr(multiaddr("/ip4/127.0.0.1/udp/30303"));
record.seq = seq; record.seq = seq;
await record.encodeTxt(privateKey); await EnrEncoder.toString(record, privateKey);
}); });
it("should properly compute the node id", () => { it("should properly compute the node id", () => {
@ -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 EnrEncoder.toBytes(record, privateKey);
const decoded = await EnrDecoder.fromRLP(encoded); const decoded = await EnrDecoder.fromRLP(encoded);
record.forEach((value, key) => { record.forEach((value, key) => {
@ -403,7 +404,7 @@ describe("ENR", function () {
it("should set field with all protocols disabled", async () => { it("should set field with all protocols disabled", async () => {
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(false); expect(decoded.relay).to.equal(false);
@ -419,7 +420,7 @@ describe("ENR", function () {
waku2Protocols.lightPush = true; waku2Protocols.lightPush = true;
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(true); expect(decoded.relay).to.equal(true);
@ -432,7 +433,7 @@ describe("ENR", function () {
waku2Protocols.relay = true; waku2Protocols.relay = true;
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(true); expect(decoded.relay).to.equal(true);
@ -445,7 +446,7 @@ describe("ENR", function () {
waku2Protocols.store = true; waku2Protocols.store = true;
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(false); expect(decoded.relay).to.equal(false);
@ -458,7 +459,7 @@ describe("ENR", function () {
waku2Protocols.filter = true; waku2Protocols.filter = true;
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(false); expect(decoded.relay).to.equal(false);
@ -471,7 +472,7 @@ describe("ENR", function () {
waku2Protocols.lightPush = true; waku2Protocols.lightPush = true;
enr.waku2 = waku2Protocols; enr.waku2 = waku2Protocols;
const txt = await enr.encodeTxt(privateKey); const txt = await EnrEncoder.toString(enr, privateKey);
const decoded = (await EnrDecoder.fromString(txt)).waku2!; const decoded = (await EnrDecoder.fromString(txt)).waku2!;
expect(decoded.relay).to.equal(false); expect(decoded.relay).to.equal(false);

View File

@ -1,4 +1,3 @@
import * as RLP from "@ethersproject/rlp";
import type { PeerId } from "@libp2p/interface-peer-id"; import type { PeerId } from "@libp2p/interface-peer-id";
import type { Multiaddr } from "@multiformats/multiaddr"; import type { Multiaddr } from "@multiformats/multiaddr";
import { import {
@ -13,15 +12,10 @@ import type {
SequenceNumber, SequenceNumber,
Waku2, Waku2,
} from "@waku/interfaces"; } from "@waku/interfaces";
import { bytesToUtf8, hexToBytes, utf8ToBytes } from "@waku/utils"; import { bytesToUtf8 } from "@waku/utils";
import debug from "debug"; import debug from "debug";
import { toString } from "uint8arrays/to-string";
import { import { ERR_INVALID_ID } from "./constants.js";
ERR_INVALID_ID,
ERR_NO_SIGNATURE,
MAX_RECORD_SIZE,
} from "./constants.js";
import { keccak256, verifySignature } from "./crypto.js"; import { keccak256, verifySignature } from "./crypto.js";
import { multiaddrFromFields } from "./multiaddr_from_fields.js"; import { multiaddrFromFields } from "./multiaddr_from_fields.js";
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js"; import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js";
@ -380,43 +374,4 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
} }
return this.signature; return this.signature;
} }
async encodeToValues(
privateKey?: Uint8Array
): Promise<(ENRKey | ENRValue | number[])[]> {
// sort keys and flatten into [k, v, k, v, ...]
const content: Array<ENRKey | ENRValue | number[]> = 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<Uint8Array> {
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<string> {
return (
ENR.RECORD_PREFIX + toString(await this.encode(privateKey), "base64url")
);
}
} }

View File

@ -33,6 +33,5 @@ export interface IEnr extends Map<ENRKey, ENRValue> {
multiaddrs?: Multiaddr[]; multiaddrs?: Multiaddr[];
waku2?: Waku2; waku2?: Waku2;
encode(privateKey?: Uint8Array): Promise<Uint8Array>;
getFullMultiaddrs(): Multiaddr[]; getFullMultiaddrs(): Multiaddr[];
} }