Key Exchange

🔄 X25519 Elliptic Curve Key Exchange

Security Level 128-bit
Performance ⭐⭐⭐⭐⭐ Excellent
Quantum Resistant ❌ No
Key Size 32 bytes (256 bits)
Shared Secret 32 bytes
Standardization RFC 7748

📖 Overview

X25519 is an elliptic curve Diffie-Hellman (ECDH) key exchange protocol using Curve25519. It enables two parties to establish a shared secret over an insecure channel without prior communication, designed for high security and performance with built-in protection against implementation pitfalls.

✨ Key Features

🔒

High Security

128-bit security level against classical attacks

Fast Performance

Optimized for modern processors and architectures

🛠️

Simple Implementation

Reduced complexity compared to other curves

🕰️

Side-Channel Resistance

Designed to resist timing and cache attacks

🌍

Wide Adoption

Used in TLS 1.3, Signal, SSH, WireGuard

📜

RFC Standardized

RFC 7748 specification and IETF approved

🎯 Common Use Cases

🌐 Network Protocols

  • TLS 1.3 key exchange in HTTPS connections
  • SSH secure shell key exchange
  • WireGuard and modern VPN implementations
  • QUIC transport protocol security

📱 Messaging & Communication

  • Signal Protocol end-to-end encryption
  • WhatsApp and Telegram secure messaging
  • Real-time communication protocols
  • IoT device secure pairing and communication

🔧 Algorithm Details

📊 X25519 Parameters

Curve
Curve25519
Montgomery curve over prime field
Field
GF(2^255 - 19)
255-bit prime field
Key Size
32 bytes
Both public and private keys
Shared Secret
32 bytes
Output shared secret size

🔐 Security Properties

🔢

Discrete Log Security

Based on elliptic curve discrete logarithm problem

🔄

Twist Security

Secure against invalid curve attacks

🛡️

Small Subgroup Protection

Protected against small subgroup attacks

⚠️

Quantum Vulnerability

Vulnerable to Shor's algorithm (like all ECDH)

🧮 Algorithm Structure

X25519 performs scalar multiplication on Curve25519:

shared_secret = private_key × public_key (mod p)

The Montgomery ladder ensures constant-time execution and security.

💻 Implementation

X25519 key exchange is straightforward to implement and use. Both parties generate key pairs and exchange public keys to derive a shared secret.

Basic Key Exchange

X25519 Diffie-Hellman Key Exchange Python
```python from metamui_crypto import X25519 import os # Alice generates her keypair alice_private = X25519.generate_private_key() alice_public = X25519.compute_public_key(alice_private) # Bob generates his keypair bob_private = X25519.generate_private_key() bob_public = X25519.compute_public_key(bob_private) # Both parties compute the same shared secret alice_shared = X25519.compute_shared_secret(alice_private, bob_public) bob_shared = X25519.compute_shared_secret(bob_private, alice_public) assert alice_shared == bob_shared print(f"Shared secret established: {len(alice_shared)} bytes") print(f"Alice public key: {alice_public.hex()}") print(f"Bob public key: {bob_public.hex()}") # The shared secret can now be used for symmetric encryption print(f"Shared secret: {alice_shared.hex()}") ```

🔒 Security Considerations

⚠️ Critical Security Guidelines

  • Always validate public keys before use
  • Use proper key derivation functions (KDF) on shared secrets
  • Never reuse private keys across sessions
  • Implement proper forward secrecy protocols
  • Consider post-quantum alternatives for long-term security

⚡ Performance Analysis

Operation Benchmarks

Operation X25519 ECDH P-256 ECDH P-521
Key Generation 25 μs 200 μs 800 μs
Public Key Computation 25 μs 200 μs 800 μs
Shared Secret 25 μs 200 μs 800 μs
Key Size 32 bytes 32 bytes 66 bytes

🎯 Use Cases and Examples

TLS 1.3 Key Exchange

Standard key exchange mechanism in modern TLS connections for secure HTTPS.

Signal Protocol

End-to-end encryption in messaging apps with perfect forward secrecy.

WireGuard VPN

High-performance VPN protocol using X25519 for key establishment.

📚 References

def derive_keys(shared_secret: bytes, context: bytes) -> dict: “"”Derive encryption and authentication keys””” hkdf = HKDF(hash_function=SHA256())

# Derive 64 bytes: 32 for encryption, 32 for authentication
key_material = hkdf.derive(
    ikm=shared_secret,
    info=context,
    length=64
)

return {
    'encryption_key': key_material[:32],
    'authentication_key': key_material[32:64]
}

Derive session keys

context = b”secure_chat_v1.0” keys = derive_keys(alice_shared, context)


### Advanced Usage

```python
# Ephemeral key exchange for forward secrecy
class EphemeralX25519:
    def __init__(self):
        self.private_key = None
        self.public_key = None
        self.generate_keypair()
    
    def generate_keypair(self):
        """Generate new ephemeral keypair"""
        self.private_key = X25519.generate_private_key()
        self.public_key = X25519.compute_public_key(self.private_key)
    
    def exchange(self, peer_public_key: bytes) -> bytes:
        """Perform key exchange and rotate keys"""
        shared_secret = X25519.compute_shared_secret(
            self.private_key, 
            peer_public_key
        )
        
        # Immediately generate new keypair for forward secrecy
        self.generate_keypair()
        
        return shared_secret
    
    def get_public_key(self) -> bytes:
        """Get current public key"""
        return self.public_key

# Secure channel establishment
class X25519SecureChannel:
    def __init__(self, identity_private_key: bytes):
        self.identity_private = identity_private_key
        self.identity_public = X25519.compute_public_key(identity_private_key)
        self.ephemeral = EphemeralX25519()
    
    def initiate_handshake(self, peer_identity_public: bytes) -> dict:
        """Initiate secure channel handshake"""
        # Generate ephemeral keypair for this session
        ephemeral_private = X25519.generate_private_key()
        ephemeral_public = X25519.compute_public_key(ephemeral_private)
        
        # Compute shared secrets
        # DH1: Our identity key with their identity key
        dh1 = X25519.compute_shared_secret(
            self.identity_private, 
            peer_identity_public
        )
        
        # DH2: Our ephemeral key with their identity key
        dh2 = X25519.compute_shared_secret(
            ephemeral_private, 
            peer_identity_public
        )
        
        # Combine shared secrets
        combined_secret = dh1 + dh2
        
        # Derive session keys
        session_keys = self.derive_session_keys(combined_secret)
        
        return {
            'ephemeral_public': ephemeral_public,
            'session_keys': session_keys,
            'ephemeral_private': ephemeral_private  # Keep for next phase
        }
    
    def complete_handshake(self, peer_ephemeral_public: bytes,
                          our_ephemeral_private: bytes,
                          peer_identity_public: bytes) -> dict:
        """Complete handshake with peer's ephemeral key"""
        # Additional DH computations
        # DH3: Our identity key with their ephemeral key
        dh3 = X25519.compute_shared_secret(
            self.identity_private,
            peer_ephemeral_public
        )
        
        # DH4: Our ephemeral key with their ephemeral key
        dh4 = X25519.compute_shared_secret(
            our_ephemeral_private,
            peer_ephemeral_public
        )
        
        # Combine all shared secrets for maximum security
        combined_secret = dh3 + dh4
        
        # Derive final session keys
        final_keys = self.derive_session_keys(combined_secret)
        
        return {'session_keys': final_keys}
    
    def derive_session_keys(self, shared_secret: bytes) -> dict:
        """Derive session keys from shared secret"""
        from metamui_crypto import HKDF, SHA256
        hkdf = HKDF(hash_function=SHA256())
        
        # Derive multiple keys
        key_material = hkdf.derive(
            ikm=shared_secret,
            info=b"X25519_SESSION_KEYS_V1",
            length=96  # 3 × 32-byte keys
        )
        
        return {
            'send_key': key_material[0:32],
            'receive_key': key_material[32:64],
            'next_header_key': key_material[64:96]
        }

# Double Ratchet implementation (simplified Signal Protocol)
class X25519DoubleRatchet:
    def __init__(self, shared_secret: bytes, sending: bool = True):
        self.root_key = shared_secret
        self.sending = sending
        
        if sending:
            # Generate initial sending chain
            self.dh_keypair = self.generate_dh_keypair()
            self.chain_key = None
        else:
            # Initialize for receiving
            self.dh_keypair = None
            self.chain_key = None
    
    def generate_dh_keypair(self) -> dict:
        """Generate DH keypair for ratchet"""
        private_key = X25519.generate_private_key()
        public_key = X25519.compute_public_key(private_key)
        
        return {
            'private': private_key,
            'public': public_key
        }
    
    def ratchet_encrypt(self, plaintext: bytes, 
                       peer_public_key: bytes) -> dict:
        """Encrypt message with ratcheting"""
        # DH ratchet step
        if self.chain_key is None:
            shared_secret = X25519.compute_shared_secret(
                self.dh_keypair['private'],
                peer_public_key
            )
            
            # Root key ratchet
            self.root_key, self.chain_key = self.kdf_rk(
                self.root_key, 
                shared_secret
            )
        
        # Chain key ratchet
        self.chain_key, message_key = self.kdf_ck(self.chain_key)
        
        # Encrypt message
        from metamui_crypto import ChaCha20Poly1305
        cipher = ChaCha20Poly1305(message_key)
        nonce = os.urandom(12)
        ciphertext = cipher.encrypt(plaintext, nonce)
        
        return {
            'dh_public': self.dh_keypair['public'],
            'nonce': nonce,
            'ciphertext': ciphertext
        }
    
    def ratchet_decrypt(self, encrypted_message: dict) -> bytes:
        """Decrypt message with ratcheting"""
        peer_dh_public = encrypted_message['dh_public']
        
        # Check if we need to perform DH ratchet
        if self.dh_keypair is None or peer_dh_public != self.last_peer_public:
            # Generate new DH keypair
            self.dh_keypair = self.generate_dh_keypair()
            
            # Compute shared secret
            shared_secret = X25519.compute_shared_secret(
                self.dh_keypair['private'],
                peer_dh_public
            )
            
            # Root key ratchet
            self.root_key, self.chain_key = self.kdf_rk(
                self.root_key,
                shared_secret
            )
            
            self.last_peer_public = peer_dh_public
        
        # Chain key ratchet
        self.chain_key, message_key = self.kdf_ck(self.chain_key)
        
        # Decrypt message
        from metamui_crypto import ChaCha20Poly1305
        cipher = ChaCha20Poly1305(message_key)
        plaintext = cipher.decrypt(
            encrypted_message['ciphertext'],
            encrypted_message['nonce']
        )
        
        return plaintext
    
    def kdf_rk(self, root_key: bytes, dh_output: bytes) -> tuple:
        """Root key derivation function"""
        from metamui_crypto import HKDF, SHA256
        hkdf = HKDF(hash_function=SHA256())
        
        key_material = hkdf.derive(
            ikm=dh_output,
            salt=root_key,
            info=b"SIGNAL_ROOT_KEY",
            length=64
        )
        
        return key_material[:32], key_material[32:64]
    
    def kdf_ck(self, chain_key: bytes) -> tuple:
        """Chain key derivation function"""
        from metamui_crypto import HMAC, SHA256
        
        # Message key
        hmac1 = HMAC(chain_key, SHA256())
        message_key = hmac1.compute(b"\x01")
        
        # Next chain key
        hmac2 = HMAC(chain_key, SHA256())
        next_chain_key = hmac2.compute(b"\x02")
        
        return next_chain_key, message_key

# VPN key exchange
class X25519VPN:
    def __init__(self, server_mode: bool = False):
        self.server_mode = server_mode
        self.sessions = {}
    
    def create_session(self, peer_id: str) -> dict:
        """Create VPN session with peer"""
        # Generate session keypair
        session_private = X25519.generate_private_key()
        session_public = X25519.compute_public_key(session_private)
        
        session = {
            'peer_id': peer_id,
            'private_key': session_private,
            'public_key': session_public,
            'shared_secret': None,
            'tunnel_keys': None,
            'created_at': time.time()
        }
        
        self.sessions[peer_id] = session
        return session
    
    def establish_tunnel(self, peer_id: str, 
                        peer_public_key: bytes) -> dict:
        """Establish VPN tunnel"""
        if peer_id not in self.sessions:
            raise ValueError("No session for peer")
        
        session = self.sessions[peer_id]
        
        # Compute shared secret
        shared_secret = X25519.compute_shared_secret(
            session['private_key'],
            peer_public_key
        )
        
        session['shared_secret'] = shared_secret
        
        # Derive tunnel keys
        tunnel_keys = self.derive_tunnel_keys(shared_secret, peer_id)
        session['tunnel_keys'] = tunnel_keys
        
        return tunnel_keys
    
    def derive_tunnel_keys(self, shared_secret: bytes, peer_id: str) -> dict:
        """Derive VPN tunnel keys"""
        from metamui_crypto import HKDF, SHA256
        hkdf = HKDF(hash_function=SHA256())
        
        # Context includes peer ID and role
        role = "server" if self.server_mode else "client"
        context = f"vpn_tunnel_{role}_{peer_id}".encode()
        
        # Derive key material
        key_material = hkdf.derive(
            ikm=shared_secret,
            info=context,
            length=128  # 4 × 32-byte keys
        )
        
        return {
            'encryption_key': key_material[0:32],
            'authentication_key': key_material[32:64],
            'iv_seed': key_material[64:96],
            'rekey_material': key_material[96:128]
        }

Implementation Details

# X25519 core operations (simplified)
class X25519Core:
    def __init__(self):
        # Curve25519 parameters
        self.p = 2**255 - 19  # Field prime
        self.a24 = 121665     # (A-2)/4 where A=486662
    
    def clamp_private_key(self, private_key: bytes) -> bytes:
        """Clamp private key according to X25519 spec"""
        key = bytearray(private_key)
        key[0] &= 248   # Clear bottom 3 bits
        key[31] &= 127  # Clear top bit
        key[31] |= 64   # Set second-highest bit
        return bytes(key)
    
    def scalar_mult(self, scalar: bytes, point: bytes) -> bytes:
        """Montgomery ladder scalar multiplication"""
        # Clamp scalar
        k = self.clamp_private_key(scalar)
        
        # Convert point to field element
        u = int.from_bytes(point, 'little') % self.p
        
        # Montgomery ladder
        x1, x2, x3 = u, 1, u
        z2, z3 = 0, 1
        
        for i in range(254, -1, -1):
            bit = (int.from_bytes(k, 'little') >> i) & 1
            
            # Conditional swap
            if bit:
                x2, x3 = x3, x2
                z2, z3 = z3, z2
            
            # Montgomery ladder step
            A = (x2 + z2) % self.p
            AA = (A * A) % self.p
            B = (x2 - z2) % self.p
            BB = (B * B) % self.p
            E = (AA - BB) % self.p
            C = (x3 + z3) % self.p
            D = (x3 - z3) % self.p
            DA = (D * A) % self.p
            CB = (C * B) % self.p
            
            x3 = pow(DA + CB, 2, self.p)
            z3 = (x1 * pow(DA - CB, 2, self.p)) % self.p
            x2 = (AA * BB) % self.p
            z2 = (E * (AA + self.a24 * E)) % self.p
            
            # Conditional swap
            if bit:
                x2, x3 = x3, x2
                z2, z3 = z3, z2
        
        # Compute final result
        result = (x2 * pow(z2, self.p - 2, self.p)) % self.p
        return result.to_bytes(32, 'little')
    
    def generate_private_key(self) -> bytes:
        """Generate random private key"""
        return os.urandom(32)
    
    def compute_public_key(self, private_key: bytes) -> bytes:
        """Compute public key from private key"""
        basepoint = bytes([9] + [0] * 31)  # Generator point
        return self.scalar_mult(private_key, basepoint)
    
    def compute_shared_secret(self, private_key: bytes, 
                             public_key: bytes) -> bytes:
        """Compute shared secret"""
        return self.scalar_mult(private_key, public_key)

Security Considerations

Best Practices

  1. Key Generation
    • Use cryptographically secure random number generator
    • Properly clamp private keys according to specification
    • Generate fresh keys for each session (ephemeral keys)
    • Validate public keys before use
  2. Implementation Security
    • Use constant-time implementations
    • Protect against side-channel attacks
    • Clear private keys from memory after use
    • Validate all inputs
  3. Protocol Design
    • Use ephemeral keys for forward secrecy
    • Combine with authentication (signatures)
    • Implement proper key derivation
    • Include context in key derivation
  4. Key Management
    • Rotate keys regularly
    • Use separate keys for different purposes
    • Implement secure key storage
    • Plan for key compromise scenarios

Common Pitfalls

# DON'T: Reuse static keys without authentication
static_private = X25519.generate_private_key()
# WRONG: Reusing static keys enables impersonation attacks
shared1 = X25519.compute_shared_secret(static_private, peer1_public)
shared2 = X25519.compute_shared_secret(static_private, peer2_public)

# DO: Use ephemeral keys or combine with authentication
ephemeral_private = X25519.generate_private_key()  # Fresh for each exchange
shared = X25519.compute_shared_secret(ephemeral_private, peer_public)

# DON'T: Use shared secret directly as encryption key
shared_secret = X25519.compute_shared_secret(private_key, public_key)
# WRONG: Direct use without key derivation
encryption_key = shared_secret

# DO: Derive keys from shared secret
from metamui_crypto import HKDF, SHA256
hkdf = HKDF(hash_function=SHA256())
encryption_key = hkdf.derive(shared_secret, b"encryption", 32)

# DON'T: Skip public key validation
def unsafe_exchange(private_key, public_key):
    # WRONG: No validation of public key
    return X25519.compute_shared_secret(private_key, public_key)

# DO: Validate public keys
def safe_exchange(private_key, public_key):
    # Validate public key is not zero or other invalid values
    if public_key == b'\x00' * 32:
        raise ValueError("Invalid public key")
    
    return X25519.compute_shared_secret(private_key, public_key)

# DON'T: Ignore small subgroup attacks
# Some implementations may be vulnerable to small subgroup attacks
# Always use a well-tested, secure implementation

# DO: Use proper key clamping and validation
def secure_keygen():
    private_key = os.urandom(32)
    # Proper clamping is handled by the library
    return private_key

Performance Characteristics

Benchmarks

Operation Time (μs) Cycles (3GHz) Throughput
Key Generation 15 45,000 66,667 ops/sec
Public Key Computation 45 135,000 22,222 ops/sec
Shared Secret Computation 45 135,000 22,222 ops/sec

Performance Comparison

Algorithm Key Exchange Time Key Size Security Level
X25519 45 μs 32 bytes 128-bit
ECDH P-256 180 μs 32 bytes 128-bit
ECDH P-384 420 μs 48 bytes 192-bit
DH-2048 8 ms 256 bytes 112-bit
ML-KEM-768 600 μs 1184 bytes 192-bit (PQ)

Optimization Strategies

# Batch key generation
def generate_keypairs_batch(count: int) -> list:
    """Generate multiple keypairs efficiently"""
    keypairs = []
    
    for _ in range(count):
        private_key = X25519.generate_private_key()
        public_key = X25519.compute_public_key(private_key)
        keypairs.append({
            'private': private_key,
            'public': public_key
        })
    
    return keypairs

# Pre-computed public keys
class PrecomputedX25519:
    def __init__(self, private_key: bytes):
        self.private_key = private_key
        self.public_key = X25519.compute_public_key(private_key)
    
    def exchange(self, peer_public_key: bytes) -> bytes:
        """Fast exchange with pre-computed public key"""
        return X25519.compute_shared_secret(self.private_key, peer_public_key)

# Parallel key exchanges
from concurrent.futures import ThreadPoolExecutor

def parallel_key_exchanges(private_keys: list, public_keys: list) -> list:
    """Perform multiple key exchanges in parallel"""
    def single_exchange(args):
        private_key, public_key = args
        return X25519.compute_shared_secret(private_key, public_key)
    
    with ThreadPoolExecutor() as executor:
        exchanges = list(zip(private_keys, public_keys))
        results = list(executor.map(single_exchange, exchanges))
    
    return results

Use Cases

1. TLS 1.3 Key Exchange

class TLS13KeyExchange:
    def __init__(self):
        self.client_private = None
        self.client_public = None
        self.server_public = None
        self.shared_secret = None
    
    def client_hello(self) -> bytes:
        """Generate client key share for TLS 1.3"""
        self.client_private = X25519.generate_private_key()
        self.client_public = X25519.compute_public_key(self.client_private)
        
        return self.client_public
    
    def server_hello(self, client_public: bytes) -> tuple:
        """Process client key share and generate server response"""
        # Generate server keypair
        server_private = X25519.generate_private_key()
        server_public = X25519.compute_public_key(server_private)
        
        # Compute shared secret
        shared_secret = X25519.compute_shared_secret(
            server_private, 
            client_public
        )
        
        return server_public, shared_secret
    
    def client_finish(self, server_public: bytes) -> bytes:
        """Complete client-side key exchange"""
        self.server_public = server_public
        self.shared_secret = X25519.compute_shared_secret(
            self.client_private,
            server_public
        )
        
        return self.shared_secret
    
    def derive_traffic_keys(self, handshake_hash: bytes) -> dict:
        """Derive TLS 1.3 traffic keys"""
        from metamui_crypto import HKDF, SHA256
        hkdf = HKDF(hash_function=SHA256())
        
        # Early secret (empty for X25519-only)
        early_secret = hkdf.extract(b"", b"")
        
        # Handshake secret
        handshake_secret = hkdf.extract(self.shared_secret, early_secret)
        
        # Derive traffic secrets
        client_secret = hkdf.expand(
            handshake_secret,
            b"tls13 c hs traffic" + handshake_hash,
            32
        )
        server_secret = hkdf.expand(
            handshake_secret,
            b"tls13 s hs traffic" + handshake_hash,
            32
        )
        
        return {
            'client_handshake_secret': client_secret,
            'server_handshake_secret': server_secret
        }

2. Signal Protocol Implementation

class SignalProtocol:
    def __init__(self, identity_private: bytes):
        self.identity_private = identity_private
        self.identity_public = X25519.compute_public_key(identity_private)
        self.prekeys = {}
        self.sessions = {}
    
    def generate_prekeys(self, count: int) -> dict:
        """Generate one-time prekeys"""
        prekeys = {}
        
        for i in range(count):
            private_key = X25519.generate_private_key()
            public_key = X25519.compute_public_key(private_key)
            
            prekeys[i] = {
                'private': private_key,
                'public': public_key
            }
        
        self.prekeys.update(prekeys)
        return {i: prekey['public'] for i, prekey in prekeys.items()}
    
    def initiate_session(self, peer_identity: bytes, 
                        peer_signed_prekey: bytes,
                        peer_onetime_prekey: bytes = None) -> dict:
        """Initiate Signal session (X3DH)"""
        # Generate ephemeral key
        ephemeral_private = X25519.generate_private_key()
        ephemeral_public = X25519.compute_public_key(ephemeral_private)
        
        # Compute shared secrets
        dh1 = X25519.compute_shared_secret(
            self.identity_private, 
            peer_signed_prekey
        )
        dh2 = X25519.compute_shared_secret(
            ephemeral_private, 
            peer_identity
        )
        dh3 = X25519.compute_shared_secret(
            ephemeral_private, 
            peer_signed_prekey
        )
        
        # Optional fourth DH if one-time prekey available
        shared_secrets = [dh1, dh2, dh3]
        if peer_onetime_prekey:
            dh4 = X25519.compute_shared_secret(
                ephemeral_private,
                peer_onetime_prekey
            )
            shared_secrets.append(dh4)
        
        # Derive master secret
        master_secret = self.kdf(b''.join(shared_secrets))
        
        # Initialize double ratchet
        session = X25519DoubleRatchet(master_secret, sending=True)
        
        return {
            'session': session,
            'ephemeral_public': ephemeral_public,
            'master_secret': master_secret
        }
    
    def kdf(self, input_material: bytes) -> bytes:
        """Key derivation function for Signal"""
        from metamui_crypto import HKDF, SHA256
        hkdf = HKDF(hash_function=SHA256())
        
        return hkdf.derive(
            ikm=input_material,
            info=b"Signal_KDF_v1",
            length=32
        )

3. WireGuard VPN

class WireGuardKeyExchange:
    def __init__(self):
        self.static_private = X25519.generate_private_key()
        self.static_public = X25519.compute_public_key(self.static_private)
        self.peers = {}
    
    def add_peer(self, peer_public: bytes, preshared_key: bytes = None):
        """Add peer configuration"""
        self.peers[peer_public] = {
            'public_key': peer_public,
            'preshared_key': preshared_key,
            'sessions': {}
        }
    
    def initiate_handshake(self, peer_public: bytes) -> dict:
        """Initiate WireGuard handshake"""
        if peer_public not in self.peers:
            raise ValueError("Unknown peer")
        
        peer = self.peers[peer_public]
        
        # Generate ephemeral keypair
        ephemeral_private = X25519.generate_private_key()
        ephemeral_public = X25519.compute_public_key(ephemeral_private)
        
        # Compute shared secrets
        # DH(static_private, peer_static_public)
        dh1 = X25519.compute_shared_secret(self.static_private, peer_public)
        
        # DH(ephemeral_private, peer_static_public)
        dh2 = X25519.compute_shared_secret(ephemeral_private, peer_public)
        
        # Derive session keys
        session_keys = self.derive_session_keys(
            dh1, dh2, peer.get('preshared_key')
        )
        
        # Store session
        session_id = os.urandom(16)
        peer['sessions'][session_id] = {
            'keys': session_keys,
            'ephemeral_private': ephemeral_private,
            'created_at': time.time()
        }
        
        return {
            'session_id': session_id,
            'ephemeral_public': ephemeral_public,
            'session_keys': session_keys
        }
    
    def respond_handshake(self, initiator_ephemeral: bytes,
                         peer_public: bytes) -> dict:
        """Respond to WireGuard handshake"""
        if peer_public not in self.peers:
            raise ValueError("Unknown peer")
        
        peer = self.peers[peer_public]
        
        # Generate our ephemeral keypair
        ephemeral_private = X25519.generate_private_key()
        ephemeral_public = X25519.compute_public_key(ephemeral_private)
        
        # Compute shared secrets
        # DH(static_private, peer_static_public)
        dh1 = X25519.compute_shared_secret(self.static_private, peer_public)
        
        # DH(static_private, initiator_ephemeral_public)
        dh2 = X25519.compute_shared_secret(
            self.static_private, 
            initiator_ephemeral
        )
        
        # DH(ephemeral_private, initiator_ephemeral_public)
        dh3 = X25519.compute_shared_secret(
            ephemeral_private, 
            initiator_ephemeral
        )
        
        # Derive session keys
        session_keys = self.derive_session_keys(
            dh1, dh2, peer.get('preshared_key'), dh3
        )
        
        return {
            'ephemeral_public': ephemeral_public,
            'session_keys': session_keys
        }
    
    def derive_session_keys(self, *shared_secrets) -> dict:
        """Derive WireGuard session keys"""
        from metamui_crypto import HKDF, BLAKE2s
        
        # Combine all shared secrets
        combined = b''.join(filter(None, shared_secrets))
        
        # Use BLAKE2s for key derivation (WireGuard uses BLAKE2s)
        hkdf = HKDF(hash_function=BLAKE2s())
        
        key_material = hkdf.derive(
            ikm=combined,
            info=b"WireGuard session keys",
            length=96  # 3 × 32-byte keys
        )
        
        return {
            'sending_key': key_material[0:32],
            'receiving_key': key_material[32:64],
            'confirmation_key': key_material[64:96]
        }

Comparison with Other Key Exchange

X25519 vs Other ECDH Curves

Curve Security Performance Key Size Standardization
X25519 128-bit Very Fast 32 bytes RFC 7748
P-256 128-bit Fast 32 bytes NIST/FIPS
P-384 192-bit Moderate 48 bytes NIST/FIPS
P-521 256-bit Slow 66 bytes NIST/FIPS

X25519 vs Post-Quantum

Algorithm Security (Classical) Security (Quantum) Key Size Performance
X25519 128-bit ~0 bits 32 bytes Very Fast
ML-KEM-768 256-bit 192-bit 1184 bytes Fast
Hybrid 128-bit 192-bit 1216 bytes Moderate

When to Use X25519

# Use X25519 for current applications
if not_post_quantum_required:
    key_exchange = X25519

# Use X25519 in hybrid mode for transition
if preparing_for_post_quantum:
    classical_secret = X25519.compute_shared_secret(x25519_private, x25519_public)
    pq_secret = MLKEM768.decapsulate(mlkem_ciphertext, mlkem_private)
    combined_secret = classical_secret + pq_secret

# Use X25519 for performance-critical applications
if performance_critical:
    key_exchange = X25519  # Fastest classical option

Migration Guide

From ECDH P-256 to X25519

# Before: ECDH P-256
# from cryptography.hazmat.primitives.asymmetric import ec
# private_key = ec.generate_private_key(ec.SECP256R1())
# public_key = private_key.public_key()

# After: X25519
from metamui_crypto import X25519
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)

# Benefits:
# - Faster performance
# - Simpler implementation
# - Better side-channel resistance
# - Smaller attack surface

From DH to X25519

# Before: Traditional DH (slow, large keys)
# from cryptography.hazmat.primitives.asymmetric import dh
# parameters = dh.generate_parameters(generator=2, key_size=2048)

# After: X25519 (fast, small keys)
from metamui_crypto import X25519
# Much simpler and faster
private_key = X25519.generate_private_key()
public_key = X25519.compute_public_key(private_key)

Test Vectors

RFC 7748 Test Vectors

# Test Vector 1
alice_private = bytes.fromhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
alice_public = bytes.fromhex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")

bob_private = bytes.fromhex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
bob_public = bytes.fromhex("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")

# Verify public key computation
computed_alice_public = X25519.compute_public_key(alice_private)
assert computed_alice_public == alice_public

computed_bob_public = X25519.compute_public_key(bob_private)
assert computed_bob_public == bob_public

# Verify shared secret computation
alice_shared = X25519.compute_shared_secret(alice_private, bob_public)
bob_shared = X25519.compute_shared_secret(bob_private, alice_public)
assert alice_shared == bob_shared

expected_shared = bytes.fromhex("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
assert alice_shared == expected_shared

Wycheproof Test Vectors

# Test edge cases and invalid inputs
def test_invalid_public_keys():
    private_key = X25519.generate_private_key()
    
    # Test all-zero public key
    zero_public = b'\x00' * 32
    try:
        X25519.compute_shared_secret(private_key, zero_public)
        # Should handle gracefully or reject
    except ValueError:
        pass  # Expected for some implementations
    
    # Test all-ones public key
    ones_public = b'\xff' * 32
    try:
        X25519.compute_shared_secret(private_key, ones_public)
    except ValueError:
        pass

References