Crypto Protocol Patterns Reference
Canonical message flows for common cryptographic protocols. Use these as a reference when comparing an implementation against a known spec, or as a starting skeleton when the implementation follows a named protocol.
Overview
Crypto Protocol Patterns Reference
Canonical message flows for common cryptographic protocols. Use these as a reference when comparing an implementation against a known spec, or as a starting skeleton when the implementation follows a named protocol.
TLS 1.3 Handshake (RFC 8446)
Parties: Client (C), Server (S) Round complexity: 1 RTT (0-RTT with resumption)
C → S: ClientHello(random, legacy_session_id, cipher_suites,
key_share[ecdhe], supported_versions=[TLS 1.3])
S → C: ServerHello(random, legacy_session_id, cipher_suite,
key_share[ecdhe], supported_versions=TLS 1.3)
Note: ECDHE shared secret derived by both sides
Note: HKDF chain: early_secret → handshake_secret → master_secret
S → C: {EncryptedExtensions} (under handshake key)
S → C: {Certificate} (server cert chain)
S → C: {CertificateVerify} (Sign(sk_S, transcript))
S → C: {Finished} (HMAC over transcript)
C → S: {Certificate} (if mutual auth requested)
C → S: {CertificateVerify} (Sign(sk_C, transcript))
C → S: {Finished} (HMAC over transcript)
Both: derive application traffic keys from master_secret
C ↔ S: {Application Data} (under application keys)
Code signals: Look for ClientHello, ServerHello, key_share,
supported_versions, cipher_suite, HKDF, transcript_hash, Finished.
Noise Protocol Framework (https://noiseprotocol.org)
Pattern family: NN, NK, NX, KN, KK, KX, XN, XK, XX, IX, ...
Noise_XX (most common: mutual auth, no prior knowledge)
Parties: Initiator (I), Responder (R) Round complexity: 1.5 RTT
I → R: msg_1 = e
(send ephemeral pubkey)
R → I: msg_2 = e, ee, s, es
(send ephemeral pubkey,
DH(e_R, e_I),
send static pubkey encrypted,
DH(s_R, e_I))
I → R: msg_3 = s, se
(send static pubkey encrypted,
DH(s_I, e_R))
Both: transport keys derived from chained hash state
I ↔ R: encrypted transport messages
State machine: CipherState → SymmetricState → HandshakeState.
Look for MixKey, MixHash, EncryptAndHash, DecryptAndHash,
Split (produces two CipherState objects for send/recv).
Code signals: HandshakeState, CipherState, SymmetricState,
h (handshake hash), ck (chaining key), n (nonce counter),
pattern strings like "XX", "IK".
Noise_IK (identity-hiding initiator, known responder static key)
I → R: msg_1 = e, es, s, ss
(ephemeral, DH(e_I, s_R), static (encrypted), DH(s_I, s_R))
R → I: msg_2 = e, ee, se
(ephemeral, DH(e_R, e_I), DH(e_R, s_I))
Signal Protocol
X3DH (Extended Triple Diffie-Hellman) Key Agreement
Parties: Alice (A), Bob (B), Server (S) Purpose: Establish shared secret for asynchronous messaging
Note over B: Publish to server:
IK_B (identity key),
SPK_B (signed prekey) + Sig(IK_B, SPK_B),
{OPK_B_i} (one-time prekeys)
Note over A: Fetch Bob's key bundle from server
A → S: fetch_prekey_bundle(Bob)
S → A: IK_B, SPK_B, Sig, OPK_B_1
Note over A: Verify Sig(IK_B, SPK_B)
Note over A: EK_A = generate ephemeral keypair
Note over A: DH1 = DH(IK_A, SPK_B)
DH2 = DH(EK_A, IK_B)
DH3 = DH(EK_A, SPK_B)
DH4 = DH(EK_A, OPK_B_1) [if one-time prekey available]
SK = KDF(DH1 || DH2 || DH3 || DH4)
A → B: InitialMessage(IK_A, EK_A, OPK_B_id, Enc(SK, initial_plaintext))
Note over B: Recompute DH1..DH4, derive SK
Decrypt initial message
Code signals: identity_key, signed_prekey, one_time_prekey,
ephemeral_key, X3DH, triple_dh, prekey_bundle.
Double Ratchet Algorithm
Parties: Alice, Bob (symmetric, both have SK from X3DH) Purpose: Forward-secret, break-in-recovery message encryption
Note over A,B: Initialize with SK from X3DH
A has B's ratchet public key
A → B: Header(dh_ratchet_pk_A, prev_chain_len, msg_num)
|| Enc(message_key, plaintext)
Note over B: DH ratchet step: new root_key, new chain_key
Derive message_key from chain_key
Decrypt
B → A: Header(dh_ratchet_pk_B, prev_chain_len, msg_num)
|| Enc(message_key, plaintext)
Note over A: DH ratchet step: new root_key, new chain_key
Derive message_key from chain_key
Decrypt
Code signals: root_key, chain_key, message_key, ratchet_key,
sending_chain, receiving_chain, skip_message_keys, MessageKey.
Diffie-Hellman Key Exchange (Classic / ECDH)
Parties: Alice (A), Bob (B) Round complexity: 1 RTT
Note over A: a = random scalar; g_a = g^a (or a·G for ECDH)
A → B: g_a
Note over B: b = random scalar; g_b = g^b (or b·G for ECDH)
B → A: g_b
Note over A: shared = g_b^a = g^(ab)
Note over B: shared = g_a^b = g^(ab)
Note over A,B: session_key = KDF(shared)
Authenticated DH: add signatures or MACs over the transcript to bind identities. Without authentication, this is vulnerable to MITM.
Challenge-Response Authentication
Variants: HMAC-based, signature-based, password-based (PAKE)
Signature-based
C → S: authenticate_request(identity)
S → C: challenge(nonce, session_id)
Note over C: σ = Sign(sk_C, nonce || session_id || identity)
C → S: response(σ, pk_C)
Note over S: Verify(pk_C, nonce || session_id || identity, σ)
alt verification succeeds
S → C: session_token
else verification fails
S → C: auth_failure
end
Code signals: challenge, nonce, sign, verify, response,
session_token, authenticate.
HMAC-based (shared secret)
C → S: auth_request(identity)
S → C: challenge(nonce)
Note over C: mac = HMAC(shared_key, nonce || identity)
C → S: response(mac)
Note over S: expected = HMAC(shared_key, nonce || identity)
ct_equal(mac, expected)
Shamir Secret Sharing
Parties: Dealer (D), Parties P1…Pn, Combiner (C) Setup: (t, n) threshold
Note over D: Split secret s into n shares using degree-(t-1) polynomial
f(x) = s + a_1·x + … + a_{t-1}·x^{t-1}
share_i = f(i) for i = 1..n
D → P1: Enc(pk_1, share_1)
D → P2: Enc(pk_2, share_2)
...
D → Pn: Enc(pk_n, share_n)
# (t-of-n parties agree to reconstruct)
P1 → C: share_1
P2 → C: share_2
...
Pt → C: share_t
Note over C: Lagrange interpolation on {(i, share_i)}
→ recover f(0) = s
Code signals: split, share, combine, threshold, lagrange,
polynomial, evaluate.
Commit-Reveal
Parties: Committer (C), Verifier (V) Purpose: Bind to a value without revealing it; reveal later
rect rgba(100, 149, 237, 0.15)
Note over C,V: Phase 1: Commit
Note over C: r = random nonce
commitment = H(value || r)
C → V: commitment
end
rect rgba(46, 204, 113, 0.15)
Note over C,V: Phase 2: Reveal
C → V: value, r
Note over V: Check H(value || r) == commitment
end
alt commitment matches
Note over V: value accepted
else mismatch
Note over V: ABORT — equivocation detected
end
Code signals: commit, reveal, commitment, open, binding,
hiding.
Generic N-Party MPC (Round-Based)
Parties: P1, P2, …, Pn Structure: Preprocessing + Online phases, or direct rounds
rect rgba(100, 149, 237, 0.15)
Note over P1,P3: Round 0: Input Commitment
Note over P1: share_1_j = Share(input_1, t, n) for j=1..n
P1 ->> P2: share_1_2
P1 ->> P3: share_1_3
Note over P2: share_2_j = Share(input_2, t, n)
P2 ->> P1: share_2_1
P2 ->> P3: share_2_3
Note over P3: share_3_j = Share(input_3, t, n)
P3 ->> P1: share_3_1
P3 ->> P2: share_3_2
end
rect rgba(46, 204, 113, 0.15)
Note over P1,P3: Round 1: Local Computation
Note over P1: result_share_1 = f(share_1_1, share_2_1, share_3_1)
Note over P2: result_share_2 = f(share_1_2, share_2_2, share_3_2)
Note over P3: result_share_3 = f(share_1_3, share_2_3, share_3_3)
end
rect rgba(155, 89, 182, 0.15)
Note over P1,P3: Round 2: Output Reconstruction
P1 ->> P2: result_share_1
P1 ->> P3: result_share_1
P2 ->> P1: result_share_2
P2 ->> P3: result_share_2
P3 ->> P1: result_share_3
P3 ->> P2: result_share_3
Note over P1: Combine(result_share_1, result_share_2, result_share_3) → output
Note over P2: Combine(...) → output
Note over P3: Combine(...) → output
end
Broadcast channel: In many MPC protocols, "send to all" is a broadcast.
Use par blocks or annotate with Note over P1: broadcast to all.
Code signals: round, broadcast, send_share, recv_share,
local_computation, reconstruct, output.
Threshold Signature (FROST / GG20 pattern)
Parties: Signers S1…St (threshold-of-n), Aggregator (A) Setup: n parties each hold a signing key share
rect rgba(100, 149, 237, 0.15)
Note over A: Coordinator selects t signers, distributes message m
A ->> S1: sign_request(m, participants=[S1..St])
A ->> S2: sign_request(m, participants=[S1..St])
A ->> St: sign_request(m, participants=[S1..St])
end
rect rgba(46, 204, 113, 0.15)
Note over S1..St: Round 1: Nonce Generation
Note over S1: (d_1, e_1) = random nonces; D_1=g^d_1, E_1=g^e_1
S1 ->> A: (D_1, E_1)
Note over S2: (d_2, e_2) = random nonces
S2 ->> A: (D_2, E_2)
Note over St: (d_t, e_t) = random nonces
St ->> A: (D_t, E_t)
end
rect rgba(155, 89, 182, 0.15)
Note over A: Aggregate binding factors, compute group commitment R
A ->> S1: commitment_list = [(D_i, E_i) for i in participants]
A ->> S2: commitment_list
A ->> St: commitment_list
end
rect rgba(241, 196, 15, 0.15)
Note over S1..St: Round 2: Partial Signatures
Note over S1: z_1 = d_1 + e_1·ρ_1 + λ_1·sk_1·c (partial sig)
S1 ->> A: z_1
Note over S2: z_2 = d_2 + e_2·ρ_2 + λ_2·sk_2·c
S2 ->> A: z_2
Note over St: z_t = ...
St ->> A: z_t
end
Note over A: σ = (R, z_1 + z_2 + ... + z_t) — final signature
Note over A: Verify(group_pk, m, σ)
Code signals: partial_sign, aggregate, nonce, commitment,
binding_factor, lagrange_coefficient, group_key, threshold_sign.
Reading Hints: Identifying Protocol Patterns in Code
| Code pattern | Protocol signal |
|---|---|
send_msg / recv_msg loop with round counter | Round-based MPC |
Two CipherState after split() | Noise protocol completion |
transcript accumulated via hash | TLS-style transcript MAC |
chain_key / root_key updated per message | Double Ratchet |
verify_prekey_signature | X3DH signal protocol |
lagrange / interpolate | Shamir / threshold |
partial_sig aggregated after broadcast | Threshold signature |
| `commitment = H(value | |
challenge sent, Sign(sk, challenge) response | Challenge-response |
g^a sent, g^b received, g^(ab) computed | Classic DH |