From 8794025d713ccbb3748816f76c1a897c815e85ec Mon Sep 17 00:00:00 2001 From: Cayman Date: Fri, 15 Sep 2023 06:06:21 -0400 Subject: [PATCH] feat: selectively use nodejs crypto for noise (#5900) * chore: add noise perf test * feat: use nodejs crypto for noise * feat: selectively use nodejs or as implementation --- packages/beacon-node/package.json | 2 + .../beacon-node/src/network/libp2p/noise.ts | 85 +++++++++++++++---- .../test/perf/network/noise/sendData.test.ts | 52 ++++++++++++ yarn.lock | 5 ++ 4 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 packages/beacon-node/test/perf/network/noise/sendData.test.ts diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 9be412fea854..ec993ccc1e42 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -160,6 +160,8 @@ "@types/supertest": "^2.0.12", "@types/tmp": "^0.2.3", "eventsource": "^2.0.2", + "it-pair": "^2.0.6", + "it-drain": "^3.0.3", "leveldown": "^6.1.1", "rewiremock": "^3.14.5", "rimraf": "^4.4.1", diff --git a/packages/beacon-node/src/network/libp2p/noise.ts b/packages/beacon-node/src/network/libp2p/noise.ts index fcd4f3c9354f..fcb00fe41893 100644 --- a/packages/beacon-node/src/network/libp2p/noise.ts +++ b/packages/beacon-node/src/network/libp2p/noise.ts @@ -1,36 +1,85 @@ +import crypto from "node:crypto"; import type {ConnectionEncrypter} from "@libp2p/interface/connection-encrypter"; -import {newInstance, ChaCha20Poly1305} from "@chainsafe/as-chacha20poly1305"; import {ICryptoInterface, noise, pureJsCrypto} from "@chainsafe/libp2p-noise"; import {digest} from "@chainsafe/as-sha256"; - -type Bytes = Uint8Array; -type Bytes32 = Uint8Array; +import {newInstance, ChaCha20Poly1305} from "@chainsafe/as-chacha20poly1305"; const ctx = newInstance(); const asImpl = new ChaCha20Poly1305(ctx); -// same to stablelib but we use as-chacha20poly1305 and as-sha256 -const lodestarCrypto: ICryptoInterface = { - ...pureJsCrypto, - hashSHA256(data: Uint8Array): Uint8Array { - return digest(data); +const CHACHA_POLY1305 = "chacha20-poly1305"; + +const nodeCrypto: Pick = { + hashSHA256(data) { + return crypto.createHash("sha256").update(data).digest(); }, - chaCha20Poly1305Encrypt(plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: Bytes32): Bytes { - return asImpl.seal(k, nonce, plaintext, ad); + chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) { + const cipher = crypto.createCipheriv(CHACHA_POLY1305, k, nonce, { + authTagLength: 16, + }); + cipher.setAAD(ad, {plaintextLength: plaintext.byteLength}); + const updated = cipher.update(plaintext); + const final = cipher.final(); + const tag = cipher.getAuthTag(); + + const encrypted = Buffer.concat([updated, tag, final]); + return encrypted; }, - chaCha20Poly1305Decrypt( - ciphertext: Uint8Array, - nonce: Uint8Array, - ad: Uint8Array, - k: Bytes32, - dst?: Uint8Array - ): Bytes | null { + chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, _dst) { + const authTag = ciphertext.slice(ciphertext.length - 16); + const text = ciphertext.slice(0, ciphertext.length - 16); + const decipher = crypto.createDecipheriv(CHACHA_POLY1305, k, nonce, { + authTagLength: 16, + }); + decipher.setAAD(ad, { + plaintextLength: text.byteLength, + }); + decipher.setAuthTag(authTag); + const updated = decipher.update(text); + const final = decipher.final(); + if (final.byteLength > 0) { + return Buffer.concat([updated, final]); + } + return updated; + }, +}; + +const asCrypto: Pick = { + hashSHA256(data) { + return digest(data); + }, + chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) { + return asImpl.seal(k, nonce, plaintext, ad); + }, + chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) { return asImpl.open(k, nonce, ciphertext, ad, dst); }, }; +// benchmarks show that for chacha20poly1305 +// the as implementation is faster for smaller payloads(<1200) +// and the node implementation is faster for larger payloads +const lodestarCrypto: ICryptoInterface = { + ...pureJsCrypto, + hashSHA256(data) { + return nodeCrypto.hashSHA256(data); + }, + chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) { + if (plaintext.length < 1200) { + return asCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k); + } + return nodeCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k); + }, + chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) { + if (ciphertext.length < 1200) { + return asCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst); + } + return nodeCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst); + }, +}; + export function createNoise(): () => ConnectionEncrypter { return noise({crypto: lodestarCrypto}); } diff --git a/packages/beacon-node/test/perf/network/noise/sendData.test.ts b/packages/beacon-node/test/perf/network/noise/sendData.test.ts new file mode 100644 index 000000000000..a6843c13ece2 --- /dev/null +++ b/packages/beacon-node/test/perf/network/noise/sendData.test.ts @@ -0,0 +1,52 @@ +import {itBench} from "@dapplion/benchmark"; +import {duplexPair} from "it-pair/duplex"; +import {createSecp256k1PeerId} from "@libp2p/peer-id-factory"; +import {pipe} from "it-pipe"; +import drain from "it-drain"; +import {createNoise} from "../../../../src/network/libp2p/noise.js"; + +describe("network / noise / sendData", () => { + const numberOfMessages = 1000; + + for (const messageLength of [ + // + 2 ** 8, + 2 ** 9, + 2 ** 10, + 1200, + 2 ** 11, + 2 ** 12, + 2 ** 14, + 2 ** 16, + ]) { + itBench({ + id: `send data - ${numberOfMessages} ${messageLength}B messages`, + beforeEach: async () => { + const peerA = await createSecp256k1PeerId(); + const peerB = await createSecp256k1PeerId(); + const noiseA = createNoise()(); + const noiseB = createNoise()(); + + const [inboundConnection, outboundConnection] = duplexPair(); + const [outbound, inbound] = await Promise.all([ + noiseA.secureOutbound(peerA, outboundConnection, peerB), + noiseB.secureInbound(peerB, inboundConnection, peerA), + ]); + + return {connA: outbound.conn, connB: inbound.conn, data: new Uint8Array(messageLength)}; + }, + fn: async ({connA, connB, data}) => { + await Promise.all([ + // + pipe(connB.source, connB.sink), + pipe(function* () { + for (let i = 0; i < numberOfMessages; i++) { + yield data; + } + }, connA.sink), + pipe(connB.source, drain), + ]); + }, + }); + } +}); diff --git a/yarn.lock b/yarn.lock index 4d90e697ab51..4290e224919a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8500,6 +8500,11 @@ it-drain@^3.0.1, it-drain@^3.0.2: resolved "https://registry.yarnpkg.com/it-drain/-/it-drain-3.0.2.tgz#4fb2ab30119072268c68a895fa5b9f2037942c44" integrity sha512-0hJvS/4Ktt9wT/bktmovjjMAY8r6FCsXqpL3zjqBBNwoL21VgQfguEnwbLSGuCip9Zq1vfU43cbHkmaRZdBfOg== +it-drain@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/it-drain/-/it-drain-3.0.3.tgz#f80719d3d0d7e7d02dc298d86ca9d0e7f7bd666b" + integrity sha512-l4s+izxUpFAR2axprpFiCaq0EtxK1QMd0LWbEtau5b+OegiZ5xdRtz35iJyh6KZY9QtuwEiQxydiOfYJc7stoA== + it-filter@^3.0.0, it-filter@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/it-filter/-/it-filter-3.0.2.tgz#19ddf6185ea21d417e6075d5796c799fa2633b69"