Skip to content

Commit

Permalink
fix: use native crypto if available (#385)
Browse files Browse the repository at this point in the history
* fix: use native crypto if available

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.

* chore: fix electron main

* chore: use subarray and also pass array length to buffer.concat
  • Loading branch information
achingbrain authored Nov 10, 2023
1 parent 1538267 commit 3dee1dc
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 5 deletions.
9 changes: 6 additions & 3 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 All @@ -83,7 +85,8 @@
"it-stream-types": "^2.0.1",
"protons-runtime": "^5.0.0",
"uint8arraylist": "^2.4.3",
"uint8arrays": "^4.0.4"
"uint8arrays": "^4.0.4",
"wherearewe": "^2.0.1"
},
"devDependencies": {
"@chainsafe/libp2p-yamux": "^5.0.0",
Expand All @@ -108,7 +111,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
86 changes: 86 additions & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import crypto from 'node:crypto'
import { newInstance, ChaCha20Poly1305 } from '@chainsafe/as-chacha20poly1305'
import { digest } from '@chainsafe/as-sha256'
import { isElectronMain } from 'wherearewe'
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], updated.byteLength + tag.byteLength + final.byteLength)
return encrypted
},

chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, _dst) {
const authTag = ciphertext.subarray(ciphertext.length - 16)
const text = ciphertext.subarray(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], updated.byteLength + final.byteLength)
}
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)
}
}

// no chacha20-poly1305 in electron https://github.com/electron/electron/issues/24024
if (isElectronMain) {
defaultCrypto.chaCha20Poly1305Encrypt = asCrypto.chaCha20Poly1305Encrypt
defaultCrypto.chaCha20Poly1305Decrypt = asCrypto.chaCha20Poly1305Decrypt
}
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 3dee1dc

Please sign in to comment.