Skip to main content

Protocol Flow (PAGE WIP...)

This page details the full handshake exchange — the sequence of on-chain events, cryptographic operations, and key derivations that establish an encrypted channel. For the conceptual overview, see Handshake.

Handshake Sequence

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 a response to its handshake using the hybrid secret. This prevents on-chain observers from correlating the two events:

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 Metadata Privacy 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. The response is encrypted, so the 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 ExecutorAddress field specifies which address will send the transaction, enabling verification regardless of executor type.