The hell is DAVE?

No it’s not a person. It’s a shorthand for Discord Audio & Video End-to-End Encryption, Discord’s (pretty new) protocol for encrypting voice calls, video calls, livestreams alike so that only the people in the call can decrypt the content (the audio and video)

The idea is Discord’s servers can give you the content without actually knowing the content. On a high level, Discord transports the encrypted content, then the recipient decrypts it, that’s it. However, there are quite a lot of interesting things behind that high-level description, which I’ll try to explain briefly down here.

Prefacing

I was making a single-file Discord bot that stays in a given voice channel, nothing more. Initially I was making it with TypeScript, as I always do that for Discord bots, but I realized hosting this website with a server and hosting that bot with another server is kind of wasteful. This website is only static, and the smallest server I can have on my service provider is a shared CPU and 256MB of RAM.

I looked into hosting both with that same server, but then I found out (with GPT) the TypeScript implementation was overly wasteful, as with all Discord.js-based bots. The point is, it does way too many things I don’t need in the background.

So I got to Golang. Well, what could that do, one thing being quite useless because the Discord libraries on there aren’t as useful, and they aren’t very up-to-date. DiscordGo for example, its latest version was 0.29. Initially I still used that though, and I did successfully write a functioning bot (wow!), but then in the process of trying to get it to connect to a voice channel I hit a brick wall.

The brick wall

I then wandered around to then find https://daveprotocol.com/. It is Discord’s whitepaper on DAVE, which sort of means it’s pretty dense and quite technical. There’s also https://docs.discord.com/developers/topics/voice-connections#indicating-dave-protocol-support, which is Discord Developers’ documentation on it.

Well, to be fair, all of this only mattered if you were to write a library. In my case I really didn’t have to go through that, but I find everything crypto interesting and worth a read (just check my osu!CTF writeup, you’ll figure out I like crypto and I solve crypto-ish stuff at a fairly basic level.)

On DAVE (bit more technical)

So as I’ve mentioned, the key idea is that Discord can send you the content without actually knowing the content. It works by combining a few major tech:

  • The Messaging Layer Security (MLS), which manages encryption keys (we’ll get to this)
  • WebRTC, which most people who tried to quote-unquote optimize Discord may have heard of that handles real-time content transmit
  • AES-128-GCM, scary name, encrypts the actual content frames

The whitepaper I linked describes protocol v1.1.

The breakdown

Let’s imagine 2 callers A and B. A is the sender and B is the receiver. A fairly simple diagram would be

A B
| |
Encrypts frame Decrypts frame
(DAVE key) (DAVE key)
\ /
Discord SFU
(sends encrypted
goop)

In transit the content is encrypted, and MLS establishes the keys between the clients. In simple terms:

  1. A and B joins a voice call
  2. Their Discord client establishes shared cryto key material through MLS
  3. A’s client encrypts the audio before sending it out
  4. Discord’s server forwards this to B
  5. B only then decrypts the audio then plays it

The server knows where to route this data but can’t decode it, which is what we want.

The key

DAVE uses MLS to manage the crypto group that represents everyone currently inside the call. Each participant derives a key for each sender from the shared secret material on the client (or, more simply, their own device).

Critically though, these keys change whenever a join or leave event happens. Let’s say for example C joins the call with A and B in it. Take a diagram:

Epoch 1 Epoch 2
(A and B, keyset A) -> (C joins, keyset B)

Now this means C can decrypt content from Epoch 2 and onwards, but never content from Epoch 1. This provides both forward and backwards secrecy around participation, in simple terms, newcomers can’t decrypt earlier content, departed users can’t decrypt future media.

These are all coordinated by Discord’s voice gateway. Well, but then a weakness would be it takes a while, and the whitepaper made sure to make that clear by noting that a removed participant may still be able to decrypt content roughly 2 seconds after that event. Useful use for this information may be discovered when you attempt to gossip the leaving user with everyone else.

Audio and video

Now, DAVE encrypts encoded content frames before WebRTC splits them into packets. This is important because then Discord wouldn’t need to replace what it already has for audio and video, i.e. its infrastructures for that.

But what about the codecs? For example Opus audio frames can be encrypted fully, while some video codecs may require headers to remain readable to package up. Well, the system is codec-aware, and for those weird codecs they can still be authenticated against tampering.

You may be asking what happens to very long calls, for example those that last from who-knows-when to around 20 years later. It’s not outside the realm of possibility! For those, DAVE already implements a key ratchet, which means the encryption keys rotate as necessary, so you can’t simply reuse the same key and nonces(!).

How do I know I’m talking to the right person?

Not common knowledge, but encryption alone doesn’t prove any participant’s identity. DAVE however already provides verification codes derived from their identity keys, and so two people can compare these codes through another trusted channel to check. Discord leverages this further by supporting persistent identity keys, which can make this reusable as well. It’s subject to the device and settings, though.

What DAVE doesn’t do

Well, the good stuff aside, it’s not magic, as you may be able to tell. The whitepaper states it can’t:

  • Protect against other people seeing the metadata of the call, for example the participants, or more recently, the length of the call.
  • Someone in the call just… simply recording it.
  • Malware or compromised devices
  • DoS or all implementation vulnerabilities, which is inevitable.

The whitepaper also already documented some known crypto and frame-processing edge cases. They did these during a Trial of Bits review, that’s a very cool name.

Takeaway

I think the most interesting design of DAVE is separating key management from content delivery. The gateway coordinates who belongs to a call and helps exchange key-management messages. MLS gives the clients the material they need, and WebRTC + SFU handles the actual media transmittion.

This is a very neat implementation that also makes multiplayer and streams a lot more practical than, say, Discord actually decrypting the content then transports it. It’s just clever design.

At the end I found DisGo (oh my god these library names) which implemented DAVE but does badly at almost everything else beginner-wise. I still lived with it though, and the final bundled executable weighed in 8MB and ran with well under the alloted resources. I just set up this website and the bot to run together on that single machine, and was happy with it.