crypto-protocol-diagram
Extracts protocol message flow from source code, RFCs, academic papers, pseudocode, informal prose, ProVerif (.pv), or Tamarin (.spthy) models and generates Mermaid sequenceDiagrams with cryptographic annotations. Use when diagramming a crypto protocol, visualizing a handshake or
Install
npx skills add https://github.com/trailofbits/skills/tree/main/plugins/trailmark/skills/crypto-protocol-diagram
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
git clone https://github.com/trailofbits/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Crypto Protocol Diagram
Produces a Mermaid sequenceDiagram (written to file) and an ASCII sequence
diagram (printed inline) from either:
- Source code implementing a cryptographic protocol, or
- A specification — RFC, academic paper, pseudocode, informal prose,
ProVerif (
.pv), or Tamarin (.spthy) model.
Tools used: Read, Write, Grep, Glob, Bash, WebFetch (for URL specs).
Unlike the diagramming-code skill (which visualizes code structure), this skill
extracts protocol semantics: who sends what to whom, what cryptographic
transformations occur at each step, and what protocol phases exist.
For call graphs, class hierarchies, or module dependency maps, use the
diagramming-code skill instead.
When to Use
- User asks to diagram, visualize, or extract a cryptographic protocol
- Input is source code implementing a handshake, key exchange, or multi-party protocol
- Input is an RFC, academic paper, pseudocode, or formal model (ProVerif/Tamarin)
- User names a specific protocol (TLS, Noise, Signal, X3DH, FROST)
When NOT to Use
- User wants a call graph, class hierarchy, or module dependency map — use
diagramming-code - User wants to formally verify a protocol — use
mermaid-to-proverif(after generating the diagram) - Input has no cryptographic protocol semantics (no parties, no message exchange)
Rationalizations to Reject
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "The protocol is simple, I can diagram from memory" | Memory-based diagrams miss steps and invert arrows | Read the source or spec systematically |
| "I'll skip the spec path since code exists" | Code may diverge from the spec — both paths catch different bugs | When both exist, run spec workflow first, then annotate code divergences |
| "Crypto annotations are optional decoration" | Without crypto annotations, the diagram is just a message flow — useless for security review | Annotate every cryptographic operation |
| "The abort path is obvious, no need for alt blocks" | Implicit abort handling hides missing error checks | Show every abort/error path with alt blocks |
| "I don't need to check the examples first" | The examples define the expected output quality bar | Study the relevant example before working on unfamiliar input |
| "ProVerif/Tamarin models are code, not specs" | Formal models are specifications — they describe intended behavior, not implementation | Use the spec workflow (S1–S5) for .pv and .spthy files |
Workflow
Protocol Diagram Progress:
- [ ] Step 0: Determine input type (code / spec / both)
- [ ] Step 1 (code) or S1–S5 (spec): Extract protocol structure
- [ ] Step 6: Generate sequenceDiagram
- [ ] Step 7: Verify and deliver
Step 0: Determine Input Type
Before doing anything else, classify the input:
| Signal | Input type |
|---|---|
Source file extensions (.py, .rs, .go, .ts, .js, .cpp, .c) |
Code |
| Function/class definitions, import statements | Code |
RFC-style section headers (§, Section X.Y, MUST/SHALL keywords) |
Spec |
Algorithm/Protocol/Figure labels, mathematical notation |
Spec |
ProVerif file (.pv) with process, let, in/out |
Spec |
Tamarin file (.spthy) with rule, --[...]-> |
Spec |
| Plain prose or numbered steps describing a protocol | Spec |
| Both source files and a spec document | Both (annotate divergences with ⚠️) |
- Code only → skip to Step 1 below
- Spec only → skip to Spec Workflow (S1–S5) below
- Both → run Spec Workflow first, then use the code-reading steps to verify
the implementation against the spec diagram and annotate any divergences with
⚠️ - Ambiguous → ask the user: "Is this a source code file, a specification document, or both?"
Step 1: Locate Protocol Entry Points
Grep for function names, type names, and comments that reveal the protocol:
# Find handshake, session, round, phase entry points
rg -l "handshake|session_init|round[_0-9]|setup|keygen|send_msg|recv_msg" {targetDir}
# Find crypto primitives in use
rg "sign|verify|encrypt|decrypt|dh|ecdh|kdf|hkdf|hmac|hash|commit|reveal|share" \
{targetDir} --type-add 'src:*.{py,rs,go,ts,js,cpp,c}' -t src -l
Start reading from the highest-level orchestration function — the one that calls into handshake phases or the main protocol loop.
Step 2: Identify Parties and Roles
Extract participant names from:
- Struct/class names:
Client,Server,Initiator,Responder,Prover,Verifier,Dealer,Party,Coordinator - Function parameter names that carry state for a role
- Comments declaring the protocol role
- Test fixtures that set up two-party or N-party scenarios
Map these to Mermaid participant declarations. Use short, readable aliases:
participant I as Initiator
participant R as Responder
Step 3: Trace Message Flow
Follow state transitions and network sends/receives. Look for patterns like:
| Pattern | Meaning |
|---|---|
send(msg) / recv() |
Direct message exchange |
serialize + transmit |
Structured message sent |
| Return value passed to other party's function | Logical message (in-process) |
round1_output → round2_input |
Round-based MPC step |
Struct fields named ephemeral_key, ciphertext, mac, tag |
Message contents |
For in-process protocol implementations (where both parties run in the same process), treat function call boundaries as logical message sends when they represent what would be a network boundary in deployment.
Step 4: Annotate Cryptographic Operations
At each protocol step, identify and label:
| Operation | Diagram annotation |
|---|---|
| Key generation | Note over A: keygen(params) → pk, sk |
| DH / ECDH | Note over A,B: DH(sk_A, pk_B) |
| KDF / HKDF | Note over A: HKDF(ikm, salt, info) |
| Signing | Note over A: Sign(sk, msg) → σ |
| Verification | Note over B: Verify(pk, msg, σ) |
| Encryption | Note over A: Enc(key, plaintext) → ct |
| Decryption | Note over B: Dec(key, ct) → plaintext |
| Commitment | Note over A: Commit(value, rand) → C |
| Hash | Note over A: H(data) → digest |
| Secret sharing | Note over D: Share(secret, t, n) → {s_i} |
| Threshold combine | Note over C: Combine({s_i}) → secret |
Keep annotations concise — use mathematical shorthand, not code.
Step 5: Identify Protocol Phases
Group message steps into named phases using rect or Note blocks:
Common phases to detect:
- Setup / Key Generation: party key creation, trusted setup, parameter gen
- Handshake / Init: ephemeral key exchange, nonce exchange, version negotiation
- Authentication: identity proof, certificate exchange, signature verification
- Key Derivation: session key derivation from shared secrets
- Data Transfer / Main Protocol: encrypted application data exchange
- Finalization / Teardown: session close, MAC verification, abort handling
Detect abort/error paths and show them with alt blocks.
Spec Workflow (S1–S5)
Use this path when the input is a specification document rather than source code. After completing S1–S5, continue with Step 6 (Generate sequenceDiagram) and Step 7 (Verify and deliver) from the code workflow above.
Step S1: Ingest the Spec
Obtain the full spec text:
- File path provided → read with the Read tool
- URL provided → fetch with WebFetch
- Pasted inline → work directly from conversation context
Then identify the spec format and read references/spec-parsing-patterns.md for format-specific extraction guidance:
| Format | Signals |
|---|---|
| RFC | RFC XXXX, MUST/SHALL/SHOULD, ABNF grammars, section-numbered prose |
| Academic paper / pseudocode | Algorithm X, Protocol X, Figure X, numbered steps, ←/→ in math mode |
| Informal prose | Numbered lists, "A sends B ...", plain English descriptions |
ProVerif (.pv) |
process, let, in(ch, x), out(ch, msg), ! (replication) |
Tamarin (.spthy) |
rule, --[ ]->, Fr(~x), !Pk(A, pk), In(m), Out(m) |
If the spec references a known named protocol (TLS, Noise, Signal, X3DH, Double Ratchet, FROST), also read references/protocol-patterns.md to use its canonical flow as a skeleton and fill in spec-specific details.
Step S2: Extract Parties and Roles
Identify all protocol participants. Look for:
- Named roles in prose or pseudocode:
Alice,Bob,Client,Server,Initiator,Responder,Prover,Verifier,Dealer,Party_i,Coordinator,Signer - Section headers: "Parties", "Roles", "Participants", "Setup", "Notation"
- ProVerif: process names at top level (
let ClientProc(...),let ServerProc(...)) - Tamarin: rule names and fact arguments (e.g.
!Pk($A, pk)—$Ais a party)
Map each role to a Mermaid participant declaration. Use short IDs with
descriptive aliases (see naming conventions in
references/mermaid-sequence-syntax.md).
Step S3: Extract Message Flow
Trace what each party sends to whom and in what order. Extraction patterns by format:
RFC / informal prose:
- Arrow notation:
A → B: msg,A -> B - Sentence patterns: "A sends B ...", "B responds with ...", "A transmits ...", "upon receiving X, B sends Y"
- Numbered steps: extract in order, inferring sender/receiver from context
Pseudocode:
- Function signatures with explicit
sender/receiverparameters send(party, msg)/receive(party)calls- Return values passed as inputs to the other party's function in the next step
ProVerif (.pv):
out(ch, msg)— send on channelchin(ch, x)— receive on channelch, bind tox- Match
out/inpairs on the same channel to identify message flows !(replication) signals a role that handles multiple sessions
Tamarin (.spthy):
In(m)premise — receive messagemOut(m)conclusion — send messagem- Rule name and ordering of rules reveal protocol rounds
Fr(~x)— fresh random value generated by a party--[ Label ]->facts — security annotations, not messages
Preserve the ordering and round structure. Group concurrent sends (broadcast)
using par blocks in the final diagram.
Step S4: Extract Cryptographic Operations
For each protocol step, identify the cryptographic operations performed and which party performs them:
| Spec notation | Operation | Diagram annotation |
|---|---|---|
keygen(), Gen(1^λ) |
Key generation | Note over A: keygen() → pk, sk |
DH(a, B), g^ab |
DH / ECDH | Note over A,B: DH(sk_A, pk_B) |
KDF(ikm), HKDF(...) |
Key derivation | Note over A: HKDF(ikm, salt, info) → k |
Sign(sk, m), σ ← Sign |
Signing | Note over A: Sign(sk, msg) → σ |
Verify(pk, m, σ) |
Verification | Note over B: Verify(pk, msg, σ) |
Enc(k, m), {m}_k |
Encryption | Note over A: Enc(k, plaintext) → ct |
Dec(k, c) |
Decryption | Note over B: Dec(k, ct) → plaintext |
H(m), hash(m) |
Hash | Note over A: H(data) → digest |
Commit(v, r), com |
Commitment | Note over A: Commit(value, rand) → C |
ProVerif senc(m, k) |
Symmetric encryption | Note over A: Enc(k, m) → ct |
ProVerif pk(sk) |
Public key derivation | Note over A: pk = pk(sk) |
ProVerif sign(m, sk) |
Signing | Note over A: Sign(sk, m) → σ |
Identify security conditions and abort paths:
- Prose: "if verification fails, abort", "only if ...", "reject if ..."
- Pseudocode:
assert,require,if ... abort - ProVerif:
if m = expected then ... else 0 - Tamarin: contradicting facts or restriction lemmas
These become alt blocks in the final diagram.
Step S5: Flag Spec Ambiguities
Before moving to Step 6, check for gaps:
- Unclear message ordering: infer from round structure or section order;
annotate with
⚠️ ordering inferred from spec structure - Implied parties: if a party's role is implied but unnamed, give it a descriptive name and note the inference
- Missing steps: if the spec omits a step that the canonical pattern for
this protocol requires, annotate:
⚠️ spec omits [step] — canonical protocol requires it - Underspecified crypto: if the spec says "encrypt" without specifying
the scheme, annotate:
⚠️ encryption scheme not specified - ProVerif/Tamarin: private channels (
cdeclared withnew cor as a private free name) represent out-of-band channels — note them
Step 6: Generate sequenceDiagram
Produce Mermaid syntax following the rules in references/mermaid-sequence-syntax.md.
Completeness over brevity. Show every distinct message type. Omit repeated
loop iterations (use loop blocks instead), but never omit a distinct protocol
step.
Correctness over aesthetics. The diagram must match what the code actually does. If the code diverges from a known spec, annotate the divergence:
Note over A,B: ⚠️ spec requires MAC here — implementation omits it
Step 7: Verify and Deliver
Before delivering:
- Every participant declared actually sends or receives at least one message
- Arrows point in the correct direction (sender → receiver)
- Cryptographic operations are on the correct party (the one computing them)
- If protocol phases are used, no arrows appear outside a phase block
-
altblocks cover known abort/error paths - Diagram renders without syntax errors (check references/mermaid-sequence-syntax.md for common pitfalls)
- If spec divergence found, annotated with
⚠️
Write the diagram to a file. Choose a filename derived from the protocol
name, e.g. noise-xx-handshake.md or x3dh-key-agreement.md. Write a
Markdown file with this structure:
# <Protocol Name> Sequence Diagram
\`\`\`mermaid
sequenceDiagram
...
\`\`\`
## Protocol Summary
- **Parties:** ...
- **Round complexity:** ...
- **Key primitives:** ...
- **Authentication:** ...
- **Forward secrecy:** ...
- **Notable:** [spec deviations or security observations, or "none"]
After writing the file, print an ASCII sequence diagram inline in the response, followed by the Protocol Summary. State the output filename so the user knows where to find the Mermaid source.
Follow all drawing conventions in references/ascii-sequence-diagram.md, including the inline output format.
Decision Tree
── Input is a spec document (not code)?
│ └─ Step S1: identify format, read references/spec-parsing-patterns.md
│
── Input is source code (not a spec)?
│ └─ Step 1: grep for handshake/round/send/recv entry points
│
── Both spec and code provided?
│ └─ Run Spec Workflow (S1–S5) first to build canonical diagram,
│ then read code and annotate divergences with ⚠️
│
── Spec is a known protocol (TLS, Noise, Signal, X3DH, FROST)?
│ └─ Read references/protocol-patterns.md and use canonical flow as skeleton
│
── Spec is ProVerif (.pv) or Tamarin (.spthy)?
│ └─ Read references/spec-parsing-patterns.md → Formal Models section
│
── Spec message ordering is ambiguous?
│ └─ Infer from round/section structure, annotate with ⚠️
│
── Can't identify parties from spec?
│ └─ Check "Parties"/"Notation" sections; for ProVerif read process names;
│ for Tamarin read rule names and fact arguments
│
── Don't know which code files implement the protocol?
│ └─ Step 1: grep for handshake/round/send/recv entry points
│
── Can't identify parties from struct names?
│ └─ Read test files — test setup reveals roles
│
── Protocol runs in-process (no network calls)?
│ └─ Treat function argument passing at role boundaries as messages
│
── MPC / threshold protocol with N parties?
│ └─ Read references/protocol-patterns.md → MPC section
│
── Mermaid syntax error?
│ └─ Read references/mermaid-sequence-syntax.md → Common Pitfalls
│
└─ ASCII drawing conventions?
└─ Read references/ascii-sequence-diagram.md
Examples
Code path — examples/simple-handshake/:
protocol.py— two-party authenticated key exchange (X25519 DH + Ed25519 signing + HKDF + ChaCha20-Poly1305)expected-output.md— exact ASCII diagram and Mermaid file the skill should produce for that protocol
Spec path (ProVerif) — examples/simple-proverif/:
model.pv— HMAC challenge-response authentication modeled in ProVerifexpected-output.md— step-by-step extraction walkthrough (parties, message flow, crypto ops) and the exact ASCII diagram and Mermaid file the skill should produce
Study the relevant example before working on an unfamiliar input.
Supporting Documentation
- references/spec-parsing-patterns.md — Extraction rules for RFC, academic paper/pseudocode, informal prose, ProVerif, and Tamarin input formats; read during Step S1
- references/mermaid-sequence-syntax.md — Participant syntax, arrow types, activations, grouping blocks, escaping rules, and common rendering pitfalls
- references/protocol-patterns.md — Canonical message flows for TLS 1.3, Noise, X3DH, Double Ratchet, Shamir secret sharing, commit-reveal, and generic MPC rounds; use as a reference when comparing implementation against spec
- references/ascii-sequence-diagram.md — Column layout, arrow conventions, self-loops, phase labels, and inline output format for the ASCII diagram
Files (skills)
-
agents
-
openai.yaml 244 B
interface: display_name: "Crypto Protocol Diagrams" short_description: "Diagram cryptographic protocols and message flows" icon_small: "assets/trail-of-bits-mark.svg" icon_large: "assets/trail-of-bits-mark.svg" brand_color: "#D83A34"
-
-
assets
-
trail-of-bits-mark.svg 3 KB · in bundle
-
-
examples
-
simple-handshake
-
expected-output.md 4.8 KB
# Expected Skill Output This file shows what the `crypto-protocol-diagram` skill should produce when run on `protocol.py`. It covers both the inline response (ASCII diagram + Protocol Summary) and the written Mermaid file. --- ## Inline Response ``` Protocol: Simple Authenticated Key Exchange Output: simple-authenticated-key-exchange.md Initiator Responder | | | -- Setup (out-of-band) -- | +--. | | | keygen() → sk_i, pk_i | |<-' | | +--. | keygen() → sk_r, pk_r | | |<-' | | | -- Handshake -- | +--. | | | keygen() → ek_i, epk_i | | | sig_i = Sign(sk_i, "msg1"||epk_i) | |<-' | | | +---------------------------------->| epk_i || sig_i | | | +--. | Verify(pk_i, sig_i) | | | keygen() → ek_r, epk_r | | | sig_r = Sign(sk_r, "msg2"||transcript) | | dh = DH(ek_r, epk_i) | | | sk = HKDF(dh, transcript) |<-' | | |<- - - - - - - - - - - - - - - - - + epk_r || sig_r | | +--. | | | Verify(pk_r, sig_r) | | | dh = DH(ek_i, epk_r) | | | sk = HKDF(dh, transcript) | |<-' | | | | -- Application Data -- | | | +---------------------------------->| ChaCha20Poly1305(sk, nonce, plaintext) | | [on signature verification failure] | abort(INVALID_SIGNATURE) | |<- - - - - - - - - - - - - - - - - + ## Protocol Summary - **Parties:** Initiator, Responder - **Round complexity:** 1 round trip (2 messages) - **Key primitives:** X25519 (ephemeral DH), Ed25519 (signing), HKDF-SHA256, ChaCha20-Poly1305 - **Authentication:** mutual — both parties sign their ephemeral keys with long-term Ed25519 keys - **Forward secrecy:** yes — session key derived from ephemeral DH; compromise of static keys does not expose past sessions - **Notable:** static keys are assumed registered out-of-band (no in-protocol key transmission); transcript binding covers only ephemeral public keys, not party identities — an implementation that doesn't bind pk_i and pk_r into the transcript is vulnerable to identity mislabeling ``` --- ## Written Mermaid File (`simple-authenticated-key-exchange.md`) ```markdown # Simple Authenticated Key Exchange Sequence Diagram \`\`\`mermaid sequenceDiagram participant I as Initiator participant R as Responder rect rgba(100, 149, 237, 0.15) Note over I,R: Setup (out-of-band key registration) Note over I: keygen() → sk_i, pk_i Note over R: keygen() → sk_r, pk_r end rect rgba(46, 204, 113, 0.15) Note over I,R: Handshake Note over I: keygen() → ek_i, epk_i Note over I: sig_i = Sign(sk_i, "msg1" || epk_i) I->>R: epk_i || sig_i Note over R: Verify(pk_i, "msg1" || epk_i, sig_i) Note over R: keygen() → ek_r, epk_r Note over R: transcript = epk_i || epk_r Note over R: sig_r = Sign(sk_r, "msg2" || transcript) Note over R: dh = DH(ek_r, epk_i) Note over R: sk_r = HKDF(dh || transcript, info="session-key-v1") R-->>I: epk_r || sig_r Note over I: Verify(pk_r, "msg2" || transcript, sig_r) Note over I: dh = DH(ek_i, epk_r) Note over I: sk_i = HKDF(dh || transcript, info="session-key-v1") end rect rgba(241, 196, 15, 0.15) Note over I,R: Application Data I->>R: ChaCha20Poly1305(sk, nonce, plaintext) end alt signature verification failure R-->>I: abort(INVALID_SIGNATURE) end \`\`\` ## Protocol Summary - **Parties:** Initiator, Responder - **Round complexity:** 1 round trip (2 messages) - **Key primitives:** X25519 (ephemeral DH), Ed25519 (signing), HKDF-SHA256, ChaCha20-Poly1305 - **Authentication:** mutual — both parties sign their ephemeral keys with long-term Ed25519 keys - **Forward secrecy:** yes — session key derived from ephemeral DH; compromise of static keys does not expose past sessions - **Notable:** static keys are assumed registered out-of-band (no in-protocol key transmission); transcript binding covers only ephemeral public keys, not party identities — an implementation that doesn't bind pk_i and pk_r into the transcript is vulnerable to identity mislabeling ``` -
protocol.py 5.7 KB
# /// script # requires-python = ">=3.12" # dependencies = ["cryptography>=42.0"] # /// """ Simple authenticated key exchange protocol (2 messages, 1 round trip). Both parties hold long-term Ed25519 signing keys registered out-of-band. The protocol establishes a forward-secret session key via ephemeral X25519 DH, with mutual authentication via signatures over the transcript. Message flow: 1. Initiator → Responder : epk_i, sig_i 2. Responder → Initiator : epk_r, sig_r Both derive: session_key = HKDF(DH(ek_i, epk_r), transcript) """ import os from cryptography.hazmat.primitives import hashes, serialization # type: ignore from cryptography.hazmat.primitives.asymmetric.ed25519 import ( # type: ignore Ed25519PrivateKey, Ed25519PublicKey, ) from cryptography.hazmat.primitives.asymmetric.x25519 import ( # type: ignore X25519PrivateKey, X25519PublicKey, ) from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 # type: ignore from cryptography.hazmat.primitives.kdf.hkdf import HKDF # type: ignore # --------------------------------------------------------------------------- # Key types # --------------------------------------------------------------------------- class StaticKeypair: """Long-term Ed25519 signing keypair (registered out-of-band).""" def __init__(self) -> None: self._sk = Ed25519PrivateKey.generate() self.pk = self._sk.public_key() def sign(self, message: bytes) -> bytes: return self._sk.sign(message) def public_bytes(self) -> bytes: return self.pk.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) class EphemeralKeypair: """Single-use X25519 DH keypair.""" def __init__(self) -> None: self._sk = X25519PrivateKey.generate() self.pk = self._sk.public_key() def exchange(self, peer_epk: X25519PublicKey) -> bytes: return self._sk.exchange(peer_epk) def public_bytes(self) -> bytes: return self.pk.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) # --------------------------------------------------------------------------- # Protocol messages # --------------------------------------------------------------------------- def _derive_session_key(dh_output: bytes, transcript: bytes) -> bytes: """HKDF-SHA256 over DH output, bound to the full transcript.""" return HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=b"session-key-v1", ).derive(dh_output + transcript) def initiator_send_msg1( static: StaticKeypair, ) -> tuple[EphemeralKeypair, bytes]: """ Initiator builds message 1. Returns (ephemeral_keypair, wire_bytes). wire_bytes = epk_i (32 bytes) || sig_i (64 bytes) """ ek = EphemeralKeypair() epk_bytes = ek.public_bytes() sig = static.sign(b"msg1:" + epk_bytes) return ek, epk_bytes + sig def responder_recv_msg1_send_msg2( msg1: bytes, initiator_pk: Ed25519PublicKey, static: StaticKeypair, ) -> tuple[bytes, bytes, bytes]: """ Responder processes message 1 and builds message 2. Returns (session_key, msg2_wire_bytes, transcript). msg2_wire_bytes = epk_r (32 bytes) || sig_r (64 bytes) """ epk_i_bytes, sig_i = msg1[:32], msg1[32:] # Verify initiator's ephemeral key is authentic. initiator_pk.verify(sig_i, b"msg1:" + epk_i_bytes) epk_i = X25519PublicKey.from_public_bytes(epk_i_bytes) ek_r = EphemeralKeypair() epk_r_bytes = ek_r.public_bytes() transcript = epk_i_bytes + epk_r_bytes sig_r = static.sign(b"msg2:" + transcript) dh_output = ek_r.exchange(epk_i) session_key = _derive_session_key(dh_output, transcript) return session_key, epk_r_bytes + sig_r, transcript def initiator_recv_msg2( msg2: bytes, ek_i: EphemeralKeypair, responder_pk: Ed25519PublicKey, epk_i_bytes: bytes, ) -> bytes: """ Initiator processes message 2 and derives the session key. Returns session_key. """ epk_r_bytes, sig_r = msg2[:32], msg2[32:] transcript = epk_i_bytes + epk_r_bytes # Verify responder's contribution is authentic. responder_pk.verify(sig_r, b"msg2:" + transcript) epk_r = X25519PublicKey.from_public_bytes(epk_r_bytes) dh_output = ek_i.exchange(epk_r) return _derive_session_key(dh_output, transcript) # --------------------------------------------------------------------------- # Application data (post-handshake) # --------------------------------------------------------------------------- def encrypt(session_key: bytes, plaintext: bytes, nonce: bytes) -> bytes: return ChaCha20Poly1305(session_key).encrypt(nonce, plaintext, None) def decrypt(session_key: bytes, ciphertext: bytes, nonce: bytes) -> bytes: return ChaCha20Poly1305(session_key).decrypt(nonce, ciphertext, None) # --------------------------------------------------------------------------- # Demo # --------------------------------------------------------------------------- if __name__ == "__main__": # Out-of-band key registration initiator_static = StaticKeypair() responder_static = StaticKeypair() # --- Handshake --- ek_i, msg1 = initiator_send_msg1(initiator_static) epk_i_bytes = msg1[:32] sk_r, msg2, _ = responder_recv_msg1_send_msg2( msg1, initiator_static.pk, responder_static, ) sk_i = initiator_recv_msg2( msg2, ek_i, responder_static.pk, epk_i_bytes, ) if sk_i != sk_r: raise RuntimeError("session keys must match") print("Handshake complete. Session keys match.") # --- Application data --- nonce = os.urandom(12) ct = encrypt(sk_i, b"hello, responder", nonce) pt = decrypt(sk_r, ct, nonce) print(f"Decrypted: {pt}")
-
-
simple-proverif
-
expected-output.md 4 KB
# Expected Output: simple-proverif This is the exact ASCII diagram and Mermaid file the `crypto-protocol-diagram` skill should produce when given `model.pv` as input. --- ## Step 0 classification Input: `model.pv` — ProVerif file (`.pv` extension, contains `process`, `let`, `in`/`out`). Input type: **Spec (ProVerif)**. ## Step S2: Parties extracted - `AliceProc` → participant `A as Alice` - `BobProc` → participant `B as Bob` ## Step S3: Message flow (channel `c`) | # | `out` | `in` | Message | |---|-------|------|---------| | 1 | `AliceProc` | `BobProc` | `identity` | | 2 | `BobProc` | `AliceProc` | `nonce` (fresh) | | 3 | `AliceProc` | `BobProc` | `mac = HMAC(k, nonce \|\| identity)` | | 4 | `BobProc` | — | `true` (auth success, conditional) | ## Step S4: Crypto operations - `AliceProc`: `hmac(key, concat(nonce, identity))` → `Note over A: mac = HMAC(k, nonce \|\| id)` - `BobProc`: `new nonce` → `Note over B: nonce ← fresh()` - `BobProc`: `checkhmac(mac, key, ...)` → `alt` block (verify succeeds / fails) ## Step S5: Ambiguities - Dolev-Yao model applies: annotate with `⚠️` - Auth success sends `true` over `c` — in a real protocol this would be implicit; annotate as optional output --- ## Inline ASCII Diagram ``` Protocol: HMAC Challenge-Response Authentication (ProVerif model) Output: hmac-challenge-response.md Alice Bob | | | -- Authentication | -- | | +--------------------->| identity | | | +--. | | | nonce ← fresh() | |<-' | | | nonce | |<- - - - - - - - - - -+ | | +--. | | | mac = HMAC(k, | | | nonce || id) | |<-' | | | +--------------------->| mac | | | +--. | | | checkHMAC(mac, k, | | | nonce || identity) | |<-' | | | [on verify success] | | +----. | | | auth_ok | |<---' | | | [on verify failure] | | | (abort) ⚠️ ProVerif note: all messages transit adversary network (Dolev-Yao model) ``` ## Protocol Summary - **Parties:** Alice (initiator), Bob (responder/verifier) - **Round complexity:** 1.5 RTT (3 messages) - **Key primitives:** HMAC with shared symmetric key; fresh nonce - **Authentication:** Unilateral (Alice authenticates to Bob only) - **Forward secrecy:** No (static shared key `k`; compromise reveals all sessions) - **Notable:** `query attacker(k)` verifies the shared key remains secret under Dolev-Yao. No mutual authentication — Bob does not prove identity to Alice. --- ## Mermaid File: hmac-challenge-response.md ````markdown # HMAC Challenge-Response Authentication Sequence Diagram ```mermaid sequenceDiagram participant A as Alice participant B as Bob Note over A,B: ⚠️ ProVerif Dolev-Yao model — all messages transit adversary network rect rgba(241, 196, 15, 0.15) Note over A,B: Authentication A->>B: identity Note over B: nonce ← fresh() B-->>A: nonce Note over A: mac = HMAC(k, nonce || identity) A->>B: mac Note over B: checkHMAC(mac, k, nonce || identity) alt verification succeeds Note over B: auth_ok else verification fails Note over B: abort end end ``` ## Protocol Summary - **Parties:** Alice (initiator), Bob (responder) - **Round complexity:** 1.5 RTT (3 messages) - **Key primitives:** HMAC with shared symmetric key; fresh nonce - **Authentication:** Unilateral (Alice → Bob only) - **Forward secrecy:** No - **Notable:** No mutual authentication. `query attacker(k)` checks key secrecy. ```` -
model.pv 1.8 KB · in bundle
-
-
-
references
-
ascii-sequence-diagram.md 2.3 KB
# ASCII Sequence Diagram Reference Rules for drawing ASCII sequence diagrams inline in responses. ## Column Layout Lay out participants as column headers, each above a vertical `|` lifeline. Space columns ~28–32 characters apart so labels fit without wrapping: ``` Initiator Responder | | | | ``` For three or more participants, extend the layout rightward: ``` Client Server CA | | | | | | ``` ## Arrow Conventions | Element | Syntax | Use for | |---------|--------|---------| | Request (→) | `+------>` | message send | | Reply (←) | `<- - - -+` | response / reply | | Lost / async | `+------x` | dropped message | - Sending end: `+` - Receiving end: `>` (rightward) or `<` (leftward) - Solid lines `---` for sends; dashed `- - -` for responses/replies - Label goes **on the arrow line**, positioned between the two lifelines ``` | | +------------------------>| epk_I | | | epk_R, Enc(k, cert_R) | |<- - - - - - - - - - - - + | | ``` ## Self-Loops (Local Computation) For operations that happen at a single party without sending a message: ``` | | +--. | | | Sign(sk, transcript) | |<-' | | | ``` ## Phase Labels Group related steps under a phase label: ``` | -- Key Exchange -- | | | +------------------------>| epk_I | | ``` ## Abort / Error Paths Place abort paths after the main flow, separated by a blank line: ``` | [on auth failure] | | abort(AUTH_FAILED) | |<- - - - - - - - - - - - + ``` ## Width and Labels - Keep lines under ~60 characters wide - If a label is too long, abbreviate it (e.g. `Enc(k,id||σ)`) and add a legend below the diagram explaining the abbreviations ## Inline Output Format ``` Protocol: <Name> Output: <filename> <Participant1> <Participant2> | | ... ## Protocol Summary - **Parties:** ... - **Round complexity:** ... - **Key primitives:** ... - **Authentication:** ... - **Forward secrecy:** ... - **Notable:** none ``` -
mermaid-sequence-syntax.md 7.5 KB
# Mermaid Sequence Diagram Syntax Reference ## Basic Structure ``` sequenceDiagram participant A as Alice participant B as Bob A->>B: Hello B-->>A: Hi ``` Every sequence diagram starts with `sequenceDiagram` on its own line (no indentation). Participants are declared with `participant` before they appear in messages; if undeclared, Mermaid auto-creates them in order of first appearance — but always declare explicitly for crypto protocols to control ordering. --- ## Participant Declarations ``` participant A # ID = display name participant A as Alice # short ID, long display name actor A as Alice # person icon instead of box ``` **Ordering:** Participants are rendered left-to-right in declaration order. Arrange them so that the dominant message direction flows left-to-right: Initiator → Responder, Client → Server, Prover → Verifier. **Naming conventions for crypto:** | Role | Suggested ID | Display name | |------|-------------|--------------| | Initiator | `I` | Initiator | | Responder | `R` | Responder | | Client | `C` | Client | | Server | `S` | Server | | Trusted third party | `TTP` | TTP | | Prover | `P` | Prover | | Verifier | `V` | Verifier | | Dealer | `D` | Dealer | | Party i | `P1`, `P2`, … | Party 1, Party 2, … | | Certificate Authority | `CA` | CA | --- ## Arrow Types | Syntax | Arrow | Use for | |--------|-------|---------| | `A->>B: msg` | solid, open arrowhead | synchronous message send | | `A-->>B: msg` | dashed, open arrowhead | reply / response | | `A->B: msg` | solid, no arrowhead | passive / internal note | | `A-->B: msg` | dashed, no arrowhead | async / passive reply | | `A-xB: msg` | solid, X head | message lost / dropped | | `A--xB: msg` | dashed, X head | async lost message | | `A-)B: msg` | solid, async arrowhead | fire-and-forget | | `A--)B: msg` | dashed, async arrowhead | async reply | **For crypto protocols**, use `->>` for all protocol messages and `-->>` for responses/replies. Reserve `-->` and `--x` for error/abort paths inside `alt` blocks. --- ## Notes Attach explanatory text to one or two participants: ``` Note over A: keygen() → pk, sk Note over A,B: shared_secret = DH(sk_A, pk_B) Note right of A: internal computation Note left of B: validation step ``` Use `Note over` for cryptographic operations that happen at a party without a message being sent. Use `Note over A,B` to annotate a shared computation or the meaning of a message between them. **Keep notes short.** Use mathematical shorthand: - `HKDF(IKM=DH_out, salt, info) → k` - `Sign(sk, transcript) → σ` - `Verify(pk, transcript, σ)` - `Enc(k, plaintext, AD) → ct || tag` - `H(nonce || msg) → c` --- ## Activation Bars Show when a participant is "active" (processing): ``` activate A A->>B: request B-->>A: response deactivate A ``` Or inline with `+` / `-`: ``` A->>+B: request B-->>-A: response ``` Use activations sparingly in protocol diagrams — they add noise unless the protocol has clear request/response pairing that benefits from showing duration. --- ## Grouping Blocks ### `rect` — colored background region ``` rect rgba(100, 149, 237, 0.15) Note over I,R: Phase 1: Key Exchange I->>R: ephemeral_pk R-->>I: ephemeral_pk end ``` Use `rect` with a distinct color per protocol phase. Suggested palette: | Phase | Color | |-------|-------| | Setup / Key Generation | `rgba(100, 149, 237, 0.15)` — blue | | Handshake | `rgba(46, 204, 113, 0.15)` — green | | Authentication | `rgba(241, 196, 15, 0.15)` — yellow | | Key Derivation | `rgba(155, 89, 182, 0.15)` — purple | | Data Transfer | `rgba(230, 196, 15, 0.12)` — gold | | Error / Abort | `rgba(231, 76, 60, 0.15)` — red | ### `loop` — repeated block ``` loop for each data chunk A->>B: Enc(k, chunk_i) → ct_i B-->>A: ack end ``` Use `loop` when a message exchange repeats N times. Avoid unrolling loops that would add 5+ identical arrow pairs. ### `alt` — conditional / branching ``` alt verification succeeds B-->>A: session_key else verification fails B-->>A: abort end ``` Use `alt` for protocol branches: success path vs. abort/error paths. Always show the error path — it's where security properties break down. ### `opt` — optional block ``` opt if session resumption requested A->>B: session_ticket end ``` Use `opt` for optional protocol extensions (e.g. session resumption, renegotiation). ### `par` — parallel execution ``` par A->>B: msg_1 and A->>C: msg_2 end ``` Use `par` for genuinely concurrent sends (broadcast in MPC, simultaneous sends to multiple parties). ### `critical` — mutual exclusion region ``` critical database access A->>DB: update end ``` Rarely needed in protocol diagrams; use for atomic operations if relevant. --- ## Message Labels Labels appear after the colon on arrow lines: ``` A->>B: ClientHello(random, cipher_suites, extensions) B-->>A: ServerHello(random, chosen_cipher, session_id) ``` **Formatting guidance:** - Use `FieldName(value)` or `Type{field: value, ...}` for structured messages - Use `||` for concatenation: `nonce || ciphertext || tag` - Use `→` to show output of a computation in a note: `KDF(shared) → k_enc, k_mac` - Keep labels under ~60 characters; move detail into `Note over` annotations - Subscript with underscore: `pk_e` (ephemeral pubkey), `k_1`, `σ_A` --- ## Common Pitfalls ### Colon in message label breaks parsing **Broken:** ``` A->>B: key: value ``` **Fixed:** Use HTML entity or restructure: ``` A->>B: key=value A->>B: "key: value" ``` ### Special characters in participant IDs Participant IDs must be alphanumeric + underscore. Avoid spaces, hyphens, brackets. **Broken:** ``` participant Party-1 ``` **Fixed:** ``` participant P1 as Party 1 ``` ### `end` keyword clash If a message label contains the word `end`, it may terminate a block early. Quote it or rephrase. ### Unclosed blocks Every `rect`, `loop`, `alt`, `opt`, `par` must have a matching `end`. Missing `end` causes the entire diagram to fail silently. ### Long label lines Labels longer than ~80 characters may overflow in some renderers. Break across multiple notes rather than one very long label. ### `Note over A,B` with wrong participant order The two participants in `Note over A,B` must be in left-to-right declaration order, not reversed. `Note over B,A` when A is declared before B will fail in some renderers. --- ## Complete Example ```mermaid sequenceDiagram participant I as Initiator participant R as Responder rect rgba(100, 149, 237, 0.15) Note over I,R: Phase 1: Key Exchange Note over I: keygen() → ek_I, epk_I I->>R: epk_I Note over R: keygen() → ek_R, epk_R R-->>I: epk_R Note over I,R: dh_out = DH(ek_I, epk_R) = DH(ek_R, epk_I) Note over I,R: k_send, k_recv = HKDF(dh_out, salt, "handshake") end rect rgba(241, 196, 15, 0.15) Note over I,R: Phase 2: Authentication Note over I: σ_I = Sign(sk_I, transcript) I->>R: Enc(k_send, identity_I || σ_I) Note over R: Verify(pk_I, transcript, σ_I) Note over R: σ_R = Sign(sk_R, transcript) R-->>I: Enc(k_recv, identity_R || σ_R) Note over I: Verify(pk_R, transcript, σ_R) end rect rgba(46, 204, 113, 0.15) Note over I,R: Phase 3: Data Transfer Note over I,R: k_app = HKDF(dh_out, transcript_hash, "application") loop application data I->>R: Enc(k_app, data_i, nonce_i) R-->>I: ack_i end end alt authentication failure R-->>I: abort(AUTHENTICATION_FAILED) end ``` -
protocol-patterns.md 10.9 KB
# 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 || nonce)` followed by `open` | Commit-reveal | | `challenge` sent, `Sign(sk, challenge)` response | Challenge-response | | `g^a` sent, `g^b` received, `g^(ab)` computed | Classic DH | -
spec-parsing-patterns.md 11.6 KB
# Spec Parsing Patterns Reference Extraction rules for turning protocol specifications into sequence diagram content. Covers four spec formats. Read the section matching your input. --- ## RFC Format RFCs follow a predictable structure. Use it to locate the right content fast. ### Locating the protocol description 1. **Find the handshake section**: search for "handshake", "key exchange", "authentication", "message flow", or "overview" in section titles 2. **Find message definitions**: sections titled "Messages", "Record Layer", "Handshake Protocol", or numbered `X.Y` subsections for each message type 3. **Find ASCII diagrams**: many RFCs embed sequence diagrams in code blocks using `->` or `<-` arrows — use these as a starting point for message ordering. If an ASCII diagram and the normative prose conflict, the prose takes precedence — RFC ASCII diagrams are illustrative, not normative. ### Extracting parties - Parties are usually named in the introduction or in a "Notation" / "Overview" section: look for "client", "server", "initiator", "responder" - Role names in RFCs are typically lowercase (`client`, `server`) — capitalise them in the diagram ### Extracting message flow Arrow patterns in RFC prose and ASCII diagrams: ``` Client Server | | |----------ClientHello------------------------->| |<---------ServerHello + {EncryptedExtensions}--| ``` Also watch for: - "The client sends a X message containing ..." - "Upon receiving X, the server MUST ..." - "The handshake proceeds as follows:" followed by a numbered list `MUST`/`SHALL` clauses describe required steps; `MAY`/`SHOULD` describe optional ones (use `opt` blocks in the diagram). ### Extracting crypto operations RFCs typically define crypto in a "Cryptographic Computations" or "Key Schedule" section. Look for: - `HKDF-Extract`, `HKDF-Expand`, `Derive-Secret` — key derivation - `Sign(sk, transcript)`, `Verify(pk, transcript, sig)` — signatures - Encryption notation: `{...}` braces around a message mean it is encrypted (TLS convention); note which key is used in the surrounding prose - Transcript hash: running hash over all handshake messages — annotate as `H(handshake_transcript)` at the point it is finalised ### ABNF grammars ABNF defines message structure, not flow. Use it to populate arrow labels: ``` ClientHello = ProtocolVersion random [SessionID] CipherSuites Extensions ``` → Arrow label: `ClientHello(version, random, session_id, cipher_suites, extensions)` --- ## Academic Paper / Pseudocode Papers vary widely, but protocol descriptions follow common conventions. ### Locating the protocol description - Look for boxes or figures labelled "Protocol X", "Figure X: Protocol name", "Algorithm X" - Section titles: "Protocol Description", "Construction", "Our Protocol", "The Scheme", "Formal Description" - Some papers give an informal description first, then a formal pseudocode box — prefer the pseudocode box for message flow accuracy ### Extracting parties - Header row of a protocol figure: `Alice | Bob`, `Client | Server`, `P | V` - Pseudocode function names: `Client.KeyGen()`, `Server.Respond()`, `Prover.Commit()`, `Verifier.Challenge()` - Subscripts in mathematical notation: `pk_A`, `sk_B` — `A` and `B` are parties ### Extracting message flow **Two-column layouts** (most common): ``` Alice Bob --- --- Compute x ← f(...) Send x ──────────────► Receive x Compute y ← g(x, ...) ◄────────────── Send y Receive y ``` Read left-to-right for Alice's column, right-to-left for Bob's. Arrows cross the column boundary to indicate message sends. **Numbered step lists**: ``` 1. A generates (pk, sk) ← KeyGen(1^λ) 2. A sends pk to B 3. B computes c ← Enc(pk, m) and sends c to A 4. A decrypts m' ← Dec(sk, c) ``` Map step numbers to rounds. A step that says "X sends Y to Z" is a message arrow. A step that says "X computes ..." is a `Note over X` annotation. **Pseudocode with explicit send/receive**: ``` function Round1_Alice(sk_A, pk_B): ek, epk = DH.KeyGen() msg = epk send(Bob, msg) return ek function Round1_Bob(pk_A): epk_A = recv(Alice) ... ``` Treat `send(Party, msg)` as a message arrow, `recv(Party)` as the receiving end of the previous arrow. ### Mathematical notation quick-reference | Notation | Meaning | |----------|---------| | `←` or `:=` | assignment / output of a computation | | `←$` or `←_R` | sample uniformly at random | | `→` or `\leftarrow` between parties | message send | | `\{m\}_k` or `Enc_k(m)` | encryption of `m` under key `k` | | `[m]_sk` or `Sign_{sk}(m)` | signature on `m` with key `sk` | | `H(m)` | hash | | `\pi` or `Π` | protocol | | `\lambda` | security parameter | | `\bot` | reject / abort | --- ## Informal Prose Informal descriptions vary the most. Apply these heuristics systematically. ### Locating the description - Section headings: "Protocol Overview", "How It Works", "Flow", "Steps" - Numbered or bulleted lists of protocol steps - Diagrams embedded as images (describe what you can infer; note you cannot read image content) ### Extracting parties - Look for the first paragraph that names the participants: "Alice and Bob", "the client and the server", "the initiator and responder" - Use the first two prominent nouns that interact: if the text says "the user authenticates to the service", parties are `User` and `Service` ### Extracting message flow — sentence patterns Arrow syntax in the table below maps to Mermaid (`->>` solid, `-->>` dashed). | Pattern | Arrow | |---------|-------| | "A sends [msg] to B" | `A->>B: msg` | | "B receives [msg] from A" | `A->>B: msg` (same arrow, B's perspective) | | "A transmits / delivers / forwards [msg]" | `A->>B: msg` (infer B from context) | | "B responds with [msg]" | `B-->>A: msg` | | "A and B exchange [msg]" | use two arrows if asymmetric, or `Note over A,B` if symmetric | | "A computes / derives / generates [value]" | `Note over A: compute value` | | "if [condition] then [action]" | `alt condition` block | | "optionally, A sends [msg]" | `opt description` block | ### Handling vague crypto If the prose says "encrypts" without specifying the scheme: - Use `Enc(k, plaintext) → ct` in the note - Add `⚠️ encryption scheme not specified in spec` If the prose says "signs" without specifying the algorithm: - Use `Sign(sk, msg) → σ` - Add `⚠️ signature algorithm not specified` --- ## Formal Verification Models ### ProVerif (`.pv`) ProVerif models processes, not a narrative sequence. Map process structure to sequence diagram steps. **Key constructs:** ```proverif (* Channel declaration *) free c: channel. (* public channel *) free s: channel [private]. (* private / secure channel *) (* Free names — constants known to attacker if not private *) free pk_A: bitstring. (* Process definition *) let ClientProc(sk: bitstring, pk_S: bitstring) = new nonce: bitstring; (* fresh random — Note over Client: nonce ← rand *) out(c, nonce); (* send on channel c → arrow to Server *) in(c, resp: bitstring); (* receive on channel c → arrow from Server *) let (m1, sig) = resp in if verify(m1, sig, pk_S) then (* conditional — alt block *) ... else 0. (* abort path *) (* Main process — composition of roles *) process ( !ClientProc(sk_A, pk_S) | !ServerProc(sk_S, pk_A) ) ``` **Extraction rules:** | Construct | Diagram element | |-----------|----------------| | `let ProcName(params) =` | Defines a role; `ProcName` → participant | | `new x: t` | `Note over Party: x ← fresh()` | | `out(ch, msg)` | Arrow from this process to the process that `in`s on same `ch` | | `in(ch, x)` | Receiving end of the matching `out` | | `if cond then P else Q` | `alt cond` / `else` block | | `let (a, b) = msg` | Destructuring — local computation, `Note over Party` | | `!P` | Replication — this role handles multiple sessions; use `loop` annotation | | `senc(m, k)` / `sdec(c, k)` | Symmetric enc/dec | | `aenc(m, pk)` / `adec(c, sk)` | Asymmetric enc/dec | | `sign(m, sk)` / `verify(m, sig, pk)` | Sign / verify | | `hash(m)` | Hash | | `pk(sk)` | Public key derivation — `Note over Party: pk = pk(sk)` | **Channel matching:** Pair each `out(ch, msg)` with the `in(ch, x)` in another process on the same channel. The sending process owns the arrow's tail; the receiving process owns the arrow's head. **Private channels** (`[private]` or declared with `new c`): represent out-of-band communication — annotate: `Note over A,B: via private channel`. **`phase N` construct:** ProVerif's `phase N` keyword sequences protocol steps that cannot execute concurrently. Steps in phase N execute only after all phase N-1 processes have terminated. If a model uses `phase`, use the phase numbers to order the diagram: steps in phase 0 come before steps in phase 1, and so on. Annotate phase boundaries: `Note over A,B: --- phase N begins ---`. **Queries** (`query attacker(x)`, `query event(...)`) are security properties, not protocol steps — omit from the diagram but mention in the Protocol Summary. ### Tamarin (`.spthy`) **Important — Dolev-Yao attacker model:** Tamarin does not model direct peer-to-peer channels. Every `Out(m)` delivers to the attacker network, and every `In(m)` can be satisfied by any message the attacker knows (including forwarded or replayed messages). For the diagram, treat `Out(m)` in one rule and `In(m)` in another as a logical A→B message, but always add this note on the diagram: ``` Note over A,B: ⚠️ Tamarin uses Dolev-Yao model — all messages transit adversary network ``` Tamarin models rules that fire when their premises are satisfied. Map rule ordering to protocol steps. **Key constructs:** ```tamarin rule Register_pk: [ Fr(~sk) ] (* fresh secret key generated *) --[ Register($A, pk(~sk)) ]-> (* security annotation — not a message *) [ !Ltk($A, ~sk) (* persistent fact — stored state *) , !Pk($A, pk(~sk)) (* persistent fact — public key *) , Out(pk(~sk)) ] (* send public key to network *) rule Client_Send: [ Fr(~n) (* fresh nonce *) , !Pk($S, pk_S) ] (* lookup server's public key *) --[ Send($C, $S, ~n) ]-> [ St_Client_1($C, $S, ~n) (* client state after round 1 *) , Out(aenc(~n, pk_S)) ] (* send encrypted nonce *) rule Server_Recv: [ In(aenc(n, pk(~sk))) (* receive encrypted nonce *) , !Ltk($S, ~sk) ] (* lookup server's secret key *) --[ Recv($S, n) ]-> [ Out(n) ] (* echo decrypted nonce *) ``` **Extraction rules:** | Construct | Diagram element | |-----------|----------------| | `Fr(~x)` in premise | `Note over Party: x ← fresh()` | | `In(m)` in premise | Receive arrow — find matching `Out(m)` rule for sender | | `Out(m)` in conclusion | Send arrow to network / other party | | `!Fact($A, ...)` | Persistent state lookup — not a message | | `St_Role_N(...)` | State fact — marks which round the party is in | | `--[ Label ]->` | Security event annotation — omit from diagram | | `$A` (public name) | Party / principal | | `~x` (fresh name) | Freshly generated secret | | `#i` (timepoint) | Ordering hint — earlier timepoint = earlier in diagram | **Rule ordering:** Reconstruct order by following state facts: `St_Role_N` consumed by a rule → `St_Role_{N+1}` produced → next rule for that role.
-
-
SKILL.md 18.4 KB
--- name: crypto-protocol-diagram description: "Extracts protocol message flow from source code, RFCs, academic papers, pseudocode, informal prose, ProVerif (.pv), or Tamarin (.spthy) models and generates Mermaid sequenceDiagrams with cryptographic annotations. Use when diagramming a crypto protocol, visualizing a handshake or key exchange flow, extracting message flow from a spec or RFC, diagramming a ProVerif or Tamarin model, or drawing sequence diagrams for TLS, Noise, Signal, X3DH, Double Ratchet, FROST, DH, or ECDH protocols." --- # Crypto Protocol Diagram Produces a Mermaid `sequenceDiagram` (written to file) and an ASCII sequence diagram (printed inline) from either: - **Source code** implementing a cryptographic protocol, or - **A specification** — RFC, academic paper, pseudocode, informal prose, ProVerif (`.pv`), or Tamarin (`.spthy`) model. **Tools used:** Read, Write, Grep, Glob, Bash, WebFetch (for URL specs). Unlike the `diagramming-code` skill (which visualizes code structure), this skill extracts **protocol semantics**: who sends what to whom, what cryptographic transformations occur at each step, and what protocol phases exist. For call graphs, class hierarchies, or module dependency maps, use the `diagramming-code` skill instead. ## When to Use - User asks to diagram, visualize, or extract a cryptographic protocol - Input is source code implementing a handshake, key exchange, or multi-party protocol - Input is an RFC, academic paper, pseudocode, or formal model (ProVerif/Tamarin) - User names a specific protocol (TLS, Noise, Signal, X3DH, FROST) ## When NOT to Use - User wants a call graph, class hierarchy, or module dependency map — use `diagramming-code` - User wants to formally verify a protocol — use `mermaid-to-proverif` (after generating the diagram) - Input has no cryptographic protocol semantics (no parties, no message exchange) ## Rationalizations to Reject | Rationalization | Why It's Wrong | Required Action | |-----------------|----------------|-----------------| | "The protocol is simple, I can diagram from memory" | Memory-based diagrams miss steps and invert arrows | Read the source or spec systematically | | "I'll skip the spec path since code exists" | Code may diverge from the spec — both paths catch different bugs | When both exist, run spec workflow first, then annotate code divergences | | "Crypto annotations are optional decoration" | Without crypto annotations, the diagram is just a message flow — useless for security review | Annotate every cryptographic operation | | "The abort path is obvious, no need for alt blocks" | Implicit abort handling hides missing error checks | Show every abort/error path with `alt` blocks | | "I don't need to check the examples first" | The examples define the expected output quality bar | Study the relevant example before working on unfamiliar input | | "ProVerif/Tamarin models are code, not specs" | Formal models are specifications — they describe intended behavior, not implementation | Use the spec workflow (S1–S5) for `.pv` and `.spthy` files | --- ## Workflow ``` Protocol Diagram Progress: - [ ] Step 0: Determine input type (code / spec / both) - [ ] Step 1 (code) or S1–S5 (spec): Extract protocol structure - [ ] Step 6: Generate sequenceDiagram - [ ] Step 7: Verify and deliver ``` --- ### Step 0: Determine Input Type Before doing anything else, classify the input: | Signal | Input type | |--------|-----------| | Source file extensions (`.py`, `.rs`, `.go`, `.ts`, `.js`, `.cpp`, `.c`) | **Code** | | Function/class definitions, import statements | **Code** | | RFC-style section headers (`§`, `Section X.Y`, `MUST`/`SHALL` keywords) | **Spec** | | `Algorithm`/`Protocol`/`Figure` labels, mathematical notation | **Spec** | | ProVerif file (`.pv`) with `process`, `let`, `in`/`out` | **Spec** | | Tamarin file (`.spthy`) with `rule`, `--[...]->` | **Spec** | | Plain prose or numbered steps describing a protocol | **Spec** | | Both source files and a spec document | **Both** (annotate divergences with `⚠️`) | - **Code only** → skip to Step 1 below - **Spec only** → skip to Spec Workflow (S1–S5) below - **Both** → run Spec Workflow first, then use the code-reading steps to verify the implementation against the spec diagram and annotate any divergences with `⚠️` - **Ambiguous** → ask the user: "Is this a source code file, a specification document, or both?" --- ### Step 1: Locate Protocol Entry Points Grep for function names, type names, and comments that reveal the protocol: ```bash # Find handshake, session, round, phase entry points rg -l "handshake|session_init|round[_0-9]|setup|keygen|send_msg|recv_msg" {targetDir} # Find crypto primitives in use rg "sign|verify|encrypt|decrypt|dh|ecdh|kdf|hkdf|hmac|hash|commit|reveal|share" \ {targetDir} --type-add 'src:*.{py,rs,go,ts,js,cpp,c}' -t src -l ``` Start reading from the highest-level orchestration function — the one that calls into handshake phases or the main protocol loop. ### Step 2: Identify Parties and Roles Extract participant names from: - Struct/class names: `Client`, `Server`, `Initiator`, `Responder`, `Prover`, `Verifier`, `Dealer`, `Party`, `Coordinator` - Function parameter names that carry state for a role - Comments declaring the protocol role - Test fixtures that set up two-party or N-party scenarios Map these to Mermaid `participant` declarations. Use short, readable aliases: ``` participant I as Initiator participant R as Responder ``` ### Step 3: Trace Message Flow Follow state transitions and network sends/receives. Look for patterns like: | Pattern | Meaning | |---------|---------| | `send(msg)` / `recv()` | Direct message exchange | | `serialize` + `transmit` | Structured message sent | | Return value passed to other party's function | Logical message (in-process) | | `round1_output` → `round2_input` | Round-based MPC step | | Struct fields named `ephemeral_key`, `ciphertext`, `mac`, `tag` | Message contents | For **in-process** protocol implementations (where both parties run in the same process), treat function call boundaries as logical message sends when they represent what would be a network boundary in deployment. ### Step 4: Annotate Cryptographic Operations At each protocol step, identify and label: | Operation | Diagram annotation | |-----------|-------------------| | Key generation | `Note over A: keygen(params) → pk, sk` | | DH / ECDH | `Note over A,B: DH(sk_A, pk_B)` | | KDF / HKDF | `Note over A: HKDF(ikm, salt, info)` | | Signing | `Note over A: Sign(sk, msg) → σ` | | Verification | `Note over B: Verify(pk, msg, σ)` | | Encryption | `Note over A: Enc(key, plaintext) → ct` | | Decryption | `Note over B: Dec(key, ct) → plaintext` | | Commitment | `Note over A: Commit(value, rand) → C` | | Hash | `Note over A: H(data) → digest` | | Secret sharing | `Note over D: Share(secret, t, n) → {s_i}` | | Threshold combine | `Note over C: Combine({s_i}) → secret` | Keep annotations concise — use mathematical shorthand, not code. ### Step 5: Identify Protocol Phases Group message steps into named phases using `rect` or `Note` blocks: Common phases to detect: - **Setup / Key Generation**: party key creation, trusted setup, parameter gen - **Handshake / Init**: ephemeral key exchange, nonce exchange, version negotiation - **Authentication**: identity proof, certificate exchange, signature verification - **Key Derivation**: session key derivation from shared secrets - **Data Transfer / Main Protocol**: encrypted application data exchange - **Finalization / Teardown**: session close, MAC verification, abort handling Detect abort/error paths and show them with `alt` blocks. --- ## Spec Workflow (S1–S5) Use this path when the input is a specification document rather than source code. After completing S1–S5, continue with Step 6 (Generate sequenceDiagram) and Step 7 (Verify and deliver) from the code workflow above. ### Step S1: Ingest the Spec Obtain the full spec text: - **File path provided** → read with the Read tool - **URL provided** → fetch with WebFetch - **Pasted inline** → work directly from conversation context Then identify the spec format and read [references/spec-parsing-patterns.md](references/spec-parsing-patterns.md) for format-specific extraction guidance: | Format | Signals | |--------|---------| | RFC | `RFC XXXX`, `MUST`/`SHALL`/`SHOULD`, ABNF grammars, section-numbered prose | | Academic paper / pseudocode | `Algorithm X`, `Protocol X`, `Figure X`, numbered steps, `←`/`→` in math mode | | Informal prose | Numbered lists, "A sends B ...", plain English descriptions | | ProVerif (`.pv`) | `process`, `let`, `in(ch, x)`, `out(ch, msg)`, `!` (replication) | | Tamarin (`.spthy`) | `rule`, `--[ ]->`, `Fr(~x)`, `!Pk(A, pk)`, `In(m)`, `Out(m)` | If the spec references a known named protocol (TLS, Noise, Signal, X3DH, Double Ratchet, FROST), also read [references/protocol-patterns.md](references/protocol-patterns.md) to use its canonical flow as a skeleton and fill in spec-specific details. ### Step S2: Extract Parties and Roles Identify all protocol participants. Look for: - **Named roles** in prose or pseudocode: `Alice`, `Bob`, `Client`, `Server`, `Initiator`, `Responder`, `Prover`, `Verifier`, `Dealer`, `Party_i`, `Coordinator`, `Signer` - **Section headers**: "Parties", "Roles", "Participants", "Setup", "Notation" - **ProVerif**: process names at top level (`let ClientProc(...)`, `let ServerProc(...)`) - **Tamarin**: rule names and fact arguments (e.g. `!Pk($A, pk)` — `$A` is a party) Map each role to a Mermaid `participant` declaration. Use short IDs with descriptive aliases (see naming conventions in [references/mermaid-sequence-syntax.md](references/mermaid-sequence-syntax.md)). ### Step S3: Extract Message Flow Trace what each party sends to whom and in what order. Extraction patterns by format: **RFC / informal prose:** - Arrow notation: `A → B: msg`, `A -> B` - Sentence patterns: "A sends B ...", "B responds with ...", "A transmits ...", "upon receiving X, B sends Y" - Numbered steps: extract in order, inferring sender/receiver from context **Pseudocode:** - Function signatures with explicit `sender`/`receiver` parameters - `send(party, msg)` / `receive(party)` calls - Return values passed as inputs to the other party's function in the next step **ProVerif (`.pv`):** - `out(ch, msg)` — send on channel `ch` - `in(ch, x)` — receive on channel `ch`, bind to `x` - Match `out`/`in` pairs on the same channel to identify message flows - `!` (replication) signals a role that handles multiple sessions **Tamarin (`.spthy`):** - `In(m)` premise — receive message `m` - `Out(m)` conclusion — send message `m` - Rule name and ordering of rules reveal protocol rounds - `Fr(~x)` — fresh random value generated by a party - `--[ Label ]->` facts — security annotations, not messages Preserve the ordering and round structure. Group concurrent sends (broadcast) using `par` blocks in the final diagram. ### Step S4: Extract Cryptographic Operations For each protocol step, identify the cryptographic operations performed and which party performs them: | Spec notation | Operation | Diagram annotation | |---------------|-----------|-------------------| | `keygen()`, `Gen(1^λ)` | Key generation | `Note over A: keygen() → pk, sk` | | `DH(a, B)`, `g^ab` | DH / ECDH | `Note over A,B: DH(sk_A, pk_B)` | | `KDF(ikm)`, `HKDF(...)` | Key derivation | `Note over A: HKDF(ikm, salt, info) → k` | | `Sign(sk, m)`, `σ ← Sign` | Signing | `Note over A: Sign(sk, msg) → σ` | | `Verify(pk, m, σ)` | Verification | `Note over B: Verify(pk, msg, σ)` | | `Enc(k, m)`, `{m}_k` | Encryption | `Note over A: Enc(k, plaintext) → ct` | | `Dec(k, c)` | Decryption | `Note over B: Dec(k, ct) → plaintext` | | `H(m)`, `hash(m)` | Hash | `Note over A: H(data) → digest` | | `Commit(v, r)`, `com` | Commitment | `Note over A: Commit(value, rand) → C` | | ProVerif `senc(m, k)` | Symmetric encryption | `Note over A: Enc(k, m) → ct` | | ProVerif `pk(sk)` | Public key derivation | `Note over A: pk = pk(sk)` | | ProVerif `sign(m, sk)` | Signing | `Note over A: Sign(sk, m) → σ` | Identify security conditions and abort paths: - Prose: "if verification fails, abort", "only if ...", "reject if ..." - Pseudocode: `assert`, `require`, `if ... abort` - ProVerif: `if m = expected then ... else 0` - Tamarin: contradicting facts or restriction lemmas These become `alt` blocks in the final diagram. ### Step S5: Flag Spec Ambiguities Before moving to Step 6, check for gaps: - **Unclear message ordering**: infer from round structure or section order; annotate with `⚠️ ordering inferred from spec structure` - **Implied parties**: if a party's role is implied but unnamed, give it a descriptive name and note the inference - **Missing steps**: if the spec omits a step that the canonical pattern for this protocol requires, annotate: `⚠️ spec omits [step] — canonical protocol requires it` - **Underspecified crypto**: if the spec says "encrypt" without specifying the scheme, annotate: `⚠️ encryption scheme not specified` - **ProVerif/Tamarin**: private channels (`c` declared with `new c` or as a private free name) represent out-of-band channels — note them --- <!-- Both code path (Steps 1–5) and spec path (Steps S1–S5) continue here --> ### Step 6: Generate sequenceDiagram Produce Mermaid syntax following the rules in [references/mermaid-sequence-syntax.md](references/mermaid-sequence-syntax.md). **Completeness over brevity.** Show every distinct message type. Omit repeated loop iterations (use `loop` blocks instead), but never omit a distinct protocol step. **Correctness over aesthetics.** The diagram must match what the code actually does. If the code diverges from a known spec, annotate the divergence: ``` Note over A,B: ⚠️ spec requires MAC here — implementation omits it ``` ### Step 7: Verify and Deliver Before delivering: - [ ] Every participant declared actually sends or receives at least one message - [ ] Arrows point in the correct direction (sender → receiver) - [ ] Cryptographic operations are on the correct party (the one computing them) - [ ] If protocol phases are used, no arrows appear outside a phase block - [ ] `alt` blocks cover known abort/error paths - [ ] Diagram renders without syntax errors (check [references/mermaid-sequence-syntax.md](references/mermaid-sequence-syntax.md) for common pitfalls) - [ ] If spec divergence found, annotated with `⚠️` **Write the diagram to a file.** Choose a filename derived from the protocol name, e.g. `noise-xx-handshake.md` or `x3dh-key-agreement.md`. Write a Markdown file with this structure: ```markdown # <Protocol Name> Sequence Diagram \`\`\`mermaid sequenceDiagram ... \`\`\` ## Protocol Summary - **Parties:** ... - **Round complexity:** ... - **Key primitives:** ... - **Authentication:** ... - **Forward secrecy:** ... - **Notable:** [spec deviations or security observations, or "none"] ``` After writing the file, print an **ASCII sequence diagram** inline in the response, followed by the Protocol Summary. State the output filename so the user knows where to find the Mermaid source. Follow all drawing conventions in [references/ascii-sequence-diagram.md](references/ascii-sequence-diagram.md), including the inline output format. --- ## Decision Tree ``` ── Input is a spec document (not code)? │ └─ Step S1: identify format, read references/spec-parsing-patterns.md │ ── Input is source code (not a spec)? │ └─ Step 1: grep for handshake/round/send/recv entry points │ ── Both spec and code provided? │ └─ Run Spec Workflow (S1–S5) first to build canonical diagram, │ then read code and annotate divergences with ⚠️ │ ── Spec is a known protocol (TLS, Noise, Signal, X3DH, FROST)? │ └─ Read references/protocol-patterns.md and use canonical flow as skeleton │ ── Spec is ProVerif (.pv) or Tamarin (.spthy)? │ └─ Read references/spec-parsing-patterns.md → Formal Models section │ ── Spec message ordering is ambiguous? │ └─ Infer from round/section structure, annotate with ⚠️ │ ── Can't identify parties from spec? │ └─ Check "Parties"/"Notation" sections; for ProVerif read process names; │ for Tamarin read rule names and fact arguments │ ── Don't know which code files implement the protocol? │ └─ Step 1: grep for handshake/round/send/recv entry points │ ── Can't identify parties from struct names? │ └─ Read test files — test setup reveals roles │ ── Protocol runs in-process (no network calls)? │ └─ Treat function argument passing at role boundaries as messages │ ── MPC / threshold protocol with N parties? │ └─ Read references/protocol-patterns.md → MPC section │ ── Mermaid syntax error? │ └─ Read references/mermaid-sequence-syntax.md → Common Pitfalls │ └─ ASCII drawing conventions? └─ Read references/ascii-sequence-diagram.md ``` --- ## Examples **Code path** — `examples/simple-handshake/`: - **`protocol.py`** — two-party authenticated key exchange (X25519 DH + Ed25519 signing + HKDF + ChaCha20-Poly1305) - **`expected-output.md`** — exact ASCII diagram and Mermaid file the skill should produce for that protocol **Spec path (ProVerif)** — `examples/simple-proverif/`: - **`model.pv`** — HMAC challenge-response authentication modeled in ProVerif - **`expected-output.md`** — step-by-step extraction walkthrough (parties, message flow, crypto ops) and the exact ASCII diagram and Mermaid file the skill should produce Study the relevant example before working on an unfamiliar input. --- ## Supporting Documentation - **[references/spec-parsing-patterns.md](references/spec-parsing-patterns.md)** — Extraction rules for RFC, academic paper/pseudocode, informal prose, ProVerif, and Tamarin input formats; read during Step S1 - **[references/mermaid-sequence-syntax.md](references/mermaid-sequence-syntax.md)** — Participant syntax, arrow types, activations, grouping blocks, escaping rules, and common rendering pitfalls - **[references/protocol-patterns.md](references/protocol-patterns.md)** — Canonical message flows for TLS 1.3, Noise, X3DH, Double Ratchet, Shamir secret sharing, commit-reveal, and generic MPC rounds; use as a reference when comparing implementation against spec - **[references/ascii-sequence-diagram.md](references/ascii-sequence-diagram.md)** — Column layout, arrow conventions, self-loops, phase labels, and inline output format for the ASCII diagram
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.