Skip to content

Commit

Permalink
fix: use native crypto if available
Browse files Browse the repository at this point in the history
Adds the lodestar [crypto
implementation](https://github.com/ChainSafe/lodestar/blob/6f27ac6e5f439577906cbfe5f9e01c59586c2af3/packages/beacon-node/src/network/libp2p/noise.ts#L3) to this module
for use in node.

I'm trying to get js-libp2p back onto the [libp2p performance dashboard](https://observablehq.com/@libp2p-workspace/performance-dashboard) and in testing this change
increases streaming throughput from around 60 MB/s to almost 300 MB/s.
  • Loading branch information
achingbrain committed Nov 10, 2023
1 parent 1538267 commit cb75fed
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 4 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
}
}
3 changes: 3 additions & 0 deletions src/crypto/index.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { pureJsCrypto } from './js'

export const defaultCrypto = pureJsCrypto
79 changes: 79 additions & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -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<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt' | 'chaCha20Poly1305Decrypt'> = {
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<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
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)
}
}
4 changes: 2 additions & 2 deletions src/noise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit cb75fed

Please sign in to comment.