This example demonstrates:
- Generating an Ed25519 keypair for signing
Deriving an X25519 keypair from the Ed25519 key with ed2curve- Generate an X25519 keypair for encryption
- Perform a Diffie-Hellman key exchange
- Usie the shared secret to encrypt/decrypt a message
Note: This example ignores the
subtle crypto APIand uses thetweetnacl-jslibrary.
This is an exercise of the tweetnacl library, learning the basics with minimal ceremony. For a higher level, more realistic use-case, see crypto.md
Ed25519/X25519 Secure Communication Demo
html
<div style="max-width: 800px; margin: 0 auto; padding: 20px; font-family: monospace;">
<h1>Ed25519/X25519 Secure Communication Demo</h1>
<button id="run-demo">Run Demonstration</button>
<pre id="message-container" style="background: #f4f4f4; color: #1a3b5d; padding: 15px; margin-top: 20px; height: 500px; overflow: auto; border: 1px solid #ddd;"></pre>
</div>
js
import nacl from '/vendor/nacl.js';
/**
* Alice and Bob's communication workflow with correct key generation
*/
async function secureCommunicationDemo() {
const messageContainer = document.getElementById('message-container');
logMessage('Starting secure communication demo...');
// Step 1. Note that subtle.crypto supports this operation
logMessage('\nGenerating Ed25519 keypairs for Alice and Bob (for signatures)...');
const aliceEd25519KeyPair = nacl.sign.keyPair();
logMessage('Alice Ed25519 Public Key: ' + uint8ArrayToHex(aliceEd25519KeyPair.publicKey));
const bobEd25519KeyPair = nacl.sign.keyPair();
logMessage('Bob Ed25519 Public Key: ' + uint8ArrayToHex(bobEd25519KeyPair.publicKey));
// Step 2. Note that subtle.crypto does NOT currently support deriving x25519 from ed25519, which is why we need tweetnacl
logMessage('\nGenerating X25519 keypairs for Alice and Bob (for encryption)...');
const aliceX25519KeyPair = nacl.box.keyPair();
logMessage('Alice X25519 Public Key: ' + uint8ArrayToHex(aliceX25519KeyPair.publicKey));
logMessage('Alice X25519 Secret Key: ' + uint8ArrayToHex(aliceX25519KeyPair.secretKey));
const bobX25519KeyPair = nacl.box.keyPair();
logMessage('Bob X25519 Public Key: ' + uint8ArrayToHex(bobX25519KeyPair.publicKey));
logMessage('Bob X25519 Secret Key: ' + uint8ArrayToHex(bobX25519KeyPair.secretKey));
// Step 3: Perform Diffie-Hellman key exchange
logMessage('\nPerforming Diffie-Hellman key exchange...');
// Alice computes the shared secret using her secret key and Bob's public key
const aliceSharedSecret = nacl.scalarMult(
aliceX25519KeyPair.secretKey,
bobX25519KeyPair.publicKey
);
logMessage('Alice computed shared secret: ' + uint8ArrayToHex(aliceSharedSecret));
// Bob computes the shared secret using his secret key and Alice's public key
const bobSharedSecret = nacl.scalarMult(
bobX25519KeyPair.secretKey,
aliceX25519KeyPair.publicKey
);
logMessage('Bob computed shared secret: ' + uint8ArrayToHex(bobSharedSecret));
// Verify that both computed the same shared secret
const secretsMatch = compareUint8Arrays(aliceSharedSecret, bobSharedSecret);
logMessage('Shared secrets match: ' + secretsMatch);
if (!secretsMatch) {
logMessage('Error: Diffie-Hellman key exchange failed!');
return;
}
// Step 4: Alice encrypts a message to Bob
logMessage('\nAlice is encrypting a message for Bob...');
const originalMessage = 'Hello Bob! This is a secret message from Alice.';
logMessage('Original message: ' + originalMessage);
// Generate a nonce for encryption
const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
logMessage('Generated nonce: ' + uint8ArrayToHex(nonce));
// Convert the message to Uint8Array
const messageUint8 = new TextEncoder().encode(originalMessage);
// Encrypt the message using the shared secret
const encryptedMessage = nacl.secretbox(messageUint8, nonce, aliceSharedSecret);
logMessage('Encrypted message: ' + uint8ArrayToHex(encryptedMessage));
// Step 5: Alice signs the encrypted message with her Ed25519 key
const signature = nacl.sign.detached(encryptedMessage, aliceEd25519KeyPair.secretKey);
logMessage('Signature: ' + uint8ArrayToHex(signature));
// Step 6: Alice sends the encrypted message, nonce, and signature to Bob
logMessage('\nTransmitting encrypted message, nonce, and signature to Bob...');
// Step 7: Bob verifies the signature using Alice's public key
logMessage('\nBob is verifying the signature...');
const signatureValid = nacl.sign.detached.verify(
encryptedMessage,
signature,
aliceEd25519KeyPair.publicKey
);
logMessage('Signature valid: ' + signatureValid);
if (!signatureValid) {
logMessage('Error: Signature verification failed!');
return;
}
// Step 8: Bob decrypts the message using the shared secret
logMessage('\nBob is decrypting the message...');
const decryptedMessage = nacl.secretbox.open(encryptedMessage, nonce, bobSharedSecret);
if (!decryptedMessage) {
logMessage('Error: Decryption failed!');
return;
}
const decryptedText = new TextDecoder().decode(decryptedMessage);
logMessage('Decrypted message: ' + decryptedText);
logMessage('\nSecure communication completed successfully!');
}
/**
* Helper function to convert Uint8Array to hex string
*/
function uint8ArrayToHex(uint8Array) {
return Array.from(uint8Array)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
//.substring(0, 32) + '...'; // Truncate for display
}
/**
* Helper function to compare two Uint8Arrays
*/
function compareUint8Arrays(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
/**
* Helper function to log messages to the UI
*/
function logMessage(message) {
const messageContainer = document.getElementById('message-container');
if (messageContainer) {
const messageElement = document.createElement('div');
messageElement.textContent = message;
messageContainer.appendChild(messageElement);
}
console.log(message);
}
// Add event listener
document.getElementById('run-demo').addEventListener('click', secureCommunicationDemo);
The actual message passed between them might look something like this:
js
const msg = {
"timestamp": 1712767452, // Unix timestamp when message was created
"version": "1.0", // Protocol version for future compatibility
"payload": {
"ciphertext": "base64EncodedEncryptedMessageString...", // The encrypted message
"nonce": "base64EncodedNonceString...", // The nonce used for encryption
"signature": "base64EncodedSignatureString..." // Ed25519 signature of the ciphertext
},
"publicKeys": {
"signing": "base64EncodedEd25519PublicKey...", // Alice's Ed25519 public key
"encryption": "base64EncodedX25519PublicKey...", // Alice's X25519 public key?? bob can derive it from ed25519
"recipient": "base64EncodedEd25519PublicKey..." // Bob's Ed25519 public key
}
}
© 2026 simpatico