DAVE (Discord Audio Video Encryption) protocol implementation for secure voice communication with user verification, key management, encrypted audio transmission, and group session handling.
Core DAVE protocol implementation for managing encrypted voice sessions.
/**
* DAVE protocol group session management for end-to-end encryption
* Handles MLS (Message Layer Security) protocol for secure voice communication
*/
class DAVESession extends EventEmitter {
/** Channel ID for this encryption session */
readonly channelId: string;
/** User ID for this encryption session */
readonly userId: string;
/** DAVE protocol version being used */
readonly protocolVersion: number;
/** Last executed transition ID */
lastTransitionId?: number;
/** Whether session is currently re-initializing */
reinitializing: boolean;
/** Underlying DAVE session implementation */
session: SessionMethods | undefined;
/**
* Create a new DAVE session
* @param protocolVersion - DAVE protocol version to use
* @param userId - Discord user ID for this session
* @param channelId - Discord channel ID for this session
* @param options - Additional DAVE session options
*/
constructor(
protocolVersion: number,
userId: string,
channelId: string,
options: DAVESessionOptions
);
/** Current voice privacy code for verification */
get voicePrivacyCode(): string | null;
/**
* Get verification code for a specific user
* @param userId - Discord user ID to get verification code for
* @returns Promise resolving to verification code string
*/
getVerificationCode(userId: string): Promise<string>;
/** Re-initialize the underlying DAVE session */
reinit(): void;
/**
* Set external sender information
* @param externalSender - External sender data buffer
*/
setExternalSender(externalSender: Buffer): void;
/**
* Prepare for MLS group transition
* @param data - Transition preparation data
* @returns true if preparation was successful
*/
prepareTransition(data: VoiceDavePrepareTransitionData): boolean;
/**
* Execute a prepared MLS transition
* @param transitionId - ID of transition to execute
* @returns true if execution was successful
*/
executeTransition(transitionId: number): boolean;
/**
* Prepare for new MLS epoch
* @param data - Epoch preparation data
*/
prepareEpoch(data: VoiceDavePrepareEpochData): void;
/**
* Recover from invalid transition state
* @param transitionId - ID of invalid transition
*/
recoverFromInvalidTransition(transitionId: number): void;
/**
* Process MLS proposals from other clients
* @param payload - Proposal payload buffer
* @param connectedClients - Set of currently connected client user IDs
* @returns Processed payload buffer or undefined if processing failed
*/
processProposals(payload: Buffer, connectedClients: Set<string>): Buffer | undefined;
/**
* Process MLS commit message
* @param payload - Commit payload buffer
* @returns Result of commit processing
*/
processCommit(payload: Buffer): TransitionResult;
/**
* Process MLS welcome message
* @param payload - Welcome payload buffer
* @returns Result of welcome processing
*/
processWelcome(payload: Buffer): TransitionResult;
/**
* Encrypt audio packet with end-to-end encryption
* @param packet - Raw audio packet to encrypt
* @returns Encrypted packet buffer
*/
encrypt(packet: Buffer): Buffer;
/**
* Decrypt audio packet with end-to-end encryption
* @param packet - Encrypted audio packet
* @param userId - User ID of packet sender
* @returns Decrypted packet buffer or null if decryption failed
*/
decrypt(packet: Buffer, userId: string): Buffer | null;
/** Reset and destroy the DAVE session */
destroy(): void;
}
interface DAVESessionOptions {
/** Number of consecutive decryption failures before re-initialization (default: 24) */
decryptionFailureTolerance?: number;
}
/** Default decryption failure tolerance */
const DEFAULT_DECRYPTION_FAILURE_TOLERANCE: number;Usage Example:
import { DAVESession, getMaxProtocolVersion } from "@discordjs/voice";
// Check maximum supported DAVE version
const maxVersion = getMaxProtocolVersion();
if (maxVersion === null) {
console.log("DAVE is not available on this system");
return;
}
// Create DAVE session
const daveSession = new DAVESession(
maxVersion,
"your-user-id",
"voice-channel-id",
{
decryptionFailureTolerance: 10,
}
);
// Handle session events
daveSession.on("keyPackage", (keyPackage) => {
console.log("Generated key package:", keyPackage);
});
daveSession.on("invalidateTransition", (transitionId) => {
console.error("Invalid transition:", transitionId);
});
// Get voice privacy code for verification
const privacyCode = daveSession.voicePrivacyCode;
if (privacyCode) {
console.log("Voice privacy code:", privacyCode);
}
// Encrypt/decrypt audio
const audioPacket = Buffer.from(/* raw audio data */);
const encryptedPacket = daveSession.encrypt(audioPacket);
// Later, decrypt received packet
const decryptedPacket = daveSession.decrypt(encryptedPacket, "sender-user-id");
if (decryptedPacket) {
console.log("Successfully decrypted audio packet");
} else {
console.log("Failed to decrypt packet");
}Functions for checking DAVE availability and capabilities.
/**
* Get the maximum supported DAVE protocol version
* @returns Maximum protocol version number, or null if DAVE is unavailable
*/
function getMaxProtocolVersion(): number | null;
/** Promise that resolves when DAVE library is loaded and ready */
const daveLoadPromise: Promise<void>;Usage Example:
import { getMaxProtocolVersion, daveLoadPromise } from "@discordjs/voice";
// Wait for DAVE to load
await daveLoadPromise;
// Check DAVE availability
const maxVersion = getMaxProtocolVersion();
if (maxVersion !== null) {
console.log(`DAVE is available, max version: ${maxVersion}`);
} else {
console.log("DAVE is not available - ensure @noble/ciphers is installed");
}Events emitted by DAVE sessions for monitoring and handling.
interface DAVESession extends EventEmitter {
/** Emitted when a new key package is generated */
on(event: "keyPackage", listener: (keyPackage: Buffer) => void): this;
/** Emitted when a transition should be invalidated */
on(event: "invalidateTransition", listener: (transitionId: number) => void): this;
/** Emitted when session encounters an error */
on(event: "error", listener: (error: Error) => void): this;
/** Emitted for debug messages */
on(event: "debug", listener: (message: string) => void): this;
}Types for MLS (Message Layer Security) protocol operations.
interface VoiceDavePrepareTransitionData {
/** Transition ID to prepare */
transitionId: number;
/** MLS proposal data */
proposals: Buffer[];
/** MLS commit data */
commit?: Buffer;
}
interface VoiceDavePrepareEpochData {
/** New epoch number */
epoch: number;
/** Epoch transition data */
data: Buffer;
}
interface TransitionResult {
/** Whether transition was successful */
success: boolean;
/** Optional error message */
error?: string;
/** Resulting state data */
data?: Buffer;
}
interface SessionMethods {
/** Encrypt data with session key */
encrypt(data: Buffer): Buffer;
/** Decrypt data with session key */
decrypt(data: Buffer, sender: string): Buffer | null;
/** Generate key package for this session */
generateKeyPackage(): Buffer;
/** Process incoming proposals */
processProposals(proposals: Buffer[]): Buffer;
/** Create commit for pending proposals */
createCommit(): Buffer;
/** Process incoming commit */
processCommit(commit: Buffer): TransitionResult;
}DAVE encryption integrates seamlessly with voice connections:
import { joinVoiceChannel, getMaxProtocolVersion } from "@discordjs/voice";
// Enable DAVE encryption (default behavior)
const connection = joinVoiceChannel({
channelId: "123456789012345678",
guildId: "987654321098765432",
adapterCreator: guild.voiceAdapterCreator,
daveEncryption: true, // This is the default
});
// Check if DAVE is active
connection.on("stateChange", (oldState, newState) => {
if (newState.status === "ready" && newState.networking.state.code === "Ready") {
const daveSession = newState.networking.state.dave;
if (daveSession) {
console.log("DAVE encryption is active");
console.log("Privacy code:", daveSession.voicePrivacyCode);
} else {
console.log("DAVE encryption is not available");
}
}
});DAVE provides user verification through privacy codes:
// Get verification codes for users in the channel
const daveSession = connection.state.networking?.state.dave;
if (daveSession) {
const userIds = ["user1", "user2", "user3"];
for (const userId of userIds) {
try {
const verificationCode = await daveSession.getVerificationCode(userId);
console.log(`Verification code for ${userId}: ${verificationCode}`);
} catch (error) {
console.error(`Failed to get verification code for ${userId}:`, error);
}
}
// Get overall privacy code
const privacyCode = daveSession.voicePrivacyCode;
console.log("Channel privacy code:", privacyCode);
}Handle DAVE-related errors and recovery scenarios:
const daveSession = new DAVESession(1, "user-id", "channel-id", {
decryptionFailureTolerance: 5,
});
daveSession.on("error", (error) => {
console.error("DAVE session error:", error);
});
daveSession.on("invalidateTransition", (transitionId) => {
console.log(`Invalidating transition ${transitionId}, attempting recovery`);
daveSession.recoverFromInvalidTransition(transitionId);
});
// Handle decryption failures
let decryptionFailures = 0;
const maxFailures = 10;
function handleDecryption(packet, userId) {
const decrypted = daveSession.decrypt(packet, userId);
if (decrypted === null) {
decryptionFailures++;
console.warn(`Decryption failed for ${userId} (${decryptionFailures}/${maxFailures})`);
if (decryptionFailures >= maxFailures) {
console.log("Too many decryption failures, reinitializing session");
daveSession.reinit();
decryptionFailures = 0;
}
} else {
decryptionFailures = 0; // Reset on successful decryption
}
return decrypted;
}DAVE requires additional dependencies to function:
# Install required dependency for DAVE support
npm install @noble/ciphersCheck availability at runtime:
import { getMaxProtocolVersion } from "@discordjs/voice";
function checkDAVESupport() {
const maxVersion = getMaxProtocolVersion();
if (maxVersion === null) {
console.log("DAVE is not available. Install @noble/ciphers for encryption support.");
return false;
} else {
console.log(`DAVE is available (max version: ${maxVersion})`);
return true;
}
}
// Disable DAVE if not available
const connection = joinVoiceChannel({
channelId: "123456789012345678",
guildId: "987654321098765432",
adapterCreator: guild.voiceAdapterCreator,
daveEncryption: checkDAVESupport(),
});@noble/ciphers to be installed separately