From c70263ad8d235358de933bf96c040dd025a242d3 Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 13:26:23 +0000 Subject: [PATCH 01/10] make stablelib a peer dependency --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 7bd19cb4..d5cf61f3 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "peerDependencies": { "@discordjs/opus": "^0.10.0", + "@stablelib/xchacha20poly1305": "~1.0.1", "erlpack": "github:discord/erlpack", "eventemitter3": "^5.0.1", "pako": "^2.1.0", @@ -80,6 +81,9 @@ "@discordjs/opus": { "optional": true }, + "@stablelib/xchacha20poly1305": { + "optional": true + }, "eventemitter3": { "optional": true }, From 6f3f6c6cc7aa55996040c8ae6c60efdaa9d23d00 Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:06:49 +0000 Subject: [PATCH 02/10] refactor: use @projectdysnomia/libsodium to perform packet encryption Not ready for production use yet. Memory WILL leak, especially when under the WASM backend. StableLib is kept as a peer dependency for those who have WASM disabled. --- lib/voice/VoiceConnection.js | 118 +++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index e1ab0896..a02ab764 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -21,8 +21,11 @@ try { /** @type {import("@snazzah/davey")?} */ let Davey = null; +/** @type {import("@stablelib/xchacha20poly1305")?} */ let StableLib = null; +/** @type {import("@projectdysnomia/libsodium")?} */ let Sodium = null; +/** @type {import("node:crypto")?} */ let crypto = null; let aes256Available = false; @@ -107,8 +110,6 @@ class VoiceConnection extends EventEmitter { reconnecting = false; reinitializing = false; samplingRate = 48_000; - sendBuffer = Buffer.allocUnsafe(16 + 32 + MAX_FRAME_SIZE); - sendHeader = Buffer.alloc(12); sequence = 0; speaking = false; ssrcUserMap = {}; @@ -119,8 +120,10 @@ class VoiceConnection extends EventEmitter { #daveDowngraded = false; #davePendingTransitions = new Map(); #nonce = 0; + /** @type {import("@projectdysnomia/libsodium").BufferPointer?} */ #nonceBuffer = null; #send; + /** @type {import("@stablelib/xchacha20poly1305").XChaCha20Poly1305?} */ #stablelib = null; /** @type {String?} */ #userID = null; @@ -142,7 +145,7 @@ class VoiceConnection extends EventEmitter { if(!Sodium && !StableLib) { try { - Sodium = require("sodium-native"); + Sodium = require("@projectdysnomia/libsodium"); } catch { try { StableLib = require("@stablelib/xchacha20poly1305"); @@ -187,6 +190,9 @@ class VoiceConnection extends EventEmitter { this.opus = {}; } + this.sendBuffer = this.#alloc(16 + 32 + MAX_FRAME_SIZE, false); + this.sendHeader = this.#alloc(12); + this.sendHeader[0] = 0x80; this.sendHeader[1] = 0x78; @@ -461,8 +467,8 @@ class VoiceConnection extends EventEmitter { } case VoiceOPCodes.SESSION_DESCRIPTION: { this.mode = packet.d.mode; - this.secret = Buffer.from(packet.d.secret_key); - this.#nonceBuffer = Buffer.alloc(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24); + this.secret = this.#transfer(Buffer.from(packet.d.secret_key)); + this.#nonceBuffer = this.#alloc(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24); if(this.mode === "aead_xchacha20_poly1305_rtpsize" && StableLib) { this.#stablelib = new StableLib.XChaCha20Poly1305(this.secret); } @@ -869,8 +875,8 @@ class VoiceConnection extends EventEmitter { const hasExtension = !!(msg[0] & 0b10000); const hasPadding = !!(msg[0] & 0b100000); const cc = msg[0] & 0b1111; - const nonce = Buffer.alloc(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24); - msg.copy(nonce, 0, msg.length - 4, msg.length); + const nonce = this.#alloc(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24); + msg.copy(nonce.buffer, 0, msg.length - 4, msg.length); // Header Size = Fixed Header Length + (CSRC Identifier Length * CSRC count) + Extension let headerSize = 12 + (cc * 4); @@ -880,23 +886,34 @@ class VoiceConnection extends EventEmitter { let data; if(this.mode === "aead_aes256_gcm_rtpsize") { - const decipher = crypto.createDecipheriv("aes-256-gcm", this.secret, nonce); + const decipher = crypto.createDecipheriv("aes-256-gcm", this.secret.buffer, nonce.buffer); decipher.setAAD(msg.subarray(0, headerSize)); decipher.setAuthTag(msg.subarray(msg.length - 20, msg.length - 4)); data = Buffer.concat([decipher.update(msg.subarray(headerSize, msg.length - 20)), decipher.final()]); } else if(Sodium) { - data = Buffer.alloc(msg.length - (headerSize + 4) - Sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES); + const msgPtr = this.#transfer(msg); + const d = this.#alloc(msg.length - (headerSize + 4) - Sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES); Sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( - data, + d, null, - msg.subarray(headerSize, msg.length - 4), - msg.subarray(0, headerSize), + msgPtr.subarray(headerSize, msg.length - 4), + msgPtr.subarray(0, headerSize), nonce, this.secret ); + + if(Sodium.native) { + data = d.buffer; + } else { + // The Buffer instance isn't guaranteed to be stable when in WASM, therefore a full copy is needed + data = Buffer.from(d.buffer); + } + + msgPtr.free(); + d.free(); } else if(this.#stablelib) { data = this.#stablelib.open( - nonce, + nonce.buffer, msg.subarray(headerSize, msg.length - 4), msg.subarray(0, headerSize) ); @@ -1155,28 +1172,28 @@ class VoiceConnection extends EventEmitter { } _sendAudioFrame(unencryptedFrame) { - const headerSize = this.sendHeader.length; - this.sendHeader.writeUInt16BE(this.sequence, 2); - this.sendHeader.writeUInt32BE(this.timestamp, 4); + const headerSize = this.sendHeader.buffer; + this.sendHeader.buffer.writeUInt16BE(this.sequence, 2); + this.sendHeader.buffer.writeUInt32BE(this.timestamp, 4); this.#nonce = (this.#nonce + 1) >>> 0; - this.#nonceBuffer.writeUInt32BE(this.#nonce, 0); + this.#nonceBuffer.buffer.writeUInt32BE(this.#nonce, 0); const frame = this.#daveReady && !unencryptedFrame.equals(SILENCE_FRAME) ? this.daveSession.encryptOpus(unencryptedFrame) : unencryptedFrame; if(this.mode === "aead_aes256_gcm_rtpsize") { - const cipher = crypto.createCipheriv("aes-256-gcm", this.secret, this.#nonceBuffer); - cipher.setAAD(this.sendHeader); + const cipher = crypto.createCipheriv("aes-256-gcm", this.secret.buffer, this.#nonceBuffer.buffer); + cipher.setAAD(this.sendHeader.buffer); const result = Buffer.concat([cipher.update(frame), cipher.final(), cipher.getAuthTag()]); - this.sendHeader.copy(this.sendBuffer, 0, 0, headerSize); - result.copy(this.sendBuffer, headerSize); - this.#nonceBuffer.copy(this.sendBuffer, headerSize + result.length, 0, 4); // nonce padding - return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + result.length + 4)); + this.sendHeader.buffer.copy(this.sendBuffer.buffer, 0, 0, headerSize); + result.copy(this.sendBuffer.buffer, headerSize); + this.#nonceBuffer.buffer.copy(this.sendBuffer.buffer, headerSize + result.length, 0, 4); // nonce padding + return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + result.length + 4).buffer); } else if(Sodium) { const ABYTES = Sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES; const length = frame.length + ABYTES; - this.sendBuffer.fill(0, headerSize + frame.length, headerSize + frame.length + ABYTES); - frame.copy(this.sendBuffer, headerSize); + this.sendBuffer.buffer.fill(0, headerSize + frame.length, headerSize + frame.length + ABYTES); + frame.copy(this.sendBuffer.buffer, headerSize); Sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( this.sendBuffer.subarray(headerSize, headerSize + length), this.sendBuffer.subarray(headerSize, headerSize + frame.length), @@ -1185,21 +1202,33 @@ class VoiceConnection extends EventEmitter { this.#nonceBuffer, this.secret ); - this.sendHeader.copy(this.sendBuffer, 0, 0, headerSize); - this.#nonceBuffer.copy(this.sendBuffer, headerSize + length, 0, 4); // nonce padding - return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + length + 4)); + this.sendHeader.buffer.copy(this.sendBuffer, 0, 0, headerSize); + this.#nonceBuffer.buffer.copy(this.sendBuffer, headerSize + length, 0, 4); // nonce padding + return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + length + 4).buffer); } else if(this.#stablelib) { const length = frame.length + StableLib.TAG_LENGTH; this.sendBuffer.fill(0, headerSize + frame.length, headerSize + length); this.#stablelib.seal( - this.#nonceBuffer, + this.#nonceBuffer.buffer, frame, - this.sendHeader, - this.sendBuffer.subarray(headerSize, headerSize + length) + this.sendHeader.buffer, + this.sendBuffer.subarray(headerSize, headerSize + length).buffer ); - this.sendHeader.copy(this.sendBuffer, 0, 0, headerSize); - this.#nonceBuffer.copy(this.sendBuffer, headerSize + length, 0, 4); // nonce padding - return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + length + 4)); + this.sendHeader.buffer.copy(this.sendBuffer.buffer, 0, 0, headerSize); + this.#nonceBuffer.buffer.copy(this.sendBuffer.buffer, headerSize + length, 0, 4); // nonce padding + return this.sendUDPPacket(this.sendBuffer.subarray(0, headerSize + length + 4).buffer); + } + } + + /** + * @param {Number} size + * @param {Boolean} safe + */ + #alloc(size, safe = true) { + if(Sodium) { + return Sodium.alloc(size, safe); + } else { + return this.#makeBufferPointer(Buffer[safe ? "alloc" : "allocUnsafe"](size)); } } @@ -1247,6 +1276,18 @@ class VoiceConnection extends EventEmitter { return true; } + /** @param {Buffer} buf */ + #makeBufferPointer(buf) { + // Keep in sync with BufferPointer in @projectdysnomia/libsodium to maintain compatibility + return { + buffer: buf, + free: () => {}, + subarray: (start, end) => { + return this.#makeBufferPointer(buf.subarray(start, end)); + } + }; + } + #reinitDaveSession(userID, channelID) { if(this.daveProtocolVersion > 0) { if(this.daveSession) { @@ -1297,6 +1338,15 @@ class VoiceConnection extends EventEmitter { this.current.timeout = setTimeout(this.#send, this.current.startTime + this.current.pausedTime + this.current.playTime - Date.now()); } + /** @param {Buffer} buf */ + #transfer(buf) { + if(Sodium) { + return Sodium.transfer(buf); + } else { + return this.#makeBufferPointer(buf); + } + } + [util.inspect.custom]() { return Base.prototype[util.inspect.custom].call(this); } diff --git a/package.json b/package.json index d5cf61f3..50bf10ff 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ }, "optionalDependencies": { "@snazzah/davey": "^0.1.12", - "@stablelib/xchacha20poly1305": "~1.0.1", + "@projectdysnomia/libsodium": "github:projectdysnomia/libsodium#acefd1b44526953eed4f0a87e31149963d052d21", "opusscript": "^0.1.1" }, "browser": { From da6fc5848934f28b4afbe6abc56737d410f6ff8e Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:19:41 +0000 Subject: [PATCH 03/10] free the nonce buffer after decryption --- lib/voice/VoiceConnection.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index a02ab764..e1b92d44 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -924,6 +924,8 @@ class VoiceConnection extends EventEmitter { data = Buffer.from(data.buffer); } + nonce.free(); + // RFC3550 5.1: Padding (may need testing) if(hasPadding) { const paddingAmount = data[data.length - 1]; From 22bceecc531169de010fe14051860352f09dcf21 Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:23:50 +0000 Subject: [PATCH 04/10] make VoiceConnection##alloc more clear --- lib/voice/VoiceConnection.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index e1b92d44..0b04f78b 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -1224,13 +1224,13 @@ class VoiceConnection extends EventEmitter { /** * @param {Number} size - * @param {Boolean} safe + * @param {Boolean} zero */ - #alloc(size, safe = true) { + #alloc(size, zero = true) { if(Sodium) { - return Sodium.alloc(size, safe); + return Sodium.alloc(size, zero); } else { - return this.#makeBufferPointer(Buffer[safe ? "alloc" : "allocUnsafe"](size)); + return this.#makeBufferPointer(zero ? Buffer.alloc(size) : Buffer.allocUnsafe(size)); } } From df128222a76d57e8cf53e56c0bfd3ffb0b8ecbb5 Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:42:58 +0000 Subject: [PATCH 05/10] pass a buffer to the stablelib constructor, not a buffer pointer --- lib/voice/VoiceConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index 0b04f78b..83bb07a5 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -470,7 +470,7 @@ class VoiceConnection extends EventEmitter { this.secret = this.#transfer(Buffer.from(packet.d.secret_key)); this.#nonceBuffer = this.#alloc(this.mode === "aead_aes256_gcm_rtpsize" ? 12 : 24); if(this.mode === "aead_xchacha20_poly1305_rtpsize" && StableLib) { - this.#stablelib = new StableLib.XChaCha20Poly1305(this.secret); + this.#stablelib = new StableLib.XChaCha20Poly1305(this.secret.buffer); } this.daveProtocolVersion = packet.d.dave_protocol_version; this.connecting = false; From cc039c12875b54aff2a9686ead5ddc1a00a4d3ef Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:47:08 +0000 Subject: [PATCH 06/10] style: merge if tree into ternary --- lib/voice/VoiceConnection.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index 83bb07a5..dc76ccbd 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -902,12 +902,8 @@ class VoiceConnection extends EventEmitter { this.secret ); - if(Sodium.native) { - data = d.buffer; - } else { - // The Buffer instance isn't guaranteed to be stable when in WASM, therefore a full copy is needed - data = Buffer.from(d.buffer); - } + // SAFE: the native backend has no concept of freeing memory, so the buffer can be used directly + data = Sodium.native ? d.buffer : Buffer.from(d.buffer); msgPtr.free(); d.free(); From 910fd3ab9cc8c79986d04343b29c7f449d0f5447 Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:49:46 +0000 Subject: [PATCH 07/10] free the sendBuffer and sendHeader when destroying the voice connection object --- lib/voice/VoiceConnection.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index dc76ccbd..e11e607c 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -1152,6 +1152,8 @@ class VoiceConnection extends EventEmitter { } _destroy() { + this.sendBuffer.free(); + this.sendHeader.free(); if(this.opus) { for(const key in this.opus) { this.opus[key].delete?.(); From 5286313dcef2593562a8ab12221d6bf15efff59a Mon Sep 17 00:00:00 2001 From: TTtie Date: Sat, 2 Nov 2024 14:53:43 +0000 Subject: [PATCH 08/10] free the nonce buffer and the secret buffer after disconnecting --- lib/voice/VoiceConnection.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index e11e607c..efcd7e81 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -694,6 +694,10 @@ class VoiceConnection extends EventEmitter { this.sessionID = null; this.token = null; this.wsSequence = -1; + this.secret?.free(); + this.#nonceBuffer?.free(); + this.secret = null; + this.#nonceBuffer = null; this.#stablelib = null; this.updateVoiceState(); /** From ca5c0c1e3c345cf77d319a8132a17a5f75d6011d Mon Sep 17 00:00:00 2001 From: TTtie Date: Sun, 23 Aug 2026 14:31:49 +0000 Subject: [PATCH 09/10] this is probably meant to be the length of the buffer --- lib/voice/VoiceConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/voice/VoiceConnection.js b/lib/voice/VoiceConnection.js index efcd7e81..05a57daa 100644 --- a/lib/voice/VoiceConnection.js +++ b/lib/voice/VoiceConnection.js @@ -1176,7 +1176,7 @@ class VoiceConnection extends EventEmitter { } _sendAudioFrame(unencryptedFrame) { - const headerSize = this.sendHeader.buffer; + const headerSize = this.sendHeader.buffer.length; this.sendHeader.buffer.writeUInt16BE(this.sequence, 2); this.sendHeader.buffer.writeUInt32BE(this.timestamp, 4); From e0df05fe86ceecb5e93d6b3cc31a5d7bf6752920 Mon Sep 17 00:00:00 2001 From: TTtie Date: Tue, 25 Aug 2026 21:37:00 +0000 Subject: [PATCH 10/10] chore: regenerate package lock --- pnpm-lock.yaml | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 506ab304..b03af70c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@discordjs/opus': specifier: ^0.10.0 version: 0.10.0(supports-color@7.2.0) + '@stablelib/xchacha20poly1305': + specifier: ~1.0.1 + version: 1.0.1 erlpack: specifier: github:discord/erlpack version: https://codeload.github.com/discord/erlpack/tar.gz/2a4c0e832f3cd4e07c92d4baec326a631ed50f59 @@ -61,12 +64,12 @@ importers: specifier: ^8.65.0 version: 8.65.0(eslint@10.8.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) optionalDependencies: + '@projectdysnomia/libsodium': + specifier: github:projectdysnomia/libsodium#acefd1b44526953eed4f0a87e31149963d052d21 + version: https://codeload.github.com/projectdysnomia/libsodium/tar.gz/acefd1b44526953eed4f0a87e31149963d052d21(sodium-native@5.1.0(bare-events@2.9.1)) '@snazzah/davey': specifier: ^0.1.12 version: 0.1.12(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - '@stablelib/xchacha20poly1305': - specifier: ~1.0.1 - version: 1.0.1 opusscript: specifier: ^0.1.1 version: 0.1.1 @@ -164,6 +167,15 @@ packages: '@emnapi/core': ^2.0.0-alpha.3 '@emnapi/runtime': ^2.0.0-alpha.3 + '@projectdysnomia/libsodium@https://codeload.github.com/projectdysnomia/libsodium/tar.gz/acefd1b44526953eed4f0a87e31149963d052d21': + resolution: {gitHosted: true, integrity: sha512-4Xlhe7PI02wryQEQrSgHJhG/KuwFTjhelDiRNJUb0YeK6Y6Mal5oypo0IouQznkFQiLubGYVvIp7yX3EGJ9euw==, tarball: https://codeload.github.com/projectdysnomia/libsodium/tar.gz/acefd1b44526953eed4f0a87e31149963d052d21} + version: 0.0.1 + peerDependencies: + sodium-native: ^4.1.1 + peerDependenciesMeta: + sodium-native: + optional: true + '@sindresorhus/base62@1.0.0': resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} engines: {node: '>=18'} @@ -1149,6 +1161,11 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@projectdysnomia/libsodium@https://codeload.github.com/projectdysnomia/libsodium/tar.gz/acefd1b44526953eed4f0a87e31149963d052d21(sodium-native@5.1.0(bare-events@2.9.1))': + optionalDependencies: + sodium-native: 5.1.0(bare-events@2.9.1) + optional: true + '@sindresorhus/base62@1.0.0': {} '@snazzah/davey-android-arm-eabi@0.1.12': @@ -1219,13 +1236,11 @@ snapshots: - '@emnapi/runtime' optional: true - '@stablelib/aead@1.0.1': - optional: true + '@stablelib/aead@1.0.1': {} '@stablelib/binary@1.0.1': dependencies: '@stablelib/int': 1.0.1 - optional: true '@stablelib/chacha20poly1305@1.0.1': dependencies: @@ -1235,35 +1250,28 @@ snapshots: '@stablelib/constant-time': 1.0.1 '@stablelib/poly1305': 1.0.1 '@stablelib/wipe': 1.0.1 - optional: true '@stablelib/chacha@1.0.1': dependencies: '@stablelib/binary': 1.0.1 '@stablelib/wipe': 1.0.1 - optional: true - '@stablelib/constant-time@1.0.1': - optional: true + '@stablelib/constant-time@1.0.1': {} - '@stablelib/int@1.0.1': - optional: true + '@stablelib/int@1.0.1': {} '@stablelib/poly1305@1.0.1': dependencies: '@stablelib/constant-time': 1.0.1 '@stablelib/wipe': 1.0.1 - optional: true - '@stablelib/wipe@1.0.1': - optional: true + '@stablelib/wipe@1.0.1': {} '@stablelib/xchacha20@1.0.1': dependencies: '@stablelib/binary': 1.0.1 '@stablelib/chacha': 1.0.1 '@stablelib/wipe': 1.0.1 - optional: true '@stablelib/xchacha20poly1305@1.0.1': dependencies: @@ -1272,7 +1280,6 @@ snapshots: '@stablelib/constant-time': 1.0.1 '@stablelib/wipe': 1.0.1 '@stablelib/xchacha20': 1.0.1 - optional: true '@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(supports-color@7.2.0))': dependencies: