whisper-app is a demonstration of the Whisper protocol in action. Try it live:
https://whisper.vorobalek.dev
whisper-server is the server-side reference implementation of the
Whisper protocol.
This app showcases the capabilities of the Whisper protocol. Below you'll find a detailed protocol description and usage examples for integration into your own projects.
The live demo site (https://whisper.vorobalek.dev) is a fully featured Progressive Web App (PWA). You can install it on Android and iOS devices for an app-like experience:
- Open the demo site in Chrome (or another supported browser) on your Android device.
- Tap the βInstallβ icon in the address bar or open the menu and select βAdd to Home screen.β
- Confirm to install the app.
- Open the demo site in Safari on your iOS device.
- Tap the βShareβ button at the bottom of the screen.
- Scroll down and select βAdd to Home Screen.β
- Tap βAddβ to confirm and launch the app from your home screen.
- Multiple dialogs (multi-peer support)
- Replies
- Emoji reactions
- Typing indication
- Message status indicators (sent / delivered / read)
- Push notifications (with user consent)
- Encrypted local storage (password-protected, never leaves your device)
- End-to-end encrypted WebRTC data channels
- QR code sharing and scanning for public keys
- Version update notifications
- Full transparency of protocol and cryptography (see privacy info popup)
- Full test coverage and source code are available
- No registration or disclosure of any personal data required
- Dual peer-to-peer channels (incoming/outgoing) for enhanced reliability and to circumvent regional restrictions
- Seamless fallback to relay servers when direct P2P connection is not possible
- Zero trust required β neither backend servers, relay servers, nor counterparties can compromise message confidentiality; security is enforced at the protocol level
- Private messages are unlinkable β it is impossible to determine the sender or recipient, and transmitted messages contain no identifying marks
Planned features (all to be implemented without compromising the core privacy and security principles):
- Group chats (protocol-level, based on MLS)
- Voice messages
- Audio/video calls
All future features will also preserve:
- Minimal trust in the server or other participants
- Minimal information stored on the server or transmitted in unencrypted form
Whisper is a privacy-first, end-to-end encrypted messenger protocol and reference application. All cryptographic operations are performed client-side, ensuring that your private data and keys never leave your device unencrypted. The protocol is designed for maximum privacy, security, and user control.
Key Privacy Guarantees:
- All data transfers are time-sensitive, signed, and verified by both server and recipient.
- Private messages use end-to-end encryption with a unique key refreshed on each reconnection.
- Chat history and your private signature key are stored encrypted, protected by your password, and never leave your device.
- All WebRTC data is transmitted with end-to-end encryption.
- The Whisper server only stores your public signature key and, if you consent, your push subscription. It relays data without retention or modification.
- All communication with the server is over HTTPS.
- No registration, no personal data collection, and no trust required β privacy and unlinkability are enforced by design.
See the Whisper Protocol Diagram (SVG) for a technical overview.
The protocol, implemented in whisper-core, provides:
- Key Generation: Each user generates a signing key pair (for authentication and integrity) β this is your persistent identity and "account." Encryption key pairs, in contrast, are generated for every new connection or reconnection to a peer, ensuring that end-to-end encryption keys are always fresh and never reused.
- Registration: Users register their public signing key and (optionally) push subscription with the server. All registration data is signed and time-sensitive.
- Connection Establishment: Peer-to-peer connections are established using WebRTC, with signaling and authentication handled via the server. All signaling messages are also signed and time-sensitive.
- End-to-End Encryption: Each session uses a unique, ephemeral encryption key derived via Diffie-Hellman, never stored or transmitted.
- Message Exchange: All messages are encrypted and signed. The server only relays encrypted payloads and never has the ability to modify, or inspect this content.
- Push Notifications: Optional push notifications are supported via VAPID and Web Push, with subscriptions stored only if the user consents.
- Open the project in a development container (see
.devcontainer/devcontainer.json). - In the container terminal, install dependencies and start the app:
npm ci npm run start
- The app will be available at
http://localhost:8080. - Ensure that ports
8080and5027are free on your host machine before starting.
<!-- docs/example.min.html -->
<!doctype html>
<html lang="en">
<head></head>
<body></body>
<script>
(function (w, h, s, p, r, m, i, n) {
if (w[p]) return;
m = 1 * new Date();
w[p] = {};
w[p].t = m;
r = h.getElementsByTagName(s)[0];
i = h.createElement(s);
i.async = true;
i.src = 'https://whisper.vorobalek.dev/core.min.js?_=' + m;
i.onload = function () {
n = Whisper.getPrototype(console);
n.initialize({
serverUrl: 'https://cluster.vorobalek.dev/whisper',
signingKeyPair: n.generateSigningKeyPair(),
}).then((x) => {
w[p] = x;
});
};
r.parentNode.insertBefore(i, r);
})(window, document, 'script', 'whisper');
</script>
</html><!-- docs/example.html -->
<!doctype html>
<html lang="en">
<head></head>
<body></body>
<script>
(function (w, h, s, p, r, m, i, n) {
if (w[p]) return;
m = 1 * new Date();
w[p] = {};
w[p].t = m;
r = h.getElementsByTagName(s)[0];
i = h.createElement(s);
i.async = true;
i.src = 'https://whisper.vorobalek.dev/core.min.js?_=' + m;
i.onload = function () {
n = Whisper.getPrototype(console);
n.initialize({
onMayWorkUnstably: (reason) => {
console.warn(reason);
},
onTrace: (...args) => {
console.trace(...args);
},
onDebug: (...args) => {
console.debug(...args);
},
onLog: (...args) => {
console.log(...args);
},
onWarn: (...args) => {
console.warn(...args);
},
onError: (...args) => {
console.error(...args);
},
serverUrl: 'https://cluster.vorobalek.dev/whisper',
version: `${m}`,
onNewVersion: () => {
console.warn('Update needed!');
},
vapidKey: 'BHAYDRAjMWXfg7dxFIOZYNLlxrVDohy_PbN7SXcrXapiZq0Jnt0VXsAx6ytkLArVVFDSfula4VRWm5HDvkVVRbA',
onPermissionDefault: () => {
console.warn('Permissions default!');
},
onPermissionGranted: () => {
console.warn('Permissions granted!');
},
onPermissionDenied: () => {
console.warn('Permissions denied!');
},
signingKeyPair: n.generateSigningKeyPair(),
onIncomingConnection: (connection) => console.warn('Incoming connection', connection),
iceServers: [
{
urls: 'stun:***:3478',
},
{
urls: 'turn:***:3478',
username: '***',
credential: '***',
},
],
focusOnDial: (peerPublicKey) => {
console.warn('Dial!', peerPublicKey);
return Promise.resolve(true);
},
requestDial: (peerPublicKey) => {
if (window.confirm(`Incoming connection request from ${peerPublicKey}.\nAccept?`)) {
return Promise.resolve(true);
}
console.warn('Declined.', peerPublicKey);
return Promise.resolve(false);
},
}).then((x) => {
w[p] = x;
});
};
r.parentNode.insertBefore(i, r);
})(window, document, 'script', 'whisper');
</script>
</html>After integration, you can interact with the protocol directly from the browser console:
// 1. Get your public key
whisper.publicKey;
// 2. Set up a message handler for a peer (replace <peer public key> with the actual key)
whisper.get('<peer public key>').onMessage = (e) => {
console.warn(e);
};
// 3. Open a connection to the peer
whisper.get('<peer public key>').open();
// 4. Send a message to the peer
whisper.get('<peer public key>').send('Hello, Whisper!');