Skip to content

session: derive E2E receive key from the peer's real device (fixes garbled inbound audio)#4

Open
roshank8s wants to merge 1 commit into
purpshell:mainfrom
roshank8s:fix/e2e-recv-key-peer-device
Open

session: derive E2E receive key from the peer's real device (fixes garbled inbound audio)#4
roshank8s wants to merge 1 commit into
purpshell:mainfrom
roshank8s:fix/e2e-recv-key-peer-device

Conversation

@roshank8s

Copy link
Copy Markdown

Problem

Inbound 1:1 call audio decodes to garbled/robotic noise, while outbound audio is clean.

Root cause

NewMediaPipeline (session.go) derives the E2E-SRTP receive key from rtp.FormatE2ESrtpParticipantID(peerJID), and util.FormatParticipantID forces a bare @lid to device :0. The peer, however, streams from its real (non-zero) device, so the AES-CTR keystream is wrong. Sending is unaffected because the send key uses our own already-device-qualified LID.

The failure is silent: the payload cipher is AES-CTR (length-preserving) and the WARP MI tag is not verified, so a wrong key produces correct-length, high-entropy garbage with no decrypt error — which the (correct) MLow decoder renders as robotic audio.

Evidence (via WithDiagnostics, on a live 1:1 call)

  • 0.00% packet loss (all RTP sequence numbers contiguous) and 0 decrypt failures — rules out transport/loss.
  • media_keys_input: peer keyed at :0, self at a real non-zero device (:78).
  • Decrypted payloads were high-entropy (not valid MLow TOC bytes).
  • Deriving the recv key from the peer's real device → clean audio: the raw decoded PCM goes from ~7 impulsive discontinuities/s (peaks railing at ±1.0) to 0, with natural speech dynamics.

Cross-reference

JotaDev66/WaCalls (stable, byte-identical MLow decoder) gets clean inbound audio because it re-keys the receive direction from the peer's real device-qualified JID, not a hard-coded :0.

Fix

Pick the peer's real device without new signaling plumbing: the WARP MI tag is HMAC-SHA1 over the packet keyed by the sender's per-participant E2E auth key (the same key ProtectAudio signs with), so the peer's device is the one whose derived recv key reproduces the tag. buildRecvKeyCandidates derives a candidate recv key per device index (default :0 first); selectRecvKey locks onto the MI-tag match on the first inbound packet and falls back to the default after a bounded number of probes.

Alternatively this could key off the peer's device-qualified JID from the accept / relay-participant list (WaCalls' approach) to avoid the candidate search — happy to switch if you'd prefer that shape.

Testing

  • go build ./..., go vet ./..., and the existing session round-trip KATs (go test .) pass.
  • Verified end-to-end on a live WhatsApp 1:1 call: previously-garbled inbound audio is now clean.

The same recv-key derivation is present on meowcaller-leg, so this applies there too.

NewMediaPipeline derived the receive-direction E2E-SRTP key from
FormatE2ESrtpParticipantID(peerJID); FormatParticipantID forces a bare
@lid to device :0, so when the peer streams from a non-zero device the
recv keystream is wrong. The payload cipher is AES-CTR (length
preserving) and the WARP MI tag is not verified, so a wrong key yields
correct-length, high-entropy garbage with no decrypt error, which the
MLow decoder renders as robotic inbound audio. Sending is unaffected:
the send key uses our own device-qualified LID.

Pick the peer's real device without new signaling: the WARP MI tag is
HMAC-SHA1 over the packet keyed by the sender's per-participant E2E auth
key, so the peer's device is the one whose derived recv key reproduces
the tag. Derive a candidate key per device index and lock onto the
MI-tag match on the first inbound packet.

Verified on a live 1:1 call: inbound audio goes from garbled to clean;
0% packet loss and 0 decrypt failures before and after.

Source of truth: https://github.com/JotaDev66/WaCalls
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant