Skip to content

fix(e2ee): verify transferred key pair after QR login and fallback to fresh registration#197

Merged
EdamAme-x merged 1 commit into
mainfrom
fix/e2ee-key-mismatch-qr-login
Jul 5, 2026
Merged

fix(e2ee): verify transferred key pair after QR login and fallback to fresh registration#197
EdamAme-x merged 1 commit into
mainfrom
fix/e2ee-key-mismatch-qr-login

Conversation

@EdamAme-x

Copy link
Copy Markdown
Member

Summary

Fixes #195.

After QR login, the E2EE key received via decodeE2EEKeyV1 may not match the server-registered public key. This happens when the primary device does not complete PIN-authorized key transfer for accounts with pre-existing identity keys — the encryptedKeyChain decrypts to a private key that does not correspond to the public key the server advertises for that keyId.

Root cause: For accounts whose E2EE identity was established on the phone, the server expects the primary device to participate in key transfer (via respondE2EELoginRequest or the QR migration flow). If this doesn't complete, the key chain in the QR login response contains an invalid/stale private key.

Fix:

  • Added verifyE2EEKeyPair() — derives the public key from a private key via nacl.scalarMult.base and compares against a registered public key
  • Added verifyStoredKeyAgainstServer() — fetches registered public keys and verifies the decoded private key matches
  • decodeE2EEKeyV1 now validates the decoded key pair before storing:
    1. Checks internal consistency (privKey → pubKey derivation matches the chain's pubKey)
    2. Checks against server-registered pubKey for the keyId
    3. Returns undefined and emits e2ee:keyMismatch event if either check fails
  • Both requestSQR and requestSQR2 now fall back to registerE2EEKeyPair() when decodeE2EEKeyV1 returns undefined — this generates a fresh Curve25519 key pair, registers it with the server, and stores it locally

Result: The local private key always matches the server-registered public key, so ECDH-derived shared secrets are symmetric between sender and receiver.

Test plan

  • deno check passes on modified files
  • All 209 existing tests pass
  • Manual: QR login with account that has pre-existing E2EE keys → verify e2ee:keyMismatch fires and fallback key registration succeeds
  • Manual: QR login with fresh account → verify key from decodeE2EEKeyV1 passes validation (no fallback needed)
  • Manual: Decrypt E2EE text messages from both self and peer after login

🤖 Generated with Claude Code

… fresh registration

Fixes #195. After QR login, the E2EE key received via decodeE2EEKeyV1
may not match the server-registered public key — this happens when the
primary device does not complete PIN-authorized key transfer for accounts
with pre-existing identity keys.

Changes:
- Add verifyE2EEKeyPair / verifyStoredKeyAgainstServer to validate that
  the decrypted private key derives to the server-registered public key
- decodeE2EEKeyV1 now rejects mismatched keys (emits e2ee:keyMismatch)
- Both requestSQR and requestSQR2 fall back to registerE2EEKeyPair when
  decodeE2EEKeyV1 returns undefined (mismatch or missing e2eeInfo)
- registerE2EEKeyPair generates a fresh Curve25519 key pair and registers
  it with the server, ensuring the local private key always matches
@EdamAme-x EdamAme-x merged commit 166da0d into main Jul 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant