mirror of https://github.com/waku-org/js-waku.git
Reduce Buffer usage in ENR module (#522)
This commit is contained in:
parent
9931011c93
commit
297d65ce03
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"extension": ["ts"],
|
"extension": ["ts"],
|
||||||
"spec": "src/**/*.spec.ts",
|
"spec": "src/**/*.spec.ts",
|
||||||
"require": ["ts-node/register", "isomorphic-fetch"],
|
"require": ["ts-node/register", "isomorphic-fetch", "jsdom-global/register"],
|
||||||
"exit": true
|
"exit": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- axios dependency in favour of fetch.
|
- `axios` dependency in favour of fetch.
|
||||||
|
- `base64url` and `bigint-buffer` dependencies.
|
||||||
|
|
||||||
## [0.16.0] - 2022-01-31
|
## [0.16.0] - 2022-01-31
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -51,15 +51,13 @@
|
||||||
"doc:cname": "echo 'js-waku.wakuconnect.dev' > build/docs/CNAME",
|
"doc:cname": "echo 'js-waku.wakuconnect.dev' > build/docs/CNAME",
|
||||||
"doc:examples": "mkdir -p build/docs/examples",
|
"doc:examples": "mkdir -p build/docs/examples",
|
||||||
"deploy": "node ci/deploy.js",
|
"deploy": "node ci/deploy.js",
|
||||||
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build && for d in examples/*; do (cd $d; npm i); done"
|
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build && for d in examples/*/; do (cd $d; npm i); done"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/libp2p-noise": "^5.0.0",
|
"@chainsafe/libp2p-noise": "^5.0.0",
|
||||||
"base64url": "^3.0.1",
|
|
||||||
"bigint-buffer": "^1.1.5",
|
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"dns-query": "^0.8.0",
|
"dns-query": "^0.8.0",
|
||||||
"ecies-geth": "^1.5.2",
|
"ecies-geth": "^1.5.2",
|
||||||
|
@ -107,6 +105,8 @@
|
||||||
"fast-check": "^2.14.0",
|
"fast-check": "^2.14.0",
|
||||||
"gh-pages": "^3.2.3",
|
"gh-pages": "^3.2.3",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
|
"jsdom": "^19.0.0",
|
||||||
|
"jsdom-global": "^3.0.2",
|
||||||
"karma": "^6.3.12",
|
"karma": "^6.3.12",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"karma-chrome-launcher": "^3.1.0",
|
||||||
"karma-env-preprocessor": "^0.1.1",
|
"karma-env-preprocessor": "^0.1.1",
|
||||||
|
|
|
@ -3,6 +3,8 @@ export * as discovery from "./lib/discovery";
|
||||||
|
|
||||||
export * as enr from "./lib/enr";
|
export * as enr from "./lib/enr";
|
||||||
|
|
||||||
|
export * as utf8 from "./lib/utf8";
|
||||||
|
|
||||||
export * as utils from "./lib/utils";
|
export * as utils from "./lib/utils";
|
||||||
|
|
||||||
export * as waku from "./lib/waku";
|
export * as waku from "./lib/waku";
|
||||||
|
|
|
@ -183,10 +183,7 @@ describe("DNS Node Discovery [live data]", function () {
|
||||||
const maxQuantity = 3;
|
const maxQuantity = 3;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
if (
|
if (process.env.CI || window?.__env__?.CI) {
|
||||||
process.env.CI ||
|
|
||||||
(typeof window !== "undefined" && window?.__env__?.CI)
|
|
||||||
) {
|
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
|
||||||
import base64url from "base64url";
|
|
||||||
import * as base32 from "hi-base32";
|
import * as base32 from "hi-base32";
|
||||||
import { ecdsaVerify } from "secp256k1";
|
import { ecdsaVerify } from "secp256k1";
|
||||||
|
|
||||||
import { ENR } from "../enr";
|
import { ENR } from "../enr";
|
||||||
import { keccak256Buf } from "../utils";
|
import { base64ToBytes, keccak256Buf } from "../utils";
|
||||||
|
|
||||||
export type ENRRootValues = {
|
export type ENRRootValues = {
|
||||||
eRoot: string;
|
eRoot: string;
|
||||||
|
@ -43,15 +42,12 @@ export class ENRTree {
|
||||||
// (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method)
|
// (Trailing recovery bit must be trimmed to pass `ecdsaVerify` method)
|
||||||
const signedComponent = root.split(" sig")[0];
|
const signedComponent = root.split(" sig")[0];
|
||||||
const signedComponentBuffer = Buffer.from(signedComponent);
|
const signedComponentBuffer = Buffer.from(signedComponent);
|
||||||
const signatureBuffer = base64url
|
const signatureBuffer = base64ToBytes(rootValues.signature).slice(0, 64);
|
||||||
.toBuffer(rootValues.signature)
|
|
||||||
.slice(0, 64);
|
|
||||||
const keyBuffer = Buffer.from(decodedPublicKey);
|
|
||||||
|
|
||||||
const isVerified = ecdsaVerify(
|
const isVerified = ecdsaVerify(
|
||||||
signatureBuffer,
|
signatureBuffer,
|
||||||
keccak256Buf(signedComponentBuffer),
|
keccak256Buf(signedComponentBuffer),
|
||||||
keyBuffer
|
new Uint8Array(decodedPublicKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(isVerified, "Unable to verify ENRTree root signature");
|
assert(isVerified, "Unable to verify ENRTree root signature");
|
||||||
|
|
|
@ -45,10 +45,7 @@ describe("Discovery", () => {
|
||||||
|
|
||||||
describe("Discovery [live data]", function () {
|
describe("Discovery [live data]", function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
if (
|
if (process.env.CI || window?.__env__?.CI) {
|
||||||
process.env.CI ||
|
|
||||||
(typeof window !== "undefined" && window?.__env__?.CI)
|
|
||||||
) {
|
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { bytesToHex } from "../utils";
|
||||||
|
|
||||||
import { NodeId } from "./types";
|
import { NodeId } from "./types";
|
||||||
|
|
||||||
export function createNodeId(buffer: Buffer): NodeId {
|
export function createNodeId(bytes: Uint8Array): NodeId {
|
||||||
if (buffer.length !== 32) {
|
if (bytes.length !== 32) {
|
||||||
throw new Error("NodeId must be 32 bytes in length");
|
throw new Error("NodeId must be 32 bytes in length");
|
||||||
}
|
}
|
||||||
return bytesToHex(buffer);
|
return bytesToHex(bytes);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ describe("ENR", function () {
|
||||||
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss"
|
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss"
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
const txt = enr.encodeTxt(keypair.privateKey);
|
const txt = await enr.encodeTxt(keypair.privateKey);
|
||||||
expect(txt.slice(0, 4)).to.be.equal("enr:");
|
|
||||||
const enr2 = ENR.decodeTxt(txt);
|
const enr2 = ENR.decodeTxt(txt);
|
||||||
expect(bytesToHex(enr2.signature as Buffer)).to.be.equal(
|
expect(bytesToHex(enr2.signature as Buffer)).to.be.equal(
|
||||||
bytesToHex(enr.signature as Buffer)
|
bytesToHex(enr.signature as Buffer)
|
||||||
|
@ -87,17 +87,21 @@ describe("ENR", function () {
|
||||||
expect(enr.ip).to.not.be.undefined;
|
expect(enr.ip).to.not.be.undefined;
|
||||||
expect(enr.ip).to.be.equal("134.209.139.210");
|
expect(enr.ip).to.be.equal("134.209.139.210");
|
||||||
expect(enr.publicKey).to.not.be.undefined;
|
expect(enr.publicKey).to.not.be.undefined;
|
||||||
expect(enr.peerId.toB58String()).to.be.equal(
|
expect(enr.peerId?.toB58String()).to.be.equal(
|
||||||
"16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ"
|
"16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw error - no id", () => {
|
it("should throw error - no id", async () => {
|
||||||
try {
|
try {
|
||||||
const txt = Buffer.from(
|
const peerId = await PeerId.create({ keyType: "secp256k1" });
|
||||||
"656e723a2d435972595a62404b574342526c4179357a7a61445a584a42476b636e68344d486342465a6e75584e467264764a6a5830346a527a6a7a",
|
const enr = ENR.createFromPeerId(peerId);
|
||||||
"hex"
|
const keypair = createKeypairFromPeerId(peerId);
|
||||||
).toString();
|
enr.setLocationMultiaddr(new Multiaddr("/ip4/18.223.219.100/udp/9000"));
|
||||||
|
|
||||||
|
enr.set("id", new Uint8Array([0]));
|
||||||
|
const txt = await enr.encodeTxt(keypair.privateKey);
|
||||||
|
|
||||||
ENR.decodeTxt(txt);
|
ENR.decodeTxt(txt);
|
||||||
assert.fail("Expect error here");
|
assert.fail("Expect error here");
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
@ -170,37 +174,6 @@ describe("ENR", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Fuzzing testcases", () => {
|
|
||||||
it("should throw error in invalid signature", () => {
|
|
||||||
const buf = Buffer.from(
|
|
||||||
"656e723a2d4b7634514147774f54385374716d7749354c486149796d494f346f6f464b664e6b456a576130663150384f73456c67426832496a622d4772445f2d623957346b6350466377796e354845516d526371584e716470566f3168656f42683246306447356c64484f494141414141414141414143455a58526f4d704141414141414141414141505f5f5f5f5f5f5f5f5f5f676d6c6b676e5930676d6c7768424c663232534a6332566a634449314e6d73786f514a78436e4536765f7832656b67595f756f45317274777a76477934306d7139654436365866485042576749494e315a48437f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f434436410d0a",
|
|
||||||
"hex"
|
|
||||||
).toString();
|
|
||||||
try {
|
|
||||||
ENR.decodeTxt(buf);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const e = err as Error;
|
|
||||||
expect(e.message).to.equal(
|
|
||||||
"Decoded ENR invalid signature: must be a byte array"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it("should throw error in invalid sequence number", () => {
|
|
||||||
const buf = Buffer.from(
|
|
||||||
"656e723a2d495334514b6b33ff583945717841337838334162436979416e537550444d764b353264433530486d31584744643574457951684d3356634a4c2d5062446b44673541507a5f706f76763022d48dcf992d5379716b306e616e636f4e572d656e7263713042676d6c6b676e5930676d6c77684838414141474a6332566a634449314e6d73786f514d31453579557370397638516a397476335a575843766146427672504e647a384b5049314e68576651577a494e315a4843434239410a",
|
|
||||||
"hex"
|
|
||||||
).toString();
|
|
||||||
try {
|
|
||||||
ENR.decodeTxt(buf);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const e = err as Error;
|
|
||||||
expect(e.message).to.equal(
|
|
||||||
"Decoded ENR invalid sequence number: must be a byte array"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Static tests", () => {
|
describe("Static tests", () => {
|
||||||
let privateKey: Buffer;
|
let privateKey: Buffer;
|
||||||
let record: ENR;
|
let record: ENR;
|
||||||
|
@ -212,9 +185,10 @@ describe("ENR", function () {
|
||||||
"hex"
|
"hex"
|
||||||
);
|
);
|
||||||
record = ENR.createV4(v4.publicKey(privateKey));
|
record = ENR.createV4(v4.publicKey(privateKey));
|
||||||
record.set("ip", Buffer.from("7f000001", "hex"));
|
record.setLocationMultiaddr(new Multiaddr("/ip4/127.0.0.1/udp/30303"));
|
||||||
record.set("udp", Buffer.from((30303).toString(16), "hex"));
|
|
||||||
record.seq = seq;
|
record.seq = seq;
|
||||||
|
// To set signature
|
||||||
|
record.encode(privateKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should properly compute the node id", () => {
|
it("should properly compute the node id", () => {
|
||||||
|
@ -228,7 +202,7 @@ describe("ENR", function () {
|
||||||
expect(decoded).to.deep.equal(record);
|
expect(decoded).to.deep.equal(record);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should encode/decode to text encoding", () => {
|
it("should encode/decode to text encoding", async () => {
|
||||||
// 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";
|
||||||
|
@ -236,7 +210,8 @@ describe("ENR", function () {
|
||||||
expect(decoded.udp).to.be.equal(30303);
|
expect(decoded.udp).to.be.equal(30303);
|
||||||
expect(decoded.ip).to.be.equal("127.0.0.1");
|
expect(decoded.ip).to.be.equal("127.0.0.1");
|
||||||
expect(decoded).to.deep.equal(record);
|
expect(decoded).to.deep.equal(record);
|
||||||
expect(record.encodeTxt(privateKey)).to.equal(testTxt);
|
const recordTxt = await record.encodeTxt(privateKey);
|
||||||
|
expect(recordTxt).to.equal(testTxt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import base64url from "base64url";
|
|
||||||
import { toBigIntBE } from "bigint-buffer";
|
|
||||||
import { Multiaddr, protocols } from "multiaddr";
|
import { Multiaddr, protocols } from "multiaddr";
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
|
@ -8,12 +6,10 @@ import PeerId from "peer-id";
|
||||||
import * as RLP from "rlp";
|
import * as RLP from "rlp";
|
||||||
import { encode as varintEncode } from "varint";
|
import { encode as varintEncode } from "varint";
|
||||||
|
|
||||||
import {
|
import { bytesToUtf8, utf8ToBytes } from "../utf8";
|
||||||
ERR_INVALID_ID,
|
import { base64ToBytes, bytesToBase64, bytesToHex } from "../utils";
|
||||||
ERR_NO_SIGNATURE,
|
|
||||||
MAX_RECORD_SIZE,
|
import { ERR_INVALID_ID, ERR_NO_SIGNATURE, MAX_RECORD_SIZE } from "./constants";
|
||||||
MULTIADDR_LENGTH_SIZE,
|
|
||||||
} from "./constants";
|
|
||||||
import {
|
import {
|
||||||
createKeypair,
|
createKeypair,
|
||||||
createKeypairFromPeerId,
|
createKeypairFromPeerId,
|
||||||
|
@ -21,28 +17,32 @@ import {
|
||||||
IKeypair,
|
IKeypair,
|
||||||
KeypairType,
|
KeypairType,
|
||||||
} from "./keypair";
|
} from "./keypair";
|
||||||
|
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec";
|
||||||
import { ENRKey, ENRValue, NodeId, SequenceNumber } from "./types";
|
import { ENRKey, ENRValue, NodeId, SequenceNumber } from "./types";
|
||||||
import * as v4 from "./v4";
|
import * as v4 from "./v4";
|
||||||
|
|
||||||
export class ENR extends Map<ENRKey, ENRValue> {
|
export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
public static readonly RECORD_PREFIX = "enr:";
|
public static readonly RECORD_PREFIX = "enr:";
|
||||||
public seq: SequenceNumber;
|
public seq: SequenceNumber;
|
||||||
public signature: Buffer | null;
|
public signature: Uint8Array | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kvs: Record<ENRKey, ENRValue> = {},
|
kvs: Record<ENRKey, ENRValue> = {},
|
||||||
seq: SequenceNumber = 1n,
|
seq: SequenceNumber = 1n,
|
||||||
signature: Buffer | null = null
|
signature: Uint8Array | null = null
|
||||||
) {
|
) {
|
||||||
super(Object.entries(kvs));
|
super(Object.entries(kvs));
|
||||||
this.seq = seq;
|
this.seq = seq;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createV4(publicKey: Buffer, kvs: Record<ENRKey, ENRValue> = {}): ENR {
|
static createV4(
|
||||||
|
publicKey: Uint8Array,
|
||||||
|
kvs: Record<ENRKey, ENRValue> = {}
|
||||||
|
): ENR {
|
||||||
return new ENR({
|
return new ENR({
|
||||||
...kvs,
|
...kvs,
|
||||||
id: Buffer.from("v4"),
|
id: utf8ToBytes("v4"),
|
||||||
secp256k1: publicKey,
|
secp256k1: publicKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,13 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
}
|
}
|
||||||
const obj: Record<ENRKey, ENRValue> = {};
|
const obj: Record<ENRKey, ENRValue> = {};
|
||||||
for (let i = 0; i < kvs.length; i += 2) {
|
for (let i = 0; i < kvs.length; i += 2) {
|
||||||
obj[kvs[i].toString()] = Buffer.from(kvs[i + 1]);
|
obj[kvs[i].toString()] = new Uint8Array(kvs[i + 1]);
|
||||||
}
|
}
|
||||||
const enr = new ENR(obj, toBigIntBE(seq), signature);
|
const enr = new ENR(
|
||||||
|
obj,
|
||||||
|
BigInt("0x" + bytesToHex(seq)),
|
||||||
|
new Uint8Array(signature)
|
||||||
|
);
|
||||||
|
|
||||||
if (!enr.verify(RLP.encode([seq, ...kvs]), signature)) {
|
if (!enr.verify(RLP.encode([seq, ...kvs]), signature)) {
|
||||||
throw new Error("Unable to verify ENR signature");
|
throw new Error("Unable to verify ENR signature");
|
||||||
|
@ -88,7 +92,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return enr;
|
return enr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static decode(encoded: Buffer): ENR {
|
static decode(encoded: Uint8Array): ENR {
|
||||||
const decoded = RLP.decode(encoded) as unknown as Buffer[];
|
const decoded = RLP.decode(encoded) as unknown as Buffer[];
|
||||||
return ENR.decodeFromValues(decoded);
|
return ENR.decodeFromValues(decoded);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +103,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
`"string encoded ENR must start with '${this.RECORD_PREFIX}'`
|
`"string encoded ENR must start with '${this.RECORD_PREFIX}'`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ENR.decode(base64url.toBuffer(encoded.slice(4)));
|
return ENR.decode(base64ToBytes(encoded.slice(4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
set(k: ENRKey, v: ENRValue): this {
|
set(k: ENRKey, v: ENRValue): this {
|
||||||
|
@ -109,9 +113,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): string {
|
get id(): string {
|
||||||
const id = this.get("id") as Buffer;
|
const id = this.get("id");
|
||||||
if (!id) throw new Error("id not found.");
|
if (!id) throw new Error("id not found.");
|
||||||
return id.toString("utf8");
|
return bytesToUtf8(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get keypairType(): KeypairType {
|
get keypairType(): KeypairType {
|
||||||
|
@ -123,27 +127,31 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get publicKey(): Buffer {
|
get publicKey(): Uint8Array | undefined {
|
||||||
switch (this.id) {
|
switch (this.id) {
|
||||||
case "v4":
|
case "v4":
|
||||||
return this.get("secp256k1") as Buffer;
|
return this.get("secp256k1");
|
||||||
default:
|
default:
|
||||||
throw new Error(ERR_INVALID_ID);
|
throw new Error(ERR_INVALID_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get keypair(): IKeypair {
|
get keypair(): IKeypair | undefined {
|
||||||
return createKeypair(this.keypairType, undefined, this.publicKey);
|
if (this.publicKey) {
|
||||||
|
const publicKey = this.publicKey;
|
||||||
|
return createKeypair(this.keypairType, undefined, publicKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
get peerId(): PeerId {
|
get peerId(): PeerId | undefined {
|
||||||
return createPeerIdFromKeypair(this.keypair);
|
return this.keypair ? createPeerIdFromKeypair(this.keypair) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get nodeId(): NodeId {
|
get nodeId(): NodeId | undefined {
|
||||||
switch (this.id) {
|
switch (this.id) {
|
||||||
case "v4":
|
case "v4":
|
||||||
return v4.nodeId(this.publicKey);
|
return this.publicKey ? v4.nodeId(this.publicKey) : undefined;
|
||||||
default:
|
default:
|
||||||
throw new Error(ERR_INVALID_ID);
|
throw new Error(ERR_INVALID_ID);
|
||||||
}
|
}
|
||||||
|
@ -266,32 +274,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
get multiaddrs(): Multiaddr[] | undefined {
|
get multiaddrs(): Multiaddr[] | undefined {
|
||||||
const raw = this.get("multiaddrs");
|
const raw = this.get("multiaddrs");
|
||||||
|
|
||||||
if (raw) {
|
if (raw) return decodeMultiaddrs(raw);
|
||||||
const multiaddrs = [];
|
|
||||||
|
|
||||||
try {
|
return;
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
while (index < raw.length) {
|
|
||||||
const sizeBytes = raw.slice(index, index + 2);
|
|
||||||
const size = Buffer.from(sizeBytes).readUInt16BE(0);
|
|
||||||
|
|
||||||
const multiaddrBytes = raw.slice(
|
|
||||||
index + MULTIADDR_LENGTH_SIZE,
|
|
||||||
index + size + MULTIADDR_LENGTH_SIZE
|
|
||||||
);
|
|
||||||
const multiaddr = new Multiaddr(multiaddrBytes);
|
|
||||||
|
|
||||||
multiaddrs.push(multiaddr);
|
|
||||||
index += size + MULTIADDR_LENGTH_SIZE;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error("Invalid value in multiaddrs field");
|
|
||||||
}
|
|
||||||
return multiaddrs;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -303,37 +288,14 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
*
|
*
|
||||||
* If the peer information only contains information that can be represented with the ENR pre-defined keys
|
* If the peer information only contains information that can be represented with the ENR pre-defined keys
|
||||||
* (ip, tcp, etc) then the usage of [[setLocationMultiaddr]] should be preferred.
|
* (ip, tcp, etc) then the usage of [[setLocationMultiaddr]] should be preferred.
|
||||||
*
|
* The multiaddresses stored in this field must be location multiaddresses,
|
||||||
* The multiaddresses stored in this field must to be location multiaddresses, ie, peer id less.
|
* ie, without a peer id.
|
||||||
*/
|
*/
|
||||||
set multiaddrs(multiaddrs: Multiaddr[] | undefined) {
|
set multiaddrs(multiaddrs: Multiaddr[] | undefined) {
|
||||||
if (multiaddrs === undefined) {
|
if (multiaddrs === undefined) {
|
||||||
this.delete("multiaddrs");
|
this.delete("multiaddrs");
|
||||||
} else {
|
} else {
|
||||||
let multiaddrsBuf = Buffer.from([]);
|
const multiaddrsBuf = encodeMultiaddrs(multiaddrs);
|
||||||
|
|
||||||
multiaddrs.forEach((multiaddr) => {
|
|
||||||
if (multiaddr.getPeerId())
|
|
||||||
throw new Error("`multiaddr` field MUST not contain peer id");
|
|
||||||
|
|
||||||
const bytes = multiaddr.bytes;
|
|
||||||
|
|
||||||
let buf = Buffer.alloc(2);
|
|
||||||
|
|
||||||
// Prepend the size of the next entry
|
|
||||||
const written = buf.writeUInt16BE(bytes.length, 0);
|
|
||||||
|
|
||||||
if (written !== MULTIADDR_LENGTH_SIZE) {
|
|
||||||
throw new Error(
|
|
||||||
`Internal error: unsigned 16-bit integer was not written in ${MULTIADDR_LENGTH_SIZE} bytes`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = Buffer.concat([buf, bytes]);
|
|
||||||
|
|
||||||
multiaddrsBuf = Buffer.concat([multiaddrsBuf, buf]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set("multiaddrs", multiaddrsBuf);
|
this.set("multiaddrs", multiaddrsBuf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,9 +389,13 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
getFullMultiaddr(
|
getFullMultiaddr(
|
||||||
protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6"
|
protocol: "udp" | "udp4" | "udp6" | "tcp" | "tcp4" | "tcp6"
|
||||||
): Multiaddr | undefined {
|
): Multiaddr | undefined {
|
||||||
|
if (this.peerId) {
|
||||||
const locationMultiaddr = this.getLocationMultiaddr(protocol);
|
const locationMultiaddr = this.getLocationMultiaddr(protocol);
|
||||||
if (locationMultiaddr) {
|
if (locationMultiaddr) {
|
||||||
return locationMultiaddr.encapsulate(`/p2p/${this.peerId.toB58String()}`);
|
return locationMultiaddr.encapsulate(
|
||||||
|
`/p2p/${this.peerId.toB58String()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -438,15 +404,16 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
* Returns the full multiaddrs from the `multiaddrs` ENR field.
|
* Returns the full multiaddrs from the `multiaddrs` ENR field.
|
||||||
*/
|
*/
|
||||||
getFullMultiaddrs(): Multiaddr[] {
|
getFullMultiaddrs(): Multiaddr[] {
|
||||||
if (this.multiaddrs) {
|
if (this.peerId && this.multiaddrs) {
|
||||||
|
const peerId = this.peerId;
|
||||||
return this.multiaddrs.map((ma) => {
|
return this.multiaddrs.map((ma) => {
|
||||||
return ma.encapsulate(`/p2p/${this.peerId.toB58String()}`);
|
return ma.encapsulate(`/p2p/${peerId.toB58String()}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(data: Buffer, signature: Buffer): 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);
|
||||||
}
|
}
|
||||||
|
@ -456,7 +423,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return v4.verify(this.publicKey, data, signature);
|
return v4.verify(this.publicKey, data, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
sign(data: Buffer, privateKey: Buffer): Buffer {
|
sign(data: Uint8Array, privateKey: Uint8Array): Uint8Array {
|
||||||
switch (this.id) {
|
switch (this.id) {
|
||||||
case "v4":
|
case "v4":
|
||||||
this.signature = v4.sign(privateKey, data);
|
this.signature = v4.sign(privateKey, data);
|
||||||
|
@ -467,7 +434,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return this.signature;
|
return this.signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeToValues(privateKey?: Buffer): (ENRKey | ENRValue | number)[] {
|
encodeToValues(privateKey?: Uint8Array): (ENRKey | ENRValue | number)[] {
|
||||||
// sort keys and flatten into [k, v, k, v, ...]
|
// sort keys and flatten into [k, v, k, v, ...]
|
||||||
const content: Array<ENRKey | ENRValue | number> = Array.from(this.keys())
|
const content: Array<ENRKey | ENRValue | number> = Array.from(this.keys())
|
||||||
.sort((a, b) => a.localeCompare(b))
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
@ -485,7 +452,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
encode(privateKey?: Buffer): Buffer {
|
encode(privateKey?: Uint8Array): Uint8Array {
|
||||||
const encoded = RLP.encode(this.encodeToValues(privateKey));
|
const encoded = RLP.encode(this.encodeToValues(privateKey));
|
||||||
if (encoded.length >= MAX_RECORD_SIZE) {
|
if (encoded.length >= MAX_RECORD_SIZE) {
|
||||||
throw new Error("ENR must be less than 300 bytes");
|
throw new Error("ENR must be less than 300 bytes");
|
||||||
|
@ -493,7 +460,7 @@ export class ENR extends Map<ENRKey, ENRValue> {
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeTxt(privateKey?: Buffer): string {
|
async encodeTxt(privateKey?: Uint8Array): Promise<string> {
|
||||||
return ENR.RECORD_PREFIX + base64url.encode(this.encode(privateKey));
|
return ENR.RECORD_PREFIX + (await bytesToBase64(this.encode(privateKey)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ export async function generateKeypair(type: KeypairType): Promise<IKeypair> {
|
||||||
|
|
||||||
export function createKeypair(
|
export function createKeypair(
|
||||||
type: KeypairType,
|
type: KeypairType,
|
||||||
privateKey?: Buffer,
|
privateKey?: Uint8Array,
|
||||||
publicKey?: Buffer
|
publicKey?: Uint8Array
|
||||||
): IKeypair {
|
): IKeypair {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case KeypairType.secp256k1:
|
case KeypairType.secp256k1:
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
import { Buffer } from "buffer";
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
import * as secp256k1 from "secp256k1";
|
import * as secp256k1 from "secp256k1";
|
||||||
|
|
||||||
import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types";
|
import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types";
|
||||||
|
|
||||||
export function secp256k1PublicKeyToCompressed(publicKey: Uint8Array): Buffer {
|
export function secp256k1PublicKeyToCompressed(
|
||||||
|
publicKey: Uint8Array
|
||||||
|
): Uint8Array {
|
||||||
if (publicKey.length === 64) {
|
if (publicKey.length === 64) {
|
||||||
publicKey = Buffer.concat([Buffer.from([4]), publicKey]);
|
publicKey = Buffer.concat([Buffer.from([4]), publicKey]);
|
||||||
}
|
}
|
||||||
return Buffer.from(secp256k1.publicKeyConvert(publicKey, true));
|
return Buffer.from(secp256k1.publicKeyConvert(publicKey, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function secp256k1PublicKeyToFull(publicKey: Uint8Array): Buffer {
|
export function secp256k1PublicKeyToFull(publicKey: Uint8Array): Uint8Array {
|
||||||
if (publicKey.length === 64) {
|
if (publicKey.length === 64) {
|
||||||
return Buffer.concat([Buffer.from([4]), publicKey]);
|
return Buffer.concat([Buffer.from([4]), publicKey]);
|
||||||
}
|
}
|
||||||
return Buffer.from(secp256k1.publicKeyConvert(publicKey, false));
|
return Buffer.from(secp256k1.publicKeyConvert(publicKey, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function secp256k1PublicKeyToRaw(publicKey: Uint8Array): Buffer {
|
export function secp256k1PublicKeyToRaw(publicKey: Uint8Array): Uint8Array {
|
||||||
return Buffer.from(secp256k1.publicKeyConvert(publicKey, false).slice(1));
|
return Buffer.from(secp256k1.publicKeyConvert(publicKey, false).slice(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
|
||||||
{
|
{
|
||||||
readonly type: KeypairType;
|
readonly type: KeypairType;
|
||||||
|
|
||||||
constructor(privateKey?: Buffer, publicKey?: Buffer) {
|
constructor(privateKey?: Uint8Array, publicKey?: Uint8Array) {
|
||||||
let pub = publicKey;
|
let pub = publicKey;
|
||||||
if (pub) {
|
if (pub) {
|
||||||
pub = secp256k1PublicKeyToCompressed(pub);
|
pub = secp256k1PublicKeyToCompressed(pub);
|
||||||
|
@ -58,12 +59,12 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sign(msg: Buffer): Buffer {
|
sign(msg: Uint8Array): Uint8Array {
|
||||||
const { signature, recid } = secp256k1.ecdsaSign(msg, this.privateKey);
|
const { signature, recid } = secp256k1.ecdsaSign(msg, this.privateKey);
|
||||||
return Buffer.concat([signature, Buffer.from([recid])]);
|
return Buffer.concat([signature, Buffer.from([recid])]);
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(msg: Buffer, sig: Buffer): boolean {
|
verify(msg: Uint8Array, sig: Uint8Array): boolean {
|
||||||
return secp256k1.ecdsaVerify(sig, msg, this.publicKey);
|
return secp256k1.ecdsaVerify(sig, msg, this.publicKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,25 +6,25 @@ export enum KeypairType {
|
||||||
|
|
||||||
export interface IKeypair {
|
export interface IKeypair {
|
||||||
type: KeypairType;
|
type: KeypairType;
|
||||||
privateKey: Buffer;
|
privateKey: Uint8Array;
|
||||||
publicKey: Buffer;
|
publicKey: Uint8Array;
|
||||||
privateKeyVerify(): boolean;
|
privateKeyVerify(): boolean;
|
||||||
publicKeyVerify(): boolean;
|
publicKeyVerify(): boolean;
|
||||||
sign(msg: Buffer): Buffer;
|
sign(msg: Uint8Array): Uint8Array;
|
||||||
verify(msg: Buffer, sig: Buffer): boolean;
|
verify(msg: Uint8Array, sig: Uint8Array): boolean;
|
||||||
hasPrivateKey(): boolean;
|
hasPrivateKey(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKeypairClass {
|
export interface IKeypairClass {
|
||||||
new (privateKey?: Buffer, publicKey?: Buffer): IKeypair;
|
new (privateKey?: Uint8Array, publicKey?: Uint8Array): IKeypair;
|
||||||
generate(): Promise<IKeypair>;
|
generate(): Promise<IKeypair>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AbstractKeypair {
|
export abstract class AbstractKeypair {
|
||||||
_privateKey?: Buffer;
|
_privateKey?: Uint8Array;
|
||||||
readonly _publicKey?: Buffer;
|
readonly _publicKey?: Uint8Array;
|
||||||
|
|
||||||
constructor(privateKey?: Buffer, publicKey?: Buffer) {
|
constructor(privateKey?: Uint8Array, publicKey?: Uint8Array) {
|
||||||
if ((this._privateKey = privateKey) && !this.privateKeyVerify()) {
|
if ((this._privateKey = privateKey) && !this.privateKeyVerify()) {
|
||||||
throw new Error("Invalid private key");
|
throw new Error("Invalid private key");
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,14 @@ export abstract class AbstractKeypair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get privateKey(): Buffer {
|
get privateKey(): Uint8Array {
|
||||||
if (!this._privateKey) {
|
if (!this._privateKey) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
return this._privateKey;
|
return this._privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
get publicKey(): Buffer {
|
get publicKey(): Uint8Array {
|
||||||
if (!this._publicKey) {
|
if (!this._publicKey) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { Multiaddr } from "multiaddr";
|
||||||
|
|
||||||
|
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec";
|
||||||
|
|
||||||
|
describe("ENR multiaddrs codec", function () {
|
||||||
|
it("Sample", async () => {
|
||||||
|
const multiaddrs = [
|
||||||
|
new Multiaddr(
|
||||||
|
"/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss"
|
||||||
|
),
|
||||||
|
new Multiaddr(
|
||||||
|
"/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss"
|
||||||
|
),
|
||||||
|
new Multiaddr(
|
||||||
|
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const bytes = encodeMultiaddrs(multiaddrs);
|
||||||
|
const result = decodeMultiaddrs(bytes);
|
||||||
|
|
||||||
|
const multiaddrsAsStr = result.map((ma) => ma.toString());
|
||||||
|
expect(multiaddrsAsStr).to.include(
|
||||||
|
"/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss"
|
||||||
|
);
|
||||||
|
expect(multiaddrsAsStr).to.include(
|
||||||
|
"/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss"
|
||||||
|
);
|
||||||
|
expect(multiaddrsAsStr).to.include(
|
||||||
|
"/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Multiaddr } from "multiaddr";
|
||||||
|
|
||||||
|
import { MULTIADDR_LENGTH_SIZE } from "./constants";
|
||||||
|
|
||||||
|
export function decodeMultiaddrs(bytes: Uint8Array): Multiaddr[] {
|
||||||
|
const multiaddrs = [];
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while (index < bytes.length) {
|
||||||
|
const sizeDataView = new DataView(
|
||||||
|
bytes.buffer,
|
||||||
|
index,
|
||||||
|
MULTIADDR_LENGTH_SIZE
|
||||||
|
);
|
||||||
|
const size = sizeDataView.getUint16(0);
|
||||||
|
index += MULTIADDR_LENGTH_SIZE;
|
||||||
|
|
||||||
|
const multiaddrBytes = bytes.slice(index, index + size);
|
||||||
|
index += size;
|
||||||
|
|
||||||
|
const multiaddr = new Multiaddr(multiaddrBytes);
|
||||||
|
multiaddrs.push(multiaddr);
|
||||||
|
}
|
||||||
|
return multiaddrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeMultiaddrs(multiaddrs: Multiaddr[]): Uint8Array {
|
||||||
|
const totalLength = multiaddrs.reduce(
|
||||||
|
(acc, ma) => acc + MULTIADDR_LENGTH_SIZE + ma.bytes.length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const bytes = new Uint8Array(totalLength);
|
||||||
|
const dataView = new DataView(bytes.buffer);
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
multiaddrs.forEach((multiaddr) => {
|
||||||
|
if (multiaddr.getPeerId())
|
||||||
|
throw new Error("`multiaddr` field MUST not contain peer id");
|
||||||
|
|
||||||
|
// Prepend the size of the next entry
|
||||||
|
dataView.setUint16(index, multiaddr.bytes.length);
|
||||||
|
index += MULTIADDR_LENGTH_SIZE;
|
||||||
|
|
||||||
|
bytes.set(multiaddr.bytes, index);
|
||||||
|
index += multiaddr.bytes.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
|
@ -6,24 +6,28 @@ import * as secp256k1 from "secp256k1";
|
||||||
import { createNodeId } from "./create";
|
import { createNodeId } from "./create";
|
||||||
import { NodeId } from "./types";
|
import { NodeId } from "./types";
|
||||||
|
|
||||||
export function hash(input: Uint8Array): Buffer {
|
export function hash(input: Uint8Array): Uint8Array {
|
||||||
return Buffer.from(keccak256.arrayBuffer(input));
|
return new Uint8Array(keccak256.arrayBuffer(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPrivateKey(): Promise<Buffer> {
|
export async function createPrivateKey(): Promise<Uint8Array> {
|
||||||
return Buffer.from(await randomBytes(32));
|
return new Uint8Array(await randomBytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publicKey(privKey: Uint8Array): Buffer {
|
export function publicKey(privKey: Uint8Array): Uint8Array {
|
||||||
return Buffer.from(secp256k1.publicKeyCreate(privKey));
|
return new Uint8Array(secp256k1.publicKeyCreate(privKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sign(privKey: Uint8Array, msg: Uint8Array): Buffer {
|
export function sign(privKey: Uint8Array, msg: Uint8Array): Uint8Array {
|
||||||
const { signature } = secp256k1.ecdsaSign(hash(msg), privKey);
|
const { signature } = secp256k1.ecdsaSign(hash(msg), privKey);
|
||||||
return Buffer.from(signature);
|
return new Uint8Array(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean {
|
export function verify(
|
||||||
|
pubKey: Uint8Array,
|
||||||
|
msg: Uint8Array,
|
||||||
|
sig: Uint8Array
|
||||||
|
): boolean {
|
||||||
// Remove the recovery id if present (byte #65)
|
// Remove the recovery id if present (byte #65)
|
||||||
return secp256k1.ecdsaVerify(sig.slice(0, 64), hash(msg), pubKey);
|
return secp256k1.ecdsaVerify(sig.slice(0, 64), hash(msg), pubKey);
|
||||||
}
|
}
|
||||||
|
@ -37,11 +41,11 @@ export function nodeId(pubKey: Uint8Array): NodeId {
|
||||||
export class ENRKeyPair {
|
export class ENRKeyPair {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly nodeId: NodeId,
|
public readonly nodeId: NodeId,
|
||||||
public readonly privateKey: Buffer,
|
public readonly privateKey: Uint8Array,
|
||||||
public readonly publicKey: Buffer
|
public readonly publicKey: Uint8Array
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static async create(privateKey?: Buffer): Promise<ENRKeyPair> {
|
public static async create(privateKey?: Uint8Array): Promise<ENRKeyPair> {
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
if (!secp256k1.privateKeyVerify(privateKey)) {
|
if (!secp256k1.privateKeyVerify(privateKey)) {
|
||||||
throw new Error("Invalid private key");
|
throw new Error("Invalid private key");
|
||||||
|
@ -54,11 +58,11 @@ export class ENRKeyPair {
|
||||||
return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
|
return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sign(msg: Buffer): Buffer {
|
public sign(msg: Uint8Array): Uint8Array {
|
||||||
return sign(this.privateKey, msg);
|
return sign(this.privateKey, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public verify(msg: Buffer, sig: Buffer): boolean {
|
public verify(msg: Uint8Array, sig: Uint8Array): boolean {
|
||||||
return verify(this.publicKey, msg, sig);
|
return verify(this.publicKey, msg, sig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Decode bytes to utf-8 string.
|
||||||
|
*/
|
||||||
// Thanks https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
|
// Thanks https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
|
||||||
export function bytesToUtf8(bytes: Uint8Array): string {
|
export function bytesToUtf8(bytes: Uint8Array): string {
|
||||||
let i = 0,
|
let i = 0,
|
||||||
|
@ -41,3 +44,42 @@ export function bytesToUtf8(bytes: Uint8Array): string {
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode utf-8 string to byte array
|
||||||
|
*/
|
||||||
|
// Thanks https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
|
||||||
|
export function utf8ToBytes(s: string): Uint8Array {
|
||||||
|
let i = 0;
|
||||||
|
const bytes = new Uint8Array(s.length * 4);
|
||||||
|
for (let ci = 0; ci != s.length; ci++) {
|
||||||
|
let c = s.charCodeAt(ci);
|
||||||
|
if (c < 128) {
|
||||||
|
bytes[i++] = c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c < 2048) {
|
||||||
|
bytes[i++] = (c >> 6) | 192;
|
||||||
|
} else {
|
||||||
|
if (c > 0xd7ff && c < 0xdc00) {
|
||||||
|
if (++ci >= s.length)
|
||||||
|
throw new Error("UTF-8 encode: incomplete surrogate pair");
|
||||||
|
const c2 = s.charCodeAt(ci);
|
||||||
|
if (c2 < 0xdc00 || c2 > 0xdfff)
|
||||||
|
throw new Error(
|
||||||
|
"UTF-8 encode: second surrogate character 0x" +
|
||||||
|
c2.toString(16) +
|
||||||
|
" at index " +
|
||||||
|
ci +
|
||||||
|
" out of range"
|
||||||
|
);
|
||||||
|
c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
|
||||||
|
bytes[i++] = (c >> 18) | 240;
|
||||||
|
bytes[i++] = ((c >> 12) & 63) | 128;
|
||||||
|
} else bytes[i++] = (c >> 12) | 224;
|
||||||
|
bytes[i++] = ((c >> 6) & 63) | 128;
|
||||||
|
}
|
||||||
|
bytes[i++] = (c & 63) | 128;
|
||||||
|
}
|
||||||
|
return bytes.subarray(0, i);
|
||||||
|
}
|
|
@ -81,3 +81,49 @@ export function equalByteArrays(
|
||||||
export function keccak256Buf(message: Message): Buffer {
|
export function keccak256Buf(message: Message): Buffer {
|
||||||
return Buffer.from(keccak256.arrayBuffer(message));
|
return Buffer.from(keccak256.arrayBuffer(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert base64 string to byte array.
|
||||||
|
*/
|
||||||
|
export function base64ToBytes(base64: string): Uint8Array {
|
||||||
|
const e = new Map<string, number>();
|
||||||
|
|
||||||
|
const len = base64.length;
|
||||||
|
const res = [];
|
||||||
|
const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
e.set(A.charAt(i), i);
|
||||||
|
}
|
||||||
|
e.set("+", 62);
|
||||||
|
e.set("/", 63);
|
||||||
|
|
||||||
|
let b = 0,
|
||||||
|
l = 0,
|
||||||
|
a;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const c = e.get(base64.charAt(i));
|
||||||
|
if (c === undefined)
|
||||||
|
throw new Error(`Invalid base64 character ${base64.charAt(i)}`);
|
||||||
|
b = (b << 6) + c;
|
||||||
|
l += 6;
|
||||||
|
while (l >= 8) {
|
||||||
|
((a = (b >>> (l -= 8)) & 0xff) || i < len - 2) && res.push(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Uint8Array(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert byte array to base64 string.
|
||||||
|
*/
|
||||||
|
export async function bytesToBase64(bytes: Uint8Array): Promise<string> {
|
||||||
|
const base64url: string = await new Promise((r) => {
|
||||||
|
const reader = new window.FileReader();
|
||||||
|
reader.onload = (): void => r(reader.result as string);
|
||||||
|
reader.readAsDataURL(new Blob([bytes]));
|
||||||
|
});
|
||||||
|
const base64 = base64url.split(",", 2)[1];
|
||||||
|
// We want URL and Filename Safe base64: https://datatracker.ietf.org/doc/html/rfc4648#section-5
|
||||||
|
// Without trailing padding
|
||||||
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
}
|
||||||
|
|
|
@ -19,10 +19,7 @@ describe("Waku Dial", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
if (
|
if (process.env.CI || window?.__env__?.CI) {
|
||||||
process.env.CI ||
|
|
||||||
(typeof window !== "undefined" && window?.__env__?.CI)
|
|
||||||
) {
|
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,13 @@ import { expect } from "chai";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bytesToUtf8,
|
|
||||||
makeLogFileName,
|
makeLogFileName,
|
||||||
NimWaku,
|
NimWaku,
|
||||||
NOISE_KEY_1,
|
NOISE_KEY_1,
|
||||||
WakuRelayMessage,
|
WakuRelayMessage,
|
||||||
} from "../../test_utils";
|
} from "../../test_utils";
|
||||||
import { delay } from "../../test_utils/delay";
|
import { delay } from "../../test_utils/delay";
|
||||||
|
import { bytesToUtf8 } from "../utf8";
|
||||||
import { hexToBytes } from "../utils";
|
import { hexToBytes } from "../utils";
|
||||||
import { Protocols, Waku } from "../waku";
|
import { Protocols, Waku } from "../waku";
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,3 @@ export * from "./async_fs";
|
||||||
export * from "./constants";
|
export * from "./constants";
|
||||||
export * from "./log_file";
|
export * from "./log_file";
|
||||||
export * from "./nim_waku";
|
export * from "./nim_waku";
|
||||||
export * from "./utf8";
|
|
||||||
|
|
|
@ -335,7 +335,6 @@ export class NimWaku {
|
||||||
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
return { peerId: this.peerId, multiaddrWithId: this.multiaddrWithId };
|
||||||
}
|
}
|
||||||
const res = await this.info();
|
const res = await this.info();
|
||||||
console.log(res);
|
|
||||||
this.multiaddrWithId = res.listenAddresses
|
this.multiaddrWithId = res.listenAddresses
|
||||||
.map((ma) => multiaddr(ma))
|
.map((ma) => multiaddr(ma))
|
||||||
.find((ma) => ma.protoNames().includes("ws"));
|
.find((ma) => ma.protoNames().includes("ws"));
|
||||||
|
|
Loading…
Reference in New Issue