From d533fd85a7a5b1fcab9eacac82031ee23604385f Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 31 May 2022 15:10:27 -0400 Subject: [PATCH 01/32] fix: filter multiaddr in nodeAddresstostring --- src/session/nodeInfo.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/session/nodeInfo.ts b/src/session/nodeInfo.ts index 9ad6876e..b0d240aa 100644 --- a/src/session/nodeInfo.ts +++ b/src/session/nodeInfo.ts @@ -11,7 +11,11 @@ export interface INodeAddress { } export function nodeAddressToString(nodeAddr: INodeAddress): string { - return nodeAddr.nodeId + ":" + Buffer.from(nodeAddr.socketAddr.bytes).toString("hex"); + // Since `sendRPCMessage` takes an ENR or Multiaddr, remove any p2p portions of the + // multiaddr since only the UDP socket addr is included in the session cache + // key (e.g. /ip4/127.0.0.1/udp/9000/p2p/Qm...) + const normalizedAddr = nodeAddr.socketAddr.decapsulateCode(421); + return nodeAddr.nodeId + ":" + Buffer.from(normalizedAddr.bytes).toString("hex"); } /** From ee9f1c25046d0b7dc4f56e3ba17507fb184c8a9b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 31 May 2022 15:20:03 -0400 Subject: [PATCH 02/32] Clarify comments --- src/session/nodeInfo.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/session/nodeInfo.ts b/src/session/nodeInfo.ts index b0d240aa..38d59068 100644 --- a/src/session/nodeInfo.ts +++ b/src/session/nodeInfo.ts @@ -11,9 +11,9 @@ export interface INodeAddress { } export function nodeAddressToString(nodeAddr: INodeAddress): string { - // Since `sendRPCMessage` takes an ENR or Multiaddr, remove any p2p portions of the - // multiaddr since only the UDP socket addr is included in the session cache - // key (e.g. /ip4/127.0.0.1/udp/9000/p2p/Qm...) + // Since the Discv5 service allows a Multiaddr in outbound message handlers and requires a multiaddr input to + // have a peer ID specified, we remove any p2p portions of the multiaddr when generating the nodeAddressString + // since only the UDP socket addr is included in the session cache key (e.g. /ip4/127.0.0.1/udp/9000/p2p/Qm...) const normalizedAddr = nodeAddr.socketAddr.decapsulateCode(421); return nodeAddr.nodeId + ":" + Buffer.from(normalizedAddr.bytes).toString("hex"); } From 5a9c9df400c40c7b2c75eb3014db31b0bc7a5c8c Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 31 May 2022 15:23:04 -0400 Subject: [PATCH 03/32] lint --- src/session/nodeInfo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session/nodeInfo.ts b/src/session/nodeInfo.ts index 38d59068..d77d0840 100644 --- a/src/session/nodeInfo.ts +++ b/src/session/nodeInfo.ts @@ -12,7 +12,7 @@ export interface INodeAddress { export function nodeAddressToString(nodeAddr: INodeAddress): string { // Since the Discv5 service allows a Multiaddr in outbound message handlers and requires a multiaddr input to - // have a peer ID specified, we remove any p2p portions of the multiaddr when generating the nodeAddressString + // have a peer ID specified, we remove any p2p portions of the multiaddr when generating the nodeAddressString // since only the UDP socket addr is included in the session cache key (e.g. /ip4/127.0.0.1/udp/9000/p2p/Qm...) const normalizedAddr = nodeAddr.socketAddr.decapsulateCode(421); return nodeAddr.nodeId + ":" + Buffer.from(normalizedAddr.bytes).toString("hex"); From 995226222e72aaca87e88bb99631761887b51bf2 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Sat, 2 Jul 2022 13:51:12 -0400 Subject: [PATCH 04/32] partial migration to noble/libp2p-crypto --- package.json | 3 +- src/enr/enr.ts | 16 +++--- src/enr/v4.ts | 24 ++++----- src/keypair/secp256k1.ts | 43 ++++++++-------- src/message/create.ts | 4 +- src/message/encode.ts | 10 ++-- src/packet/create.ts | 16 +++--- src/packet/encode.ts | 54 ++++++++++---------- src/transport/udp.ts | 9 ++-- src/util/crypto.ts | 20 ++++---- test/unit/enr/enr.test.ts | 4 +- test/unit/message/codec.test.ts | 4 +- test/unit/session/crypto.test.ts | 22 ++++----- types/bcrypto/index.d.ts | 84 -------------------------------- 14 files changed, 118 insertions(+), 195 deletions(-) delete mode 100644 types/bcrypto/index.d.ts diff --git a/package.json b/package.json index 20498ff6..798ece62 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,8 @@ "@libp2p/peer-id": "^1.1.13", "@multiformats/multiaddr": "^10.2.0", "base64url": "^3.0.1", - "bcrypto": "^5.4.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.0", "bigint-buffer": "^1.1.5", "debug": "^4.3.1", "dgram": "^1.0.1", diff --git a/src/enr/enr.ts b/src/enr/enr.ts index 5966e198..03c67510 100644 --- a/src/enr/enr.ts +++ b/src/enr/enr.ts @@ -315,17 +315,17 @@ export class ENR extends Map { } return v4.verify(this.publicKey, data, signature); } - sign(data: Buffer, privateKey: Buffer): Buffer { + async sign(data: Buffer, privateKey: Buffer): Promise { switch (this.id) { case "v4": - this.signature = v4.sign(privateKey, data); + this.signature = await v4.sign(privateKey, data); break; default: throw new Error(ERR_INVALID_ID); } return this.signature; } - encodeToValues(privateKey?: Buffer): (ENRKey | ENRValue | number)[] { + async encodeToValues(privateKey?: Buffer): Promise<(ENRKey | ENRValue | number)[]> { // sort keys and flatten into [k, v, k, v, ...] const content: Array = Array.from(this.keys()) .sort((a, b) => a.localeCompare(b)) @@ -333,7 +333,7 @@ export class ENR extends Map { .flat(); content.unshift(Number(this.seq)); if (privateKey) { - content.unshift(this.sign(RLP.encode(content), privateKey)); + content.unshift(await this.sign(RLP.encode(content), privateKey)); } else { if (!this.signature) { throw new Error(ERR_NO_SIGNATURE); @@ -342,14 +342,14 @@ export class ENR extends Map { } return content; } - encode(privateKey?: Buffer): Buffer { - const encoded = RLP.encode(this.encodeToValues(privateKey)); + async encode(privateKey?: Buffer): Promise { + const encoded = RLP.encode(await this.encodeToValues(privateKey)); if (encoded.length >= MAX_RECORD_SIZE) { throw new Error("ENR must be less than 300 bytes"); } return encoded; } - encodeTxt(privateKey?: Buffer): string { - return "enr:" + base64url.encode(Buffer.from(this.encode(privateKey))); + async encodeTxt(privateKey?: Buffer): Promise { + return "enr:" + base64url.encode(Buffer.from(await this.encode(privateKey))); } } diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 90d3c8a2..31cbc066 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -1,31 +1,31 @@ -import keccak from "bcrypto/lib/keccak.js"; -import secp256k1 from "bcrypto/lib/secp256k1.js"; +import { keccak_256 as keccak } from "@noble/hashes/sha3"; +import * as secp256k1 from "@noble/secp256k1"; import { NodeId } from "./types.js"; import { createNodeId } from "./create.js"; export function hash(input: Buffer): Buffer { - return keccak.digest(input); + return Buffer.from(keccak(input)); } export function createPrivateKey(): Buffer { - return secp256k1.privateKeyGenerate(); + return Buffer.from(secp256k1.utils.randomPrivateKey()); } export function publicKey(privKey: Buffer): Buffer { - return secp256k1.publicKeyCreate(privKey); + return Buffer.from(secp256k1.getPublicKey(Uint8Array.from(privKey))); } -export function sign(privKey: Buffer, msg: Buffer): Buffer { - return secp256k1.sign(hash(msg), privKey); +export async function sign(privKey: Buffer, msg: Buffer): Promise { + return Buffer.from(await secp256k1.sign(Uint8Array.from(hash(msg)), privKey)); } export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { - return secp256k1.verify(hash(msg), sig, pubKey); + return secp256k1.verify(Uint8Array.from(sig), Uint8Array.from(hash(msg)), Uint8Array.from(pubKey)); } export function nodeId(pubKey: Buffer): NodeId { - return createNodeId(hash(secp256k1.publicKeyConvert(pubKey, false).slice(1))); + return createNodeId(hash(pubKey)); } export class ENRKeyPair { @@ -35,7 +35,7 @@ export class ENRKeyPair { public constructor(privateKey?: Buffer) { if (privateKey) { - if (!secp256k1.privateKeyVerify(privateKey)) { + if (!secp256k1.utils.isValidPrivateKey(Uint8Array.from(privateKey))) { throw new Error("Invalid private key"); } } @@ -44,8 +44,8 @@ export class ENRKeyPair { this.nodeId = nodeId(this.publicKey); } - public sign(msg: Buffer): Buffer { - return sign(this.privateKey, msg); + public async sign(msg: Buffer): Promise { + return await sign(this.privateKey, msg); } public verify(msg: Buffer, sig: Buffer): boolean { diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index a461d739..543e7c38 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -1,7 +1,7 @@ -import secp256k1 from "bcrypto/lib/secp256k1.js"; +import * as secp from "@noble/secp256k1"; import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types.js"; import { ERR_INVALID_KEYPAIR_TYPE } from "./constants.js"; - +/* export function secp256k1PublicKeyToCompressed(publicKey: Buffer): Buffer { if (publicKey.length === 64) { publicKey = Buffer.concat([Buffer.from([4]), publicKey]); @@ -19,47 +19,48 @@ export function secp256k1PublicKeyToFull(publicKey: Buffer): Buffer { export function secp256k1PublicKeyToRaw(publicKey: Buffer): Buffer { return secp256k1.publicKeyConvert(publicKey, false).slice(1); } - +*/ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends AbstractKeypair implements IKeypair { readonly type: KeypairType; constructor(privateKey?: Buffer, publicKey?: Buffer) { - let pub = publicKey; - if (pub) { - pub = secp256k1PublicKeyToCompressed(pub); - } - super(privateKey, pub); + super(privateKey, publicKey); this.type = KeypairType.Secp256k1; } static generate(): Secp256k1Keypair { - const privateKey = secp256k1.privateKeyGenerate(); - const publicKey = secp256k1.publicKeyCreate(privateKey); - return new Secp256k1Keypair(privateKey, publicKey); + const privateKey = secp.utils.randomPrivateKey(); + const publicKey = secp.getPublicKey(privateKey); + return new Secp256k1Keypair(Buffer.from(privateKey), Buffer.from(publicKey)); } privateKeyVerify(key = this._privateKey): boolean { if (key) { - return secp256k1.privateKeyVerify(key); - } - return true; - } - publicKeyVerify(key = this._publicKey): boolean { - if (key) { - return secp256k1.publicKeyVerify(key); + return secp.utils.isValidPrivateKey(secp.utils.bytesToHex(Uint8Array.from(key))); } return true; } + sign(msg: Buffer): Buffer { - return secp256k1.sign(msg, this.privateKey); + return Buffer.from(secp.sign(secp.utils.bytesToHex(Uint8Array.from(msg)), this.privateKey)); } verify(msg: Buffer, sig: Buffer): boolean { - return secp256k1.verify(msg, sig, this.publicKey); + return secp.verify( + Uint8Array.from(sig), + Uint8Array.from(msg), + this.publicKey + ); } deriveSecret(keypair: IKeypair): Buffer { if (keypair.type !== this.type) { throw new Error(ERR_INVALID_KEYPAIR_TYPE); } - return secp256k1.derive(keypair.publicKey, this.privateKey); + const secret = Buffer.from( + secp.getSharedSecret( + Uint8Array.from(this.privateKey), + Uint8Array.from(keypair.publicKey) + ) + ); + return secret; } }; diff --git a/src/message/create.ts b/src/message/create.ts index 30fa6e71..26ecb1e6 100644 --- a/src/message/create.ts +++ b/src/message/create.ts @@ -1,4 +1,4 @@ -import { randomBytes } from "bcrypto/lib/random.js"; +import { randomBytes } from "@noble/hashes/utils"; import { toBigIntBE } from "bigint-buffer"; import { @@ -14,7 +14,7 @@ import { import { SequenceNumber, ENR } from "../enr/index.js"; export function createRequestId(): RequestId { - return toBigIntBE(randomBytes(8)); + return toBigIntBE(Buffer.from(randomBytes(8))); } export function createPingMessage(enrSeq: SequenceNumber): IPingMessage { diff --git a/src/message/encode.ts b/src/message/encode.ts index 11f7b9a8..dc5b7883 100644 --- a/src/message/encode.ts +++ b/src/message/encode.ts @@ -17,7 +17,7 @@ import { ITalkRespMessage, } from "./types.js"; -export function encode(message: Message): Buffer { +export async function encode(message: Message): Promise { switch (message.type) { case MessageType.PING: return encodePingMessage(message as IPingMessage); @@ -71,10 +71,10 @@ export function encodeFindNodeMessage(m: IFindNodeMessage): Buffer { return Buffer.concat([Buffer.from([MessageType.FINDNODE]), RLP.encode([toBuffer(m.id), m.distances])]); } -export function encodeNodesMessage(m: INodesMessage): Buffer { +export async function encodeNodesMessage(m: INodesMessage): Promise { return Buffer.concat([ Buffer.from([MessageType.NODES]), - RLP.encode([toBuffer(m.id), m.total, m.enrs.map((enr) => enr.encodeToValues())]), + RLP.encode([toBuffer(m.id), m.total, await Promise.all(m.enrs.map(async (enr) => enr.encodeToValues()))]), ]); } @@ -86,10 +86,10 @@ export function encodeTalkRespMessage(m: ITalkRespMessage): Buffer { return Buffer.concat([Buffer.from([MessageType.TALKRESP]), RLP.encode([toBuffer(m.id), m.response])]); } -export function encodeRegTopicMessage(m: IRegTopicMessage): Buffer { +export async function encodeRegTopicMessage(m: IRegTopicMessage): Promise { return Buffer.concat([ Buffer.from([MessageType.REGTOPIC]), - RLP.encode([toBuffer(m.id), m.topic, m.enr.encodeToValues(), m.ticket]), + RLP.encode([toBuffer(m.id), m.topic, await m.enr.encodeToValues(), m.ticket]), ]); } diff --git a/src/packet/create.ts b/src/packet/create.ts index 94a7d767..d67599f4 100644 --- a/src/packet/create.ts +++ b/src/packet/create.ts @@ -1,10 +1,14 @@ -import { randomBytes } from "bcrypto/lib/random.js"; +import { randomBytes } from "@noble/hashes/utils"; import { NodeId, SequenceNumber } from "../enr/index.js"; import { ID_NONCE_SIZE, MASKING_IV_SIZE, NONCE_SIZE } from "./constants.js"; import { encodeMessageAuthdata, encodeWhoAreYouAuthdata } from "./encode.js"; import { IHeader, IPacket, PacketType } from "./types.js"; -export function createHeader(flag: PacketType, authdata: Buffer, nonce = randomBytes(NONCE_SIZE)): IHeader { +export function createHeader( + flag: PacketType, + authdata: Buffer, + nonce = Buffer.from(randomBytes(NONCE_SIZE)) +): IHeader { return { protocolId: "discv5", version: 1, @@ -18,8 +22,8 @@ export function createHeader(flag: PacketType, authdata: Buffer, nonce = randomB export function createRandomPacket(srcId: NodeId): IPacket { const authdata = encodeMessageAuthdata({ srcId }); const header = createHeader(PacketType.Message, authdata); - const maskingIv = randomBytes(MASKING_IV_SIZE); - const message = randomBytes(44); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE)); + const message = Buffer.from(randomBytes(44)); return { maskingIv, header, @@ -28,10 +32,10 @@ export function createRandomPacket(srcId: NodeId): IPacket { } export function createWhoAreYouPacket(nonce: Buffer, enrSeq: SequenceNumber): IPacket { - const idNonce = randomBytes(ID_NONCE_SIZE); + const idNonce = Buffer.from(randomBytes(ID_NONCE_SIZE)); const authdata = encodeWhoAreYouAuthdata({ idNonce, enrSeq }); const header = createHeader(PacketType.WhoAreYou, authdata, nonce); - const maskingIv = randomBytes(MASKING_IV_SIZE); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE)); const message = Buffer.alloc(0); return { maskingIv, diff --git a/src/packet/encode.ts b/src/packet/encode.ts index 7c2c7d38..9c78c5c3 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -1,4 +1,4 @@ -import cipher from "bcrypto/lib/cipher.js"; +import Crypto from "@libp2p/crypto"; import { toBigIntBE, toBufferBE } from "bigint-buffer"; import errcode from "err-code"; @@ -29,28 +29,30 @@ import { } from "./constants.js"; import { IHandshakeAuthdata, IHeader, IMessageAuthdata, IPacket, IWhoAreYouAuthdata, PacketType } from "./types.js"; -export function encodePacket(destId: string, packet: IPacket): Buffer { - return Buffer.concat([packet.maskingIv, encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); +export async function encodePacket(destId: string, packet: IPacket): Promise { + return Buffer.concat([packet.maskingIv, await encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); } -export function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Buffer { - const ctx = new cipher.Cipher("AES-128-CTR"); - ctx.init(fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); - return ctx.update( - Buffer.concat([ - // static header - Buffer.from(header.protocolId, "ascii"), - numberToBuffer(header.version, VERSION_SIZE), - numberToBuffer(header.flag, FLAG_SIZE), - header.nonce, - numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), - // authdata - header.authdata, - ]) - ); +export async function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Promise { + const ctx = await Crypto.aes.create(Uint8Array.from(fromHex(destId).slice(0, MASKING_KEY_SIZE)), Uint8Array.from(maskingIv)); + const encodedHeader = await ctx.encrypt( + Uint8Array.from( + Buffer.concat([ + // static header + Buffer.from(header.protocolId, "ascii"), + numberToBuffer(header.version, VERSION_SIZE), + numberToBuffer(header.flag, FLAG_SIZE), + header.nonce, + numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), + // authdata + header.authdata, + ]) + ) + ); + return Buffer.from(encodedHeader) } -export function decodePacket(srcId: string, data: Buffer): IPacket { +export async function decodePacket(srcId: string, data: Buffer): Promise { if (data.length < MIN_PACKET_SIZE) { throw errcode(new Error(`Packet too small: ${data.length}`), ERR_TOO_SMALL); } @@ -59,7 +61,7 @@ export function decodePacket(srcId: string, data: Buffer): IPacket { } const maskingIv = data.slice(0, MASKING_IV_SIZE); - const [header, headerBuf] = decodeHeader(srcId, maskingIv, data.slice(MASKING_IV_SIZE)); + const [header, headerBuf] = await decodeHeader(srcId, maskingIv, data.slice(MASKING_IV_SIZE)); const message = data.slice(MASKING_IV_SIZE + headerBuf.length); return { @@ -73,11 +75,11 @@ export function decodePacket(srcId: string, data: Buffer): IPacket { /** * Return the decoded header and the header as a buffer */ -export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [IHeader, Buffer] { - const ctx = new cipher.Decipher("AES-128-CTR"); - ctx.init(fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); + export async function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): Promise<[IHeader, Buffer]> { + const ctx = await Crypto.aes.create(Uint8Array.from(fromHex(srcId).slice(0, MASKING_KEY_SIZE)), Uint8Array.from(maskingIv)); + // unmask the static header - const staticHeaderBuf = ctx.update(data.slice(0, STATIC_HEADER_SIZE)); + const staticHeaderBuf = Buffer.from(await ctx.decrypt(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE)))); // validate the static header field by field const protocolId = staticHeaderBuf.slice(0, PROTOCOL_SIZE).toString("ascii"); @@ -109,7 +111,9 @@ export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [I ); // Once the authdataSize is known, unmask the authdata - const authdata = ctx.update(data.slice(STATIC_HEADER_SIZE, STATIC_HEADER_SIZE + authdataSize)); + const authdata = Buffer.from( + await ctx.decrypt(Uint8Array.from(data.slice(STATIC_HEADER_SIZE, STATIC_HEADER_SIZE + authdataSize))) + ); return [ { diff --git a/src/transport/udp.ts b/src/transport/udp.ts index 82bfbde2..b5907d23 100644 --- a/src/transport/udp.ts +++ b/src/transport/udp.ts @@ -44,17 +44,16 @@ export class UDPTransportService public async send(to: Multiaddr, toId: string, packet: IPacket): Promise { const nodeAddr = to.toOptions(); - return new Promise((resolve) => - this.socket.send(encodePacket(toId, packet), nodeAddr.port, nodeAddr.host, () => resolve()) - ); + const encodedPacket = await encodePacket(toId, packet); + return new Promise((resolve) => this.socket.send(encodedPacket, nodeAddr.port, nodeAddr.host, () => resolve())); } - public handleIncoming = (data: Buffer, rinfo: IRemoteInfo): void => { + public async handleIncoming(data: Buffer, rinfo: IRemoteInfo): Promise { const multiaddr = new Multiaddr( `/${String(rinfo.family).endsWith("4") ? "ip4" : "ip6"}/${rinfo.address}/udp/${rinfo.port}` ); try { - const packet = decodePacket(this.srcId, data); + const packet = await decodePacket(this.srcId, data); this.emit("packet", multiaddr, packet); } catch (e: unknown) { this.emit("decodeError", e as Error, multiaddr); diff --git a/src/util/crypto.ts b/src/util/crypto.ts index 55a2fa41..bb19ec0e 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -1,15 +1,13 @@ -import cipher from "bcrypto/lib/cipher.js"; +import { aes } from "@libp2p/crypto"; -export function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Buffer { - const ctx = new cipher.Cipher("AES-128-CTR"); - ctx.init(key, iv); - ctx.update(pt); - return ctx.final(); +export async function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { + const ctx = await aes.create(Uint8Array.from(key), Uint8Array.from(iv)) + const encoded = await ctx.encrypt(Uint8Array.from(pt)) + return Buffer.from(encoded) } -export function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Buffer { - const ctx = new cipher.Decipher("AES-128-CTR"); - ctx.init(key, iv); - ctx.update(pt); - return ctx.final(); +export async function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { + const ctx = await aes.create(Uint8Array.from(key), Uint8Array.from(iv)) + const decoded = await ctx.decrypt(Uint8Array.from(pt)) + return Buffer.from(decoded) } diff --git a/test/unit/enr/enr.test.ts b/test/unit/enr/enr.test.ts index 01158dd2..20982471 100644 --- a/test/unit/enr/enr.test.ts +++ b/test/unit/enr/enr.test.ts @@ -13,7 +13,7 @@ describe("ENR", function () { const enr = ENR.createFromPeerId(peerId); const keypair = createKeypairFromPeerId(peerId); enr.setLocationMultiaddr(new Multiaddr("/ip4/18.223.219.100/udp/9000")); - 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); expect(toHex(enr2.signature as Buffer)).to.be.equal(toHex(enr.signature as Buffer)); @@ -35,7 +35,7 @@ describe("ENR", function () { const enr = ENR.createFromPeerId(peerId); enr.setLocationMultiaddr(new Multiaddr("/ip6/aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa/udp/9000")); const keypair = createKeypairFromPeerId(peerId); - const enr2 = ENR.decodeTxt(enr.encodeTxt(keypair.privateKey)); + const enr2 = ENR.decodeTxt(await enr.encodeTxt(keypair.privateKey)); expect(enr2.udp6).to.equal(9000); expect(enr2.ip6).to.equal("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa"); }); diff --git a/test/unit/message/codec.test.ts b/test/unit/message/codec.test.ts index cbd164e4..06b961ba 100644 --- a/test/unit/message/codec.test.ts +++ b/test/unit/message/codec.test.ts @@ -81,8 +81,8 @@ describe("message", () => { }, ]; for (const { message, expected } of testCases) { - it(`should encode/decode message type ${MessageType[message.type]}`, () => { - const actual = encode(message); + it(`should encode/decode message type ${MessageType[message.type]}`, async () => { + const actual = await encode(message); expect(actual).to.deep.equal(expected); expect(decode(actual)).to.deep.equal(message); }); diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index de7a6dc7..30d2fe6c 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ import { expect } from "chai"; -import secp256k1 from "bcrypto/lib/secp256k1.js"; -import { randomBytes } from "bcrypto/lib/random.js"; +import * as secp from "@noble/secp256k1"; +import { randomBytes } from "@libp2p/crypto"; import { deriveKey, @@ -26,7 +26,7 @@ describe("session crypto", () => { ); const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); - expect(secp256k1.derive(remotePK, localSK)).to.deep.equal(expected); + expect(secp.getSharedSecret(Uint8Array.from(remotePK), Uint8Array.from(localSK))).to.deep.equal(expected); }); it("key derivation should produce expected keys", () => { @@ -38,7 +38,7 @@ describe("session crypto", () => { const ephemKey = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); const destPubkey = Buffer.from("0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91", "hex"); - const secret = secp256k1.derive(destPubkey, ephemKey); + const secret = secp.getSharedSecret(Uint8Array.from(destPubkey), Uint8Array.from(ephemKey)); const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const challengeData = Buffer.from( @@ -46,7 +46,7 @@ describe("session crypto", () => { "hex" ); - expect(deriveKey(secret, firstNodeId, secondNodeId, challengeData)).to.deep.equal(expected); + expect(deriveKey(Buffer.from(secret), firstNodeId, secondNodeId, challengeData)).to.deep.equal(expected); }); it("symmetric keys should be derived correctly", () => { @@ -55,13 +55,13 @@ describe("session crypto", () => { const enr1 = ENR.createV4(v4.publicKey(sk1)); const enr2 = ENR.createV4(v4.publicKey(sk2)); const nonce = randomBytes(32); - const [a1, b1, pk] = generateSessionKeys(enr1.nodeId, createNodeContact(enr2), nonce); + const [a1, b1, pk] = generateSessionKeys(enr1.nodeId, createNodeContact(enr2), Buffer.from(nonce)); const [a2, b2] = deriveKeysFromPubkey( createKeypair(KeypairType.Secp256k1, sk2), enr2.nodeId, enr1.nodeId, pk, - nonce + Buffer.from(nonce) ); expect(a1).to.deep.equal(a2); @@ -107,10 +107,10 @@ describe("session crypto", () => { }); it("encrypted data should successfully be decrypted", () => { - const key = randomBytes(16); - const nonce = randomBytes(12); - const msg = randomBytes(16); - const ad = randomBytes(16); + const key = Buffer.from(randomBytes(16)); + const nonce = Buffer.from(randomBytes(12)); + const msg = Buffer.from(randomBytes(16)); + const ad = Buffer.from(randomBytes(16)); const cipher = encryptMessage(key, nonce, msg, ad); const decrypted = decryptMessage(key, nonce, cipher, ad); diff --git a/types/bcrypto/index.d.ts b/types/bcrypto/index.d.ts deleted file mode 100644 index 624a6f81..00000000 --- a/types/bcrypto/index.d.ts +++ /dev/null @@ -1,84 +0,0 @@ -/// - -declare module "bcrypto/lib/keccak.js" { - /** - * keccak.js - Keccak/SHA3 implementation for bcrypto - * Copyright (c) 2017-2019, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcrypto - * - * Parts of this software are based on emn178/js-sha3: - * Copyright (c) 2015-2017, Chen, Yi-Cyuan (MIT License). - * https://github.com/emn178/js-sha3 - * - * Parts of this software are based on rhash/RHash: - * Copyright (c) 2005-2014, Aleksey Kravchenko - * https://github.com/rhash/RHash - * - * Resources: - * https://en.wikipedia.org/wiki/SHA-3 - * https://keccak.team/specifications.html - * https://csrc.nist.gov/projects/hash-functions/sha-3-project/sha-3-standardization - * http://dx.doi.org/10.6028/NIST.FIPS.202 - * https://github.com/rhash/RHash/blob/master/librhash/sha3.c - * https://github.com/emn178/js-sha3/blob/master/src/sha3.js - */ - class Keccak { - native: number; - id: string; - size: number; - bits: number; - blockSize: number; - zero: Buffer; - ctx: Keccak; - static hash(): Keccak; - // static hmac(bits?: number, pad?: number, len?: number): HMAC; - static digest(data: Buffer, bits?: number, pad?: number, len?: number): Buffer; - static root(left: Buffer, right: Buffer, bits?: number, pad?: number, len?: number): Buffer; - static multi(x: Buffer, y: Buffer, z: Buffer, bits?: number, pad?: number, len?: number): Buffer; - static mac(data: Buffer, key: Buffer, bits?: number, pad?: number, len?: number): Buffer; - init(bits?: number): this; - update(data: Buffer): this; - final(pad: number, len?: number): Buffer; - } - - export = Keccak; -} - -declare module "bcrypto/lib/secp256k1.js" { - export function privateKeyGenerate(): Buffer; - export function privateKeyVerify(key: Buffer): boolean; - export function publicKeyCreate(key: Buffer, compress?: boolean): Buffer; - export function publicKeyConvert(pub: Buffer, compress?: boolean): Buffer; - export function publicKeyVerify(pub: Buffer): boolean; - export function sign(msg: Buffer, key: Buffer): Buffer; - export function verify(msg: Buffer, sig: Buffer, key: Buffer): boolean; - export function derive(pub: Buffer, priv: Buffer, compress?: boolean): Buffer; -} - -declare module "bcrypto/lib/hkdf.js" { - export function extract(hash: any, ikm: Buffer, salt?: Buffer): Buffer; - export function expand(hash: any, prk: Buffer, info: Buffer, length: number): Buffer; -} - -declare module "bcrypto/lib/sha256.js" { - export function digest(data: Buffer): Buffer; -} -declare module "bcrypto/lib/cipher.js" { - class CipherBase { - init(key: Buffer, iv: Buffer): this; - update(data: Buffer): Buffer; - final(): Buffer; - setAAD(data: Buffer): this; - setAuthTag(data: Buffer): this; - getAuthTag(): Buffer; - } - export class Cipher extends CipherBase { - constructor(name: string); - } - export class Decipher extends CipherBase { - constructor(name: string); - } -} -declare module "bcrypto/lib/random.js" { - export function randomBytes(size: number): Buffer; -} From c4f7e2689294eb083456092eb61331da659f4f36 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Sat, 2 Jul 2022 16:07:17 -0400 Subject: [PATCH 05/32] switch from libp2p aes to noble hashes --- src/keypair/secp256k1.ts | 4 ++-- src/packet/encode.ts | 16 ++++++++-------- src/session/crypto.ts | 21 ++++++++++++--------- src/session/service.ts | 20 ++++++++++---------- src/util/crypto.ts | 16 +++++++++------- test/unit/session/crypto.test.ts | 4 ++-- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index 543e7c38..c867e6c3 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -36,13 +36,13 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab privateKeyVerify(key = this._privateKey): boolean { if (key) { - return secp.utils.isValidPrivateKey(secp.utils.bytesToHex(Uint8Array.from(key))); + return secp.utils.isValidPrivateKey(Uint8Array.from(key)); } return true; } sign(msg: Buffer): Buffer { - return Buffer.from(secp.sign(secp.utils.bytesToHex(Uint8Array.from(msg)), this.privateKey)); + return Buffer.from(secp.sign(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); } verify(msg: Buffer, sig: Buffer): boolean { return secp.verify( diff --git a/src/packet/encode.ts b/src/packet/encode.ts index 9c78c5c3..e2efcd38 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -1,4 +1,5 @@ -import Crypto from "@libp2p/crypto"; +import { crypto } from '@noble/hashes/crypto' + import { toBigIntBE, toBufferBE } from "bigint-buffer"; import errcode from "err-code"; @@ -29,13 +30,15 @@ import { } from "./constants.js"; import { IHandshakeAuthdata, IHeader, IMessageAuthdata, IPacket, IWhoAreYouAuthdata, PacketType } from "./types.js"; +const Crypto = crypto.node ?? crypto.web + export async function encodePacket(destId: string, packet: IPacket): Promise { return Buffer.concat([packet.maskingIv, await encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); } export async function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Promise { - const ctx = await Crypto.aes.create(Uint8Array.from(fromHex(destId).slice(0, MASKING_KEY_SIZE)), Uint8Array.from(maskingIv)); - const encodedHeader = await ctx.encrypt( + const ctx = Crypto.createCipheriv('aes-128-gcm', fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); + return ctx.update( Uint8Array.from( Buffer.concat([ // static header @@ -49,7 +52,6 @@ export async function encodeHeader(destId: string, maskingIv: Buffer, header: IH ]) ) ); - return Buffer.from(encodedHeader) } export async function decodePacket(srcId: string, data: Buffer): Promise { @@ -76,7 +78,7 @@ export async function decodePacket(srcId: string, data: Buffer): Promise { - const ctx = await Crypto.aes.create(Uint8Array.from(fromHex(srcId).slice(0, MASKING_KEY_SIZE)), Uint8Array.from(maskingIv)); + const ctx = Crypto.createDecipheriv(fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); // unmask the static header const staticHeaderBuf = Buffer.from(await ctx.decrypt(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE)))); @@ -111,9 +113,7 @@ export async function decodePacket(srcId: string, data: Buffer): Promise { const nodeAddr = getNodeAddress(contact); const nodeAddrStr = nodeAddressToString(nodeAddr); @@ -200,7 +200,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte let packet, initiatingSession; if (session) { // Encrypt the message and send - packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, encode(request)); + packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, await encode(request)); initiatingSession = false; } else { // No session exists, start a new handshake @@ -232,7 +232,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte /** * Sends an RPC response */ - public sendResponse(nodeAddr: INodeAddress, response: ResponseMessage): void { + public async sendResponse(nodeAddr: INodeAddress, response: ResponseMessage): Promise { const nodeAddrStr = nodeAddressToString(nodeAddr); // Check for an established session @@ -245,7 +245,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte // Encrypt the message and send let packet; try { - packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, encode(response)); + packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, await encode(response)); } catch (e) { log("Could not encrypt response: %s", e); return; @@ -287,7 +287,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.activeChallenges.set(nodeAddrStr, { data: challengeData, remoteEnr: remoteEnr ?? undefined }); } - public processInboundPacket = (src: Multiaddr, packet: IPacket): void => { + public async processInboundPacket (src: Multiaddr, packet: IPacket): Promise { switch (packet.header.flag) { case PacketType.WhoAreYou: return this.handleChallenge(src, packet); @@ -298,7 +298,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } }; - private handleChallenge(src: Multiaddr, packet: IPacket): void { + private async handleChallenge(src: Multiaddr, packet: IPacket): Promise { // First decode the authdata let authdata; try { @@ -380,7 +380,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte // Encrypt the message with an auth header and respond // First if a new version of our ENR is requested, obtain it for the header - const updatedEnr = authdata.enrSeq < this.enr.seq ? this.enr.encode(this.keypair.privateKey) : null; + const updatedEnr = authdata.enrSeq < this.enr.seq ? await this.enr.encode(this.keypair.privateKey) : null; // Generate a new session and authentication packet const [authPacket, session] = Session.encryptWithHeader( @@ -389,7 +389,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.enr.nodeId, updatedEnr, encodeChallengeData(packet.maskingIv, packet.header), - encode(requestCall.request) + await encode(requestCall.request) ); // There are two quirks with an established session at this point. @@ -476,7 +476,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } /** Handle a message that contains an authentication header */ - private handleHandshake(src: Multiaddr, packet: IPacket): void { + private async handleHandshake(src: Multiaddr, packet: IPacket): Promise { // Needs to match an outgoing WHOAREYOU packet (so we have the required nonce to be signed). // If it doesn't we drop the packet. // This will lead to future outgoing WHOAREYOU packets if they proceed to send further encrypted packets @@ -553,7 +553,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } } - private handleMessage(src: Multiaddr, packet: IPacket): void { + private async handleMessage(src: Multiaddr, packet: IPacket): Promise { let authdata; try { authdata = decodeMessageAuthdata(packet.header.authdata); diff --git a/src/util/crypto.ts b/src/util/crypto.ts index bb19ec0e..f81590c0 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -1,13 +1,15 @@ -import { aes } from "@libp2p/crypto"; +import { crypto } from "@noble/hashes/crypto"; + +const Crypto = crypto.node ?? crypto.web export async function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { - const ctx = await aes.create(Uint8Array.from(key), Uint8Array.from(iv)) - const encoded = await ctx.encrypt(Uint8Array.from(pt)) - return Buffer.from(encoded) + const ctx = Crypto.createCipheriv('aes-128-gcm', key, iv); + ctx.update(pt); + return ctx.final(); } export async function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { - const ctx = await aes.create(Uint8Array.from(key), Uint8Array.from(iv)) - const decoded = await ctx.decrypt(Uint8Array.from(pt)) - return Buffer.from(decoded) + const ctx = Crypto.createDecipheriv('aes-128-gcm', key, iv); + ctx.update(pt); + return ctx.final(); } diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 30d2fe6c..8f3d36ce 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -26,7 +26,7 @@ describe("session crypto", () => { ); const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); - expect(secp.getSharedSecret(Uint8Array.from(remotePK), Uint8Array.from(localSK))).to.deep.equal(expected); + expect(Buffer.from(secp.getSharedSecret(Uint8Array.from(localSK), Uint8Array.from(remotePK)))).to.deep.equal(expected); }); it("key derivation should produce expected keys", () => { @@ -38,7 +38,7 @@ describe("session crypto", () => { const ephemKey = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); const destPubkey = Buffer.from("0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91", "hex"); - const secret = secp.getSharedSecret(Uint8Array.from(destPubkey), Uint8Array.from(ephemKey)); + const secret = secp.getSharedSecret(Uint8Array.from(ephemKey), Uint8Array.from(destPubkey)); const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const challengeData = Buffer.from( From 69c0020f68f683ba5467419e179c9884fee0b086 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Sat, 2 Jul 2022 21:17:31 -0400 Subject: [PATCH 06/32] More incomplete changes --- src/keypair/secp256k1.ts | 4 ++-- src/keypair/types.ts | 2 +- src/session/crypto.ts | 2 +- src/session/session.ts | 6 +++--- test/unit/enr.test.ts | 4 ++-- test/unit/session/crypto.test.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index c867e6c3..18ded4e7 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -41,8 +41,8 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab return true; } - sign(msg: Buffer): Buffer { - return Buffer.from(secp.sign(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); + async sign(msg: Buffer): Promise { + return Buffer.from(await secp.sign(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); } verify(msg: Buffer, sig: Buffer): boolean { return secp.verify( diff --git a/src/keypair/types.ts b/src/keypair/types.ts index 3e695ecc..fa9bde82 100644 --- a/src/keypair/types.ts +++ b/src/keypair/types.ts @@ -10,7 +10,7 @@ export interface IKeypair { publicKey: Buffer; privateKeyVerify(): boolean; publicKeyVerify(): boolean; - sign(msg: Buffer): Buffer; + sign(msg: Buffer): Promise; verify(msg: Buffer, sig: Buffer): boolean; deriveSecret(keypair: IKeypair): Buffer; hasPrivateKey(): boolean; diff --git a/src/session/crypto.ts b/src/session/crypto.ts index 65e39887..9b1efa00 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -64,7 +64,7 @@ export function deriveKeysFromPubkey( } // Generates a signature given a keypair. -export function idSign(kpriv: IKeypair, challengeData: Buffer, ephemPK: Buffer, destNodeId: NodeId): Buffer { +export async function idSign(kpriv: IKeypair, challengeData: Buffer, ephemPK: Buffer, destNodeId: NodeId): Promise { const signingNonce = generateIdSignatureInput(challengeData, ephemPK, destNodeId); return kpriv.sign(signingNonce); } diff --git a/src/session/session.ts b/src/session/session.ts index 22e33e99..b3be1101 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -118,20 +118,20 @@ export class Session { /** * Encrypts a message and produces an handshake packet. */ - static encryptWithHeader( + static async encryptWithHeader( remoteContact: NodeContact, localKey: IKeypair, localNodeId: NodeId, updatedEnr: Buffer | null, challengeData: Buffer, message: Buffer - ): [IPacket, Session] { + ): Promise<[IPacket, Session]> { // generate session keys const [encryptionKey, decryptionKey, ephPubkey] = generateSessionKeys(localNodeId, remoteContact, challengeData); const keys = { encryptionKey, decryptionKey }; // construct nonce signature - const idSignature = idSign(localKey, challengeData, ephPubkey, getNodeId(remoteContact)); + const idSignature = await idSign(localKey, challengeData, ephPubkey, getNodeId(remoteContact)); // create authdata const authdata = encodeHandshakeAuthdata({ diff --git a/test/unit/enr.test.ts b/test/unit/enr.test.ts index fe77b49c..57eaec98 100644 --- a/test/unit/enr.test.ts +++ b/test/unit/enr.test.ts @@ -22,8 +22,8 @@ describe("ENR", () => { expect(record.nodeId).to.equal("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); }); - it("should encode/decode to RLP encoding", () => { - const decoded = ENR.decode(record.encode(privateKey)); + it("should encode/decode to RLP encoding", async () => { + const decoded = ENR.decode(await record.encode(privateKey)); expect(decoded).to.deep.equal(record); }); diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 8f3d36ce..2edab5a8 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -68,7 +68,7 @@ describe("session crypto", () => { expect(b1).to.deep.equal(b2); }); - it("id signature should match expected value", () => { + it("id signature should match expected value", async () => { const expected = Buffer.from( "94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6", "hex" @@ -82,7 +82,7 @@ describe("session crypto", () => { const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); const nodeIdB = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; - const actual = idSign(createKeypair(KeypairType.Secp256k1, localSK), challengeData, ephemPK, nodeIdB); + const actual = await idSign(createKeypair(KeypairType.Secp256k1, localSK), challengeData, ephemPK, nodeIdB); expect(actual).to.deep.equal(expected); expect( idVerify( From 338643e1f2954e067c29c56f6e97a1e919158a10 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 4 Jul 2022 14:11:57 -0400 Subject: [PATCH 07/32] More fixes --- src/enr/v4.ts | 2 +- src/keypair/secp256k1.ts | 27 ++++++++++++--------------- src/session/crypto.ts | 32 +++++++++++++++++++++++--------- src/session/service.ts | 8 ++++---- test/unit/session/crypto.test.ts | 6 ++++-- 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 31cbc066..fb49ccac 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -13,7 +13,7 @@ export function createPrivateKey(): Buffer { } export function publicKey(privKey: Buffer): Buffer { - return Buffer.from(secp256k1.getPublicKey(Uint8Array.from(privKey))); + return Buffer.from(secp256k1.getPublicKey(Uint8Array.from(privKey), true)); } export async function sign(privKey: Buffer, msg: Buffer): Promise { diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index 18ded4e7..3a5f450c 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -1,30 +1,34 @@ import * as secp from "@noble/secp256k1"; import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types.js"; import { ERR_INVALID_KEYPAIR_TYPE } from "./constants.js"; -/* + export function secp256k1PublicKeyToCompressed(publicKey: Buffer): Buffer { if (publicKey.length === 64) { publicKey = Buffer.concat([Buffer.from([4]), publicKey]); } - return secp256k1.publicKeyConvert(publicKey, true); + return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(true)); } export function secp256k1PublicKeyToFull(publicKey: Buffer): Buffer { if (publicKey.length === 64) { return Buffer.concat([Buffer.from([4]), publicKey]); } - return secp256k1.publicKeyConvert(publicKey, false); + return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false)); } export function secp256k1PublicKeyToRaw(publicKey: Buffer): Buffer { - return secp256k1.publicKeyConvert(publicKey, false).slice(1); + return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false)).slice(1); } -*/ + export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends AbstractKeypair implements IKeypair { readonly type: KeypairType; constructor(privateKey?: Buffer, publicKey?: Buffer) { - super(privateKey, publicKey); + let pub = publicKey; + if (pub) { + pub = secp256k1PublicKeyToCompressed(pub); + } + super(privateKey, pub); this.type = KeypairType.Secp256k1; } @@ -45,21 +49,14 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab return Buffer.from(await secp.sign(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); } verify(msg: Buffer, sig: Buffer): boolean { - return secp.verify( - Uint8Array.from(sig), - Uint8Array.from(msg), - this.publicKey - ); + return secp.verify(Uint8Array.from(sig), Uint8Array.from(msg), this.publicKey); } deriveSecret(keypair: IKeypair): Buffer { if (keypair.type !== this.type) { throw new Error(ERR_INVALID_KEYPAIR_TYPE); } const secret = Buffer.from( - secp.getSharedSecret( - Uint8Array.from(this.privateKey), - Uint8Array.from(keypair.publicKey) - ) + secp.getSharedSecret(Uint8Array.from(this.privateKey), Uint8Array.from(keypair.publicKey), true) ); return secret; } diff --git a/src/session/crypto.ts b/src/session/crypto.ts index 9b1efa00..bbb85225 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -1,4 +1,4 @@ -import * as hkdf from "@noble/hashes/hkdf"; +import * as hkdf from "@noble/hashes/hkdf"; import { sha256 } from "@noble/hashes/sha256"; import { crypto } from "@noble/hashes/crypto"; @@ -7,7 +7,7 @@ import { generateKeypair, IKeypair, createKeypair } from "../keypair/index.js"; import { fromHex } from "../util/index.js"; import { getNodeId, getPublicKey, NodeContact } from "./nodeInfo.js"; -const Crypto = crypto.node ?? crypto.web +const Crypto = crypto.node ?? crypto.web; // Implementation for generating session keys in the Discv5 protocol. // Currently, Diffie-Hellman key agreement is performed with known public key types. Session keys @@ -48,7 +48,14 @@ export function generateSessionKeys( export function deriveKey(secret: Buffer, firstId: NodeId, secondId: NodeId, challengeData: Buffer): [Buffer, Buffer] { const info = Buffer.concat([Buffer.from(KEY_AGREEMENT_STRING), fromHex(firstId), fromHex(secondId)]); - const output = Buffer.from(hkdf.expand(sha256, hkdf.extract(sha256, Uint8Array.from(secret), Uint8Array.from(challengeData)), Uint8Array.from(info), 2 * KEY_LENGTH)); + const output = Buffer.from( + hkdf.expand( + sha256, + hkdf.extract(sha256, Uint8Array.from(secret), Uint8Array.from(challengeData)), + Uint8Array.from(info), + 2 * KEY_LENGTH + ) + ); return [output.slice(0, KEY_LENGTH), output.slice(KEY_LENGTH, 2 * KEY_LENGTH)]; } @@ -64,7 +71,12 @@ export function deriveKeysFromPubkey( } // Generates a signature given a keypair. -export async function idSign(kpriv: IKeypair, challengeData: Buffer, ephemPK: Buffer, destNodeId: NodeId): Promise { +export async function idSign( + kpriv: IKeypair, + challengeData: Buffer, + ephemPK: Buffer, + destNodeId: NodeId +): Promise { const signingNonce = generateIdSignatureInput(challengeData, ephemPK, destNodeId); return kpriv.sign(signingNonce); } @@ -82,9 +94,11 @@ export function idVerify( } export function generateIdSignatureInput(challengeData: Buffer, ephemPK: Buffer, nodeId: NodeId): Buffer { - const hash = sha256.create().update(Uint8Array.from(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)]))).digest(); - return Buffer.from(hash) - + const hash = sha256 + .create() + .update(Uint8Array.from(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)]))) + .digest(); + return Buffer.from(hash); } export function decryptMessage(key: Buffer, nonce: Buffer, data: Buffer, aad: Buffer): Buffer { @@ -92,7 +106,7 @@ export function decryptMessage(key: Buffer, nonce: Buffer, data: Buffer, aad: Bu throw new Error("message data not long enough"); } - const ctx = Crypto.createDecipheriv('aes-128-gcm', key, nonce); + const ctx = Crypto.createDecipheriv("aes-128-gcm", key, nonce); ctx.setAAD(aad); ctx.setAuthTag(data.slice(data.length - MAC_LENGTH)); return Buffer.concat([ @@ -102,7 +116,7 @@ export function decryptMessage(key: Buffer, nonce: Buffer, data: Buffer, aad: Bu } export function encryptMessage(key: Buffer, nonce: Buffer, data: Buffer, aad: Buffer): Buffer { - const ctx = Crypto.createCipheriv('aes-128-gcm', key, nonce); + const ctx = Crypto.createCipheriv("aes-128-gcm", key, nonce); ctx.setAAD(aad); return Buffer.concat([ ctx.update(data), diff --git a/src/session/service.ts b/src/session/service.ts index f5ed89d0..799784f5 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -287,7 +287,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.activeChallenges.set(nodeAddrStr, { data: challengeData, remoteEnr: remoteEnr ?? undefined }); } - public async processInboundPacket (src: Multiaddr, packet: IPacket): Promise { + public async processInboundPacket(src: Multiaddr, packet: IPacket): Promise { switch (packet.header.flag) { case PacketType.WhoAreYou: return this.handleChallenge(src, packet); @@ -296,9 +296,9 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte case PacketType.Message: return this.handleMessage(src, packet); } - }; + } - private async handleChallenge(src: Multiaddr, packet: IPacket): Promise { + private async handleChallenge(src: Multiaddr, packet: IPacket): Promise { // First decode the authdata let authdata; try { @@ -383,7 +383,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte const updatedEnr = authdata.enrSeq < this.enr.seq ? await this.enr.encode(this.keypair.privateKey) : null; // Generate a new session and authentication packet - const [authPacket, session] = Session.encryptWithHeader( + const [authPacket, session] = await Session.encryptWithHeader( requestCall.contact, this.keypair, this.enr.nodeId, diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 2edab5a8..2e761095 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -26,7 +26,9 @@ describe("session crypto", () => { ); const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); - expect(Buffer.from(secp.getSharedSecret(Uint8Array.from(localSK), Uint8Array.from(remotePK)))).to.deep.equal(expected); + expect(Buffer.from(secp.getSharedSecret(Uint8Array.from(localSK), Uint8Array.from(remotePK), true))).to.deep.equal( + expected + ); }); it("key derivation should produce expected keys", () => { @@ -38,7 +40,7 @@ describe("session crypto", () => { const ephemKey = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); const destPubkey = Buffer.from("0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91", "hex"); - const secret = secp.getSharedSecret(Uint8Array.from(ephemKey), Uint8Array.from(destPubkey)); + const secret = secp.getSharedSecret(Uint8Array.from(ephemKey), Uint8Array.from(destPubkey), true); const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const challengeData = Buffer.from( From 98d16b1d108e5dea3f928d31870d1ed8fce5bf08 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 4 Jul 2022 15:21:40 -0400 Subject: [PATCH 08/32] switch sign from async to sync --- src/keypair/secp256k1.ts | 20 +++++++++++++++++--- src/keypair/types.ts | 2 +- src/session/crypto.ts | 14 ++++---------- test/unit/kademlia/util.test.ts | 8 ++++---- test/unit/session/crypto.test.ts | 4 ++-- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index 3a5f450c..ebc336f2 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -1,7 +1,13 @@ import * as secp from "@noble/secp256k1"; import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types.js"; import { ERR_INVALID_KEYPAIR_TYPE } from "./constants.js"; - +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +secp.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { + const h = hmac.create(sha256, key); + msgs.forEach((msg) => h.update(msg)); + return h.digest(); +}; export function secp256k1PublicKeyToCompressed(publicKey: Buffer): Buffer { if (publicKey.length === 64) { publicKey = Buffer.concat([Buffer.from([4]), publicKey]); @@ -45,8 +51,16 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab return true; } - async sign(msg: Buffer): Promise { - return Buffer.from(await secp.sign(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); + publicKeyVerify(): boolean { + try { + secp.Point.fromHex(Uint8Array.from(this.publicKey)).assertValidity(); + return true; + } catch { + return false; + } + } + sign(msg: Buffer): Buffer { + return Buffer.from(secp.signSync(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); } verify(msg: Buffer, sig: Buffer): boolean { return secp.verify(Uint8Array.from(sig), Uint8Array.from(msg), this.publicKey); diff --git a/src/keypair/types.ts b/src/keypair/types.ts index fa9bde82..3e695ecc 100644 --- a/src/keypair/types.ts +++ b/src/keypair/types.ts @@ -10,7 +10,7 @@ export interface IKeypair { publicKey: Buffer; privateKeyVerify(): boolean; publicKeyVerify(): boolean; - sign(msg: Buffer): Promise; + sign(msg: Buffer): Buffer; verify(msg: Buffer, sig: Buffer): boolean; deriveSecret(keypair: IKeypair): Buffer; hasPrivateKey(): boolean; diff --git a/src/session/crypto.ts b/src/session/crypto.ts index bbb85225..70b00886 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -71,12 +71,7 @@ export function deriveKeysFromPubkey( } // Generates a signature given a keypair. -export async function idSign( - kpriv: IKeypair, - challengeData: Buffer, - ephemPK: Buffer, - destNodeId: NodeId -): Promise { +export function idSign(kpriv: IKeypair, challengeData: Buffer, ephemPK: Buffer, destNodeId: NodeId): Buffer { const signingNonce = generateIdSignatureInput(challengeData, ephemPK, destNodeId); return kpriv.sign(signingNonce); } @@ -94,10 +89,9 @@ export function idVerify( } export function generateIdSignatureInput(challengeData: Buffer, ephemPK: Buffer, nodeId: NodeId): Buffer { - const hash = sha256 - .create() - .update(Uint8Array.from(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)]))) - .digest(); + const hash = sha256( + Uint8Array.from(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)])) + ); return Buffer.from(hash); } diff --git a/test/unit/kademlia/util.test.ts b/test/unit/kademlia/util.test.ts index 6336fe88..48a62ea6 100644 --- a/test/unit/kademlia/util.test.ts +++ b/test/unit/kademlia/util.test.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ import { expect } from "chai"; -import { randomBytes } from "bcrypto/lib/random.js"; +import { randomBytes } from "@noble/hashes/utils"; import { distance, log2Distance } from "../../../src/kademlia/index.js"; import { createNodeId } from "../../../src/enr/index.js"; @@ -17,9 +17,9 @@ describe("Kademlia distance function", () => { }); it("triangle inequality", () => { - const a = createNodeId(randomBytes(32)); - const b = createNodeId(randomBytes(32)); - const c = createNodeId(randomBytes(32)); + const a = createNodeId(Buffer.from(randomBytes(32))); + const b = createNodeId(Buffer.from(randomBytes(32))); + const c = createNodeId(Buffer.from(randomBytes(32))); expect(distance(a, b) <= distance(a, c) + distance(c, b)).to.be.true; }); }); diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 2e761095..bc2e0093 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -83,8 +83,8 @@ describe("session crypto", () => { ); const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); const nodeIdB = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; - - const actual = await idSign(createKeypair(KeypairType.Secp256k1, localSK), challengeData, ephemPK, nodeIdB); + const keypair = createKeypair(KeypairType.Secp256k1, localSK); + const actual = await idSign(keypair, challengeData, ephemPK, nodeIdB); expect(actual).to.deep.equal(expected); expect( idVerify( From a2e6c1e9a6fe15b5b6f834914e991c7fc76764bb Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 4 Jul 2022 15:34:54 -0400 Subject: [PATCH 09/32] Remove async nonsense --- src/enr/enr.ts | 16 ++++++++-------- src/enr/v4.ts | 13 ++++++++++--- src/keypair/secp256k1.ts | 1 + src/message/encode.ts | 4 ++-- test/unit/enr.test.ts | 4 ++-- test/unit/enr/enr.test.ts | 2 +- test/unit/session/crypto.test.ts | 4 ++-- 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/enr/enr.ts b/src/enr/enr.ts index 03c67510..5966e198 100644 --- a/src/enr/enr.ts +++ b/src/enr/enr.ts @@ -315,17 +315,17 @@ export class ENR extends Map { } return v4.verify(this.publicKey, data, signature); } - async sign(data: Buffer, privateKey: Buffer): Promise { + sign(data: Buffer, privateKey: Buffer): Buffer { switch (this.id) { case "v4": - this.signature = await v4.sign(privateKey, data); + this.signature = v4.sign(privateKey, data); break; default: throw new Error(ERR_INVALID_ID); } return this.signature; } - async encodeToValues(privateKey?: Buffer): Promise<(ENRKey | ENRValue | number)[]> { + encodeToValues(privateKey?: Buffer): (ENRKey | ENRValue | number)[] { // sort keys and flatten into [k, v, k, v, ...] const content: Array = Array.from(this.keys()) .sort((a, b) => a.localeCompare(b)) @@ -333,7 +333,7 @@ export class ENR extends Map { .flat(); content.unshift(Number(this.seq)); if (privateKey) { - content.unshift(await this.sign(RLP.encode(content), privateKey)); + content.unshift(this.sign(RLP.encode(content), privateKey)); } else { if (!this.signature) { throw new Error(ERR_NO_SIGNATURE); @@ -342,14 +342,14 @@ export class ENR extends Map { } return content; } - async encode(privateKey?: Buffer): Promise { - const encoded = RLP.encode(await this.encodeToValues(privateKey)); + encode(privateKey?: Buffer): Buffer { + const encoded = RLP.encode(this.encodeToValues(privateKey)); if (encoded.length >= MAX_RECORD_SIZE) { throw new Error("ENR must be less than 300 bytes"); } return encoded; } - async encodeTxt(privateKey?: Buffer): Promise { - return "enr:" + base64url.encode(Buffer.from(await this.encode(privateKey))); + encodeTxt(privateKey?: Buffer): string { + return "enr:" + base64url.encode(Buffer.from(this.encode(privateKey))); } } diff --git a/src/enr/v4.ts b/src/enr/v4.ts index fb49ccac..3dc96cac 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -1,11 +1,18 @@ import { keccak_256 as keccak } from "@noble/hashes/sha3"; import * as secp256k1 from "@noble/secp256k1"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { + const h = hmac.create(sha256, key); + msgs.forEach((msg) => h.update(msg)); + return h.digest(); +}; import { NodeId } from "./types.js"; import { createNodeId } from "./create.js"; export function hash(input: Buffer): Buffer { - return Buffer.from(keccak(input)); + return Buffer.from(keccak(Uint8Array.from(input))); } export function createPrivateKey(): Buffer { @@ -16,8 +23,8 @@ export function publicKey(privKey: Buffer): Buffer { return Buffer.from(secp256k1.getPublicKey(Uint8Array.from(privKey), true)); } -export async function sign(privKey: Buffer, msg: Buffer): Promise { - return Buffer.from(await secp256k1.sign(Uint8Array.from(hash(msg)), privKey)); +export function sign(privKey: Buffer, msg: Buffer): Buffer { + return Buffer.from(secp256k1.signSync(Uint8Array.from(hash(msg)), privKey)); } export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index ebc336f2..aabc9057 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -8,6 +8,7 @@ secp.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { msgs.forEach((msg) => h.update(msg)); return h.digest(); }; + export function secp256k1PublicKeyToCompressed(publicKey: Buffer): Buffer { if (publicKey.length === 64) { publicKey = Buffer.concat([Buffer.from([4]), publicKey]); diff --git a/src/message/encode.ts b/src/message/encode.ts index dc5b7883..8d5985f4 100644 --- a/src/message/encode.ts +++ b/src/message/encode.ts @@ -71,10 +71,10 @@ export function encodeFindNodeMessage(m: IFindNodeMessage): Buffer { return Buffer.concat([Buffer.from([MessageType.FINDNODE]), RLP.encode([toBuffer(m.id), m.distances])]); } -export async function encodeNodesMessage(m: INodesMessage): Promise { +export function encodeNodesMessage(m: INodesMessage): Buffer { return Buffer.concat([ Buffer.from([MessageType.NODES]), - RLP.encode([toBuffer(m.id), m.total, await Promise.all(m.enrs.map(async (enr) => enr.encodeToValues()))]), + RLP.encode([toBuffer(m.id), m.total, m.enrs.map((enr) => enr.encodeToValues())]), ]); } diff --git a/test/unit/enr.test.ts b/test/unit/enr.test.ts index 57eaec98..6ed174a9 100644 --- a/test/unit/enr.test.ts +++ b/test/unit/enr.test.ts @@ -27,7 +27,7 @@ describe("ENR", () => { 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 const testTxt = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"; @@ -35,7 +35,7 @@ describe("ENR", () => { expect(decoded.udp).to.be.equal(30303); expect(decoded.ip).to.be.equal("127.0.0.1"); expect(decoded).to.deep.equal(record); - expect(record.encodeTxt(privateKey)).to.equal(testTxt); + expect(await record.encodeTxt(privateKey)).to.equal(testTxt); }); }); describe("ENR Multiformats support", () => { diff --git a/test/unit/enr/enr.test.ts b/test/unit/enr/enr.test.ts index 20982471..9b87b5e8 100644 --- a/test/unit/enr/enr.test.ts +++ b/test/unit/enr/enr.test.ts @@ -35,7 +35,7 @@ describe("ENR", function () { const enr = ENR.createFromPeerId(peerId); enr.setLocationMultiaddr(new Multiaddr("/ip6/aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa/udp/9000")); const keypair = createKeypairFromPeerId(peerId); - const enr2 = ENR.decodeTxt(await enr.encodeTxt(keypair.privateKey)); + const enr2 = ENR.decodeTxt(enr.encodeTxt(keypair.privateKey)); expect(enr2.udp6).to.equal(9000); expect(enr2.ip6).to.equal("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa"); }); diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index bc2e0093..4866acb5 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -70,7 +70,7 @@ describe("session crypto", () => { expect(b1).to.deep.equal(b2); }); - it("id signature should match expected value", async () => { + it("id signature should match expected value", () => { const expected = Buffer.from( "94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6", "hex" @@ -84,7 +84,7 @@ describe("session crypto", () => { const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); const nodeIdB = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const keypair = createKeypair(KeypairType.Secp256k1, localSK); - const actual = await idSign(keypair, challengeData, ephemPK, nodeIdB); + const actual = idSign(keypair, challengeData, ephemPK, nodeIdB); expect(actual).to.deep.equal(expected); expect( idVerify( From d5a8102b37eb82bbeabf169f8bb9094f3c81755a Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 05:46:25 -0400 Subject: [PATCH 10/32] Requested fixes --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 798ece62..96dd8919 100644 --- a/package.json +++ b/package.json @@ -99,9 +99,10 @@ "@libp2p/interfaces": "^3.0.2", "@libp2p/peer-id": "^1.1.13", "@multiformats/multiaddr": "^10.2.0", - "base64url": "^3.0.1", "@noble/hashes": "^1.1.2", "@noble/secp256k1": "^1.6.0", + "base64url": "^3.0.1", + "bcrypto": "^5.4.0", "bigint-buffer": "^1.1.5", "debug": "^4.3.1", "dgram": "^1.0.1", From 4dc562075caf929d9f0f6510f369a0dbfbc63329 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:36:33 -0400 Subject: [PATCH 11/32] set correct opts on secp256k1.sign --- src/enr/v4.ts | 4 ++-- src/keypair/secp256k1.ts | 2 +- src/session/crypto.ts | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 3dc96cac..78a8eeee 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -12,7 +12,7 @@ import { NodeId } from "./types.js"; import { createNodeId } from "./create.js"; export function hash(input: Buffer): Buffer { - return Buffer.from(keccak(Uint8Array.from(input))); + return Buffer.from(keccak(input).buffer); } export function createPrivateKey(): Buffer { @@ -20,7 +20,7 @@ export function createPrivateKey(): Buffer { } export function publicKey(privKey: Buffer): Buffer { - return Buffer.from(secp256k1.getPublicKey(Uint8Array.from(privKey), true)); + return Buffer.from(secp256k1.getPublicKey(privKey, true)); } export function sign(privKey: Buffer, msg: Buffer): Buffer { diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index aabc9057..93da51c8 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -61,7 +61,7 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab } } sign(msg: Buffer): Buffer { - return Buffer.from(secp.signSync(Uint8Array.from(msg), Uint8Array.from(this.privateKey))); + return Buffer.from(secp.signSync(msg, this.privateKey, { der: false })); } verify(msg: Buffer, sig: Buffer): boolean { return secp.verify(Uint8Array.from(sig), Uint8Array.from(msg), this.publicKey); diff --git a/src/session/crypto.ts b/src/session/crypto.ts index 70b00886..f5785bf7 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -1,7 +1,6 @@ import * as hkdf from "@noble/hashes/hkdf"; import { sha256 } from "@noble/hashes/sha256"; import { crypto } from "@noble/hashes/crypto"; - import { NodeId } from "../enr/index.js"; import { generateKeypair, IKeypair, createKeypair } from "../keypair/index.js"; import { fromHex } from "../util/index.js"; From d9ff9631acb2fb909ee36d7283a906cc1e7fa2f5 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 11:28:04 -0400 Subject: [PATCH 12/32] Fix nodeId hashing --- src/enr/v4.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 78a8eeee..2732b7f5 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -10,6 +10,7 @@ secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { import { NodeId } from "./types.js"; import { createNodeId } from "./create.js"; +import { secp256k1PublicKeyToRaw } from "../keypair/secp256k1.js"; export function hash(input: Buffer): Buffer { return Buffer.from(keccak(input).buffer); @@ -32,7 +33,7 @@ export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { } export function nodeId(pubKey: Buffer): NodeId { - return createNodeId(hash(pubKey)); + return createNodeId(hash(secp256k1PublicKeyToRaw(pubKey))); } export class ENRKeyPair { From 7f70f8dd2d9dd639e6621f0013f7fd4e9e5779ca Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 11:33:26 -0400 Subject: [PATCH 13/32] Buffer constructor clean-up --- src/enr/enr.ts | 2 +- src/enr/v4.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/enr/enr.ts b/src/enr/enr.ts index 5966e198..276079c4 100644 --- a/src/enr/enr.ts +++ b/src/enr/enr.ts @@ -350,6 +350,6 @@ export class ENR extends Map { return encoded; } encodeTxt(privateKey?: Buffer): string { - return "enr:" + base64url.encode(Buffer.from(this.encode(privateKey))); + return "enr:" + base64url.encode(this.encode(privateKey)); } } diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 2732b7f5..0be666b3 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -21,11 +21,11 @@ export function createPrivateKey(): Buffer { } export function publicKey(privKey: Buffer): Buffer { - return Buffer.from(secp256k1.getPublicKey(privKey, true)); + return Buffer.from(secp256k1.getPublicKey(privKey, true).buffer); } export function sign(privKey: Buffer, msg: Buffer): Buffer { - return Buffer.from(secp256k1.signSync(Uint8Array.from(hash(msg)), privKey)); + return Buffer.from(secp256k1.signSync(hash(msg), privKey).buffer); } export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { From 37f6d62485d46c80e5c10b3d02c5dc4466b55c88 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 11:36:11 -0400 Subject: [PATCH 14/32] remove redundant async logic --- src/message/encode.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/message/encode.ts b/src/message/encode.ts index 8d5985f4..11f7b9a8 100644 --- a/src/message/encode.ts +++ b/src/message/encode.ts @@ -17,7 +17,7 @@ import { ITalkRespMessage, } from "./types.js"; -export async function encode(message: Message): Promise { +export function encode(message: Message): Buffer { switch (message.type) { case MessageType.PING: return encodePingMessage(message as IPingMessage); @@ -86,10 +86,10 @@ export function encodeTalkRespMessage(m: ITalkRespMessage): Buffer { return Buffer.concat([Buffer.from([MessageType.TALKRESP]), RLP.encode([toBuffer(m.id), m.response])]); } -export async function encodeRegTopicMessage(m: IRegTopicMessage): Promise { +export function encodeRegTopicMessage(m: IRegTopicMessage): Buffer { return Buffer.concat([ Buffer.from([MessageType.REGTOPIC]), - RLP.encode([toBuffer(m.id), m.topic, await m.enr.encodeToValues(), m.ticket]), + RLP.encode([toBuffer(m.id), m.topic, m.enr.encodeToValues(), m.ticket]), ]); } From 7624bf827430173a8c601b6103150f168fbc5ac5 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 11:44:02 -0400 Subject: [PATCH 15/32] fix v4 signature --- src/enr/v4.ts | 2 +- test/unit/enr.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 0be666b3..77344af7 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -25,7 +25,7 @@ export function publicKey(privKey: Buffer): Buffer { } export function sign(privKey: Buffer, msg: Buffer): Buffer { - return Buffer.from(secp256k1.signSync(hash(msg), privKey).buffer); + return Buffer.from(secp256k1.signSync(hash(msg), privKey, { der: false }).buffer); } export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { diff --git a/test/unit/enr.test.ts b/test/unit/enr.test.ts index 6ed174a9..fe77b49c 100644 --- a/test/unit/enr.test.ts +++ b/test/unit/enr.test.ts @@ -22,12 +22,12 @@ describe("ENR", () => { expect(record.nodeId).to.equal("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); }); - it("should encode/decode to RLP encoding", async () => { - const decoded = ENR.decode(await record.encode(privateKey)); + it("should encode/decode to RLP encoding", () => { + const decoded = ENR.decode(record.encode(privateKey)); expect(decoded).to.deep.equal(record); }); - it("should encode/decode to text encoding", async () => { + it("should encode/decode to text encoding", () => { // spec enr https://eips.ethereum.org/EIPS/eip-778 const testTxt = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8"; @@ -35,7 +35,7 @@ describe("ENR", () => { expect(decoded.udp).to.be.equal(30303); expect(decoded.ip).to.be.equal("127.0.0.1"); expect(decoded).to.deep.equal(record); - expect(await record.encodeTxt(privateKey)).to.equal(testTxt); + expect(record.encodeTxt(privateKey)).to.equal(testTxt); }); }); describe("ENR Multiformats support", () => { From 1c925d632f29da26062eec704ab6308d5457b436 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:11:37 -0400 Subject: [PATCH 16/32] Remove redundant asyncs --- src/packet/encode.ts | 47 ++++++++++++++++++++++---------------------- src/transport/udp.ts | 11 ++++++----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/packet/encode.ts b/src/packet/encode.ts index e2efcd38..e41c7d49 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -1,5 +1,4 @@ -import { crypto } from '@noble/hashes/crypto' - +import { crypto } from "@noble/hashes/crypto"; import { toBigIntBE, toBufferBE } from "bigint-buffer"; import errcode from "err-code"; @@ -30,31 +29,31 @@ import { } from "./constants.js"; import { IHandshakeAuthdata, IHeader, IMessageAuthdata, IPacket, IWhoAreYouAuthdata, PacketType } from "./types.js"; -const Crypto = crypto.node ?? crypto.web +const Crypto = crypto.node ?? crypto.web; -export async function encodePacket(destId: string, packet: IPacket): Promise { - return Buffer.concat([packet.maskingIv, await encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); +export function encodePacket(destId: string, packet: IPacket): Buffer { + return Buffer.concat([packet.maskingIv, encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); } -export async function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Promise { - const ctx = Crypto.createCipheriv('aes-128-gcm', fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); +export function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Buffer { + const ctx = Crypto.createCipheriv("aes-128-gcm", fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); return ctx.update( - Uint8Array.from( - Buffer.concat([ - // static header - Buffer.from(header.protocolId, "ascii"), - numberToBuffer(header.version, VERSION_SIZE), - numberToBuffer(header.flag, FLAG_SIZE), - header.nonce, - numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), - // authdata - header.authdata, - ]) - ) - ); + Uint8Array.from( + Buffer.concat([ + // static header + Buffer.from(header.protocolId, "ascii"), + numberToBuffer(header.version, VERSION_SIZE), + numberToBuffer(header.flag, FLAG_SIZE), + header.nonce, + numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), + // authdata + header.authdata, + ]) + ) + ); } -export async function decodePacket(srcId: string, data: Buffer): Promise { +export function decodePacket(srcId: string, data: Buffer): IPacket { if (data.length < MIN_PACKET_SIZE) { throw errcode(new Error(`Packet too small: ${data.length}`), ERR_TOO_SMALL); } @@ -63,7 +62,7 @@ export async function decodePacket(srcId: string, data: Buffer): Promise { +export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [IHeader, Buffer] { const ctx = Crypto.createDecipheriv(fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); // unmask the static header - const staticHeaderBuf = Buffer.from(await ctx.decrypt(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE)))); + const staticHeaderBuf = Buffer.from(ctx.decrypt(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE)))); // validate the static header field by field const protocolId = staticHeaderBuf.slice(0, PROTOCOL_SIZE).toString("ascii"); diff --git a/src/transport/udp.ts b/src/transport/udp.ts index b5907d23..ed96cfaa 100644 --- a/src/transport/udp.ts +++ b/src/transport/udp.ts @@ -44,19 +44,20 @@ export class UDPTransportService public async send(to: Multiaddr, toId: string, packet: IPacket): Promise { const nodeAddr = to.toOptions(); - const encodedPacket = await encodePacket(toId, packet); - return new Promise((resolve) => this.socket.send(encodedPacket, nodeAddr.port, nodeAddr.host, () => resolve())); + return new Promise((resolve) => + this.socket.send(encodePacket(toId, packet), nodeAddr.port, nodeAddr.host, () => resolve()) + ); } - public async handleIncoming(data: Buffer, rinfo: IRemoteInfo): Promise { + public handleIncoming(data: Buffer, rinfo: IRemoteInfo): void { const multiaddr = new Multiaddr( `/${String(rinfo.family).endsWith("4") ? "ip4" : "ip6"}/${rinfo.address}/udp/${rinfo.port}` ); try { - const packet = await decodePacket(this.srcId, data); + const packet = decodePacket(this.srcId, data); this.emit("packet", multiaddr, packet); } catch (e: unknown) { this.emit("decodeError", e as Error, multiaddr); } - }; + } } From 5cc64acc3d1f087a538bfba732efbb006cc43fb4 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:20:25 -0400 Subject: [PATCH 17/32] Remove even more redundant async logic --- src/session/service.ts | 4 ++-- src/session/session.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/session/service.ts b/src/session/service.ts index 799784f5..de26b0fd 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -175,7 +175,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte /** * Sends an RequestMessage to a node. */ - public async sendRequest(contact: NodeContact, request: RequestMessage): Promise { + public sendRequest(contact: NodeContact, request: RequestMessage): void { const nodeAddr = getNodeAddress(contact); const nodeAddrStr = nodeAddressToString(nodeAddr); @@ -200,7 +200,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte let packet, initiatingSession; if (session) { // Encrypt the message and send - packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, await encode(request)); + packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, encode(request)); initiatingSession = false; } else { // No session exists, start a new handshake diff --git a/src/session/session.ts b/src/session/session.ts index b3be1101..22e33e99 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -118,20 +118,20 @@ export class Session { /** * Encrypts a message and produces an handshake packet. */ - static async encryptWithHeader( + static encryptWithHeader( remoteContact: NodeContact, localKey: IKeypair, localNodeId: NodeId, updatedEnr: Buffer | null, challengeData: Buffer, message: Buffer - ): Promise<[IPacket, Session]> { + ): [IPacket, Session] { // generate session keys const [encryptionKey, decryptionKey, ephPubkey] = generateSessionKeys(localNodeId, remoteContact, challengeData); const keys = { encryptionKey, decryptionKey }; // construct nonce signature - const idSignature = await idSign(localKey, challengeData, ephPubkey, getNodeId(remoteContact)); + const idSignature = idSign(localKey, challengeData, ephPubkey, getNodeId(remoteContact)); // create authdata const authdata = encodeHandshakeAuthdata({ From 0098fac305d1e2248a40fe7152aab2188f54b490 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:23:48 -0400 Subject: [PATCH 18/32] Make handleIncoming an arrow function again --- src/transport/udp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transport/udp.ts b/src/transport/udp.ts index ed96cfaa..33949bcc 100644 --- a/src/transport/udp.ts +++ b/src/transport/udp.ts @@ -49,7 +49,7 @@ export class UDPTransportService ); } - public handleIncoming(data: Buffer, rinfo: IRemoteInfo): void { + public handleIncoming = (data: Buffer, rinfo: IRemoteInfo): void => { const multiaddr = new Multiaddr( `/${String(rinfo.family).endsWith("4") ? "ip4" : "ip6"}/${rinfo.address}/udp/${rinfo.port}` ); From 19b0721c298d573698fb436caecba0c525c8d854 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:29:59 -0400 Subject: [PATCH 19/32] Fix decodeHeader --- src/packet/encode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packet/encode.ts b/src/packet/encode.ts index e41c7d49..dfbc014b 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -77,10 +77,10 @@ export function decodePacket(srcId: string, data: Buffer): IPacket { * Return the decoded header and the header as a buffer */ export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [IHeader, Buffer] { - const ctx = Crypto.createDecipheriv(fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); + const ctx = Crypto.createDecipheriv("aes-128-gcm", fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); // unmask the static header - const staticHeaderBuf = Buffer.from(ctx.decrypt(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE)))); + const staticHeaderBuf = ctx.update(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE))); // validate the static header field by field const protocolId = staticHeaderBuf.slice(0, PROTOCOL_SIZE).toString("ascii"); From 9c9fee608da99129b3e9db31674b7aa812a20667 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:38:00 -0400 Subject: [PATCH 20/32] Remove redundant async logic --- src/session/service.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/session/service.ts b/src/session/service.ts index de26b0fd..80e76db7 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -232,7 +232,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte /** * Sends an RPC response */ - public async sendResponse(nodeAddr: INodeAddress, response: ResponseMessage): Promise { + public sendResponse(nodeAddr: INodeAddress, response: ResponseMessage): void { const nodeAddrStr = nodeAddressToString(nodeAddr); // Check for an established session @@ -245,7 +245,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte // Encrypt the message and send let packet; try { - packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, await encode(response)); + packet = session.encryptMessage(this.enr.nodeId, nodeAddr.nodeId, encode(response)); } catch (e) { log("Could not encrypt response: %s", e); return; @@ -287,7 +287,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.activeChallenges.set(nodeAddrStr, { data: challengeData, remoteEnr: remoteEnr ?? undefined }); } - public async processInboundPacket(src: Multiaddr, packet: IPacket): Promise { + public processInboundPacket(src: Multiaddr, packet: IPacket): void { switch (packet.header.flag) { case PacketType.WhoAreYou: return this.handleChallenge(src, packet); @@ -298,7 +298,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } } - private async handleChallenge(src: Multiaddr, packet: IPacket): Promise { + private handleChallenge(src: Multiaddr, packet: IPacket): void { // First decode the authdata let authdata; try { @@ -380,16 +380,16 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte // Encrypt the message with an auth header and respond // First if a new version of our ENR is requested, obtain it for the header - const updatedEnr = authdata.enrSeq < this.enr.seq ? await this.enr.encode(this.keypair.privateKey) : null; + const updatedEnr = authdata.enrSeq < this.enr.seq ? this.enr.encode(this.keypair.privateKey) : null; // Generate a new session and authentication packet - const [authPacket, session] = await Session.encryptWithHeader( + const [authPacket, session] = Session.encryptWithHeader( requestCall.contact, this.keypair, this.enr.nodeId, updatedEnr, encodeChallengeData(packet.maskingIv, packet.header), - await encode(requestCall.request) + encode(requestCall.request) ); // There are two quirks with an established session at this point. @@ -476,7 +476,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } /** Handle a message that contains an authentication header */ - private async handleHandshake(src: Multiaddr, packet: IPacket): Promise { + private handleHandshake(src: Multiaddr, packet: IPacket): void { // Needs to match an outgoing WHOAREYOU packet (so we have the required nonce to be signed). // If it doesn't we drop the packet. // This will lead to future outgoing WHOAREYOU packets if they proceed to send further encrypted packets @@ -553,7 +553,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte } } - private async handleMessage(src: Multiaddr, packet: IPacket): Promise { + private handleMessage(src: Multiaddr, packet: IPacket): void { let authdata; try { authdata = decodeMessageAuthdata(packet.header.authdata); From 855277b8750f5d6340028101d7837e5dd435e1ac Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:41:22 -0400 Subject: [PATCH 21/32] restore arrow function --- src/session/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session/service.ts b/src/session/service.ts index 80e76db7..00d4d6f1 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -287,7 +287,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte this.activeChallenges.set(nodeAddrStr, { data: challengeData, remoteEnr: remoteEnr ?? undefined }); } - public processInboundPacket(src: Multiaddr, packet: IPacket): void { + public processInboundPacket = (src: Multiaddr, packet: IPacket): void => { switch (packet.header.flag) { case PacketType.WhoAreYou: return this.handleChallenge(src, packet); From bb2e797c71c34dec4ac222aa4def298fe7253475 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:48:16 -0400 Subject: [PATCH 22/32] Remove bcrypto and dgram deps --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 96dd8919..3c9964dc 100644 --- a/package.json +++ b/package.json @@ -102,10 +102,8 @@ "@noble/hashes": "^1.1.2", "@noble/secp256k1": "^1.6.0", "base64url": "^3.0.1", - "bcrypto": "^5.4.0", "bigint-buffer": "^1.1.5", "debug": "^4.3.1", - "dgram": "^1.0.1", "err-code": "^3.0.1", "ip6addr": "^0.2.3", "is-ip": "^3.1.0", From 89e1a455d9a9a30da0bc50e2d8c4c74967d843e6 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:53:36 -0400 Subject: [PATCH 23/32] Remove redundant Uint8Array conversions --- src/enr/v4.ts | 4 ++-- src/keypair/secp256k1.ts | 10 ++++------ src/packet/encode.ts | 24 +++++++++++------------- src/session/crypto.ts | 13 ++----------- test/e2e/mainnetBootnodes.test.ts | 3 +-- test/unit/session/crypto.test.ts | 6 ++---- 6 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 77344af7..355bfc0c 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -29,7 +29,7 @@ export function sign(privKey: Buffer, msg: Buffer): Buffer { } export function verify(pubKey: Buffer, msg: Buffer, sig: Buffer): boolean { - return secp256k1.verify(Uint8Array.from(sig), Uint8Array.from(hash(msg)), Uint8Array.from(pubKey)); + return secp256k1.verify(sig, hash(msg), pubKey); } export function nodeId(pubKey: Buffer): NodeId { @@ -43,7 +43,7 @@ export class ENRKeyPair { public constructor(privateKey?: Buffer) { if (privateKey) { - if (!secp256k1.utils.isValidPrivateKey(Uint8Array.from(privateKey))) { + if (!secp256k1.utils.isValidPrivateKey(privateKey)) { throw new Error("Invalid private key"); } } diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index 93da51c8..4527183b 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -47,14 +47,14 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab privateKeyVerify(key = this._privateKey): boolean { if (key) { - return secp.utils.isValidPrivateKey(Uint8Array.from(key)); + return secp.utils.isValidPrivateKey(key); } return true; } publicKeyVerify(): boolean { try { - secp.Point.fromHex(Uint8Array.from(this.publicKey)).assertValidity(); + secp.Point.fromHex(this.publicKey).assertValidity(); return true; } catch { return false; @@ -64,15 +64,13 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab return Buffer.from(secp.signSync(msg, this.privateKey, { der: false })); } verify(msg: Buffer, sig: Buffer): boolean { - return secp.verify(Uint8Array.from(sig), Uint8Array.from(msg), this.publicKey); + return secp.verify(sig, msg, this.publicKey); } deriveSecret(keypair: IKeypair): Buffer { if (keypair.type !== this.type) { throw new Error(ERR_INVALID_KEYPAIR_TYPE); } - const secret = Buffer.from( - secp.getSharedSecret(Uint8Array.from(this.privateKey), Uint8Array.from(keypair.publicKey), true) - ); + const secret = Buffer.from(secp.getSharedSecret(this.privateKey, keypair.publicKey, true)); return secret; } }; diff --git a/src/packet/encode.ts b/src/packet/encode.ts index dfbc014b..663cdc1c 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -38,18 +38,16 @@ export function encodePacket(destId: string, packet: IPacket): Buffer { export function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Buffer { const ctx = Crypto.createCipheriv("aes-128-gcm", fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); return ctx.update( - Uint8Array.from( - Buffer.concat([ - // static header - Buffer.from(header.protocolId, "ascii"), - numberToBuffer(header.version, VERSION_SIZE), - numberToBuffer(header.flag, FLAG_SIZE), - header.nonce, - numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), - // authdata - header.authdata, - ]) - ) + Buffer.concat([ + // static header + Buffer.from(header.protocolId, "ascii"), + numberToBuffer(header.version, VERSION_SIZE), + numberToBuffer(header.flag, FLAG_SIZE), + header.nonce, + numberToBuffer(header.authdataSize, AUTHDATA_SIZE_SIZE), + // authdata + header.authdata, + ]) ); } @@ -80,7 +78,7 @@ export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [I const ctx = Crypto.createDecipheriv("aes-128-gcm", fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); // unmask the static header - const staticHeaderBuf = ctx.update(Uint8Array.from(data.slice(0, STATIC_HEADER_SIZE))); + const staticHeaderBuf = ctx.update(data.slice(0, STATIC_HEADER_SIZE)); // validate the static header field by field const protocolId = staticHeaderBuf.slice(0, PROTOCOL_SIZE).toString("ascii"); diff --git a/src/session/crypto.ts b/src/session/crypto.ts index f5785bf7..deca7455 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -47,14 +47,7 @@ export function generateSessionKeys( export function deriveKey(secret: Buffer, firstId: NodeId, secondId: NodeId, challengeData: Buffer): [Buffer, Buffer] { const info = Buffer.concat([Buffer.from(KEY_AGREEMENT_STRING), fromHex(firstId), fromHex(secondId)]); - const output = Buffer.from( - hkdf.expand( - sha256, - hkdf.extract(sha256, Uint8Array.from(secret), Uint8Array.from(challengeData)), - Uint8Array.from(info), - 2 * KEY_LENGTH - ) - ); + const output = Buffer.from(hkdf.expand(sha256, hkdf.extract(sha256, secret, challengeData), info, 2 * KEY_LENGTH)); return [output.slice(0, KEY_LENGTH), output.slice(KEY_LENGTH, 2 * KEY_LENGTH)]; } @@ -88,9 +81,7 @@ export function idVerify( } export function generateIdSignatureInput(challengeData: Buffer, ephemPK: Buffer, nodeId: NodeId): Buffer { - const hash = sha256( - Uint8Array.from(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)])) - ); + const hash = sha256(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)])); return Buffer.from(hash); } diff --git a/test/e2e/mainnetBootnodes.test.ts b/test/e2e/mainnetBootnodes.test.ts index 9adc706b..95020bda 100644 --- a/test/e2e/mainnetBootnodes.test.ts +++ b/test/e2e/mainnetBootnodes.test.ts @@ -6,7 +6,6 @@ import { unmarshalPrivateKey } from "@libp2p/crypto/keys"; import { Discv5, ENR } from "../../src/index.js"; let port = 9000; - describe("discv5 integration test", function () { this.timeout("5min"); @@ -49,7 +48,7 @@ describe("discv5 integration test", function () { discv5.on("discovered", (enr) => { foundENRs.push(enr); }); - + discv5.enableLogs() await discv5.findRandomNode(); expect(foundENRs).to.have.length.greaterThan(0, "Should found some ENRs"); diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 4866acb5..36e5acf8 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -26,9 +26,7 @@ describe("session crypto", () => { ); const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); - expect(Buffer.from(secp.getSharedSecret(Uint8Array.from(localSK), Uint8Array.from(remotePK), true))).to.deep.equal( - expected - ); + expect(Buffer.from(secp.getSharedSecret(localSK, remotePK, true))).to.deep.equal(expected); }); it("key derivation should produce expected keys", () => { @@ -40,7 +38,7 @@ describe("session crypto", () => { const ephemKey = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); const destPubkey = Buffer.from("0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91", "hex"); - const secret = secp.getSharedSecret(Uint8Array.from(ephemKey), Uint8Array.from(destPubkey), true); + const secret = secp.getSharedSecret(ephemKey, destPubkey, true); const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const challengeData = Buffer.from( From 5d248802eeeb9cb12205557413746ebd8fbf9f4b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:58:14 -0400 Subject: [PATCH 24/32] Use buffer.from view --- src/enr/v4.ts | 2 +- src/keypair/secp256k1.ts | 10 +++++----- src/message/create.ts | 2 +- src/packet/create.ts | 8 ++++---- src/session/crypto.ts | 6 ++++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 355bfc0c..4e2e741a 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -17,7 +17,7 @@ export function hash(input: Buffer): Buffer { } export function createPrivateKey(): Buffer { - return Buffer.from(secp256k1.utils.randomPrivateKey()); + return Buffer.from(secp256k1.utils.randomPrivateKey().buffer); } export function publicKey(privKey: Buffer): Buffer { diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index 4527183b..c7e08614 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -20,11 +20,11 @@ export function secp256k1PublicKeyToFull(publicKey: Buffer): Buffer { if (publicKey.length === 64) { return Buffer.concat([Buffer.from([4]), publicKey]); } - return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false)); + return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false).buffer); } export function secp256k1PublicKeyToRaw(publicKey: Buffer): Buffer { - return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false)).slice(1); + return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false).buffer).slice(1); } export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends AbstractKeypair implements IKeypair { @@ -42,7 +42,7 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab static generate(): Secp256k1Keypair { const privateKey = secp.utils.randomPrivateKey(); const publicKey = secp.getPublicKey(privateKey); - return new Secp256k1Keypair(Buffer.from(privateKey), Buffer.from(publicKey)); + return new Secp256k1Keypair(Buffer.from(privateKey.buffer), Buffer.from(publicKey.buffer)); } privateKeyVerify(key = this._privateKey): boolean { @@ -61,7 +61,7 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab } } sign(msg: Buffer): Buffer { - return Buffer.from(secp.signSync(msg, this.privateKey, { der: false })); + return Buffer.from(secp.signSync(msg, this.privateKey, { der: false }).buffer); } verify(msg: Buffer, sig: Buffer): boolean { return secp.verify(sig, msg, this.publicKey); @@ -70,7 +70,7 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab if (keypair.type !== this.type) { throw new Error(ERR_INVALID_KEYPAIR_TYPE); } - const secret = Buffer.from(secp.getSharedSecret(this.privateKey, keypair.publicKey, true)); + const secret = Buffer.from(secp.getSharedSecret(this.privateKey, keypair.publicKey, true).buffer); return secret; } }; diff --git a/src/message/create.ts b/src/message/create.ts index 26ecb1e6..0a28871a 100644 --- a/src/message/create.ts +++ b/src/message/create.ts @@ -14,7 +14,7 @@ import { import { SequenceNumber, ENR } from "../enr/index.js"; export function createRequestId(): RequestId { - return toBigIntBE(Buffer.from(randomBytes(8))); + return toBigIntBE(Buffer.from(randomBytes(8).buffer)); } export function createPingMessage(enrSeq: SequenceNumber): IPingMessage { diff --git a/src/packet/create.ts b/src/packet/create.ts index d67599f4..7ea99d40 100644 --- a/src/packet/create.ts +++ b/src/packet/create.ts @@ -22,8 +22,8 @@ export function createHeader( export function createRandomPacket(srcId: NodeId): IPacket { const authdata = encodeMessageAuthdata({ srcId }); const header = createHeader(PacketType.Message, authdata); - const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE)); - const message = Buffer.from(randomBytes(44)); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE).buffer); + const message = Buffer.from(randomBytes(44).buffer); return { maskingIv, header, @@ -32,10 +32,10 @@ export function createRandomPacket(srcId: NodeId): IPacket { } export function createWhoAreYouPacket(nonce: Buffer, enrSeq: SequenceNumber): IPacket { - const idNonce = Buffer.from(randomBytes(ID_NONCE_SIZE)); + const idNonce = Buffer.from(randomBytes(ID_NONCE_SIZE).buffer); const authdata = encodeWhoAreYouAuthdata({ idNonce, enrSeq }); const header = createHeader(PacketType.WhoAreYou, authdata, nonce); - const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE)); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE).buffer); const message = Buffer.alloc(0); return { maskingIv, diff --git a/src/session/crypto.ts b/src/session/crypto.ts index deca7455..b853e7e2 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -47,7 +47,9 @@ export function generateSessionKeys( export function deriveKey(secret: Buffer, firstId: NodeId, secondId: NodeId, challengeData: Buffer): [Buffer, Buffer] { const info = Buffer.concat([Buffer.from(KEY_AGREEMENT_STRING), fromHex(firstId), fromHex(secondId)]); - const output = Buffer.from(hkdf.expand(sha256, hkdf.extract(sha256, secret, challengeData), info, 2 * KEY_LENGTH)); + const output = Buffer.from( + hkdf.expand(sha256, hkdf.extract(sha256, secret, challengeData), info, 2 * KEY_LENGTH).buffer + ); return [output.slice(0, KEY_LENGTH), output.slice(KEY_LENGTH, 2 * KEY_LENGTH)]; } @@ -82,7 +84,7 @@ export function idVerify( export function generateIdSignatureInput(challengeData: Buffer, ephemPK: Buffer, nodeId: NodeId): Buffer { const hash = sha256(Buffer.concat([Buffer.from(ID_SIGNATURE_TEXT), challengeData, ephemPK, fromHex(nodeId)])); - return Buffer.from(hash); + return Buffer.from(hash.buffer); } export function decryptMessage(key: Buffer, nonce: Buffer, data: Buffer, aad: Buffer): Buffer { From aaec0e8883730767b242379bd717796399fb1851 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 13:01:21 -0400 Subject: [PATCH 25/32] lint fixes --- src/session/service.ts | 2 +- src/transport/udp.ts | 2 +- src/util/crypto.ts | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/session/service.ts b/src/session/service.ts index 00d4d6f1..886cb573 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -296,7 +296,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte case PacketType.Message: return this.handleMessage(src, packet); } - } + }; private handleChallenge(src: Multiaddr, packet: IPacket): void { // First decode the authdata diff --git a/src/transport/udp.ts b/src/transport/udp.ts index 33949bcc..82bfbde2 100644 --- a/src/transport/udp.ts +++ b/src/transport/udp.ts @@ -59,5 +59,5 @@ export class UDPTransportService } catch (e: unknown) { this.emit("decodeError", e as Error, multiaddr); } - } + }; } diff --git a/src/util/crypto.ts b/src/util/crypto.ts index f81590c0..f5e36452 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -1,15 +1,15 @@ import { crypto } from "@noble/hashes/crypto"; -const Crypto = crypto.node ?? crypto.web +const Crypto = crypto.node ?? crypto.web; export async function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { - const ctx = Crypto.createCipheriv('aes-128-gcm', key, iv); - ctx.update(pt); - return ctx.final(); + const ctx = Crypto.createCipheriv("aes-128-gcm", key, iv); + ctx.update(pt); + return ctx.final(); } export async function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { - const ctx = Crypto.createDecipheriv('aes-128-gcm', key, iv); + const ctx = Crypto.createDecipheriv("aes-128-gcm", key, iv); ctx.update(pt); return ctx.final(); } From 384adb5b45261d09a225f3b458546008289c8b47 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 13:02:10 -0400 Subject: [PATCH 26/32] add lint:fix script :-) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3c9964dc..c840c363 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "build": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn build", "lint": "eslint --color --ext .ts src/ test/", + "lint:fix": "eslint --color --fix --ext .ts src/ test/", "test": "yarn test:unit && yarn test:e2e", "test:unit": "mocha 'test/unit/**/*.test.ts'", "test:e2e": "mocha 'test/e2e/**/*.test.ts'" From af5c2326438a0e72e44636c8003af3397ed3172b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 13:06:09 -0400 Subject: [PATCH 27/32] Make linter happy --- src/session/service.ts | 1 + test/e2e/mainnetBootnodes.test.ts | 3 ++- test/unit/enr.test.ts | 4 ++++ test/unit/enr/enr.test.ts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/session/service.ts b/src/session/service.ts index 886cb573..99b60482 100644 --- a/src/session/service.ts +++ b/src/session/service.ts @@ -784,6 +784,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte const entry = this.pendingRequests.get(nodeAddrStr); if (entry) { // if it exists, there must be a request here + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion const request = entry.shift()!; if (!entry.length) { this.pendingRequests.delete(nodeAddrStr); diff --git a/test/e2e/mainnetBootnodes.test.ts b/test/e2e/mainnetBootnodes.test.ts index 95020bda..8b929a69 100644 --- a/test/e2e/mainnetBootnodes.test.ts +++ b/test/e2e/mainnetBootnodes.test.ts @@ -41,6 +41,7 @@ describe("discv5 integration test", function () { for (let i = 0; i < bootCount; i++) { const bootEnr = ENR.decodeTxt(bootnodesENRText[i]); discv5.addEnr(bootEnr); + //eslint-disable-next-line no-console console.log("BOOTNODE", bootEnr.ip, bootEnr.udp); } @@ -48,7 +49,7 @@ describe("discv5 integration test", function () { discv5.on("discovered", (enr) => { foundENRs.push(enr); }); - discv5.enableLogs() + discv5.enableLogs(); await discv5.findRandomNode(); expect(foundENRs).to.have.length.greaterThan(0, "Should found some ENRs"); diff --git a/test/unit/enr.test.ts b/test/unit/enr.test.ts index fe77b49c..b02532f3 100644 --- a/test/unit/enr.test.ts +++ b/test/unit/enr.test.ts @@ -58,11 +58,13 @@ describe("ENR Multiformats support", () => { record.set("ip", tuples0[0][1]); record.set("udp", tuples0[1][1]); // and get the multiaddr + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(record.getLocationMultiaddr("udp")!.toString()).to.equal(multi0.toString()); // set the multiaddr const multi1 = new Multiaddr("/ip4/0.0.0.0/udp/30300"); record.setLocationMultiaddr(multi1); // and get the multiaddr + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(record.getLocationMultiaddr("udp")!.toString()).to.equal(multi1.toString()); // and get the underlying records const tuples1 = multi1.tuples(); @@ -81,11 +83,13 @@ describe("ENR Multiformats support", () => { record.set("ip", tuples0[0][1]); record.set("tcp", tuples0[1][1]); // and get the multiaddr + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal(multi0.toString()); // set the multiaddr const multi1 = new Multiaddr("/ip4/0.0.0.0/tcp/30300"); record.setLocationMultiaddr(multi1); // and get the multiaddr + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(record.getLocationMultiaddr("tcp")!.toString()).to.equal(multi1.toString()); // and get the underlying records const tuples1 = multi1.tuples(); diff --git a/test/unit/enr/enr.test.ts b/test/unit/enr/enr.test.ts index 9b87b5e8..6120cb35 100644 --- a/test/unit/enr/enr.test.ts +++ b/test/unit/enr/enr.test.ts @@ -17,6 +17,7 @@ describe("ENR", function () { expect(txt.slice(0, 4)).to.be.equal("enr:"); const enr2 = ENR.decodeTxt(txt); expect(toHex(enr2.signature as Buffer)).to.be.equal(toHex(enr.signature as Buffer)); + //eslint-disable-next-line @typescript-eslint/no-non-null-assertion const multiaddr = enr2.getLocationMultiaddr("udp")!; expect(multiaddr.toString()).to.be.equal("/ip4/18.223.219.100/udp/9000"); }); From f4572f43ac4557180f1f8081b83ce8804177062c Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 13:35:51 -0400 Subject: [PATCH 28/32] consolidate secp in crypto utility --- src/enr/v4.ts | 9 +-------- src/keypair/secp256k1.ts | 29 +++++++++++------------------ src/util/crypto.ts | 10 ++++++++++ test/unit/session/crypto.test.ts | 6 +++--- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/enr/v4.ts b/src/enr/v4.ts index 4e2e741a..3358c596 100644 --- a/src/enr/v4.ts +++ b/src/enr/v4.ts @@ -1,12 +1,5 @@ import { keccak_256 as keccak } from "@noble/hashes/sha3"; -import * as secp256k1 from "@noble/secp256k1"; -import { hmac } from "@noble/hashes/hmac"; -import { sha256 } from "@noble/hashes/sha256"; -secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { - const h = hmac.create(sha256, key); - msgs.forEach((msg) => h.update(msg)); - return h.digest(); -}; +import { secp256k1 } from "../util/crypto.js"; import { NodeId } from "./types.js"; import { createNodeId } from "./create.js"; diff --git a/src/keypair/secp256k1.ts b/src/keypair/secp256k1.ts index c7e08614..c1ae4693 100644 --- a/src/keypair/secp256k1.ts +++ b/src/keypair/secp256k1.ts @@ -1,30 +1,23 @@ -import * as secp from "@noble/secp256k1"; import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types.js"; import { ERR_INVALID_KEYPAIR_TYPE } from "./constants.js"; -import { hmac } from "@noble/hashes/hmac"; -import { sha256 } from "@noble/hashes/sha256"; -secp.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { - const h = hmac.create(sha256, key); - msgs.forEach((msg) => h.update(msg)); - return h.digest(); -}; +import { secp256k1 } from "../util/crypto.js"; export function secp256k1PublicKeyToCompressed(publicKey: Buffer): Buffer { if (publicKey.length === 64) { publicKey = Buffer.concat([Buffer.from([4]), publicKey]); } - return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(true)); + return Buffer.from(secp256k1.Point.fromHex(publicKey).toRawBytes(true)); } export function secp256k1PublicKeyToFull(publicKey: Buffer): Buffer { if (publicKey.length === 64) { return Buffer.concat([Buffer.from([4]), publicKey]); } - return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false).buffer); + return Buffer.from(secp256k1.Point.fromHex(publicKey).toRawBytes(false).buffer); } export function secp256k1PublicKeyToRaw(publicKey: Buffer): Buffer { - return Buffer.from(secp.Point.fromHex(publicKey).toRawBytes(false).buffer).slice(1); + return Buffer.from(secp256k1.Point.fromHex(publicKey).toRawBytes(false).buffer).slice(1); } export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends AbstractKeypair implements IKeypair { @@ -40,37 +33,37 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair extends Ab } static generate(): Secp256k1Keypair { - const privateKey = secp.utils.randomPrivateKey(); - const publicKey = secp.getPublicKey(privateKey); + const privateKey = secp256k1.utils.randomPrivateKey(); + const publicKey = secp256k1.getPublicKey(privateKey); return new Secp256k1Keypair(Buffer.from(privateKey.buffer), Buffer.from(publicKey.buffer)); } privateKeyVerify(key = this._privateKey): boolean { if (key) { - return secp.utils.isValidPrivateKey(key); + return secp256k1.utils.isValidPrivateKey(key); } return true; } publicKeyVerify(): boolean { try { - secp.Point.fromHex(this.publicKey).assertValidity(); + secp256k1.Point.fromHex(this.publicKey).assertValidity(); return true; } catch { return false; } } sign(msg: Buffer): Buffer { - return Buffer.from(secp.signSync(msg, this.privateKey, { der: false }).buffer); + return Buffer.from(secp256k1.signSync(msg, this.privateKey, { der: false }).buffer); } verify(msg: Buffer, sig: Buffer): boolean { - return secp.verify(sig, msg, this.publicKey); + return secp256k1.verify(sig, msg, this.publicKey); } deriveSecret(keypair: IKeypair): Buffer { if (keypair.type !== this.type) { throw new Error(ERR_INVALID_KEYPAIR_TYPE); } - const secret = Buffer.from(secp.getSharedSecret(this.privateKey, keypair.publicKey, true).buffer); + const secret = Buffer.from(secp256k1.getSharedSecret(this.privateKey, keypair.publicKey, true).buffer); return secret; } }; diff --git a/src/util/crypto.ts b/src/util/crypto.ts index f5e36452..0aeebc86 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -1,4 +1,14 @@ import { crypto } from "@noble/hashes/crypto"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +import * as secp256k1 from "@noble/secp256k1"; + +secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { + const h = hmac.create(sha256, key); + msgs.forEach((msg) => h.update(msg)); + return h.digest(); +}; +export * as secp256k1 from "@noble/secp256k1"; const Crypto = crypto.node ?? crypto.web; diff --git a/test/unit/session/crypto.test.ts b/test/unit/session/crypto.test.ts index 36e5acf8..38577171 100644 --- a/test/unit/session/crypto.test.ts +++ b/test/unit/session/crypto.test.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ import { expect } from "chai"; -import * as secp from "@noble/secp256k1"; +import { secp256k1 } from "../../../src/util/crypto.js"; import { randomBytes } from "@libp2p/crypto"; import { @@ -26,7 +26,7 @@ describe("session crypto", () => { ); const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); - expect(Buffer.from(secp.getSharedSecret(localSK, remotePK, true))).to.deep.equal(expected); + expect(Buffer.from(secp256k1.getSharedSecret(localSK, remotePK, true))).to.deep.equal(expected); }); it("key derivation should produce expected keys", () => { @@ -38,7 +38,7 @@ describe("session crypto", () => { const ephemKey = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); const destPubkey = Buffer.from("0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91", "hex"); - const secret = secp.getSharedSecret(ephemKey, destPubkey, true); + const secret = secp256k1.getSharedSecret(ephemKey, destPubkey, true); const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; const challengeData = Buffer.from( From 66c1753ed17806adda77a433946ddfdb07a47160 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 5 Jul 2022 14:17:09 -0400 Subject: [PATCH 29/32] Use crypto instead of noble re-export --- src/packet/encode.ts | 4 +--- src/session/crypto.ts | 4 +--- src/session/session.ts | 6 +++--- src/util/crypto.ts | 5 ++--- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/packet/encode.ts b/src/packet/encode.ts index 663cdc1c..216e0e4b 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -1,4 +1,4 @@ -import { crypto } from "@noble/hashes/crypto"; +import Crypto from "crypto"; import { toBigIntBE, toBufferBE } from "bigint-buffer"; import errcode from "err-code"; @@ -29,8 +29,6 @@ import { } from "./constants.js"; import { IHandshakeAuthdata, IHeader, IMessageAuthdata, IPacket, IWhoAreYouAuthdata, PacketType } from "./types.js"; -const Crypto = crypto.node ?? crypto.web; - export function encodePacket(destId: string, packet: IPacket): Buffer { return Buffer.concat([packet.maskingIv, encodeHeader(destId, packet.maskingIv, packet.header), packet.message]); } diff --git a/src/session/crypto.ts b/src/session/crypto.ts index b853e7e2..7e4df7c6 100644 --- a/src/session/crypto.ts +++ b/src/session/crypto.ts @@ -1,13 +1,11 @@ import * as hkdf from "@noble/hashes/hkdf"; import { sha256 } from "@noble/hashes/sha256"; -import { crypto } from "@noble/hashes/crypto"; +import Crypto from "crypto"; import { NodeId } from "../enr/index.js"; import { generateKeypair, IKeypair, createKeypair } from "../keypair/index.js"; import { fromHex } from "../util/index.js"; import { getNodeId, getPublicKey, NodeContact } from "./nodeInfo.js"; -const Crypto = crypto.node ?? crypto.web; - // Implementation for generating session keys in the Discv5 protocol. // Currently, Diffie-Hellman key agreement is performed with known public key types. Session keys // are then derived using the HKDF (SHA2-256) key derivation function. diff --git a/src/session/session.ts b/src/session/session.ts index 22e33e99..6715243a 100644 --- a/src/session/session.ts +++ b/src/session/session.ts @@ -18,7 +18,7 @@ import { idVerify, } from "./crypto.js"; import { IKeypair } from "../keypair/index.js"; -import { randomBytes } from "crypto"; +import { randomBytes } from "@noble/hashes/utils"; import { RequestId } from "../message/index.js"; import { IChallenge } from "."; import { getNodeId, NodeContact } from "./nodeInfo.js"; @@ -144,7 +144,7 @@ export class Session { }); const header = createHeader(PacketType.Handshake, authdata); - const maskingIv = randomBytes(MASKING_IV_SIZE); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE).buffer); const aad = encodeChallengeData(maskingIv, header); // encrypt the message @@ -176,7 +176,7 @@ export class Session { encryptMessage(srcId: NodeId, destId: NodeId, message: Buffer): IPacket { const authdata = encodeMessageAuthdata({ srcId }); const header = createHeader(PacketType.Message, authdata); - const maskingIv = randomBytes(MASKING_IV_SIZE); + const maskingIv = Buffer.from(randomBytes(MASKING_IV_SIZE).buffer); const aad = encodeChallengeData(maskingIv, header); const ciphertext = encryptMessage(this.keys.encryptionKey, header.nonce, message, aad); return { diff --git a/src/util/crypto.ts b/src/util/crypto.ts index 0aeebc86..fa97417e 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -1,4 +1,4 @@ -import { crypto } from "@noble/hashes/crypto"; +import Crypto from "crypto"; import { hmac } from "@noble/hashes/hmac"; import { sha256 } from "@noble/hashes/sha256"; import * as secp256k1 from "@noble/secp256k1"; @@ -8,9 +8,8 @@ secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { msgs.forEach((msg) => h.update(msg)); return h.digest(); }; -export * as secp256k1 from "@noble/secp256k1"; -const Crypto = crypto.node ?? crypto.web; +export * as secp256k1 from "@noble/secp256k1"; export async function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { const ctx = Crypto.createCipheriv("aes-128-gcm", key, iv); From ad5c5df7b8f80249bc115150782cf11d3aba1567 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:34:02 -0400 Subject: [PATCH 30/32] Fix aes cipher mode --- src/packet/encode.ts | 4 ++-- src/util/crypto.ts | 4 ++-- test/e2e/mainnetBootnodes.test.ts | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/packet/encode.ts b/src/packet/encode.ts index 216e0e4b..c755729f 100644 --- a/src/packet/encode.ts +++ b/src/packet/encode.ts @@ -34,7 +34,7 @@ export function encodePacket(destId: string, packet: IPacket): Buffer { } export function encodeHeader(destId: string, maskingIv: Buffer, header: IHeader): Buffer { - const ctx = Crypto.createCipheriv("aes-128-gcm", fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); + const ctx = Crypto.createCipheriv("aes-128-ctr", fromHex(destId).slice(0, MASKING_KEY_SIZE), maskingIv); return ctx.update( Buffer.concat([ // static header @@ -73,7 +73,7 @@ export function decodePacket(srcId: string, data: Buffer): IPacket { * Return the decoded header and the header as a buffer */ export function decodeHeader(srcId: string, maskingIv: Buffer, data: Buffer): [IHeader, Buffer] { - const ctx = Crypto.createDecipheriv("aes-128-gcm", fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); + const ctx = Crypto.createDecipheriv("aes-128-ctr", fromHex(srcId).slice(0, MASKING_KEY_SIZE), maskingIv); // unmask the static header const staticHeaderBuf = ctx.update(data.slice(0, STATIC_HEADER_SIZE)); diff --git a/src/util/crypto.ts b/src/util/crypto.ts index fa97417e..0ef6067b 100644 --- a/src/util/crypto.ts +++ b/src/util/crypto.ts @@ -11,13 +11,13 @@ secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { export * as secp256k1 from "@noble/secp256k1"; -export async function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { +export function aesCtrEncrypt(key: Buffer, iv: Buffer, pt: Buffer): Buffer { const ctx = Crypto.createCipheriv("aes-128-gcm", key, iv); ctx.update(pt); return ctx.final(); } -export async function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Promise { +export function aesCtrDecrypt(key: Buffer, iv: Buffer, pt: Buffer): Buffer { const ctx = Crypto.createDecipheriv("aes-128-gcm", key, iv); ctx.update(pt); return ctx.final(); diff --git a/test/e2e/mainnetBootnodes.test.ts b/test/e2e/mainnetBootnodes.test.ts index 8b929a69..13aca9eb 100644 --- a/test/e2e/mainnetBootnodes.test.ts +++ b/test/e2e/mainnetBootnodes.test.ts @@ -49,7 +49,6 @@ describe("discv5 integration test", function () { discv5.on("discovered", (enr) => { foundENRs.push(enr); }); - discv5.enableLogs(); await discv5.findRandomNode(); expect(foundENRs).to.have.length.greaterThan(0, "Should found some ENRs"); From 1035bfdc9901c7ae2da0e1e2f520cd94489a26c5 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 7 Jul 2022 15:54:08 -0400 Subject: [PATCH 31/32] add benchmark script --- test/unit/benchmark.test.js | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/unit/benchmark.test.js diff --git a/test/unit/benchmark.test.js b/test/unit/benchmark.test.js new file mode 100644 index 00000000..4febb890 --- /dev/null +++ b/test/unit/benchmark.test.js @@ -0,0 +1,42 @@ +import { itBench } from "@dapplion/benchmark"; +import { expect } from "chai"; +import { randomBytes } from "crypto"; +import { decryptMessage, encryptMessage, createKeypair, KeypairType, idSign, idVerify, v4 } from "../../lib/index.js"; + +itBench("benchmark aes cipher encryption/decryptions", () => { + const key = Buffer.from(randomBytes(16)); + const nonce = Buffer.from(randomBytes(12)); + const msg = Buffer.from(randomBytes(16)); + const ad = Buffer.from(randomBytes(16)); + + const cipher = encryptMessage(key, nonce, msg, ad); + const decrypted = decryptMessage(key, nonce, cipher, ad); + expect(decrypted).to.deep.equal(msg); +}); + +itBench("benchmark sign/verify", () => { + const expected = Buffer.from( + "94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6", + "hex" + ); + + const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); + const challengeData = Buffer.from( + "000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000", + "hex" + ); + const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); + const nodeIdB = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; + const keypair = createKeypair(KeypairType.Secp256k1, localSK); + const actual = idSign(keypair, challengeData, ephemPK, nodeIdB); + expect(actual).to.deep.equal(expected); + expect( + idVerify( + createKeypair(KeypairType.Secp256k1, undefined, v4.publicKey(localSK)), + challengeData, + ephemPK, + nodeIdB, + actual + ) + ).to.be.true; +}) \ No newline at end of file From 612a76d9fc3168711f618eeb485317585ddccb45 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:32:01 -0400 Subject: [PATCH 32/32] Add more complete crypto benchmark suite --- bench/crypto/index.bench.js | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 bench/crypto/index.bench.js diff --git a/bench/crypto/index.bench.js b/bench/crypto/index.bench.js new file mode 100644 index 00000000..504c9874 --- /dev/null +++ b/bench/crypto/index.bench.js @@ -0,0 +1,84 @@ +import { itBench } from "@dapplion/benchmark"; +import { expect } from "chai"; +import { randomBytes } from "crypto"; +import { + decryptMessage, + encryptMessage, + createKeypair, + KeypairType, + idSign, + idVerify, + v4, + Secp256k1Keypair, + deriveKey, + generateIdSignatureInput, +} from "../../lib/index.js"; +import { hash } from "../../lib/enr/v4.js"; + +itBench("benchmark aes cipher encryption/decryptions", () => { + const key = Buffer.from(randomBytes(16)); + const nonce = Buffer.from(randomBytes(12)); + const msg = Buffer.from(randomBytes(16)); + const ad = Buffer.from(randomBytes(16)); + + const cipher = encryptMessage(key, nonce, msg, ad); + const decrypted = decryptMessage(key, nonce, cipher, ad); + expect(decrypted).to.deep.equal(msg); +}); + +itBench("benchmark sign/verify", () => { + const expected = Buffer.from( + "94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6", + "hex" + ); + + const localSK = Buffer.from("fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736", "hex"); + const challengeData = Buffer.from( + "000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000", + "hex" + ); + const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); + const nodeIdB = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; + const keypair = createKeypair(KeypairType.Secp256k1, localSK); + const actual = idSign(keypair, challengeData, ephemPK, nodeIdB); + expect(actual).to.deep.equal(expected); + expect( + idVerify( + createKeypair(KeypairType.Secp256k1, undefined, v4.publicKey(localSK)), + challengeData, + ephemPK, + nodeIdB, + actual + ) + ).to.be.true; +}); + +itBench("benchmark key derivation", () => { + const secret = Buffer.from("022c82f214eb37159111712add00040fcdf73fd4d7d0b7c0f980da4d099aa59ba4"); + const firstNodeId = "aaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"; + const secondNodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; + const challengeData = Buffer.from( + "000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000", + "hex" + ); + + deriveKey(Buffer.from(secret), firstNodeId, secondNodeId, challengeData); +}); + +itBench("benchmark secp256k1.generatePrivateKey", () => { + Secp256k1Keypair.generate(); +}); + +itBench("benchmark keccak hash", () => { + hash(Buffer.from("secret message")); +}); + +itBench("benchmark sha256 hash", () => { + const challengeData = Buffer.from( + "000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000", + "hex" + ); + const ephemPK = Buffer.from("039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231", "hex"); + const nodeId = "bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"; + generateIdSignatureInput(challengeData, ephemPK, nodeId); +})