mirror of https://github.com/waku-org/js-waku.git
Merge pull request #1216 from waku-org/chore/libp2p-crypto-enr
This commit is contained in:
commit
36534af0e6
|
@ -28228,6 +28228,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/peer-id-factory": "^2.0.1",
|
"@libp2p/peer-id-factory": "^2.0.1",
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
|
@ -28274,6 +28275,7 @@
|
||||||
"@libp2p/interface-connection-manager": "^1.3.7",
|
"@libp2p/interface-connection-manager": "^1.3.7",
|
||||||
"@libp2p/interface-libp2p": "^1.1.1",
|
"@libp2p/interface-libp2p": "^1.1.1",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/interface-peer-store": "^1.2.8",
|
"@libp2p/interface-peer-store": "^1.2.8",
|
||||||
"@libp2p/interface-registrar": "^2.0.8",
|
"@libp2p/interface-registrar": "^2.0.8",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
"@multiformats/multiaddr": "^11.4.0",
|
||||||
|
@ -28520,16 +28522,13 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libp2p/peer-id": "^2.0.2",
|
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"uint8arrays": "^4.0.3"
|
"uint8arrays": "^4.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@libp2p/interface-connection": "^3.0.8",
|
"@libp2p/interface-connection": "^3.0.8",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
"@libp2p/interface-peer-info": "^1.0.8",
|
|
||||||
"@libp2p/interface-peer-store": "^1.2.8",
|
"@libp2p/interface-peer-store": "^1.2.8",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
@ -32902,6 +32901,7 @@
|
||||||
"@ethersproject/rlp": "^5.7.0",
|
"@ethersproject/rlp": "^5.7.0",
|
||||||
"@libp2p/crypto": "^1.0.12",
|
"@libp2p/crypto": "^1.0.12",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/peer-id": "^2.0.2",
|
"@libp2p/peer-id": "^2.0.2",
|
||||||
"@libp2p/peer-id-factory": "^2.0.1",
|
"@libp2p/peer-id-factory": "^2.0.1",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
"@multiformats/multiaddr": "^11.4.0",
|
||||||
|
@ -32949,6 +32949,7 @@
|
||||||
"@libp2p/interface-connection-manager": "^1.3.7",
|
"@libp2p/interface-connection-manager": "^1.3.7",
|
||||||
"@libp2p/interface-libp2p": "^1.1.1",
|
"@libp2p/interface-libp2p": "^1.1.1",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/interface-peer-store": "^1.2.8",
|
"@libp2p/interface-peer-store": "^1.2.8",
|
||||||
"@libp2p/interface-registrar": "^2.0.8",
|
"@libp2p/interface-registrar": "^2.0.8",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
"@multiformats/multiaddr": "^11.4.0",
|
||||||
|
@ -33151,10 +33152,7 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"@libp2p/interface-connection": "^3.0.8",
|
"@libp2p/interface-connection": "^3.0.8",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
"@libp2p/interface-peer-info": "^1.0.8",
|
|
||||||
"@libp2p/interface-peer-store": "^1.2.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-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
||||||
import { multiaddr } from "@multiformats/multiaddr";
|
import { multiaddr } from "@multiformats/multiaddr";
|
||||||
import { ENR } from "@waku/enr";
|
import { ENR } from "@waku/enr";
|
||||||
|
import { EnrCreator } from "@waku/enr";
|
||||||
import type { Waku2 } from "@waku/interfaces";
|
import type { Waku2 } from "@waku/interfaces";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ import { fetchNodesUntilCapabilitiesFulfilled } from "./fetch_nodes.js";
|
||||||
|
|
||||||
async function createEnr(waku2: Waku2): Promise<ENR> {
|
async function createEnr(waku2: Waku2): Promise<ENR> {
|
||||||
const peerId = await createSecp256k1PeerId();
|
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.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
|
||||||
enr.multiaddrs = [
|
enr.multiaddrs = [
|
||||||
multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"),
|
multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"),
|
||||||
|
|
|
@ -7,7 +7,6 @@ import type { PeerInfo } from "@libp2p/interface-peer-info";
|
||||||
import type { PeerStore } from "@libp2p/interface-peer-store";
|
import type { PeerStore } from "@libp2p/interface-peer-store";
|
||||||
import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
|
import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
|
||||||
import type { IEnr } from "@waku/interfaces";
|
import type { IEnr } from "@waku/interfaces";
|
||||||
import { multiaddrsToPeerInfo } from "@waku/utils";
|
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
import { DnsNodeDiscovery, NodeCapabilityCount } from "./dns.js";
|
import { DnsNodeDiscovery, NodeCapabilityCount } from "./dns.js";
|
||||||
|
@ -94,14 +93,16 @@ export class PeerDiscoveryDns
|
||||||
this._started = true;
|
this._started = true;
|
||||||
for await (const peer of this.nextPeer()) {
|
for await (const peer of this.nextPeer()) {
|
||||||
if (!this._started) return;
|
if (!this._started) return;
|
||||||
const peerInfos = multiaddrsToPeerInfo(peer.getFullMultiaddrs());
|
|
||||||
peerInfos.forEach(async (peerInfo) => {
|
const peerInfo = peer.peerInfo;
|
||||||
|
if (!peerInfo) continue;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await this._components.peerStore.getTags(peerInfo.id)).find(
|
(await this._components.peerStore.getTags(peerInfo.id)).find(
|
||||||
({ name }) => name === DEFAULT_BOOTSTRAP_TAG_NAME
|
({ name }) => name === DEFAULT_BOOTSTRAP_TAG_NAME
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
await this._components.peerStore.tagPeer(
|
await this._components.peerStore.tagPeer(
|
||||||
peerInfo.id,
|
peerInfo.id,
|
||||||
|
@ -114,7 +115,6 @@ export class PeerDiscoveryDns
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent<PeerInfo>("peer", { detail: peerInfo })
|
new CustomEvent<PeerInfo>("peer", { detail: peerInfo })
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/peer-id-factory": "^2.0.1",
|
"@libp2p/peer-id-factory": "^2.0.1",
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
|
|
|
@ -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<ENRKey, ENRValue> = {}
|
||||||
|
): Promise<ENR> {
|
||||||
|
// 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<ENRKey, ENRValue> = {}
|
||||||
|
): Promise<ENR> {
|
||||||
|
switch (peerId.type) {
|
||||||
|
case "secp256k1":
|
||||||
|
return EnrCreator.fromPublicKey(getPublicKeyFromPeerId(peerId), kvs);
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,14 +2,6 @@ import * as secp from "@noble/secp256k1";
|
||||||
import { concat } from "@waku/utils";
|
import { concat } from "@waku/utils";
|
||||||
import sha3 from "js-sha3";
|
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.
|
* ECDSA Sign a message with the given private key.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
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 fromValues(decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fromValues(values: Uint8Array[]): Promise<ENR> {
|
||||||
|
const { signature, seq, kvs } = checkValues(values);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,29 @@
|
||||||
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
||||||
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
|
||||||
import { multiaddr } from "@multiformats/multiaddr";
|
import { multiaddr } from "@multiformats/multiaddr";
|
||||||
|
import * as secp from "@noble/secp256k1";
|
||||||
import type { Waku2 } from "@waku/interfaces";
|
import type { Waku2 } from "@waku/interfaces";
|
||||||
import { bytesToHex, hexToBytes, utf8ToBytes } from "@waku/utils";
|
import { bytesToHex, hexToBytes, utf8ToBytes } from "@waku/utils";
|
||||||
import { assert, expect } from "chai";
|
import { assert, expect } from "chai";
|
||||||
import { equals } from "uint8arrays/equals";
|
import { equals } from "uint8arrays/equals";
|
||||||
|
|
||||||
import { ERR_INVALID_ID } from "./constants.js";
|
import { ERR_INVALID_ID } from "./constants.js";
|
||||||
import { getPublicKey } from "./crypto.js";
|
import { EnrCreator } from "./creator.js";
|
||||||
import { ENR } from "./enr.js";
|
import { EnrDecoder } from "./decoder.js";
|
||||||
import { createKeypairFromPeerId, IKeypair } from "./keypair/index.js";
|
import { EnrEncoder } from "./encoder.js";
|
||||||
|
import {
|
||||||
|
ENR,
|
||||||
|
TransportProtocol,
|
||||||
|
TransportProtocolPerIpVersion,
|
||||||
|
} from "./enr.js";
|
||||||
|
import { getPrivateKeyFromPeerId } from "./peer_id.js";
|
||||||
|
|
||||||
describe("ENR", function () {
|
describe("ENR", function () {
|
||||||
describe("Txt codec", () => {
|
describe("Txt codec", () => {
|
||||||
it("should encodeTxt and decodeTxt", async () => {
|
it("should encodeTxt and decodeTxt", async () => {
|
||||||
const peerId = await createSecp256k1PeerId();
|
const peerId = await createSecp256k1PeerId();
|
||||||
const enr = await ENR.createFromPeerId(peerId);
|
const enr = await EnrCreator.fromPeerId(peerId);
|
||||||
const keypair = await createKeypairFromPeerId(peerId);
|
const privateKey = await getPrivateKeyFromPeerId(peerId);
|
||||||
enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
|
enr.setLocationMultiaddr(multiaddr("/ip4/18.223.219.100/udp/9000"));
|
||||||
enr.multiaddrs = [
|
enr.multiaddrs = [
|
||||||
multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"),
|
multiaddr("/dns4/node1.do-ams.wakuv2.test.statusim.net/tcp/443/wss"),
|
||||||
|
@ -32,14 +40,14 @@ describe("ENR", function () {
|
||||||
lightPush: false,
|
lightPush: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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";
|
||||||
|
|
||||||
expect(bytesToHex(enr2.signature)).to.be.equal(bytesToHex(enr.signature));
|
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(ma.toString()).to.be.equal("/ip4/18.223.219.100/udp/9000");
|
||||||
expect(enr2.multiaddrs).to.not.be.undefined;
|
expect(enr2.multiaddrs).to.not.be.undefined;
|
||||||
expect(enr2.multiaddrs!.length).to.be.equal(3);
|
expect(enr2.multiaddrs!.length).to.be.equal(3);
|
||||||
|
@ -64,7 +72,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");
|
||||||
|
@ -73,7 +81,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);
|
||||||
|
@ -92,7 +100,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;
|
||||||
|
@ -106,14 +114,14 @@ describe("ENR", function () {
|
||||||
it("should throw error - no id", async () => {
|
it("should throw error - no id", async () => {
|
||||||
try {
|
try {
|
||||||
const peerId = await createSecp256k1PeerId();
|
const peerId = await createSecp256k1PeerId();
|
||||||
const enr = await ENR.createFromPeerId(peerId);
|
const enr = await EnrCreator.fromPeerId(peerId);
|
||||||
const keypair = await createKeypairFromPeerId(peerId);
|
const privateKey = await getPrivateKeyFromPeerId(peerId);
|
||||||
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(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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;
|
||||||
|
@ -125,7 +133,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;
|
||||||
|
@ -179,7 +187,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;
|
||||||
});
|
});
|
||||||
|
@ -194,10 +202,10 @@ describe("ENR", function () {
|
||||||
privateKey = hexToBytes(
|
privateKey = hexToBytes(
|
||||||
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
);
|
);
|
||||||
record = await ENR.createV4(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", () => {
|
||||||
|
@ -207,8 +215,8 @@ 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 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;
|
||||||
|
@ -219,7 +227,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
|
||||||
|
@ -239,7 +247,7 @@ describe("ENR", function () {
|
||||||
privateKey = hexToBytes(
|
privateKey = hexToBytes(
|
||||||
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
);
|
);
|
||||||
record = await ENR.createV4(getPublicKey(privateKey));
|
record = await EnrCreator.fromPublicKey(secp.getPublicKey(privateKey));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get / set UDP multiaddr", () => {
|
it("should get / set UDP multiaddr", () => {
|
||||||
|
@ -253,16 +261,16 @@ describe("ENR", function () {
|
||||||
record.set("ip", tuples0[0][1]);
|
record.set("ip", tuples0[0][1]);
|
||||||
record.set("udp", tuples0[1][1]);
|
record.set("udp", tuples0[1][1]);
|
||||||
// and get the multiaddr
|
// and get the multiaddr
|
||||||
expect(record.getLocationMultiaddr("udp")!.toString()).to.equal(
|
expect(
|
||||||
multi0.toString()
|
record.getLocationMultiaddr(TransportProtocol.UDP)!.toString()
|
||||||
);
|
).to.equal(multi0.toString());
|
||||||
// set the multiaddr
|
// set the multiaddr
|
||||||
const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300");
|
const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300");
|
||||||
record.setLocationMultiaddr(multi1);
|
record.setLocationMultiaddr(multi1);
|
||||||
// and get the multiaddr
|
// and get the multiaddr
|
||||||
expect(record.getLocationMultiaddr("udp")!.toString()).to.equal(
|
expect(
|
||||||
multi1.toString()
|
record.getLocationMultiaddr(TransportProtocol.UDP)!.toString()
|
||||||
);
|
).to.equal(multi1.toString());
|
||||||
// and get the underlying records
|
// and get the underlying records
|
||||||
const tuples1 = multi1.tuples();
|
const tuples1 = multi1.tuples();
|
||||||
expect(record.get("ip")).to.deep.equal(tuples1[0][1]);
|
expect(record.get("ip")).to.deep.equal(tuples1[0][1]);
|
||||||
|
@ -281,16 +289,16 @@ describe("ENR", function () {
|
||||||
record.set("ip", tuples0[0][1]);
|
record.set("ip", tuples0[0][1]);
|
||||||
record.set("tcp", tuples0[1][1]);
|
record.set("tcp", tuples0[1][1]);
|
||||||
// and get the multiaddr
|
// and get the multiaddr
|
||||||
expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal(
|
expect(
|
||||||
multi0.toString()
|
record.getLocationMultiaddr(TransportProtocol.TCP)!.toString()
|
||||||
);
|
).to.equal(multi0.toString());
|
||||||
// set the multiaddr
|
// set the multiaddr
|
||||||
const multi1 = multiaddr("/ip4/0.0.0.0/tcp/30300");
|
const multi1 = multiaddr("/ip4/0.0.0.0/tcp/30300");
|
||||||
record.setLocationMultiaddr(multi1);
|
record.setLocationMultiaddr(multi1);
|
||||||
// and get the multiaddr
|
// and get the multiaddr
|
||||||
expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal(
|
expect(
|
||||||
multi1.toString()
|
record.getLocationMultiaddr(TransportProtocol.TCP)!.toString()
|
||||||
);
|
).to.equal(multi1.toString());
|
||||||
// and get the underlying records
|
// and get the underlying records
|
||||||
const tuples1 = multi1.tuples();
|
const tuples1 = multi1.tuples();
|
||||||
expect(record.get("ip")).to.deep.equal(tuples1[0][1]);
|
expect(record.get("ip")).to.deep.equal(tuples1[0][1]);
|
||||||
|
@ -303,12 +311,12 @@ describe("ENR", function () {
|
||||||
const ip6 = "::1";
|
const ip6 = "::1";
|
||||||
const tcp = 8080;
|
const tcp = 8080;
|
||||||
const udp = 8080;
|
const udp = 8080;
|
||||||
let peerId;
|
let peerId: PeerId;
|
||||||
let enr: ENR;
|
let enr: ENR;
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
peerId = await createSecp256k1PeerId();
|
peerId = await createSecp256k1PeerId();
|
||||||
enr = await ENR.createFromPeerId(peerId);
|
enr = await EnrCreator.fromPeerId(peerId);
|
||||||
enr.ip = ip4;
|
enr.ip = ip4;
|
||||||
enr.ip6 = ip6;
|
enr.ip6 = ip6;
|
||||||
enr.tcp = tcp;
|
enr.tcp = tcp;
|
||||||
|
@ -318,43 +326,43 @@ describe("ENR", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly create location multiaddrs - udp4", () => {
|
it("should properly create location multiaddrs - udp4", () => {
|
||||||
expect(enr.getLocationMultiaddr("udp4")).to.deep.equal(
|
expect(
|
||||||
multiaddr(`/ip4/${ip4}/udp/${udp}`)
|
enr.getLocationMultiaddr(TransportProtocolPerIpVersion.UDP4)
|
||||||
);
|
).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly create location multiaddrs - tcp4", () => {
|
it("should properly create location multiaddrs - tcp4", () => {
|
||||||
expect(enr.getLocationMultiaddr("tcp4")).to.deep.equal(
|
expect(
|
||||||
multiaddr(`/ip4/${ip4}/tcp/${tcp}`)
|
enr.getLocationMultiaddr(TransportProtocolPerIpVersion.TCP4)
|
||||||
);
|
).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly create location multiaddrs - udp6", () => {
|
it("should properly create location multiaddrs - udp6", () => {
|
||||||
expect(enr.getLocationMultiaddr("udp6")).to.deep.equal(
|
expect(
|
||||||
multiaddr(`/ip6/${ip6}/udp/${udp}`)
|
enr.getLocationMultiaddr(TransportProtocolPerIpVersion.UDP6)
|
||||||
);
|
).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${udp}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly create location multiaddrs - tcp6", () => {
|
it("should properly create location multiaddrs - tcp6", () => {
|
||||||
expect(enr.getLocationMultiaddr("tcp6")).to.deep.equal(
|
expect(
|
||||||
multiaddr(`/ip6/${ip6}/tcp/${tcp}`)
|
enr.getLocationMultiaddr(TransportProtocolPerIpVersion.TCP6)
|
||||||
);
|
).to.deep.equal(multiaddr(`/ip6/${ip6}/tcp/${tcp}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly create location multiaddrs - udp", () => {
|
it("should properly create location multiaddrs - udp", () => {
|
||||||
// default to ip4
|
// default to ip4
|
||||||
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal(
|
||||||
multiaddr(`/ip4/${ip4}/udp/${udp}`)
|
multiaddr(`/ip4/${ip4}/udp/${udp}`)
|
||||||
);
|
);
|
||||||
// if ip6 is set, use it
|
// if ip6 is set, use it
|
||||||
enr.ip = undefined;
|
enr.ip = undefined;
|
||||||
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal(
|
||||||
multiaddr(`/ip6/${ip6}/udp/${udp}`)
|
multiaddr(`/ip6/${ip6}/udp/${udp}`)
|
||||||
);
|
);
|
||||||
// if ip6 does not exist, use ip4
|
// if ip6 does not exist, use ip4
|
||||||
enr.ip6 = undefined;
|
enr.ip6 = undefined;
|
||||||
enr.ip = ip4;
|
enr.ip = ip4;
|
||||||
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.UDP)).to.deep.equal(
|
||||||
multiaddr(`/ip4/${ip4}/udp/${udp}`)
|
multiaddr(`/ip4/${ip4}/udp/${udp}`)
|
||||||
);
|
);
|
||||||
enr.ip6 = ip6;
|
enr.ip6 = ip6;
|
||||||
|
@ -362,34 +370,53 @@ describe("ENR", function () {
|
||||||
|
|
||||||
it("should properly create location multiaddrs - tcp", () => {
|
it("should properly create location multiaddrs - tcp", () => {
|
||||||
// default to ip4
|
// default to ip4
|
||||||
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal(
|
||||||
multiaddr(`/ip4/${ip4}/tcp/${tcp}`)
|
multiaddr(`/ip4/${ip4}/tcp/${tcp}`)
|
||||||
);
|
);
|
||||||
// if ip6 is set, use it
|
// if ip6 is set, use it
|
||||||
enr.ip = undefined;
|
enr.ip = undefined;
|
||||||
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal(
|
||||||
multiaddr(`/ip6/${ip6}/tcp/${tcp}`)
|
multiaddr(`/ip6/${ip6}/tcp/${tcp}`)
|
||||||
);
|
);
|
||||||
// if ip6 does not exist, use ip4
|
// if ip6 does not exist, use ip4
|
||||||
enr.ip6 = undefined;
|
enr.ip6 = undefined;
|
||||||
enr.ip = ip4;
|
enr.ip = ip4;
|
||||||
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(
|
expect(enr.getLocationMultiaddr(TransportProtocol.TCP)).to.deep.equal(
|
||||||
multiaddr(`/ip4/${ip4}/tcp/${tcp}`)
|
multiaddr(`/ip4/${ip4}/tcp/${tcp}`)
|
||||||
);
|
);
|
||||||
enr.ip6 = ip6;
|
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 () => {
|
describe("waku2 key round trip", async () => {
|
||||||
let peerId;
|
let peerId;
|
||||||
let enr: ENR;
|
let enr: ENR;
|
||||||
let waku2Protocols: Waku2;
|
let waku2Protocols: Waku2;
|
||||||
let keypair: IKeypair;
|
let privateKey: Uint8Array;
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
peerId = await createSecp256k1PeerId();
|
peerId = await createSecp256k1PeerId();
|
||||||
enr = await ENR.createFromPeerId(peerId);
|
enr = await EnrCreator.fromPeerId(peerId);
|
||||||
keypair = await createKeypairFromPeerId(peerId);
|
privateKey = await getPrivateKeyFromPeerId(peerId);
|
||||||
waku2Protocols = {
|
waku2Protocols = {
|
||||||
relay: false,
|
relay: false,
|
||||||
store: false,
|
store: false,
|
||||||
|
@ -401,8 +428,8 @@ 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(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -417,8 +444,8 @@ describe("ENR", function () {
|
||||||
waku2Protocols.lightPush = true;
|
waku2Protocols.lightPush = true;
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -430,8 +457,8 @@ describe("ENR", function () {
|
||||||
waku2Protocols.relay = true;
|
waku2Protocols.relay = true;
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -443,8 +470,8 @@ describe("ENR", function () {
|
||||||
waku2Protocols.store = true;
|
waku2Protocols.store = true;
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -456,8 +483,8 @@ describe("ENR", function () {
|
||||||
waku2Protocols.filter = true;
|
waku2Protocols.filter = true;
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -469,8 +496,8 @@ describe("ENR", function () {
|
||||||
waku2Protocols.lightPush = true;
|
waku2Protocols.lightPush = true;
|
||||||
|
|
||||||
enr.waku2 = waku2Protocols;
|
enr.waku2 = waku2Protocols;
|
||||||
const txt = await enr.encodeTxt(keypair.privateKey);
|
const txt = await EnrEncoder.toString(enr, 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);
|
||||||
|
@ -484,7 +511,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);
|
||||||
|
|
|
@ -1,58 +1,38 @@
|
||||||
import * as RLP from "@ethersproject/rlp";
|
|
||||||
import type { PeerId } from "@libp2p/interface-peer-id";
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
||||||
|
import type { PeerInfo } from "@libp2p/interface-peer-info";
|
||||||
import type { Multiaddr } from "@multiformats/multiaddr";
|
import type { Multiaddr } from "@multiformats/multiaddr";
|
||||||
import {
|
|
||||||
convertToBytes,
|
|
||||||
convertToString,
|
|
||||||
} from "@multiformats/multiaddr/convert";
|
|
||||||
import type {
|
import type {
|
||||||
ENRKey,
|
ENRKey,
|
||||||
ENRValue,
|
ENRValue,
|
||||||
IEnr,
|
IEnr,
|
||||||
NodeId,
|
NodeId,
|
||||||
SequenceNumber,
|
SequenceNumber,
|
||||||
Waku2,
|
|
||||||
} from "@waku/interfaces";
|
} from "@waku/interfaces";
|
||||||
import { bytesToHex, 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 {
|
import { ERR_INVALID_ID } from "./constants.js";
|
||||||
ERR_INVALID_ID,
|
import { keccak256, verifySignature } from "./crypto.js";
|
||||||
ERR_NO_SIGNATURE,
|
import { locationMultiaddrFromEnrFields } from "./get_multiaddr.js";
|
||||||
MAX_RECORD_SIZE,
|
import { createPeerIdFromPublicKey } from "./peer_id.js";
|
||||||
} from "./constants.js";
|
import { RawEnr } from "./raw_enr.js";
|
||||||
import { compressPublicKey, keccak256, verifySignature } from "./crypto.js";
|
|
||||||
import {
|
|
||||||
createKeypair,
|
|
||||||
createKeypairFromPeerId,
|
|
||||||
createPeerIdFromKeypair,
|
|
||||||
IKeypair,
|
|
||||||
KeypairType,
|
|
||||||
} from "./keypair/index.js";
|
|
||||||
import { multiaddrFromFields } from "./multiaddr_from_fields.js";
|
|
||||||
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec.js";
|
|
||||||
import * as v4 from "./v4.js";
|
import * as v4 from "./v4.js";
|
||||||
import { decodeWaku2, encodeWaku2 } from "./waku2_codec.js";
|
|
||||||
|
|
||||||
const log = debug("waku:enr");
|
const log = debug("waku:enr");
|
||||||
|
|
||||||
export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
export enum TransportProtocol {
|
||||||
public static readonly RECORD_PREFIX = "enr:";
|
TCP = "tcp",
|
||||||
public seq: SequenceNumber;
|
UDP = "udp",
|
||||||
public signature?: Uint8Array;
|
}
|
||||||
public peerId?: PeerId;
|
export enum TransportProtocolPerIpVersion {
|
||||||
|
TCP4 = "tcp4",
|
||||||
|
UDP4 = "udp4",
|
||||||
|
TCP6 = "tcp6",
|
||||||
|
UDP6 = "udp6",
|
||||||
|
}
|
||||||
|
|
||||||
private constructor(
|
export class ENR extends RawEnr implements IEnr {
|
||||||
kvs: Record<ENRKey, ENRValue> = {},
|
public static readonly RECORD_PREFIX = "enr:";
|
||||||
seq: SequenceNumber = BigInt(1),
|
public peerId?: PeerId;
|
||||||
signature?: Uint8Array
|
|
||||||
) {
|
|
||||||
super(Object.entries(kvs));
|
|
||||||
this.seq = seq;
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async create(
|
static async create(
|
||||||
kvs: Record<ENRKey, ENRValue> = {},
|
kvs: Record<ENRKey, ENRValue> = {},
|
||||||
|
@ -63,8 +43,7 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
try {
|
try {
|
||||||
const publicKey = enr.publicKey;
|
const publicKey = enr.publicKey;
|
||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
const keypair = createKeypair(enr.keypairType, undefined, publicKey);
|
enr.peerId = await createPeerIdFromPublicKey(publicKey);
|
||||||
enr.peerId = await createPeerIdFromKeypair(keypair);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("Could not calculate peer id for ENR", e);
|
log("Could not calculate peer id for ENR", e);
|
||||||
|
@ -73,122 +52,6 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
return enr;
|
return enr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createV4(
|
|
||||||
publicKey: Uint8Array,
|
|
||||||
kvs: Record<ENRKey, ENRValue> = {}
|
|
||||||
): Promise<ENR> {
|
|
||||||
// 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<ENRKey, ENRValue> = {}
|
|
||||||
): Promise<ENR> {
|
|
||||||
const keypair = await createKeypairFromPeerId(peerId);
|
|
||||||
switch (keypair.type) {
|
|
||||||
case KeypairType.secp256k1:
|
|
||||||
return ENR.createV4(keypair.publicKey, kvs);
|
|
||||||
default:
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 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":
|
|
||||||
return this.get("secp256k1");
|
|
||||||
default:
|
|
||||||
throw new Error(ERR_INVALID_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get keypair(): IKeypair | undefined {
|
|
||||||
if (this.publicKey) {
|
|
||||||
const publicKey = this.publicKey;
|
|
||||||
return createKeypair(this.keypairType, undefined, publicKey);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nodeId(): NodeId | undefined {
|
get nodeId(): NodeId | undefined {
|
||||||
switch (this.id) {
|
switch (this.id) {
|
||||||
case "v4":
|
case "v4":
|
||||||
|
@ -197,193 +60,9 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
throw new Error(ERR_INVALID_ID);
|
throw new Error(ERR_INVALID_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getLocationMultiaddr: (
|
||||||
get ip(): string | undefined {
|
protocol: TransportProtocol | TransportProtocolPerIpVersion
|
||||||
const raw = this.get("ip");
|
) => Multiaddr | undefined = locationMultiaddrFromEnrFields.bind({}, this);
|
||||||
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: "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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocationMultiaddr(multiaddr: Multiaddr): void {
|
setLocationMultiaddr(multiaddr: Multiaddr): void {
|
||||||
const protoNames = multiaddr.protoNames();
|
const protoNames = multiaddr.protoNames();
|
||||||
|
@ -409,6 +88,32 @@ export class ENR extends Map<ENRKey, ENRValue> 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
|
* Returns the full multiaddr from the ENR fields matching the provided
|
||||||
* `protocol` parameter.
|
* `protocol` parameter.
|
||||||
|
@ -418,7 +123,7 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
* @param protocol
|
* @param protocol
|
||||||
*/
|
*/
|
||||||
getFullMultiaddr(
|
getFullMultiaddr(
|
||||||
protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6"
|
protocol: TransportProtocol | TransportProtocolPerIpVersion
|
||||||
): Multiaddr | undefined {
|
): Multiaddr | undefined {
|
||||||
if (this.peerId) {
|
if (this.peerId) {
|
||||||
const locationMultiaddr = this.getLocationMultiaddr(protocol);
|
const locationMultiaddr = this.getLocationMultiaddr(protocol);
|
||||||
|
@ -442,28 +147,6 @@ export class ENR extends Map<ENRKey, ENRValue> implements IEnr {
|
||||||
return [];
|
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 {
|
verify(data: Uint8Array, signature: Uint8Array): boolean {
|
||||||
if (!this.get("id") || this.id !== "v4") {
|
if (!this.get("id") || this.id !== "v4") {
|
||||||
throw new Error(ERR_INVALID_ID);
|
throw new Error(ERR_INVALID_ID);
|
||||||
|
@ -484,43 +167,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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
export * from "./constants.js";
|
export * from "./constants.js";
|
||||||
|
export * from "./creator.js";
|
||||||
|
export * from "./decoder.js";
|
||||||
export * from "./enr.js";
|
export * from "./enr.js";
|
||||||
export * from "./keypair/index.js";
|
export * from "./peer_id.js";
|
||||||
export * from "./waku2_codec.js";
|
export * from "./waku2_codec.js";
|
||||||
export * from "./crypto.js";
|
export * from "./crypto.js";
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { unmarshalPrivateKey, unmarshalPublicKey } from "@libp2p/crypto/keys";
|
|
||||||
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 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 async function createPeerIdFromKeypair(
|
|
||||||
keypair: IKeypair
|
|
||||||
): Promise<PeerId> {
|
|
||||||
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<IKeypair> {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicKey = peerId.publicKey
|
|
||||||
? unmarshalPublicKey(peerId.publicKey)
|
|
||||||
: undefined;
|
|
||||||
const privateKey = peerId.privateKey
|
|
||||||
? await unmarshalPrivateKey(peerId.privateKey)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return createKeypair(
|
|
||||||
keypairType,
|
|
||||||
privateKey?.marshal(),
|
|
||||||
publicKey?.marshal()
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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<Secp256k1Keypair> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { unmarshalPrivateKey, unmarshalPublicKey } from "@libp2p/crypto/keys";
|
||||||
|
import { supportedKeys } from "@libp2p/crypto/keys";
|
||||||
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
||||||
|
import { peerIdFromKeys } from "@libp2p/peer-id";
|
||||||
|
|
||||||
|
export function createPeerIdFromPublicKey(
|
||||||
|
publicKey: Uint8Array
|
||||||
|
): Promise<PeerId> {
|
||||||
|
const _publicKey = new supportedKeys.secp256k1.Secp256k1PublicKey(publicKey);
|
||||||
|
return peerIdFromKeys(_publicKey.bytes, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPublicKeyFromPeerId(peerId: PeerId): Uint8Array {
|
||||||
|
if (peerId.type !== "secp256k1") {
|
||||||
|
throw new Error("Unsupported peer id type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmarshalPublicKey(peerId.publicKey).marshal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used in tests
|
||||||
|
export async function getPrivateKeyFromPeerId(
|
||||||
|
peerId: PeerId
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
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();
|
||||||
|
}
|
|
@ -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<ENRKey, ENRValue> {
|
||||||
|
public seq: SequenceNumber;
|
||||||
|
public signature?: Uint8Array;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
kvs: Record<ENRKey, ENRValue> = {},
|
||||||
|
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 ENR.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 ENR.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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import type { NodeId } from "@waku/interfaces";
|
||||||
import { bytesToHex } from "@waku/utils";
|
import { bytesToHex } from "@waku/utils";
|
||||||
|
|
||||||
import { keccak256 } from "./crypto.js";
|
import { keccak256 } from "./crypto.js";
|
||||||
|
|
||||||
export async function sign(
|
export async function sign(
|
||||||
privKey: Uint8Array,
|
privKey: Uint8Array,
|
||||||
msg: Uint8Array
|
msg: Uint8Array
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
"@libp2p/interface-connection-manager": "^1.3.7",
|
"@libp2p/interface-connection-manager": "^1.3.7",
|
||||||
"@libp2p/interface-libp2p": "^1.1.1",
|
"@libp2p/interface-libp2p": "^1.1.1",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
|
"@libp2p/interface-peer-info": "^1.0.8",
|
||||||
"@libp2p/interface-peer-store": "^1.2.8",
|
"@libp2p/interface-peer-store": "^1.2.8",
|
||||||
"@libp2p/interface-registrar": "^2.0.8",
|
"@libp2p/interface-registrar": "^2.0.8",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
"@multiformats/multiaddr": "^11.4.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { PeerId } from "@libp2p/interface-peer-id";
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
||||||
|
import type { PeerInfo } from "@libp2p/interface-peer-info";
|
||||||
import type { Multiaddr } from "@multiformats/multiaddr";
|
import type { Multiaddr } from "@multiformats/multiaddr";
|
||||||
|
|
||||||
export type ENRKey = string;
|
export type ENRKey = string;
|
||||||
|
@ -32,7 +33,10 @@ export interface IEnr extends Map<ENRKey, ENRValue> {
|
||||||
udp6?: number;
|
udp6?: number;
|
||||||
multiaddrs?: Multiaddr[];
|
multiaddrs?: Multiaddr[];
|
||||||
waku2?: Waku2;
|
waku2?: Waku2;
|
||||||
|
peerInfo: PeerInfo | undefined;
|
||||||
|
|
||||||
encode(privateKey?: Uint8Array): Promise<Uint8Array>;
|
/**
|
||||||
|
* @deprecated: use { @link IEnr.peerInfo } instead.
|
||||||
|
*/
|
||||||
getFullMultiaddrs(): Multiaddr[];
|
getFullMultiaddrs(): Multiaddr[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@ export interface Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_PEER_EXCHANGE_TAG_NAME = "peer-exchange";
|
export const DEFAULT_PEER_EXCHANGE_TAG_NAME = "peer-exchange";
|
||||||
const DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_VALUE = 50;
|
const DEFAULT_PEER_EXCHANGE_TAG_VALUE = 50;
|
||||||
const DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_TTL = 120000;
|
const DEFAULT_PEER_EXCHANGE_TAG_TTL = 120000;
|
||||||
|
|
||||||
export class PeerExchangeDiscovery
|
export class PeerExchangeDiscovery
|
||||||
extends EventEmitter<PeerDiscoveryEvents>
|
extends EventEmitter<PeerDiscoveryEvents>
|
||||||
|
@ -165,37 +165,35 @@ export class PeerExchangeDiscovery
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { peerId } = ENR;
|
const peerInfo = ENR.peerInfo;
|
||||||
const multiaddrs = ENR.getFullMultiaddrs();
|
|
||||||
|
|
||||||
if (!peerId || !multiaddrs || multiaddrs.length === 0) continue;
|
|
||||||
|
|
||||||
if (
|
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
|
({ name }) => name === DEFAULT_PEER_EXCHANGE_TAG_NAME
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
await this.components.peerStore.tagPeer(
|
await this.components.peerStore.tagPeer(
|
||||||
peerId,
|
peerInfo.id,
|
||||||
DEFAULT_PEER_EXCHANGE_TAG_NAME,
|
DEFAULT_PEER_EXCHANGE_TAG_NAME,
|
||||||
{
|
{
|
||||||
value:
|
value: this.options.tagValue ?? DEFAULT_PEER_EXCHANGE_TAG_VALUE,
|
||||||
this.options.tagValue ??
|
ttl: this.options.tagTTL ?? DEFAULT_PEER_EXCHANGE_TAG_TTL,
|
||||||
DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_VALUE,
|
|
||||||
ttl:
|
|
||||||
this.options.tagTTL ?? DEFAULT_PEER_EXCHANGE_BOOTSTRAP_TAG_TTL,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent<PeerInfo>("peer", {
|
new CustomEvent<PeerInfo>("peer", {
|
||||||
detail: {
|
detail: peerInfo,
|
||||||
id: peerId,
|
|
||||||
multiaddrs,
|
|
||||||
protocols: [],
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -50,16 +50,13 @@
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libp2p/peer-id": "^2.0.2",
|
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"uint8arrays": "^4.0.3"
|
"uint8arrays": "^4.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@libp2p/interface-connection": "^3.0.8",
|
"@libp2p/interface-connection": "^3.0.8",
|
||||||
"@libp2p/interface-peer-id": "^2.0.1",
|
"@libp2p/interface-peer-id": "^2.0.1",
|
||||||
"@libp2p/interface-peer-info": "^1.0.8",
|
|
||||||
"@libp2p/interface-peer-store": "^1.2.8",
|
"@libp2p/interface-peer-store": "^1.2.8",
|
||||||
"@multiformats/multiaddr": "^11.4.0",
|
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-json": "^6.0.0",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import type { Connection } from "@libp2p/interface-connection";
|
import type { Connection } from "@libp2p/interface-connection";
|
||||||
import type { PeerId } from "@libp2p/interface-peer-id";
|
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 type { Peer, PeerStore } from "@libp2p/interface-peer-store";
|
||||||
import { peerIdFromString } from "@libp2p/peer-id";
|
|
||||||
import type { Multiaddr } from "@multiformats/multiaddr";
|
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
const log = debug("waku:libp2p-utils");
|
const log = debug("waku:libp2p-utils");
|
||||||
|
@ -78,20 +75,6 @@ export async function selectPeerForProtocol(
|
||||||
return { peer, protocol };
|
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(
|
export function selectConnection(
|
||||||
connections: Connection[]
|
connections: Connection[]
|
||||||
): Connection | undefined {
|
): Connection | undefined {
|
||||||
|
|
Loading…
Reference in New Issue