Skip to content

Commit

Permalink
feat: selectively use nodejs crypto for noise (#5900)
Browse files Browse the repository at this point in the history
* chore: add noise perf test

* feat: use nodejs crypto for noise

* feat: selectively use nodejs or as implementation
  • Loading branch information
wemeetagain authored Sep 15, 2023
1 parent 37f404f commit 8794025
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 18 deletions.
2 changes: 2 additions & 0 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
85 changes: 67 additions & 18 deletions packages/beacon-node/src/network/libp2p/noise.ts
Original file line number Diff line number Diff line change
@@ -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<ICryptoInterface, "hashSHA256" | "chaCha20Poly1305Encrypt" | "chaCha20Poly1305Decrypt"> = {
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<ICryptoInterface, "hashSHA256" | "chaCha20Poly1305Encrypt" | "chaCha20Poly1305Decrypt"> = {
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});
}
52 changes: 52 additions & 0 deletions packages/beacon-node/test/perf/network/noise/sendData.test.ts
Original file line number Diff line number Diff line change
@@ -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<Uint8Array>();
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),
]);
},
});
}
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 8794025

Please sign in to comment.