diff --git a/bot.py b/bot.py index 15a79e7..72d66eb 100644 --- a/bot.py +++ b/bot.py @@ -448,14 +448,12 @@ class Bot: model = self.room_models.get(room_id, DEFAULT_MODEL) caller_device_id = content.get("device_id", "") - # Generate our E2EE key and publish it as a timeline event FIRST. - # Element Call only shares its key after seeing ours. + # Publish a placeholder key first to trigger Element Call + # to share its key with us. We'll republish the real shared + # key once we receive the caller's key. import secrets - our_key = secrets.token_bytes(32) - await self._publish_encryption_key(room_id, our_key) - - # Now check timeline for caller's key (they may have published before us) - caller_key = await self._get_call_encryption_key(room_id, event.sender, caller_device_id) + placeholder_key = secrets.token_bytes(16) + await self._publish_encryption_key(room_id, placeholder_key) vs = VoiceSession( nio_client=self.client, @@ -463,7 +461,12 @@ class Bot: device_id=BOT_DEVICE_ID, lk_url=LK_URL, model=model, + publish_key_cb=lambda key: asyncio.ensure_future( + self._publish_encryption_key(room_id, key)), ) + + # Check timeline for caller's key + caller_key = await self._get_call_encryption_key(room_id, event.sender, caller_device_id) if caller_key: vs.on_encryption_key(event.sender, caller_device_id, caller_key, 0) diff --git a/voice.py b/voice.py index badc941..80eec6b 100644 --- a/voice.py +++ b/voice.py @@ -83,7 +83,8 @@ def _build_e2ee_options(shared_key: bytes) -> rtc.E2EEOptions: class VoiceSession: - def __init__(self, nio_client, room_id, device_id, lk_url, model="claude-sonnet"): + def __init__(self, nio_client, room_id, device_id, lk_url, model="claude-sonnet", + publish_key_cb=None): self.nio_client = nio_client self.room_id = room_id self.device_id = device_id @@ -94,6 +95,7 @@ class VoiceSession: self._task = None self._http_session = None self._e2ee_key: bytes | None = None + self._publish_key_cb = publish_key_cb def on_encryption_key(self, sender, device_id, key, index): """Receive E2EE key from Element Call participant.""" @@ -186,14 +188,15 @@ class VoiceSession: if self._e2ee_key: shared_key = self._e2ee_key - logger.info("Using E2EE key (%d bytes)", len(shared_key)) + logger.info("Using caller E2EE key (%d bytes)", len(shared_key)) + # Republish caller's key as our own so both sides share the same key + if self._publish_key_cb: + self._publish_key_cb(shared_key) e2ee_opts = _build_e2ee_options(shared_key) else: - # Generate our own key as fallback — bot.py already published one - import secrets - shared_key = secrets.token_bytes(32) - logger.warning("No caller E2EE key received — using generated key") - e2ee_opts = _build_e2ee_options(shared_key) + # No key received — connect WITHOUT E2EE so at least audio works + logger.warning("No caller E2EE key received — connecting WITHOUT encryption") + e2ee_opts = None room_opts = rtc.RoomOptions(e2ee=e2ee_opts) self.lk_room = rtc.Room()