Merge branch 'plan/e2ee' - End-to-end encryption for relay connections

Implements E2EE for relay connections only, keeping direct connections
and CLI plaintext. The relay acts as a dumb byte forwarder with no
access to decrypted messages.

Key changes:
- Daemon keypair persistence in $PASEO_HOME/daemon-keypair.json
- Encrypted relay transport using ECDH P-256 + AES-256-GCM
- Client-side E2EE using daemon's public key from pairing offer
- WebCrypto compatibility for React Native
This commit is contained in:
Mohamed Boudra
2026-02-04 15:00:34 +07:00
29 changed files with 1311 additions and 314 deletions
+1
View File
@@ -184,6 +184,7 @@ export default async function globalSetup() {
process.env.E2E_DAEMON_PORT = String(port);
process.env.E2E_RELAY_PORT = String(relayPort);
process.env.E2E_RELAY_SESSION_ID = offer.sessionId;
process.env.E2E_RELAY_DAEMON_PUBLIC_KEY = offer.daemonPublicKeyB64;
process.env.E2E_METRO_PORT = String(metroPort);
console.log(`[e2e] Test daemon started on port ${port}, Metro on port ${metroPort}, home: ${paseoHome}`);
@@ -3,8 +3,11 @@ import { test, expect } from './fixtures';
test('connects via relay when direct endpoints fail', async ({ page }) => {
const relayPort = process.env.E2E_RELAY_PORT;
const sessionId = process.env.E2E_RELAY_SESSION_ID;
if (!relayPort || !sessionId) {
throw new Error('E2E_RELAY_PORT or E2E_RELAY_SESSION_ID is not set (expected from globalSetup).');
const daemonPublicKeyB64 = process.env.E2E_RELAY_DAEMON_PUBLIC_KEY;
if (!relayPort || !sessionId || !daemonPublicKeyB64) {
throw new Error(
'E2E_RELAY_PORT, E2E_RELAY_SESSION_ID, or E2E_RELAY_DAEMON_PUBLIC_KEY is not set (expected from globalSetup).'
);
}
const nowIso = new Date().toISOString();
@@ -14,6 +17,7 @@ test('connects via relay when direct endpoints fail', async ({ page }) => {
id: 'relay-only-daemon',
label: 'relay-daemon',
endpoints: [relayEndpoint],
daemonPublicKeyB64,
relay: { endpoint: relayEndpoint, sessionId },
metadata: null,
createdAt: nowIso,