Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reuse encrypted data to avoid memory allocation #242

Merged
merged 1 commit into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/@types/handshake-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export interface IHandshake {
remotePeer: PeerId
remoteExtensions: NoiseExtensions
encrypt: (plaintext: bytes, session: NoiseSession) => bytes
decrypt: (ciphertext: bytes, session: NoiseSession) => { plaintext: bytes, valid: boolean }
decrypt: (ciphertext: bytes, session: NoiseSession, dst?: Uint8Array) => { plaintext: bytes, valid: boolean }
}
2 changes: 1 addition & 1 deletion src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export interface ICryptoInterface {
generateX25519SharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array

chaCha20Poly1305Encrypt: (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32) => bytes
chaCha20Poly1305Decrypt: (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32) => bytes | null
chaCha20Poly1305Decrypt: (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array) => bytes | null
}
4 changes: 2 additions & 2 deletions src/crypto/stablelib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ export const stablelib: ICryptoInterface = {
return ctx.seal(nonce, plaintext, ad)
},

chaCha20Poly1305Decrypt (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32): bytes | null {
chaCha20Poly1305Decrypt (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array): bytes | null {
const ctx = new ChaCha20Poly1305(k)

return ctx.open(nonce, ciphertext, ad)
return ctx.open(nonce, ciphertext, ad, dst)
}
}
12 changes: 11 additions & 1 deletion src/crypto/streaming.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TAG_LENGTH } from '@stablelib/chacha20poly1305'
import type { Transform } from 'it-stream-types'
import type { Uint8ArrayList } from 'uint8arraylist'
import type { IHandshake } from '../@types/handshake-interface.js'
Expand Down Expand Up @@ -35,7 +36,16 @@ export function decryptStream (handshake: IHandshake, metrics?: MetricsRegistry)
end = chunk.length
}

const { plaintext: decrypted, valid } = handshake.decrypt(chunk.subarray(i, end), handshake.session)
if (end - TAG_LENGTH < i) {
throw new Error('Invalid chunk')
}
const encrypted = chunk.subarray(i, end)
// memory allocation is not cheap so reuse the encrypted Uint8Array
// see https://github.com/ChainSafe/js-libp2p-noise/pull/242#issue-1422126164
// this is ok because chacha20 reads bytes one by one and don't reread after that
// it's also tested in https://github.com/ChainSafe/as-chacha20poly1305/pull/1/files#diff-25252846b58979dcaf4e41d47b3eadd7e4f335e7fb98da6c049b1f9cd011f381R48
const dst = chunk.subarray(i, end - TAG_LENGTH)
const { plaintext: decrypted, valid } = handshake.decrypt(encrypted, handshake.session, dst)
if (!valid) {
metrics?.decryptErrors.increment()
throw new Error('Failed to validate decrypted chunk')
Expand Down
4 changes: 2 additions & 2 deletions src/handshake-xx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ export class XXHandshake implements IHandshake {
return this.xx.encryptWithAd(cs, new Uint8Array(0), plaintext)
}

public decrypt (ciphertext: Uint8Array, session: NoiseSession): { plaintext: bytes, valid: boolean } {
public decrypt (ciphertext: Uint8Array, session: NoiseSession, dst?: Uint8Array): { plaintext: bytes, valid: boolean } {
const cs = this.getCS(session, false)

return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext)
return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext, dst)
}

public getRemoteStaticKey (): bytes {
Expand Down
8 changes: 4 additions & 4 deletions src/handshakes/abstract-handshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export abstract class AbstractHandshake {
return e
}

public decryptWithAd (cs: CipherState, ad: Uint8Array, ciphertext: Uint8Array): {plaintext: bytes, valid: boolean} {
const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext)
public decryptWithAd (cs: CipherState, ad: Uint8Array, ciphertext: Uint8Array, dst?: Uint8Array): {plaintext: bytes, valid: boolean} {
const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext, dst)
if (valid) cs.n.increment()

return { plaintext, valid }
Expand Down Expand Up @@ -60,10 +60,10 @@ export abstract class AbstractHandshake {
return ciphertext
}

protected decrypt (k: bytes32, n: Nonce, ad: bytes, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
protected decrypt (k: bytes32, n: Nonce, ad: bytes, ciphertext: bytes, dst?: Uint8Array): {plaintext: bytes, valid: boolean} {
n.assertValue()

const encryptedMessage = this.crypto.chaCha20Poly1305Decrypt(ciphertext, n.getBytes(), ad, k)
const encryptedMessage = this.crypto.chaCha20Poly1305Decrypt(ciphertext, n.getBytes(), ad, k, dst)

if (encryptedMessage) {
return {
Expand Down