go-dave is a DAVE implementation for Go, browsers, and Electron. DAVE is the end-to-end encryption protocol used for encrypted WebRTC voice and video sessions.
Protocol reference: daveprotocol.com
Use it to:
- create and advance DAVE/MLS sessions
- exchange key material with your gateway or voice server
- encrypt outgoing encoded audio and video frames
- decrypt incoming encoded audio and video frames
- show verification codes and fingerprints to users
The project ships in three forms:
- native Go:
github.com/FlameInTheDark/go-dave - server-side Go for voice gateways and Pion SFUs:
github.com/FlameInTheDark/go-dave/server - browser and Electron WASM:
@flameinthedark/go-dave
go get github.com/FlameInTheDark/go-davenpm install @flameinthedark/go-daveMost integrations only need a single DAVESession and a handful of methods.
import dave "github.com/FlameInTheDark/go-dave"
// Create one session per user in the voice channel.
session, err := dave.NewDAVESession(dave.DAVEProtocolVersion, userID, channelID, nil)
if err != nil {
log.Fatal(err)
}
// Set the external sender announced by the gateway.
if err := session.SetExternalSender(externalSenderBuffer); err != nil {
log.Fatal(err)
}
// Send this key package to your gateway or voice server.
keyPackage, err := session.GetSerializedKeyPackage()
if err != nil {
log.Fatal(err)
}
// Apply proposals as the roster changes.
result, err := session.ProcessProposals(
dave.ProposalsAppend,
proposalsBuffer,
recognizedUserIDs,
)
if err != nil {
log.Fatal(err)
}
// Apply the commit produced for this session, or a welcome if you joined through one.
if result != nil && len(result.Commit) > 0 {
if err := session.ProcessCommit(result.Commit); err != nil {
log.Fatal(err)
}
}
if len(welcomeBuffer) > 0 {
if err := session.ProcessWelcome(welcomeBuffer); err != nil {
log.Fatal(err)
}
}
// Encrypt outgoing frames and decrypt incoming frames once the session is ready.
if session.Ready() {
encryptedPacket, err := session.EncryptOpus(outgoingPacket)
if err != nil {
log.Fatal(err)
}
decryptedPacket, err := session.Decrypt(remoteUserID, dave.MediaTypeAudio, incomingPacket)
if err != nil {
log.Fatal(err)
}
_ = keyPackage
_ = encryptedPacket
_ = decryptedPacket
}import { loadGoDave } from '@flameinthedark/go-dave'
// Load the bundled WASM runtime once for the whole app.
const GoDave = await loadGoDave()
// Create one session per user in the voice channel.
const session = GoDave.createSession(
GoDave.DAVE_PROTOCOL_VERSION,
userId,
channelId,
)
// Set the external sender announced by the gateway.
session.setExternalSender(externalSenderBuffer)
// Send this key package to your gateway or voice server.
const keyPackage = session.getSerializedKeyPackage()
// Apply proposals as the roster changes.
const result = session.processProposals(
GoDave.ProposalsOperationType.APPEND,
proposalsBuffer,
recognizedUserIds,
)
// Apply the commit produced for this session, or a welcome if you joined through one.
if (result.commit) {
session.processCommit(result.commit)
}
if (welcomeBuffer) {
session.processWelcome(welcomeBuffer)
}
// Encrypt outgoing frames and decrypt incoming frames once the session is ready.
if (session.getState().ready) {
const encryptedPacket = session.encryptOpus(outgoingPacket)
if (encryptedPacket) {
console.log('send encrypted packet', encryptedPacket)
}
const decryptedPacket = session.decrypt(
remoteUserId,
GoDave.MediaType.AUDIO,
incomingPacket,
)
if (decryptedPacket) {
console.log({ keyPackage, decryptedPacket })
}
}encrypt(...), encryptOpus(...), and decrypt(...) return null in WASM when a frame is intentionally dropped. Recoverable frame-path failures are logged with console.warn(...) instead of terminating the runtime.