Mermaid to ProVerif
Reads a Mermaid sequenceDiagram describing a cryptographic protocol and
produces a ProVerif model (.pv file) that can be passed directly to the
ProVerif verifier.
Tools used: Read, Write, Grep, Glob.
The typical input is the output of the crypto-protocol-diagram skill — a
Mermaid sequenceDiagram annotated with cryptographic operations (Sign,
Verify, DH, HKDF, Enc, Dec, etc.) and message arrows.
When to Use
- User asks to formally verify a cryptographic protocol described as a Mermaid sequenceDiagram
- User wants to generate a ProVerif model (.pv file) from a protocol diagram
- User wants to prove secrecy, authentication, or forward secrecy properties
- Input is the output of the
crypto-protocol-diagramskill
When NOT to Use
- No Mermaid sequenceDiagram exists yet — use
crypto-protocol-diagramfirst to generate one - User wants to verify properties of non-cryptographic systems (state machines, access control)
- User wants to run ProVerif on an existing .pv file — just run
proverif model.pvdirectly
Rationalizations to Reject
Workflow
Step 1: Parse Participants and Channels
From the Mermaid diagram:
- Extract every
participantoractordeclaration. Each becomes a ProVerif process. - Count message arrows (
->>,-->>,-x,--x). Each distinctA ->> B: labelcreates a communication step on a channel. - Decide channel model:
- Public channel for any message sent over the network before a secure channel is established (e.g., ClientHello, ephemeral keys, ciphertext to be decrypted by the peer).
- Private channel only for internal state threading within a single party process (not for cross-party messages).
- Default: declare one shared public channel
cfor all cross-party messages. Add per-flow channels only when two distinct parallel sessions must be independent.
Step 2: Inventory Cryptographic Operations
Walk through every Note over annotation and message label. Build a list of
all distinct operations used. Map each to a ProVerif declaration category:
Consult references/crypto-to-proverif-mapping.md [blocked] for exact ProVerif syntax for each.
Step 3: Declare Types, Functions, and Equations
Build the cryptographic preamble in this order:
- Types — declare custom types used to distinguish key material:
- Constants — for fixed strings used as domain separators or labels:
- Functions — constructors and destructors. Destructors use inline
reducso that the process aborts on verification or decryption failure:
- Equations — algebraic identities on constructors only (not on destructors, which already have their rewrite rules inline):
Only declare what the diagram actually uses. Do not add functions for operations not present.
Step 4: Identify and Declare Events
Events mark security-relevant moments in the protocol execution. Extract them by identifying:
- Begin events (
event beginRole(params)): triggered immediately before a party sends a message that depends on a long-term identity commitment (e.g., right before sending a signed message or a MAC'd message). - End events (
event endRole(params)): triggered immediately after a party successfully verifies the peer's identity (e.g., afterVerify(...)or MAC check passes, session key confirmed). - Secrecy markers: any key or nonce that should remain unknown to the attacker after the handshake.
Parameters should uniquely identify the session: the parties' public keys, plus the session key or a transcript hash.
Step 5: Formulate Security Queries
Write one query per security property. Choose from:
Reachability (always add first — structural sanity check):
Verify that the success events are actually reachable. If ProVerif reports any
of these as false, the model has a structural bug (dead receive, type mismatch,
impossible guard) and no other query result should be trusted. Once the model
is validated, comment them out if they slow down the main property checks:
Secrecy (key not derivable by attacker):
Declare a private free name and encrypt it under the session key. The attacker
knowing private_I is equivalent to breaking the session key:
Weak authentication (if B accepted, A ran at some point with matching params — does not prevent replay):
Injective authentication (prevents replay — each B-accept corresponds to a distinct A-run):
Forward secrecy: add a ForwardSecrecyTest process to the main process
that leaks both long-term secret keys to the attacker, then check that a past
session key remains secret. Pair it with a free fs_witness: key [private]
declaration and query attacker(fs_witness). See
references/security-properties.md [blocked] →
Forward Secrecy, and the worked example in
examples/simple-handshake/sample-output.pv.
Choose the strongest applicable query for each property. See references/security-properties.md [blocked] for the full decision tree.
Step 6: Write Participant Processes
Write one let process per participant. Structure each process to mirror the
Mermaid diagram step-by-step, in order.
Template for a two-party protocol:
Rules for writing processes:
- Each
A ->> B: msg_contentsin the diagram becomes:out(c, msg_contents)in A's processin(c, x)(with matching destructuring) in B's process
- Each
Note over A: op → resultbecomes alet result = op inbinding - Each
Note over A: Verify(...)becomes alet _ = verify(...) inbinding (the destructor aborts on failure — no explicit else needed, modeling abort) - Use
altblocks in the diagram asif/then/elsein the process - Long-term keys are process parameters; ephemeral values use
new
N-party or MPC protocols: write one process per distinct role. For
threshold protocols, write a single role process and replicate it !N times
in the main process.
Step 7: Write Main Process and Finalize
The main process:
- Generates long-term keys with
new - Publishes public keys to the attacker via
out(c, pk(sk)) - Runs participant processes in parallel under replication (
!) to allow multiple sessions - Optionally leaks long-term keys for forward-secrecy analysis
Place the full file in this order:
Step 8: Verify and Deliver
Before writing the file:
- Every participant in the diagram has a matching
letprocess - Every
out(c, ...)has a matchingin(c, ...)on the other side with compatible types - Every function used in a process is declared in the preamble
- Every destructor uses inline
reduc(not a separateequationblock) - Every event in a query is declared and triggered in a process
- Long-term public keys are output to channel
cin the main process (attacker can see them — that is the Dolev-Yao model) - No unused declarations (clean up anything added speculatively)
- If
tabledeclarations are present: everyinsert T(...)has a correspondingget T(...)with compatible column types and matching pattern constraints (=keyvs bare name) - If
noselectis used: its tuple structure matches the actual message shapes sent onc(e.g., pairs →mess(c, (x, y))) - If the Key Exposure Oracle pattern is used:
event key_exposed(sk_type)is declared, the oraclein(c, guess: sk_type); if pk(guess) = pk_new then event key_exposed(guess)appears at the end of the process that holds the secret, and the query isquery x: sk_type; event(key_exposed(x))
Write the model to a .pv file. Choose a filename from the protocol name,
e.g. noise-xx-handshake.pv or x3dh-key-agreement.pv.
After writing, print a brief summary:
Decision Tree
Example
examples/simple-handshake/ contains a worked example:
diagram.md— Mermaid sequenceDiagram for a two-party authenticated key exchange (X25519 DH + Ed25519 signing + HKDF)sample-output.pv— exact ProVerif model the skill should produce, with secrecy and injective authentication queries
Study this before working on an unfamiliar protocol.
Supporting Documentation
- references/crypto-to-proverif-mapping.md [blocked] — Mapping table from Mermaid cryptographic annotations to ProVerif function declarations, equations, and process patterns
- references/proverif-syntax.md [blocked] — ProVerif language reference: types, functions, equations, processes, events, queries, and common pitfalls
- references/security-properties.md [blocked] — Decision guide for choosing the right queries: secrecy, authentication (weak vs injective), forward secrecy, unlinkability, and how to model them

