Skip to content

Commit 95c8f32

Browse files
committed
feat(voice): add aead_xchacha20_poly1305_rtpsize encryption mode, remove old modes (#1228)
1 parent 249bff7 commit 95c8f32

File tree

6 files changed

+27
-27
lines changed

6 files changed

+27
-27
lines changed

changelog/1228.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for ``aead_xchacha20_poly1305_rtpsize`` encryption mode for voice connections, and remove deprecated ``xsalsa20_poly1305*`` modes.

changelog/1228.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Raise PyNaCl version requirement to ``v1.5.0``.

disnake/gateway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ async def initial_connection(self, data: VoiceReadyPayload) -> None:
10381038
state.port = struct.unpack_from(">H", recv, len(recv) - 2)[0]
10391039
_log.debug("detected ip: %s port: %s", state.ip, state.port)
10401040

1041-
# there *should* always be at least one supported mode (xsalsa20_poly1305)
1041+
# there *should* always be at least one supported mode
10421042
modes: List[SupportedModes] = [
10431043
mode for mode in data["modes"] if mode in self._connection.supported_modes
10441044
]

disnake/types/voice.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
from .member import MemberWithUser
88
from .snowflake import Snowflake
99

10-
SupportedModes = Literal["xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305"]
10+
SupportedModes = Literal[
11+
# "aead_aes256_gcm_rtpsize", # supported in libsodium, but not exposed by pynacl
12+
"aead_xchacha20_poly1305_rtpsize",
13+
]
1114

1215

1316
class _VoiceState(TypedDict):

disnake/voice_client.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,7 @@ def __init__(self, client: Client, channel: abc.Connectable) -> None:
228228
self.ws: DiscordVoiceWebSocket = MISSING
229229

230230
warn_nacl = not has_nacl
231-
supported_modes: Tuple[SupportedModes, ...] = (
232-
"xsalsa20_poly1305_lite",
233-
"xsalsa20_poly1305_suffix",
234-
"xsalsa20_poly1305",
235-
)
231+
supported_modes: Tuple[SupportedModes, ...] = ("aead_xchacha20_poly1305_rtpsize",)
236232

237233
@property
238234
def guild(self) -> Guild:
@@ -512,36 +508,35 @@ def _get_voice_packet(self, data):
512508
header = bytearray(12)
513509

514510
# Formulate rtp header
515-
header[0] = 0x80
516-
header[1] = 0x78
511+
header[0] = 0x80 # version = 2
512+
header[1] = 0x78 # payload type = 120 (opus)
517513
struct.pack_into(">H", header, 2, self.sequence)
518514
struct.pack_into(">I", header, 4, self.timestamp)
519515
struct.pack_into(">I", header, 8, self.ssrc)
520516

521517
encrypt_packet = getattr(self, f"_encrypt_{self.mode}")
522518
return encrypt_packet(header, data)
523519

524-
def _encrypt_xsalsa20_poly1305(self, header: bytes, data) -> bytes:
525-
box = nacl.secret.SecretBox(bytes(self.secret_key))
526-
nonce = bytearray(24)
527-
nonce[:12] = header
520+
def _get_nonce(self, pad: int):
521+
# returns (nonce, padded_nonce).
522+
# n.b. all currently implemented modes use the same nonce size (192 bits / 24 bytes)
523+
nonce = struct.pack(">I", self._lite_nonce)
528524

529-
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
525+
self._lite_nonce += 1
526+
if self._lite_nonce > 4294967295:
527+
self._lite_nonce = 0
530528

531-
def _encrypt_xsalsa20_poly1305_suffix(self, header: bytes, data) -> bytes:
532-
box = nacl.secret.SecretBox(bytes(self.secret_key))
533-
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
529+
return (nonce, nonce.ljust(pad, b"\0"))
534530

535-
return header + box.encrypt(bytes(data), nonce).ciphertext + nonce
531+
def _encrypt_aead_xchacha20_poly1305_rtpsize(self, header: bytes, data) -> bytes:
532+
box = nacl.secret.Aead(bytes(self.secret_key))
533+
nonce, padded_nonce = self._get_nonce(nacl.secret.Aead.NONCE_SIZE)
536534

537-
def _encrypt_xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes:
538-
box = nacl.secret.SecretBox(bytes(self.secret_key))
539-
nonce = bytearray(24)
540-
541-
nonce[:4] = struct.pack(">I", self._lite_nonce)
542-
self.checked_add("_lite_nonce", 1, 4294967295)
543-
544-
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4]
535+
return (
536+
header
537+
+ box.encrypt(bytes(data), aad=bytes(header), nonce=padded_nonce).ciphertext
538+
+ nonce
539+
)
545540

546541
def play(
547542
self, source: AudioSource, *, after: Optional[Callable[[Optional[Exception]], Any]] = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ speed = [
4949
'cchardet; python_version < "3.10"',
5050
]
5151
voice = [
52-
"PyNaCl>=1.3.0,<1.6",
52+
"PyNaCl>=1.5.0,<1.6",
5353
]
5454
docs = [
5555
"sphinx==7.0.1",

0 commit comments

Comments
 (0)