diff --git a/package.json b/package.json index adccecc..bbd67b3 100644 --- a/package.json +++ b/package.json @@ -68,13 +68,15 @@ "prepublish": "npm run build" }, "dependencies": { + "@chainsafe/as-chacha20poly1305": "^0.1.0", + "@chainsafe/as-sha256": "^0.4.1", "@libp2p/crypto": "^2.0.0", "@libp2p/interface": "^0.1.0", "@libp2p/logger": "^3.0.0", "@libp2p/peer-id": "^3.0.0", + "@noble/ciphers": "^0.4.0", "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", - "@noble/ciphers": "^0.4.0", "it-byte-stream": "^1.0.0", "it-length-prefixed": "^9.0.1", "it-length-prefixed-stream": "^1.0.0", @@ -108,7 +110,7 @@ "sinon": "^16.1.3" }, "browser": { - "./dist/src/alloc-unsafe.js": "./dist/src/alloc-unsafe-browser.js", + "./dist/src/crypto/index.js": "./dist/src/crypto/index.browser.js", "util": false } } diff --git a/src/crypto/index.browser.ts b/src/crypto/index.browser.ts new file mode 100644 index 0000000..f691a85 --- /dev/null +++ b/src/crypto/index.browser.ts @@ -0,0 +1,3 @@ +import { pureJsCrypto } from './js' + +export const defaultCrypto = pureJsCrypto diff --git a/src/crypto/index.ts b/src/crypto/index.ts new file mode 100644 index 0000000..a00634d --- /dev/null +++ b/src/crypto/index.ts @@ -0,0 +1,79 @@ +import crypto from 'node:crypto' +import { newInstance, ChaCha20Poly1305 } from '@chainsafe/as-chacha20poly1305' +import { digest } from '@chainsafe/as-sha256' +import { pureJsCrypto } from './js.js' +import type { ICryptoInterface } from '../crypto.js' + +const ctx = newInstance() +const asImpl = new ChaCha20Poly1305(ctx) +const CHACHA_POLY1305 = 'chacha20-poly1305' +const nodeCrypto: Pick = { + hashSHA256 (data) { + return crypto.createHash('sha256').update(data).digest() + }, + + 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, 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 +export const defaultCrypto: 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) + } +} diff --git a/src/noise.ts b/src/noise.ts index 2277a75..9a57813 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -3,7 +3,7 @@ import { lpStream, type LengthPrefixedStream } from 'it-length-prefixed-stream' import { duplexPair } from 'it-pair/duplex' import { pipe } from 'it-pipe' import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js' -import { pureJsCrypto } from './crypto/js.js' +import { defaultCrypto } from './crypto/index.js' import { decryptStream, encryptStream } from './crypto/streaming.js' import { uint16BEDecode, uint16BEEncode } from './encoder.js' import { XXHandshake } from './handshake-xx.js' @@ -49,7 +49,7 @@ export class Noise implements INoiseConnection { constructor (init: NoiseInit = {}) { const { staticNoiseKey, extensions, crypto, prologueBytes, metrics } = init - this.crypto = crypto ?? pureJsCrypto + this.crypto = crypto ?? defaultCrypto this.extensions = extensions this.metrics = metrics ? registerMetrics(metrics) : undefined