Skip to content

Commit 0b4b7f6

Browse files
authored
feat: reuse encrypted data to avoid memory allocation (#242)
feat: reused ciphertext allocation in decrypt function
1 parent 9137bf6 commit 0b4b7f6

6 files changed

Lines changed: 21 additions & 11 deletions

File tree

src/@types/handshake-interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ export interface IHandshake {
88
remotePeer: PeerId
99
remoteExtensions: NoiseExtensions
1010
encrypt: (plaintext: bytes, session: NoiseSession) => bytes
11-
decrypt: (ciphertext: bytes, session: NoiseSession) => { plaintext: bytes, valid: boolean }
11+
decrypt: (ciphertext: bytes, session: NoiseSession, dst?: Uint8Array) => { plaintext: bytes, valid: boolean }
1212
}

src/crypto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ export interface ICryptoInterface {
1212
generateX25519SharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array
1313

1414
chaCha20Poly1305Encrypt: (plaintext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32) => bytes
15-
chaCha20Poly1305Decrypt: (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32) => bytes | null
15+
chaCha20Poly1305Decrypt: (ciphertext: Uint8Array, nonce: Uint8Array, ad: Uint8Array, k: bytes32, dst?: Uint8Array) => bytes | null
1616
}

src/crypto/stablelib.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export const stablelib: ICryptoInterface = {
5252
return ctx.seal(nonce, plaintext, ad)
5353
},
5454

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

58-
return ctx.open(nonce, ciphertext, ad)
58+
return ctx.open(nonce, ciphertext, ad, dst)
5959
}
6060
}

src/crypto/streaming.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TAG_LENGTH } from '@stablelib/chacha20poly1305'
12
import type { Transform } from 'it-stream-types'
23
import type { Uint8ArrayList } from 'uint8arraylist'
34
import type { IHandshake } from '../@types/handshake-interface.js'
@@ -35,7 +36,16 @@ export function decryptStream (handshake: IHandshake, metrics?: MetricsRegistry)
3536
end = chunk.length
3637
}
3738

38-
const { plaintext: decrypted, valid } = handshake.decrypt(chunk.subarray(i, end), handshake.session)
39+
if (end - TAG_LENGTH < i) {
40+
throw new Error('Invalid chunk')
41+
}
42+
const encrypted = chunk.subarray(i, end)
43+
// memory allocation is not cheap so reuse the encrypted Uint8Array
44+
// see https://github.com/ChainSafe/js-libp2p-noise/pull/242#issue-1422126164
45+
// this is ok because chacha20 reads bytes one by one and don't reread after that
46+
// it's also tested in https://github.com/ChainSafe/as-chacha20poly1305/pull/1/files#diff-25252846b58979dcaf4e41d47b3eadd7e4f335e7fb98da6c049b1f9cd011f381R48
47+
const dst = chunk.subarray(i, end - TAG_LENGTH)
48+
const { plaintext: decrypted, valid } = handshake.decrypt(encrypted, handshake.session, dst)
3949
if (!valid) {
4050
metrics?.decryptErrors.increment()
4151
throw new Error('Failed to validate decrypted chunk')

src/handshake-xx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ export class XXHandshake implements IHandshake {
147147
return this.xx.encryptWithAd(cs, new Uint8Array(0), plaintext)
148148
}
149149

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

153-
return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext)
153+
return this.xx.decryptWithAd(cs, new Uint8Array(0), ciphertext, dst)
154154
}
155155

156156
public getRemoteStaticKey (): bytes {

src/handshakes/abstract-handshake.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export abstract class AbstractHandshake {
2121
return e
2222
}
2323

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

2828
return { plaintext, valid }
@@ -60,10 +60,10 @@ export abstract class AbstractHandshake {
6060
return ciphertext
6161
}
6262

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

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

6868
if (encryptedMessage) {
6969
return {

0 commit comments

Comments
 (0)