Claude Skill

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

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download trailofbits-skills-plugins_trailmark_skills_crypto-protocol-diagram-123037e.zip · 27 KB
trailofbits/skills 7234 616 forks CC-BY-SA-4.0 Updated 8h ago
Part of trailofbits/skills — 100 skills

Install

skills CLI npx skills add https://github.com/trailofbits/skills/tree/main/plugins/trailmark/skills/crypto-protocol-diagram
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
Git 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) — $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).

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

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
  • alt blocks 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 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

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.

No comments yet.

Reviews (0)

No reviews yet.

Related