From 8438382d522197fc402d74c84d307c7d5c3831d6 Mon Sep 17 00:00:00 2001 From: srujaniyengar Date: Mon, 5 Jan 2026 08:11:59 +0530 Subject: [PATCH 1/4] refactor(ui):updated to v1.1 --- static/index.html | 38 +++++++--- static/script.js | 178 +++++++++++++++++++--------------------------- static/style.css | 43 ++++++++++- 3 files changed, 144 insertions(+), 115 deletions(-) diff --git a/static/index.html b/static/index.html index de6e2a3..d869209 100644 --- a/static/index.html +++ b/static/index.html @@ -11,7 +11,6 @@ -
@@ -27,7 +26,10 @@
-

GHOSTLINK v1.0

+

GHOSTLINK v1.1

+
+ πŸ”’ END-TO-END ENCRYPTED +
@@ -36,7 +38,6 @@

GHOSTLINK v1.0

-
@@ -73,6 +74,11 @@

GHOSTLINK v1.0

TARGET_ACQUISITION
+
+ + +
+
@@ -95,7 +101,6 @@

GHOSTLINK v1.0

-
@@ -104,12 +109,10 @@

GHOSTLINK v1.0

ESTABLISHING TUNNEL...
-
--s
- @@ -129,7 +132,6 @@

GHOSTLINK v1.0

-
@@ -140,7 +142,10 @@

GHOSTLINK v1.0

...
- +
+ + +
@@ -157,11 +162,24 @@

GHOSTLINK v1.0

- + +
ALERT
- + \ No newline at end of file diff --git a/static/script.js b/static/script.js index 4203093..5313e94 100644 --- a/static/script.js +++ b/static/script.js @@ -8,6 +8,9 @@ const state = { isIpValid: false, isPortValid: false, sseSource: null, + // NEW: Session state + fingerprint: null, + username: null }; // --- DOM Elements --- @@ -25,13 +28,14 @@ const els = { // Home myIpDisplay: document.getElementById('myIpDisplay'), myLocalIpDisplay: document.getElementById('myLocalIpDisplay'), - natTypeDisplay: document.getElementById('natTypeDisplay'), // New NAT Element + natTypeDisplay: document.getElementById('natTypeDisplay'), apiErrorMsg: document.getElementById('apiErrorMsg'), copyBtn: document.getElementById('copyBtn'), copyLocalBtn: document.getElementById('copyLocalBtn'), connectForm: document.getElementById('connectForm'), peerIpInput: document.getElementById('peerIp'), peerPortInput: document.getElementById('peerPort'), + usernameInput: document.getElementById('usernameInput'), // New Input ipError: document.getElementById('ipError'), portError: document.getElementById('portError'), submitBtn: document.querySelector('#connectForm button'), @@ -41,7 +45,7 @@ const els = { vizPeerIp: document.getElementById('vizPeerIp'), punchLogs: document.getElementById('punchLogs'), punchTimeout: document.getElementById('punchTimeout'), - cancelPunchBtn: document.getElementById('cancelPunchBtn'), // New Cancel Button + cancelPunchBtn: document.getElementById('cancelPunchBtn'), // Connected / Chat chatMessages: document.getElementById('chatMessages'), @@ -49,7 +53,13 @@ const els = { chatForm: document.getElementById('chatForm'), chatInput: document.getElementById('chatInput'), sendBtn: document.getElementById('sendBtn'), - disconnectBtn: document.getElementById('disconnectBtn'), // New Disconnect Button + disconnectBtn: document.getElementById('disconnectBtn'), + verifyIdentityBtn: document.getElementById('verifyIdentityBtn'), // New Button + + // SAS Modal (New) + sasModal: document.getElementById('sasModal'), + sasDisplay: document.getElementById('sasDisplay'), + closeSasBtn: document.getElementById('closeSasBtn'), // Toast toast: document.getElementById('toast'), @@ -59,22 +69,13 @@ const els = { // --- Initialization --- async function init() { toggleSubmitButton(); - - // Initial fetch of the complete application state await fetchState(); - - // Connect to SSE for real-time updates connectSSE(); - setupEventListeners(); } // --- State Logic --- -/** - * Fetches the complete state from the backend via the new /api/state endpoint. - * Replaces old /api/ip, /api/status, and /api/peer calls. - */ async function fetchState() { els.myIpDisplay.style.opacity = '0.5'; @@ -83,9 +84,6 @@ async function fetchState() { if (!res.ok) throw new Error(`Server error`); const jsonResponse = await res.json(); - - // The server returns: { "state": { public_ip: "...", ... } } - // We must unwrap the "state" key. const appState = jsonResponse.state; if (appState) { @@ -106,34 +104,26 @@ async function fetchState() { } } -/** - * Centralizes logic for updating the frontend state from a backend AppState object. - * Can be called from the REST API fetch or from an SSE Disconnected event. - */ function syncState(data) { if (!data) return; - // 1. Public IP if (data.public_ip) state.fullAddress = data.public_ip; - - // 2. Local IP if (data.local_ip) state.localAddress = data.local_ip; - // 3. Peer IP if (data.peer_ip) state.peerAddress = data.peer_ip; - else if (data.peer_ip === null) state.peerAddress = null; // Explicit reset + else if (data.peer_ip === null) state.peerAddress = null; - // 4. NAT Type (New) if (data.nat_type) { state.natType = data.nat_type; renderNatType(); } - // 5. Status + // Capture Security Fingerprint if available + if (data.fingerprint) { + state.fingerprint = data.fingerprint; + } + if (data.status) { - // reuse handleStatusChange to trigger UI transitions if needed, - // but strictly speaking, fetchState is usually for init/refresh. - // We pass the whole data object so handleStatusChange can see the fields. handleStatusChange(data.status, data); } } @@ -141,36 +131,30 @@ function syncState(data) { function handleStatusChange(statusStr, data = {}) { const normStatus = (statusStr || 'DISCONNECTED').toUpperCase(); - // Handle Data Syncing based on Event Structure vs API Structure - - // CASE A: Disconnected Event via SSE (AppEvent::Disconnected { state }) if (normStatus === 'DISCONNECTED' && data.state) { syncState(data.state); - // Update UI to reflect any changes in public/local IP renderMyInfo(true); } - // CASE B: Standard AppState via REST API (/api/state) or top-level event fields - // (Note: syncState calls handleStatusChange, so we avoid infinite recursion by not calling syncState back) - // Logic: Handling Disconnection Messages - // Simplified: Directly display message if present in the event payload if (normStatus === 'DISCONNECTED') { if (data.message) { showToast(data.message); } } + // Capture fingerprint from connected event if present + if (normStatus === 'CONNECTED' && data.fingerprint) { + state.fingerprint = data.fingerprint; + } + state.connectionStatus = normStatus.toLowerCase(); - // 1. Update Badge renderStatusBadge(); - // 2. Switch Views els.viewHome.classList.remove('active'); els.viewPunching.classList.remove('active'); els.viewConnected.classList.remove('active'); - // Reset buttons when state changes resetDisconnectButtons(); if (normStatus === 'PUNCHING') { @@ -178,10 +162,11 @@ function handleStatusChange(statusStr, data = {}) { } else if (normStatus === 'CONNECTED') { enterConnectedState(data); } else { - // DISCONNECTED els.viewHome.classList.add('active'); els.submitBtn.disabled = !(state.isIpValid && state.isPortValid); els.submitBtn.innerText = "INITIATE LINK SEQUENCE"; + // Reset per-session state + state.fingerprint = null; } } @@ -195,20 +180,13 @@ function resetDisconnectButtons() { async function enterPunchingState(data) { els.viewPunching.classList.add('active'); - - // If peer info is missing locally, we rely on fetchState or what we have. - // Since /api/peer is gone, we don't fetch it explicitly anymore. - // It should have been synced via fetchState() or previous input. - els.vizClientIp.innerText = state.fullAddress || "Unknown"; els.vizPeerIp.innerText = state.peerAddress || "Target"; - // Handle Timeout Display (from AppEvent::Punching { timeout }) if (data.timeout !== undefined && data.timeout !== null) { els.punchTimeout.innerText = `${data.timeout}s`; } - // Handle Logs (from AppEvent::Punching { message }) if (data.message) { addLog(data.message); } @@ -216,41 +194,54 @@ async function enterPunchingState(data) { async function enterConnectedState(data) { els.viewConnected.classList.add('active'); - - // Update chat header with peer info els.chatPeerIp.innerText = state.peerAddress || "Connected Peer"; - if (data.message) { - console.log("Connected:", data.message); + // Update Modal Text + if (state.fingerprint) { + els.sasDisplay.innerText = state.fingerprint; + } else { + els.sasDisplay.innerText = "VERIFYING..."; + } + + // Optional: Send identity if username was set + if (state.username) { + sendIdentityMessage(); } } +function sendIdentityMessage() { + // Small delay to ensure connection is ready + setTimeout(async () => { + try { + await fetch('/api/message', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: `IDENTIFIED AS: ${state.username}` }) + }); + // Clear username so we don't send it again on re-renders + state.username = null; + } catch (e) { + console.warn("Failed to send identity", e); + } + }, 500); +} + // --- SSE (Real-time Events) --- function connectSSE() { if (state.sseSource && state.sseSource.readyState !== EventSource.CLOSED) { return; } - // Endpoint: /api/events state.sseSource = new EventSource('/api/events'); state.sseSource.onmessage = (event) => { try { const data = JSON.parse(event.data); - // AppEvent Structure: - // { status: "DISCONNECTED", state: { ... } } - // { status: "PUNCHING", timeout: 10, message: "..." } - // { status: "CONNECTED", message: "..." } - // { status: "MESSAGE", content: "...", from_me: true/false } - // { status: "CLEAR_CHAT" } - if (data.status) { if (data.status === 'MESSAGE') { - // Handle chat message addChatMessage(data.content, data.from_me); } else if (data.status === 'CLEAR_CHAT') { - // Handle clear chat event clearChatUI(); } else { handleStatusChange(data.status, data); @@ -270,14 +261,9 @@ function connectSSE() { function renderNatType() { if (!els.natTypeDisplay) return; - const type = state.natType; els.natTypeDisplay.innerText = type; - - // Remove old classes els.natTypeDisplay.classList.remove('cone', 'symmetric'); - - // Add specific color class if (type.toLowerCase().includes('cone')) { els.natTypeDisplay.classList.add('cone'); } else if (type.toLowerCase().includes('symmetric')) { @@ -319,7 +305,6 @@ function renderMyInfo(success) { els.copyBtn.style.display = 'none'; } - // Render local IP if (success && state.localAddress) { els.myLocalIpDisplay.innerText = state.localAddress; els.myLocalIpDisplay.classList.remove('error'); @@ -343,7 +328,6 @@ function addLog(message) { // --- Chat Functions --- function clearChatUI() { - // Reset chat container to initial state with welcome message els.chatMessages.innerHTML = `
@@ -355,13 +339,7 @@ function clearChatUI() { `; } -/** - * Adds a chat message to the chat UI - * @param {string} content - Message content - * @param {boolean} fromMe - True if message was sent by the user, false if received from peer - */ function addChatMessage(content, fromMe) { - // Remove welcome message if it exists const welcome = els.chatMessages.querySelector('.chat-welcome'); if (welcome) { welcome.remove(); @@ -387,21 +365,14 @@ function addChatMessage(content, fromMe) { messageDiv.appendChild(bubbleDiv); els.chatMessages.appendChild(messageDiv); - - // Auto-scroll to bottom els.chatMessages.scrollTop = els.chatMessages.scrollHeight; } -/** - * Handles chat form submission - */ async function handleChatSubmit(e) { e.preventDefault(); - const message = els.chatInput.value.trim(); if (!message) return; - // Disable send button temporarily els.sendBtn.disabled = true; try { @@ -411,11 +382,7 @@ async function handleChatSubmit(e) { body: JSON.stringify({ message }) }); - if (!res.ok) { - throw new Error('Failed to send message'); - } - - // Clear input and refocus + if (!res.ok) throw new Error('Failed to send message'); els.chatInput.value = ''; els.chatInput.focus(); @@ -437,6 +404,12 @@ async function handleConnect(e) { const port = parseInt(els.peerPortInput.value.trim(), 10); state.peerAddress = `${ip}:${port}`; + // Capture optional username + const username = els.usernameInput.value.trim(); + if (username) { + state.username = username; + } + const btn = els.submitBtn; btn.innerText = "INITIATING..."; btn.disabled = true; @@ -450,7 +423,6 @@ async function handleConnect(e) { if (!res.ok) throw new Error(); els.punchLogs.innerHTML = ''; - // No longer using lastSavedMessage } catch (err) { showToast("CONNECTION FAILED TO START"); @@ -464,7 +436,6 @@ async function handleConnect(e) { async function handleDisconnect(e) { if(e) e.preventDefault(); - // Provide immediate UI feedback to prevent double-clicks if(els.disconnectBtn) els.disconnectBtn.disabled = true; if(els.cancelPunchBtn) { els.cancelPunchBtn.disabled = true; @@ -474,16 +445,10 @@ async function handleDisconnect(e) { try { const res = await fetch('/api/disconnect', { method: 'POST' }); if (!res.ok) throw new Error("Disconnect failed"); - - // Success: We do nothing here. The backend will process the request, - // send a command, update state, and the SSE stream will push - // a "DISCONNECTED" event, which triggers `handleStatusChange` to switch the UI. - } catch (err) { console.error('Disconnect failed:', err); showToast("FAILED TO DISCONNECT"); - // Re-enable buttons on failure so user can try again if(els.disconnectBtn) els.disconnectBtn.disabled = false; if(els.cancelPunchBtn) { els.cancelPunchBtn.disabled = false; @@ -492,6 +457,15 @@ async function handleDisconnect(e) { } } +// --- Modal Logic --- +function toggleSasModal() { + if (els.sasModal.classList.contains('active')) { + els.sasModal.classList.remove('active'); + } else { + els.sasModal.classList.add('active'); + } +} + // --- Validation & Utilities --- function toggleSubmitButton() { els.submitBtn.disabled = !(state.isIpValid && state.isPortValid); @@ -540,10 +514,6 @@ const validators = { port: (p) => { const n = parseInt(p, 10); return !isNaN(n) && n > 0 && n <= 65535; } }; -/** - * Parses IP:Port format and populates both fields if detected. - * Returns true if parsing happened, false otherwise. - */ function parseIpPort(inputValue) { const match = inputValue.match(/^([0-9.]+):(\d+)$/); if (match) { @@ -560,10 +530,8 @@ function parseIpPort(inputValue) { function handleIpValidation(eventType) { const val = els.peerIpInput.value.trim(); - // Try to parse IP:Port format on paste if (eventType === 'input' && val.includes(':')) { if (parseIpPort(val)) { - // Successfully parsed, validate both fields handleIpValidation('input'); handlePortValidation(); return; @@ -624,9 +592,13 @@ function setupEventListeners() { if(els.chatForm) els.chatForm.addEventListener('submit', handleChatSubmit); - // New Disconnect Listeners + // Disconnect & Cancel if(els.disconnectBtn) els.disconnectBtn.addEventListener('click', handleDisconnect); if(els.cancelPunchBtn) els.cancelPunchBtn.addEventListener('click', handleDisconnect); + + // Modal + if(els.verifyIdentityBtn) els.verifyIdentityBtn.addEventListener('click', toggleSasModal); + if(els.closeSasBtn) els.closeSasBtn.addEventListener('click', toggleSasModal); } -init(); +init(); \ No newline at end of file diff --git a/static/style.css b/static/style.css index 698ff6a..483f3a0 100644 --- a/static/style.css +++ b/static/style.css @@ -143,6 +143,8 @@ body:has(#view-connected.active) .vignette { width: 100%; } +.title-group { display: flex; align-items: baseline; gap: 15px; } + .header h1 { font-size: 3rem; letter-spacing: 6px; color: #fff; text-shadow: 0 0 20px rgba(255,255,255,0.5); @@ -152,6 +154,16 @@ body:has(#view-connected.active) .vignette { .version { font-size: 1rem; color: var(--accent); opacity: 0.7; } +/* NEW: E2EE Badge Style */ +.e2ee-badge { + font-family: var(--font-mono); font-size: 0.8rem; + color: var(--success); border: 1px solid var(--success); + padding: 2px 8px; border-radius: 4px; + background: rgba(0, 255, 157, 0.1); + display: flex; align-items: center; gap: 6px; + letter-spacing: 1px; +} + .status-badge { display: inline-flex; align-items: center; gap: 12px; font-family: var(--font-mono); font-size: 1rem; color: var(--text-dim); @@ -159,7 +171,6 @@ body:has(#view-connected.active) .vignette { .status-dot { width: 10px; height: 10px; background: var(--danger); box-shadow: 0 0 10px var(--danger); transition: background-color 0.3s; } /* --- DYNAMIC STATE: Connected Header --- */ -/* Shrink the main header when connected */ body:has(#view-connected.active) .header { margin-bottom: 0; padding: 0.5rem 2rem; @@ -387,6 +398,34 @@ input.error { border-color: var(--danger); box-shadow: 0 0 10px rgba(255, 0, 60, #chatInput { background: transparent; border: none; padding: 0; font-size: 1.2rem; flex: 1; } .btn-send { background: var(--text-main); color: #000; border: none; padding: 10px 30px; cursor: pointer; font-weight: bold; font-size: 1rem; letter-spacing: 2px; } +/* NEW: Verification Modal Styles */ +.modal-overlay { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0,0,0,0.8); backdrop-filter: blur(5px); + z-index: 2000; display: none; align-items: center; justify-content: center; +} +.modal-overlay.active { display: flex; } + +.modal-window { + background: var(--bg-color); border: 1px solid var(--accent); + width: 500px; padding: 0; box-shadow: 0 0 30px rgba(0, 243, 255, 0.2); +} + +.modal-header { + background: rgba(0, 243, 255, 0.1); padding: 1rem 2rem; + display: flex; justify-content: space-between; align-items: center; + border-bottom: 1px solid var(--accent); color: var(--accent); + font-weight: bold; letter-spacing: 2px; +} + +.modal-body { padding: 2rem; text-align: center; } +.sas-fingerprint { + font-family: var(--font-mono); font-size: 2.5rem; + color: #fff; margin: 1.5rem 0; letter-spacing: 5px; + border: 1px dashed rgba(255,255,255,0.3); padding: 1rem; +} +.modal-subtext { color: var(--text-dim); font-size: 0.9rem; } + .toast { position: fixed; bottom: 50px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.95); border: 1px solid var(--accent); @@ -398,4 +437,4 @@ input.error { border-color: var(--danger); box-shadow: 0 0 10px rgba(255, 0, 60, transition: opacity 0.5s ease-in-out; box-shadow: 0 0 30px var(--accent-dim); } -.toast.show { opacity: 1; } +.toast.show { opacity: 1; } \ No newline at end of file From cd0b97a442ae3d753f1a5196825cce82ceb1e260 Mon Sep 17 00:00:00 2001 From: srujaniyengar Date: Mon, 5 Jan 2026 18:01:42 +0530 Subject: [PATCH 2/4] docs: update readme for v1.1 --- README.md | 147 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 61c6c6d..f581c81 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,7 @@ **High-Performance Serverless P2P Messaging** -GhostLink is a decentralized chat application engineered for direct peer-to-peer communication. By leveraging UDP hole punching and a custom reliability layer, it eliminates the need for central relay servers, ensuring messages travel the shortest path possible between peers. - -This project prioritizes low-latency networking, asynchronous runtime efficiency, and clean architecture. +GhostLink is a decentralized chat app. It connects users directly without using central servers. Direct communication reduces latency and improves performance. --- @@ -29,105 +27,128 @@ This project prioritizes low-latency networking, asynchronous runtime efficiency | Version | Status | Description | |---------|--------------|---------------------------------------------------------------------------------------------------------------| -| v1.0 | **Stable** | Current release. Feature complete for text exchange and reliable UDP transport. | -| v1.1 | **In Progress** | Security Hardening (QUIC). Replacing the KCP stack with QUIC (via `quinn`) to implement TLS 1.3 End-to-End Encryption. | +| v1.0 | **Legacy** | First release. Plaintext messaging with reliable UDP transport. | +| v1.1 | **Stable** | **Added Security**. End-to-End Encryption (E2EE) using X25519 and ChaCha20-Poly1305 encryption. | --- -## βš™οΈ Architectural Overview - -GhostLink operates by decoupling the Web Interface from the P2P Networking Core, bridging them via shared thread-safe state. +## βš™οΈ Architecture -[![](https://mermaid.ink/img/pako:eNqFVFtv2jAU_iuWHyYqESBcSskQUkt3QYOWNUVIW_ZgklNikdiZ7YzSqv99xwnXdWrzdI59vu87t_iZhjIC6lHHcQIRSvHAl14gCDExpOARAdKJmFoFogh4SOQ6jJkyZHyHUTpfLBXLYmt8sUbjZ0CnCjQIwwyXgozZBhSpzEZnAf1lectvDguM7C8GaJCJjPIE-vXFoL9Qg8vHPK3PmcqID-oPqC0ORPQ_QRdphlIYJRMyTZgAUrmPgVwpxsWp5DYqAVUqTzDi6HAn72PiQCYsjDmSfWBp9pEMYyYEJPqEbwJasyUXyy3dziVjueThjg6hhnzl2ki12bJ9liplxmDo26U1kfheMaEzaftdNvIOEs4WPOFmc1qefz-7KTOxVtE7Hu6bes11KLGZmxPMt-G0hKBB5qiZHfpwJLTN-1ZFoN7NuoWMN2DWUq32ObM1GdVv_8lXhiswpfzserr1yR1omatD4n6GGRCfi1XdNwpY-rZ6Gwm_xFKbMSLIpcIpGghNruBEfL-ur4_c10fN10etQw7lNpO-4-z3BPddY-FDmaZMRPqMOM7gaNVK0MHHa6z7dw7akNGUVK6kUnK97UgJtjMtYcV0nRr5nuM0Sc3eFXHHt5YOaxaW7X3pkeCGs4Q_AW6N1vavtaD9QpeYw37bSq82BnQRhqtTBtgdsld22lNmUyoDjtM7FZ5lEbM0Fd__VJ_7ZaXYS1qlS8Uj6hmVQ5WmgP-LdemzZQlo8TQF1EOzeJpoIF4QkzHxQ8p0B1MyX8bUe2CJRi8vtK45w305hOAEQQ1lLgz1Wm5BQb1n-kg9t9eqNc4vXLfTbLVb3U6VbqjnNBu13oXbcbvdntttXzTa3ZcqfSpE3VoDMW6v0eueu63OebtKIeL420_K97V8WunLX189u5E?type=png)](https://mermaid.live/edit#pako:eNqFVFtv2jAU_iuWHyYqESBcSskQUkt3QYOWNUVIW_ZgklNikdiZ7YzSqv99xwnXdWrzdI59vu87t_iZhjIC6lHHcQIRSvHAl14gCDExpOARAdKJmFoFogh4SOQ6jJkyZHyHUTpfLBXLYmt8sUbjZ0CnCjQIwwyXgozZBhSpzEZnAf1lectvDguM7C8GaJCJjPIE-vXFoL9Qg8vHPK3PmcqID-oPqC0ORPQ_QRdphlIYJRMyTZgAUrmPgVwpxsWp5DYqAVUqTzDi6HAn72PiQCYsjDmSfWBp9pEMYyYEJPqEbwJasyUXyy3dziVjueThjg6hhnzl2ki12bJ9liplxmDo26U1kfheMaEzaftdNvIOEs4WPOFmc1qefz-7KTOxVtE7Hu6bes11KLGZmxPMt-G0hKBB5qiZHfpwJLTN-1ZFoN7NuoWMN2DWUq32ObM1GdVv_8lXhiswpfzserr1yR1omatD4n6GGRCfi1XdNwpY-rZ6Gwm_xFKbMSLIpcIpGghNruBEfL-ur4_c10fN10etQw7lNpO-4-z3BPddY-FDmaZMRPqMOM7gaNVK0MHHa6z7dw7akNGUVK6kUnK97UgJtjMtYcV0nRr5nuM0Sc3eFXHHt5YOaxaW7X3pkeCGs4Q_AW6N1vavtaD9QpeYw37bSq82BnQRhqtTBtgdsld22lNmUyoDjtM7FZ5lEbM0Fd__VJ_7ZaXYS1qlS8Uj6hmVQ5WmgP-LdemzZQlo8TQF1EOzeJpoIF4QkzHxQ8p0B1MyX8bUe2CJRi8vtK45w305hOAEQQ1lLgz1Wm5BQb1n-kg9t9eqNc4vXLfTbLVb3U6VbqjnNBu13oXbcbvdntttXzTa3ZcqfSpE3VoDMW6v0eueu63OebtKIeL420_K97V8WunLX189u5E) +GhostLink separates the Web Interface from the P2P core. These parts communicate through thread-safe state. Version 1.1 also adds a secure handshake layer before starting a stream. ### Communication Flow -[![](https://mermaid.ink/img/pako:eNqVVsFu20YQ_ZXpnmxUZEnKsiweXJB2ERupBcK0EaRVUWzIkUSY5LK7yzSq4d5yyye0P5cv6SxJhZIluYkOAld8M_Pe25ldPbJEpMh8pvCPGssELzO-kLyYlUAfXmtR1sU7lO26_a641FmSVbzUEABXcK9Q0tPRq6VQ-uesfDjeBU6Du98bMD0Y7K2oNco9wNiAXgmxyBHiu_vp_lThOlX4QqrwC7lwm1wLnQqNIN4b7oPQh2jJFYLrw3WKpc70Ci4zlZj3KzgyTI43TQis8_PvYx_eLAXwAq5_hKMwK9OsXMCtsVLpDh5bhLQCH96KGrhE8Jyh7diuO7Rdf-Q4DhxF9bs8S-A62qoQfluFsK_gTs7skWu7jmN7_ulOib3qjU0KElGtrIorenUdKSh4WfM8X8H7jLduyPSH-CZ-0UHPhzgr6lzzEkWt4ErQTkZ1mSyJ-nMLg7YyJMTuQcGMXYiyxETPWG9C-AKmReVCVPBTs1PkaKGM3rYexJpr7Izq-gMibPo10BqLSqv-5ZpV06wkA8uUysVvpzMGWkC4jWxQHdpwvIwg4skDamoXmfiGijOAS6V9MJtw_CzaeCezxVKDmENXsR2ORCKRViDrHH0I8lz82WQA67xJ-n-JiMznfz9145FKUSmS3RKbiiYrzIVxYIV6gxUntY014QFrwl7sc2uCXWvC3sgda057a0YHrMlxvimoFbPPmVHnzOl-ZzbzBL0xwTNjbnmC1P40XzoT5QYhEtoumoe9fW_bNgRzOoPgb9fyQKHJo8zP6wDTdXGdJKjUvM7hTnIKVjzvy3xd2x1suW1INzWf__m4tr2xCtPvSKjZfvyQKa02VIZWO4x9bSu4eL2eQr724Ou74GAH7IoJvolpYLXi9jM9vEnt4TT0jfmlqoTUcF_RVZfinjPpihQ3obFoSJG21xfR1pH0EqTPRcBf6eff4Ia2ni-oaWfsCkkivBEyT_tjrq3bYkkPG7CFzFLma1njgBUoC26W7NEEzJheYoEzZtKlXD6YPE8UQ9feL0IU6zAp6sWS-XOeK1rVVUrD013wXyDkGMoLUZea-cNhk4L5j-wD890JXVSnZ6478oYnw_FowFbMtzzHnpy5I3c8nrjjkzPnZPw0YH81RV3boRh34njDoeedjJzxgCENlJA37Z8Mmot5tmBP_wHtB3Ud?type=png)](https://mermaid.live/edit#pako:eNqVVsFu20YQ_ZXpnmxUZEnKsiweXJB2ERupBcK0EaRVUWzIkUSY5LK7yzSq4d5yyye0P5cv6SxJhZIluYkOAld8M_Pe25ldPbJEpMh8pvCPGssELzO-kLyYlUAfXmtR1sU7lO26_a641FmSVbzUEABXcK9Q0tPRq6VQ-uesfDjeBU6Du98bMD0Y7K2oNco9wNiAXgmxyBHiu_vp_lThOlX4QqrwC7lwm1wLnQqNIN4b7oPQh2jJFYLrw3WKpc70Ci4zlZj3KzgyTI43TQis8_PvYx_eLAXwAq5_hKMwK9OsXMCtsVLpDh5bhLQCH96KGrhE8Jyh7diuO7Rdf-Q4DhxF9bs8S-A62qoQfluFsK_gTs7skWu7jmN7_ulOib3qjU0KElGtrIorenUdKSh4WfM8X8H7jLduyPSH-CZ-0UHPhzgr6lzzEkWt4ErQTkZ1mSyJ-nMLg7YyJMTuQcGMXYiyxETPWG9C-AKmReVCVPBTs1PkaKGM3rYexJpr7Izq-gMibPo10BqLSqv-5ZpV06wkA8uUysVvpzMGWkC4jWxQHdpwvIwg4skDamoXmfiGijOAS6V9MJtw_CzaeCezxVKDmENXsR2ORCKRViDrHH0I8lz82WQA67xJ-n-JiMznfz9145FKUSmS3RKbiiYrzIVxYIV6gxUntY014QFrwl7sc2uCXWvC3sgda057a0YHrMlxvimoFbPPmVHnzOl-ZzbzBL0xwTNjbnmC1P40XzoT5QYhEtoumoe9fW_bNgRzOoPgb9fyQKHJo8zP6wDTdXGdJKjUvM7hTnIKVjzvy3xd2x1suW1INzWf__m4tr2xCtPvSKjZfvyQKa02VIZWO4x9bSu4eL2eQr724Ou74GAH7IoJvolpYLXi9jM9vEnt4TT0jfmlqoTUcF_RVZfinjPpihQ3obFoSJG21xfR1pH0EqTPRcBf6eff4Ia2ni-oaWfsCkkivBEyT_tjrq3bYkkPG7CFzFLma1njgBUoC26W7NEEzJheYoEzZtKlXD6YPE8UQ9feL0IU6zAp6sWS-XOeK1rVVUrD013wXyDkGMoLUZea-cNhk4L5j-wD890JXVSnZ6478oYnw_FowFbMtzzHnpy5I3c8nrjjkzPnZPw0YH81RV3boRh34njDoeedjJzxgCENlJA37Z8Mmot5tmBP_wHtB3Ud) +```mermaid +sequenceDiagram + participant UserA as User A (Client) + participant STUN as STUN Server + participant UserB as User B (Peer) + + Note over UserA, UserB: 1. Discovery Phase + UserA->>STUN: Who am I? (Binding Request) + STUN-->>UserA: Public IP:Port + UserB->>STUN: Who am I? (Binding Request) + STUN-->>UserB: Public IP:Port + + Note over UserA, UserB: 2. Secure Handshake (UDP) + UserA->>UserB: SYN {PubKey_A, CipherMode} + UserB->>UserA: SYN-ACK {PubKey_B} + + Note over UserA, UserB: 3. Key Derivation (Local) + UserA->>UserA: ECDH(PrivA, PubB) + HKDF + UserB->>UserB: ECDH(PrivB, PubA) + HKDF + + Note over UserA, UserB: 4. Encrypted Transport + UserA-->>UserB: Encrypted Message + UserB-->>UserA: Encrypted Acknowledgement +``` -1. **Initialization**: The application starts the Axum web server and the Tokio UDP listener simultaneously. -2. **Discovery**: The UDP layer queries a STUN server to resolve the machine's Public IP and punch a NAT hole. -3. **Signaling**: The user manually exchanges Public IPs with a peer via the Web UI. -4. **Transport**: Messages are routed from the UI β†’ Shared State β†’ KCP Stream β†’ Peer. +Steps: +1. **Initialization**: The app starts an HTTP web server and a UDP listener. +2. **Discovery**: The UDP layer uses a STUN server to get the public IP and open a connection. +3. **Secure Handshake**: Peers exchange public keys over UDP. +4. **Transport**: A reliable, encrypted stream is created for data transfer. --- -## πŸš€ Key Technical Features +## πŸš€ Features -- **True Peer-to-Peer**: Direct client-to-client connections minimize latency and remove dependency on third-party infrastructure. -- **Reliable UDP (ARQ)**: Utilizes KCP (via `tokio_kcp`) to provide TCP-like reliability with the speed advantages of UDP. -- **Automated NAT Traversal**: Integrated STUN client allows for seamless connectivity across different network configurations without manual port forwarding. -- **Asynchronous Core**: Built entirely on the Tokio runtime for non-blocking I/O and high concurrency. -- **Real-Time Updates**: State changes are pushed to the browser immediately via Server-Sent Events (SSE). +- **End-to-End Encryption**: Uses X25519 keys and HKDF for secure communication. +- **Forward Secrecy**: Creates session keys for each connection. +- **Identity Verification**: Shows SAS codes so users can verify connections. +- **Reliable UDP**: Uses KCP for fast, reliable transport. +- **NAT Traversal**: Connects through networks using STUN. +- **Real-Time Updates**: Sends live updates to the web UI using SSE. --- -## πŸ› οΈ Technology Stack +## πŸ› οΈ Technology -| Component | Technology | Role | -|---------------|---------------|-----------------------------------------------------| -| Runtime | Tokio | Asynchronous I/O scheduler and task management. | -| Transport | Tokio KCP | Reliable UDP protocol implementation. | -| Web Framework | Axum | HTTP/REST interface and SSE stream handling. | -| State | Arc/RwLock | Thread-safe state synchronization between tasks. | -| Discovery | STUN | Public IP resolution and NAT hole punching. | +| Component | Technology | Purpose | +|---------------|------------------------|--------------------------------------------------| +| Runtime | Tokio | Manages I/O and tasks. | +| Transport | Tokio KCP | Handles reliable UDP communication. | +| Cryptography | RustCrypto | Provides secure key and encryption functions. | +| Web Framework | Axum | HTTP REST API and real-time event streaming. | +| State | Arc/RwLock | Ensures thread-safe state management. | +| Discovery | STUN | Resolves public IPs and opens connections. | --- -## πŸ”’ Security Notice +## πŸ”’ Security -> **WARNING**: *Protocol Status: Cleartext* +**STATUS: Encrypted** -GhostLink v1.0 transmits data in plain text. While the transport layer provides reliability, it does not currently implement end-to-end encryption. +GhostLink v1.1 uses encryption to secure data: +- **Key Exchange**: Uses X25519 elliptic-curve. +- **Ciphers**: Uses ChaCha20-Poly1305 or AES-256-GCM. +- **Verification**: Users can check fingerprints to avoid interception. -Do not transmit sensitive data (PII, credentials, financial information) over this version. Encryption is slated for the v1.1 release cycle (via QUIC). +Private keys are only stored in memory and never sent or saved. --- -## πŸ“¦ Installation & Usage +## πŸ“¦ Installation -### Prerequisites +### Requirements -Ensure you have the latest stable version of Rust and Cargo installed. +Install the latest versions of Rust and Cargo. ### Quick Start -**Step 1**: Clone the repository. -```bash -git clone https://github.com/pushkar-gr/ghostlink.git -cd ghostlink -``` - -**Step 2**: Build and run. -```bash -cargo run --release -``` - -The first build may take a moment to compile dependencies. - -**Step 3**: Initiate Connection. -- Navigate to `http://localhost:8080` in your web browser. -- Copy your Public IP displayed on the dashboard. -- Share your IP with a friend and input their IP into the Target Address field. -- Click **Establish Link**. +1. **Clone the repo**: + ```bash + git clone https://github.com/pushkar-gr/ghostlink.git + cd ghostlink + ``` +2. **Build and run**: + ```bash + cargo run --release + ``` +3. **Create a connection**: + - Open `http://localhost:8080` in your browser. + - Copy your public IP. + - Share it with a peer. + - Set an optional alias. + - Press **Establish Link**. + - Verify the fingerprint matches your peer’s. --- ## 🀝 Contributing -We welcome contributions! πŸš€ - 1. Fork the repository. -2. Create a feature branch. +2. Create a branch: ```bash - git checkout -b feature/amazing-feature + git checkout -b feature/example-feature ``` -3. Commit your changes. +3. Make and commit your changes: ```bash - git commit -m "Add some amazing feature" + git commit -m "Explain the feature" ``` -4. Push to the branch. +4. Push the branch: ```bash - git push origin feature/amazing-feature + git push origin feature/example-feature ``` 5. Open a Pull Request. @@ -135,8 +156,8 @@ We welcome contributions! πŸš€ ## πŸ“„ License -This project is open-source and available under the **GNU General Public License v3.0**. See the [LICENSE](./LICENSE) file for details. +This project is licensed under the **GNU General Public License v3.0**. See the [LICENSE](./LICENSE) file for details. --- -*Happy Chatting!* πŸ‘» +*Start chatting today!* πŸ‘» \ No newline at end of file From 17e6dc6bbd55da807e79118743cf6d946b5890f5 Mon Sep 17 00:00:00 2001 From: pushkar-gr Date: Mon, 5 Jan 2026 19:55:00 +0530 Subject: [PATCH 3/4] fix(frontend): was display name twice and had to close E2EE manually --- static/index.html | 14 +++++++------- static/script.js | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/static/index.html b/static/index.html index d869209..7eae4fe 100644 --- a/static/index.html +++ b/static/index.html @@ -52,7 +52,7 @@

GHOSTLINK v1.1

PUBLIC_ENDPOINT
--.--.--.-- - +
@@ -60,7 +60,7 @@

GHOSTLINK v1.1

LOCAL_ENDPOINT
--.--.--.-- - +
@@ -117,7 +117,7 @@

GHOSTLINK v1.1

- +
@@ -143,8 +143,8 @@

GHOSTLINK v1.1

- - + +
@@ -166,7 +166,7 @@

GHOSTLINK v1.1