Skip to main content

Handshake

Verbeth uses a hybrid key exchange combining X25519 (classical) and ML-KEM-768 (post-quantum) to establish encrypted channels.

Overview

Unlike Signal's X3DH which uses prekeys stored on a server, Verbeth uses ephemeral-only key exchange:

X3DH (Signal)Verbeth
Prekey server requiredNo server infrastructure
Offline initiationInitiator must wait for response
Multiple DH operationsEphemeral + KEM hybrid

The tradeoff: Verbeth requires the responder to come online before the channel is established, but eliminates server trust entirely.

Hybrid Key Exchange

Verbeth combines two key exchange mechanisms:

X25519 (Classical)

  • Well-understood elliptic curve Diffie-Hellman
  • 128-bit security against classical computers
  • Vulnerable to quantum computers running Shor's algorithm

ML-KEM-768 (Post-Quantum)

  • NIST-standardized lattice-based KEM (formerly Kyber)
  • 192-bit security against quantum computers
  • Larger keys (1184 bytes public, 1088 bytes ciphertext)

Why Hybrid?

Defense-in-depth:

  • If X25519 is broken (quantum), ML-KEM protects
  • If ML-KEM is broken (cryptanalysis), X25519 protects
  • Security holds if either primitive remains secure

This protects against "Harvest Now, Decrypt Later" (HNDL) attacks where adversaries record encrypted traffic today hoping to decrypt with future quantum computers.

Protocol Flow

Alice (Initiator)                              Bob (Responder)
───────────────── ───────────────

1. Generate ephemeral X25519 keypair (a, A)
2. Generate ML-KEM-768 keypair (kemPk, kemSk)
3. Create identity proof

─────── Handshake Event ───────►
│ recipientHash: H(bob_addr) │
│ ephemeralPubKey: A │
│ kemPublicKey: kemPk │
│ identityProof: {...} │
└───────────────────────────────┘

4. Generate ephemeral keypair (r, R)
5. Compute X25519: x_ss = ECDH(r, A)
6. Encapsulate KEM: (ct, kem_ss) = Encap(kemPk)
7. Compute hybrid tag:
tag = HKDF(x_ss || kem_ss, "verbeth:hsr-hybrid:v1")
8. Encrypt response to A

◄───── HandshakeResponse ──────
│ inResponseTo: tag │
│ responderEphemeralR: R │
│ ciphertext: Enc(A, response) │
└───────────────────────────────┘

9. Decrypt response, extract R, ct
10. Compute X25519: x_ss = ECDH(a, R)
11. Decapsulate KEM: kem_ss = Decap(ct, kemSk)
12. Verify tag matches
13. Derive root key from hybrid secret

═══════ Channel Established ═══════

Hybrid Tag Computation

The inResponseTo tag links response to handshake using the hybrid secret:

function computeHybridTag(
ecdhSecret: Uint8Array, // X25519 shared secret
kemSecret: Uint8Array // ML-KEM shared secret
): `0x${string}` {
const okm = hkdf(sha256, kemSecret, ecdhSecret, "verbeth:hsr-hybrid:v1", 32);
return keccak256(okm);
}

Observers cannot link HandshakeResponse to its Handshake without the shared secrets. See Security Model for detailed analysis against classical and quantum adversaries.

Root Key Derivation

The initial root key for the Double Ratchet combines both secrets:

function hybridInitialSecret(
x25519Secret: Uint8Array,
kemSecret: Uint8Array
): Uint8Array {
const combined = concat([x25519Secret, kemSecret]);
return hkdf(sha256, combined, zeros(32), "VerbethHybrid", 32);
}

This root key is post-quantum secure. All subsequent ratchet keys derive from it, propagating PQ security through the entire conversation.

On-Chain Events

Handshake Event

event Handshake(
bytes32 indexed recipientHash,
address indexed sender,
bytes ephemeralPubKey, // 32 bytes X25519
bytes kemPublicKey, // 1184 bytes ML-KEM-768
bytes plaintextPayload // Identity proof + note
);

HandshakeResponse Event

event HandshakeResponse(
bytes32 indexed inResponseTo, // Hybrid tag
address indexed responder,
bytes responderEphemeralR, // 32 bytes X25519
bytes ciphertext // Encrypted response (includes KEM ciphertext)
);

Gas Considerations

ComponentSizeNotes
X25519 ephemeral32 bytesMinimal
ML-KEM public key1184 bytesDominates handshake cost
ML-KEM ciphertext1088 bytesIn encrypted response
Identity proof~500 bytesSignature + message

Handshake initiation costs more due to the KEM public key. Response is encrypted, so KEM ciphertext is hidden in the blob.

Executor Abstraction

Handshake transactions can be sent via:

  • EOA: Direct wallet transaction
  • Safe Module: Session key authorized by Safe

The identity proof's ExecutorSafeAddress field specifies which address will send the transaction, enabling verification regardless of executor type.

Security Properties

PropertyGuarantee
Forward secrecyEphemeral keys provide FS from message 0
HNDL resistanceML-KEM protects root key against quantum
Identity bindingProof ties keys to Ethereum address
Quantum unlinkabilityTag derivation hides handshake-response link