diff --git a/Gemfile b/Gemfile index a9db9d0..b440847 100644 --- a/Gemfile +++ b/Gemfile @@ -5,4 +5,5 @@ gem "just-the-docs", "0.12.0" group :jekyll_plugins do gem 'jekyll-katex' + gem 'jekyll-redirect-from' end diff --git a/Gemfile.lock b/Gemfile.lock index ebfbfc8..3fb7756 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,6 +61,8 @@ GEM jekyll-katex (1.0.0) execjs (~> 2.7) jekyll (>= 3.6, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) jekyll-sass-converter (3.1.0) sass-embedded (~> 1.75) jekyll-seo-tag (2.8.0) @@ -116,6 +118,7 @@ PLATFORMS DEPENDENCIES jekyll (~> 4.4) jekyll-katex + jekyll-redirect-from just-the-docs (= 0.12.0) BUNDLED WITH diff --git a/_config.yml b/_config.yml index c3166e5..8b5c5f4 100644 --- a/_config.yml +++ b/_config.yml @@ -9,6 +9,14 @@ aux_links: plugins: - jekyll-katex + - jekyll-redirect-from + +sass: + quiet_deps: true + silence_deprecations: + - color-functions + - global-builtin + - import callouts: highlight: diff --git a/_data/linear_sequence.yml b/_data/linear_sequence.yml new file mode 100644 index 0000000..5eda762 --- /dev/null +++ b/_data/linear_sequence.yml @@ -0,0 +1,11 @@ +# Ordered reading sequence for the "Getting Started" linear path of the manual. +# Only pages using `layout: linear` will show prev/next navigation, and they +# must appear in this list for the links to resolve. Order here is the order +# in which readers are expected to move through the pages. +- manual/installation.md +- manual/tutorial/index.md +- manual/tutorial/hello-frog.md +- manual/tutorial/otp-ots.md +- manual/worked-examples/index.md +- manual/worked-examples/chained-encryption.md +- manual/worked-examples/kemdem-cpa.md diff --git a/_includes/head_custom.html b/_includes/head_custom.html index fb03278..970837f 100644 --- a/_includes/head_custom.html +++ b/_includes/head_custom.html @@ -1,3 +1,45 @@ - - + + + + diff --git a/_includes/prev_next.html b/_includes/prev_next.html new file mode 100644 index 0000000..deb2467 --- /dev/null +++ b/_includes/prev_next.html @@ -0,0 +1,44 @@ +{%- comment -%} + Renders previous/next navigation for the linear reading path of the manual. + Used by _layouts/linear.html. Sequence is defined in _data/linear_sequence.yml. + Finds the current page by matching page.path against entries in the sequence, + then resolves neighbors to real page objects so we get URLs and titles for free. +{%- endcomment -%} +{%- assign seq = site.data.linear_sequence -%} +{%- assign current_index = -1 -%} +{%- for entry in seq -%} + {%- if entry == page.path -%} + {%- assign current_index = forloop.index0 -%} + {%- endif -%} +{%- endfor -%} +{%- if current_index >= 0 -%} + {%- assign prev_index = current_index | minus: 1 -%} + {%- assign next_index = current_index | plus: 1 -%} + {%- assign last_index = seq.size | minus: 1 -%} + {%- assign prev_path = "" -%} + {%- assign next_path = "" -%} + {%- for entry in seq -%} + {%- if forloop.index0 == prev_index -%}{%- assign prev_path = entry -%}{%- endif -%} + {%- if forloop.index0 == next_index -%}{%- assign next_path = entry -%}{%- endif -%} + {%- endfor -%} + {%- assign prev_page = site.pages | where: "path", prev_path | first -%} + {%- assign next_page = site.pages | where: "path", next_path | first -%} + +{%- endif -%} diff --git a/_layouts/linear.html b/_layouts/linear.html new file mode 100644 index 0000000..132a532 --- /dev/null +++ b/_layouts/linear.html @@ -0,0 +1,6 @@ +--- +layout: default +--- +{{ content }} + +{% include prev_next.html %} diff --git a/_plugins/rouge_prooffrog.rb b/_plugins/rouge_prooffrog.rb new file mode 100644 index 0000000..8d317de --- /dev/null +++ b/_plugins/rouge_prooffrog.rb @@ -0,0 +1,65 @@ +# Rouge lexer for FrogLang (the ProofFrog language). +# +# Registers the `prooffrog` tag (plus per-file-type aliases) so that fenced +# code blocks like ```prooffrog are syntax-highlighted server-side by Rouge +# at Jekyll build time. This replaces the previous client-side Prism setup. +# +# The token set mirrors the old Prism grammar in assets/js/prism-prooffrog.js. + +require 'rouge' + +module Rouge + module Lexers + class ProofFrog < Rouge::RegexLexer + title 'ProofFrog' + desc 'FrogLang, the language of the ProofFrog proof assistant' + tag 'prooffrog' + aliases 'froglang', 'primitive', 'scheme', 'game', 'proof' + filenames '*.primitive', '*.scheme', '*.game', '*.proof' + + # Top-level construct introducers + declarations = %w[Primitive Scheme Game Reduction Phase] + + # Built-in types + builtins = %w[Bool Void Int BitString Set Map Array] + + # Reserved words + keywords = %w[ + import export as extends compose against requires + if else for return in to + union subsets induction from calls + Adversary oracles proof + let assume theorem games + ] + + state :root do + rule %r(\s+), Text + rule %r(//.*), Comment::Single + + # Single-quoted import path strings + rule %r('[^']*'), Str::Single + + # Numbers: binary literals and decimal integers + rule %r(\b0b[01]+\b), Num::Bin + rule %r(\b\d+\b), Num::Integer + + # None / true / false + rule %r(\bNone\b), Keyword::Constant + rule %r(\b(?:true|false)\b), Keyword::Constant + + rule %r(\b(?:#{builtins.join('|')})\b), Name::Builtin + rule %r(\b(?:#{declarations.join('|')})\b), Keyword::Declaration + rule %r(\b(?:#{keywords.join('|')})\b), Keyword + + # Operators (longest match first) + rule %r(<-|[=!<>]=|&&|\|\|), Operator + rule %r([+\-*/\\|!<>=]), Operator + + rule %r([{}\[\]();:,.?]), Punctuation + + rule %r(\w+), Name + rule %r(.), Text + end + end + end +end diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss index 4ca29ee..637b22e 100644 --- a/_sass/custom/custom.scss +++ b/_sass/custom/custom.scss @@ -9,7 +9,80 @@ .token.number { color: #005cc5; } .token.operator { color: #d73a49; } .token.punctuation { color: #24292e; } -pre:has(> code[class*="language-"]) { background: #f6f8fa; padding: 1em; } +pre:has(> code[class*="language-"]) { background: $sidebar-color; padding: 1em; } + +// Code blocks: remove border, use sidebar background color +div.highlighter-rouge, +figure.highlight, +.highlight, +pre.highlight { + background-color: $sidebar-color; +} + +div.highlighter-rouge div.highlight, +figure.highlight pre, +figure.highlight :not(pre) > code { + border: 0; + background-color: $sidebar-color; +} + +.highlight .table-wrapper td, +.highlight .table-wrapper pre { + background-color: $sidebar-color; +} + +// Inline code: remove border, use sidebar background color +:not(pre, figure) > code { + background-color: $sidebar-color; + border: 0; + padding-left: 3px; + padding-right: 3px; +} + +// Prev/next navigation at the bottom of pages on the linear reading path. +.prev-next-nav { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid $border-color; + + > div { + flex: 1 1 0; + min-width: 0; + } + + &__next { + text-align: right; + } + + a { + display: inline-block; + padding: 0.75rem 1rem; + border: 1px solid $border-color; + border-radius: 4px; + text-decoration: none; + max-width: 100%; + + &:hover { + background-color: $sidebar-color; + } + } + + &__label { + display: block; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: $grey-dk-000; + } + + &__title { + display: block; + font-weight: 500; + } +} .site-title::after { background-image: url("../../prooffrog.png"); @@ -19,3 +92,12 @@ pre:has(> code[class*="language-"]) { background: #f6f8fa; padding: 1em; } background-size: cover; margin-left: 10px; } + +/* Label column: no left border, right-aligned */ +.table-labels td:last-child, +.table-labels th:last-child { + border-left: none; + text-align: right; + width: 1%; + white-space: nowrap; +} diff --git a/assets/diagram.png b/assets/diagram.png deleted file mode 100644 index c5be996..0000000 Binary files a/assets/diagram.png and /dev/null differ diff --git a/assets/diagram.svg b/assets/diagram.svg new file mode 100644 index 0000000..c6eaa3c --- /dev/null +++ b/assets/diagram.svg @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + FrogLang Source Files + .primitive .scheme .game .proof + + + + + + Parsing + ANTLR grammars → AST nodes (frog_ast) + + + + + + Semantic Analysis + Type checking, name resolution, import resolution + + + + + + PROOF ENGINE + + + + Proof Setup + Load let bindings, definitions, assumptions + Build namespace, type maps, method lookup + + + + + + For each adjacent pair of games in proof + + + + + + EQUIVALENCE CHECKING (per hop) + + + + Current game AST + + + Next game AST + + + + + + + Inline Methods & Apply Assumptions + Substitute method bodies; apply user-supplied equivalences + + + + + + CORE PIPELINE — loop until AST unchanging (max 200 iterations) + + + + + + + + + Sampling + Merge/split/sink samples + + + Random Functions + RF → uniform + + + Inlining + Inline, deduplicate, alias + + + Algebraic + XOR/ModInt/Group + + + + Structural + Topo sort, field pruning + + + Control Flow + Branch elimination + + + Types & Tuples + Null guards, folding + + + Symbolic + Z3 + SymPy + + + + + + + + + + + AST + changed? + + + yes + + + + repeat + + + no + + + + + + + Standardization (single pass) + Rename variables/fields to canonical IDs, sort statements + + + + + + + Canonical form A + + + Canonical form B + + + + + + + + + + A ≡ B ? + (structurally) + + + yes + + + + no + + + + + Branch conditions + equiv? (Z3) + + + yes + + + + + + + no + + + + + + Accept + + + Diagnostics + Classify diff hunks between + canonical forms; match near-misses; + detect engine limitations + + + + + + + + + + HopResult per step + Valid/invalid status + diagnostic report + + + + + + + + + + + CLI + proof_frog prove + + + Web Editor + proof_frog web + + + LSP / MCP + VSCode, Claude + diff --git a/assets/js/prism-prooffrog.js b/assets/js/prism-prooffrog.js deleted file mode 100644 index 994e01b..0000000 --- a/assets/js/prism-prooffrog.js +++ /dev/null @@ -1,43 +0,0 @@ -// ── Prism.js language grammar for ProofFrog's FrogLang ────────────────────── -// Defines Prism.languages.prooffrog for syntax highlighting FrogLang files -// (.primitive, .scheme, .game, .proof). - -(function (Prism) { - Prism.languages.prooffrog = { - // Line comments: // to end of line - "comment": /\/\/.*/, - - // Import path strings: single-quoted - "string": /'[^']*'/, - - // Numbers: binary (0b...) and decimal - "number": /\b0b[01]+\b|\b\d+\b/, - - // Built-in type names - "builtin": /\b(?:Bool|Void|Int|BitString|Set|Map|Array)\b/, - - // Boolean literals - "boolean": /\b(?:true|false)\b/, - - // None literal - "constant": /\bNone\b/, - - // Declaration keywords (top-level construct introducers) - "class-name": /\b(?:Primitive|Scheme|Game|Reduction|Phase)\b/, - - // Keywords - "keyword": /\b(?:import|export|as|extends|compose|against|requires|if|else|for|return|in|to|union|subsets|induction|from|calls|Adversary|oracles|proof|let|assume|theorem|games)\b/, - - // Operators (longest match first via alternation order) - "operator": /<-|[=!<>]=|&&|\|\||[+\-*\/\\|!<>=]/, - - // Punctuation - "punctuation": /[{}[\]();:,.?]/, - }; - - // Register for all FrogLang file extensions - Prism.languages.primitive = Prism.languages.prooffrog; - Prism.languages.scheme = Prism.languages.prooffrog; - Prism.languages.game = Prism.languages.prooffrog; - Prism.languages.proof = Prism.languages.prooffrog; -})(Prism); diff --git a/assets/manual/kemdem-cpa/joc-game-0-reduction.png b/assets/manual/kemdem-cpa/joc-game-0-reduction.png new file mode 100644 index 0000000..0ba6091 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-0-reduction.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-0.png b/assets/manual/kemdem-cpa/joc-game-0.png new file mode 100644 index 0000000..888de20 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-0.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-1-keyspace.png b/assets/manual/kemdem-cpa/joc-game-1-keyspace.png new file mode 100644 index 0000000..b90c93d Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-1-keyspace.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-1-reduction.png b/assets/manual/kemdem-cpa/joc-game-1-reduction.png new file mode 100644 index 0000000..ebe3568 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-1-reduction.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-1-reduction2.png b/assets/manual/kemdem-cpa/joc-game-1-reduction2.png new file mode 100644 index 0000000..ff642dd Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-1-reduction2.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-1.png b/assets/manual/kemdem-cpa/joc-game-1.png new file mode 100644 index 0000000..8876723 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-1.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-2-keyspace.png b/assets/manual/kemdem-cpa/joc-game-2-keyspace.png new file mode 100644 index 0000000..f42507b Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-2-keyspace.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-2-reduction.png b/assets/manual/kemdem-cpa/joc-game-2-reduction.png new file mode 100644 index 0000000..cfde0cd Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-2-reduction.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-2-reduction2.png b/assets/manual/kemdem-cpa/joc-game-2-reduction2.png new file mode 100644 index 0000000..6a162ea Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-2-reduction2.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-2.png b/assets/manual/kemdem-cpa/joc-game-2.png new file mode 100644 index 0000000..999be66 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-2.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-3-reduction.png b/assets/manual/kemdem-cpa/joc-game-3-reduction.png new file mode 100644 index 0000000..5b685b7 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-3-reduction.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-3.png b/assets/manual/kemdem-cpa/joc-game-3.png new file mode 100644 index 0000000..60146ab Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-3.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-kem-cpa.png b/assets/manual/kemdem-cpa/joc-game-kem-cpa.png new file mode 100644 index 0000000..bf43e51 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-kem-cpa.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-pke-cpa.png b/assets/manual/kemdem-cpa/joc-game-pke-cpa.png new file mode 100644 index 0000000..fd1efb6 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-pke-cpa.png differ diff --git a/assets/manual/kemdem-cpa/joc-game-symenc-cots.png b/assets/manual/kemdem-cpa/joc-game-symenc-cots.png new file mode 100644 index 0000000..26ce5b2 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-game-symenc-cots.png differ diff --git a/assets/manual/kemdem-cpa/joc-primitive-kem.png b/assets/manual/kemdem-cpa/joc-primitive-kem.png new file mode 100644 index 0000000..58b5c9e Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-primitive-kem.png differ diff --git a/assets/manual/kemdem-cpa/joc-primitive-pke.png b/assets/manual/kemdem-cpa/joc-primitive-pke.png new file mode 100644 index 0000000..0d96aab Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-primitive-pke.png differ diff --git a/assets/manual/kemdem-cpa/joc-primitive-symenc.png b/assets/manual/kemdem-cpa/joc-primitive-symenc.png new file mode 100644 index 0000000..2f72657 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-primitive-symenc.png differ diff --git a/assets/manual/kemdem-cpa/joc-scheme-kemdem.png b/assets/manual/kemdem-cpa/joc-scheme-kemdem.png new file mode 100644 index 0000000..fbbea93 Binary files /dev/null and b/assets/manual/kemdem-cpa/joc-scheme-kemdem.png differ diff --git a/design.md b/design.md deleted file mode 100644 index cd1a192..0000000 --- a/design.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Design -layout: default -nav_order: 5 ---- - -# Design of ProofFrog - -## Philosophy - -Cryptographic proofs provide theoretical guarantees on the security of constructions, but human fallibility means that even expert-reviewed proofs may hide flaws or errors. -Proof assistants are software tools built for formally verifying each step in a proof, and have the potential to prevent erroneous proofs from being published and insecure constructions from being implemented. -Unfortunately, existing tooling for verifying cryptographic proofs has found limited adoption in the cryptographic community, in part due to concerns with ease of use. - -ProofFrog is a tool for verifying transitions in cryptographic game-hopping proofs. -It focuses on stating and justifying the validity of a sequence of games: verifying that the starting and ending games correctly match the security definition, and checking each hop as either an *interchangeability-based hop* (where the two adjacent games have zero distinguishing advantage, demonstrated by code equivalence) or a *reduction-based hop* (where the two adjacent games have bounded distinguishing advantage, justified by exhibiting a reduction to an assumed security property). -The accumulation of bounded advantages and the assessment of the final security bound remain tasks for the proof's author. - -To check interchangeability of two games, ProofFrog manipulates abstract syntax trees (ASTs) of games to arrive at a canonical form, instead of working at the level of logical formulae. -Treating games as ASTs allows us to leverage techniques from compiler design and static analysis to prove output equivalence of games, thereby demonstrating the validity of hops in a game sequence. -The main technique used in our engine is to take pairs of game ASTs and perform a variety of transformations in an attempt to coerce the two ASTs into canonical forms, which can then be compared. -These transformations are performed on the AST with little user guidance, which makes writing a proof in many cases as simple as just specifying which reductions are being leveraged. - -ProofFrog also targets ease of use: although it implements a domain-specific language that a user must learn, the language has an imperative C or Java-like syntax that should be comfortable for the average cryptographer. -The proof syntax is intentionally designed for improved readability by closely mimicking that of a typical pen-and-paper proof. -ProofFrog is aimed at an introductory audience: while its expressivity and scope are smaller than existing tools such as EasyCrypt and CryptoVerif, it prioritizes ease of use and has been able to verify game hop sequences from a wide swath of textbook-level proofs. - -It is important to note that ProofFrog's engine does not have any formal guarantees: the soundness of its transformations has not been verified. - -## Engine Functionality - -Information about ProofFrog's engine can be found in [Ross Evans' master's thesis](https://uwspace.uwaterloo.ca/bitstream/handle/10012/20441/Evans_Ross.pdf) and [eprint 2025/418](https://eprint.iacr.org/2025/418). - -A diagram summarizing ProofFrog's engine functionality is presented below. - -![ProofFrog Functionality Diagram](/assets/diagram.png) - -- The `InstantiationTransformer` takes parameterized ASTs and rewrites parameters in terms of variables defined inside the `let` section of the proof file. -- The `InlineTransformer` is used to inline function calls. This can happen during composition of games with reductions, or when calling a scheme method from within a game's oracle. -- "Tuple Expansion" takes tuples where the indices for all reads and writes can be statically determined, and rewrites them as multiple variable declarations. -- ["Copy Propagation](https://en.wikipedia.org/wiki/Copy_propagation)" removes variables that are copies of others -- "Statement Sorting" attempts to create a canonical ordering of statements within a block via a combination of depth first search and topological sorting. It also performs dead code elimination for statements that have no effect on the return value of an oracle. -- "Slice Simplification" is an advanced form of copy propagation used when one variable is a copy of another, but there is an intermediate concatenated bitstring that the variable is sliced out of -- "Symbolic Computation" takes integer variables and simplifies expressions they are used in into a simplest form via [SymPy](https://www.sympy.org/en/index.html) -- The "Remove Duplicated Fields" transformation searches each pair of fields in a game with the same type and unifies the values if it can be statically determined they share the same value throughout the game's entire lifetime -- The "Apply User Assumptions" transformation utilizes assumptions about variables that a user can specify between pair of games to simplify conditions -- "Apply Branch Elimination" takes branches where the conditions are `true` or `false` and simplifies them -- "Remove Unnecessary Fields" deletes any fields that do not affect the return value of any oracle -- "Canonicalize Returns" takes return statements that return variables and (when the value of the variable can be statically determined) rewrite them to return that variable -- "Collapse Branches" takes adjacent if/else-if branches with identical blocks of code and rewrites them into one branch where the condition is the OR of the previous two conditions -- "Simplify Not Operations" just takes `!(a == b)` and rewrites it to `a != b` -- "Simplify Tuple Copies" takes tuples of the form `[a[0], a[1], ..., a[n-1]]` and rewrites them into just `a` -- "Remove Unreachable Code" attempts to use [Z3](https://github.com/Z3Prover/z3) to determine when a return statement is guaranteed to have executed, and deletes all code remaining in an oracle past that point -- Normalization of field and variable names just assigns each field a numeric name based on the first appearance in the AST. diff --git a/examples.md b/examples.md index ec4aeb4..d1f4d04 100644 --- a/examples.md +++ b/examples.md @@ -1,131 +1,195 @@ --- title: Examples layout: default -nav_order: 4 +nav_order: 3 --- -# Examples +# Examples Catalogue +{: .no_toc } -Below are a list of examples that ProofFrog can currently verify. -Many are adapted from [The Joy of Cryptography](https://joyofcryptography.com/). -In such cases, we will indicate which claim in the textbook is being proved. References and examples are from the old PDF preview version, and need to be updated to the final print edition. +The [ProofFrog/examples](https://github.com/ProofFrog/examples) repository contains a growing collection of cryptographic proofs verified by ProofFrog. This page organizes them by topic. -## One-Time Uniform Ciphertexts implies One-Time Secrecy +- Beginner denotes a proof that is a good starting point for learning ProofFrog +- Rich example denotes a substantial proof with multiple hops or techniques -This proves [Theorem 2.15](https://joyofcryptography.com/pdf/book.pdf#page=49). +--- + +- TOC +{:toc} + +--- + +## Joy of Cryptography (MIT Press Edition) + +The [`examples/joy`](https://github.com/ProofFrog/examples/tree/main/joy) directory contains ProofFrog formulations of constructions from Chapters 1 and 2 of [The Joy of Cryptography](https://joyofcryptography.com/) by Mike Rosulek. These are designed to be read alongside the textbook and are the best place to start learning ProofFrog. + +| Proof | Description | | +|:------|:------------|-| +| [OTPCorrectness](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch1/OTPCorrectness.proof) | [One-time pad](https://github.com/ProofFrog/examples/blob/main/joy/Schemes/SymEnc/OTP.scheme) is [correct](https://github.com/ProofFrog/examples/blob/main/joy/Games/SymEnc/Correctness.game) (Claim 1.2.3) | Beginner | +| [OTPSecure](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch2/OTPSecure.proof) | [One-time pad](https://github.com/ProofFrog/examples/blob/main/joy/Schemes/SymEnc/OTP.scheme) has [one-time secrecy](https://github.com/ProofFrog/examples/blob/main/joy/Games/SymEnc/OneTimeSecrecy.game) (Example 2.5.4) | Beginner | +| [OTPSecureLR](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch2/OTPSecureLR.proof) | [One-time pad](https://github.com/ProofFrog/examples/blob/main/joy/Schemes/SymEnc/OTP.scheme) has [left-or-right one-time secrecy](https://github.com/ProofFrog/examples/blob/main/joy/Games/SymEnc/OneTimeSecrecyLR.game) | Beginner | +| [ChainedEncryptionSecure](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch2/ChainedEncryptionSecure.proof) | [Chained encryption](https://github.com/ProofFrog/examples/blob/main/joy/Schemes/SymEnc/ChainedEncryption.scheme) has [one-time secrecy](https://github.com/ProofFrog/examples/blob/main/joy/Games/SymEnc/OneTimeSecrecy.game) (Claim 2.6.2) | Beginner | +{: .table-labels } + +**Joy of Cryptography exercises**. The [README file about the Joy of Cryptography examples](https://github.com/ProofFrog/examples/tree/main/joy#exercises) also lists exercises from Chapter 2 that are doable in ProofFrog — try them yourself! Solutions are not publicly available, but instructors can contact Douglas Stebila to obtain a copy. + +--- -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/OTUCimpliesOTS.proof). +## Symmetric Encryption -## CPA$ Security implies CPA Security +### Security notion implications -This proves [Claim 7.3](https://joyofcryptography.com/pdf/book.pdf#page=145). +| Proof | Description | | +|:------|:------------|-| +| [INDOT$_implies_INDOT](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/INDOT%24_implies_INDOT.proof) | [IND-OT$](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT%24.game) implies [IND-OT](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT.game) | Beginner | +| [INDCPA$_MultiChal_implies_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/INDCPA%24_MultiChal_implies_INDCPA_MultiChal.proof) | [IND-CPA$ (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCPA%24_MultiChal.game) security implies [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCPA_MultiChal.game) security | +{: .table-labels } -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/CPA%24impliesCPA.proof). +### Basic constructions -## Composing Two Symmetric Encryption Schemes for One-Time Uniform Ciphertexts +| Proof | Description | | +|:------|:------------|-| +| [ModOTP_INDOT](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/ModOTP_INDOT.proof) | The [modular one-time pad](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/ModOTP.scheme) ({% katex %}\mathrm{Enc}(k, m) = m + k \bmod q{% endkatex %}) has [IND-OT](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT.game) | Beginner | +{: .table-labels } -This proof analyzes a symmetric encryption scheme {% katex %}\Sigma{% endkatex %} that composes two symmetric encryption schemes {% katex %}S{% endkatex %} and {% katex %}T{% endkatex %} where {% katex %}S.C = T.M{% endkatex %}, and -{% katex display %} -\Sigma.\mathrm{KeyGen}() = (S.\mathrm{KeyGen()}, T.\mathrm{KeyGen()}) -{% endkatex %} -{% katex display %} -\Sigma.\mathrm{Enc}((k_S, k_T), m) = T.\mathrm{Enc}(k_T, S.\mathrm{Enc}(k_S, m)) -{% endkatex %} -{% katex display %} -\Sigma.\mathrm{Dec}((k_S, k_T), c) = S.\mathrm{Dec}(k_S, T.\mathrm{Dec}(k_T, c)) -{% endkatex %} +### PRF-based encryption -If {% katex %}T{% endkatex %} has one-time uniform ciphertexts, then so does {% katex %}\Sigma{% endkatex %}. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/GeneralDoubleOTUC.proof). +The [PRF-based symmetric encryption scheme](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/SymEncPRF.scheme) {% katex %}\Sigma{% endkatex %} encrypts a message {% katex %}m{% endkatex %} under key {% katex %}k{% endkatex %} by sampling a random {% katex %}r{% endkatex %} and outputting {% katex %}(r,\; F(k, r) \oplus m){% endkatex %}, where {% katex %}F{% endkatex %} is a [PRF](https://github.com/ProofFrog/examples/blob/main/Primitives/PRF.primitive). -## OTUC implies Double OTUC +| Proof | Description | | +|:------|:------------|-| +| [SymEncPRF_INDOT$](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDOT%24.proof) | [PRF-based symmetric encryption](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/SymEncPRF.scheme) has [IND-OT$](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT%24.game) | +| [SymEncPRF_INDCPA$_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDCPA%24_MultiChal.proof) | [PRF-based symmetric encryption](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/SymEncPRF.scheme) is [IND-CPA$ (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCPA%24_MultiChal.game) secure | Rich example | +{: .table-labels } -If a symmetric encryption scheme has one-time uniform ciphertexts, then the double encryption scheme (composing two copies of it) also has one-time uniform ciphertexts. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/OTUCimpliesDoubleOTUC.proof). +### Composition of encryption schemes -## Composing Two Symmetric Encryption Schemes for CPA$ security +Given two [symmetric encryption](https://github.com/ProofFrog/examples/blob/main/Primitives/SymEnc.primitive) schemes {% katex %}S{% endkatex %} and {% katex %}T{% endkatex %} where {% katex %}S.\mathcal{C} = T.\mathcal{M}{% endkatex %}, the [composed scheme](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/GeneralDoubleSymEnc.scheme) encrypts as {% katex %}\Sigma.\mathrm{Enc}((k_S, k_T), m) = T.\mathrm{Enc}(k_T, S.\mathrm{Enc}(k_S, m)){% endkatex %}. -This proof analyzes the same encryption scheme {% katex %}\Sigma{% endkatex %} as in the prior heading. If {% katex %}T{% endkatex %} is CPA$ secure, then so is {% katex %}\Sigma{% endkatex %}. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/DoubleCPA%24.proof). +| Proof | Description | +|:------|:------------| +| [INDOT$_implies_DoubleSymEnc_INDOT$](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/INDOT%24_implies_DoubleSymEnc_INDOT%24.proof) | If a scheme has [IND-OT$](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT%24.game), then [double-encrypting](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/DoubleSymEnc.scheme) with two copies of it also has IND-OT$ | +| [GeneralDoubleSymEnc_INDOT$](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/GeneralDoubleSymEnc_INDOT%24.proof) | If {% katex %}T{% endkatex %} has [IND-OT$](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT%24.game), so does {% katex %}\Sigma{% endkatex %} | +| [GeneralDoubleSymEnc_INDCPA$_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/GeneralDoubleSymEnc_INDCPA%24_MultiChal.proof) | If {% katex %}T{% endkatex %} is [IND-CPA$ (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCPA%24_MultiChal.game) secure, so is {% katex %}\Sigma{% endkatex %} | -## Double One-Time Pad has One-Time Uniform Ciphertexts +### Authenticated encryption -This proves [Claim 2.13](https://joyofcryptography.com/pdf/book.pdf#page=45). +| Proof | Description | | +|:------|:------------|-| +| [EncryptThenMAC_INDCCA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/EncryptThenMAC_INDCCA_MultiChal.proof) | [Encrypt-then-MAC](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/EncryptThenMAC.scheme) is [IND-CCA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCCA_MultiChal.game) secure | Rich example | +{: .table-labels } -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Book/2/2_13.proof). +--- + +## Pseudorandom Generators -## Pseudo One-Time Pad has One-Time Uniform Ciphertexts +| Proof | Description | +|:------|:------------| +| [TriplingPRG_PRGSecurity](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/TriplingPRG_PRGSecurity.proof) | A [length-tripling PRG](https://github.com/ProofFrog/examples/blob/main/Schemes/PRG/TriplingPRG.scheme) built by applying a length-doubling [PRG](https://github.com/ProofFrog/examples/blob/main/Primitives/PRG.primitive) twice is [secure](https://github.com/ProofFrog/examples/blob/main/Games/PRG/PRGSecurity.game) | +| [CounterPRG_PRGSecurity](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/CounterPRG_PRGSecurity.proof) | A [counter-mode PRG](https://github.com/ProofFrog/examples/blob/main/Schemes/PRG/CounterPRG.scheme) built from a [PRF](https://github.com/ProofFrog/examples/blob/main/Primitives/PRF.primitive) is [secure](https://github.com/ProofFrog/examples/blob/main/Games/PRG/PRGSecurity.game) | -This proves [Claim 5.4](https://joyofcryptography.com/pdf/book.pdf#page=102). +--- -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Book/5/5_3.proof). +## Pseudorandom Functions -## Pseudorandomness of a length-tripling PRG +| Proof | Description | +|:------|:------------| +| [PRFSecurity_implies_PRFSecurity_MultiKey](https://github.com/ProofFrog/examples/blob/main/Proofs/PRF/PRFSecurity_implies_PRFSecurity_MultiKey.proof) | [Multi-key PRF security](https://github.com/ProofFrog/examples/blob/main/Games/PRF/PRFSecurity_MultiKey.game) follows from [single-key PRF security](https://github.com/ProofFrog/examples/blob/main/Games/PRF/PRFSecurity.game) via a hybrid argument | + +--- -This proves [Claim 5.5](https://joyofcryptography.com/pdf/book.pdf#page=105). +## Group-Based Assumptions -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/TriplingPRGSecure.proof). +These proofs establish implications between Diffie–Hellman-type assumptions. -## One-Time Secrecy implies CPA Security for Public Key Encryption Schemes +| Proof | Description | +|:------|:------------| +| [DDH_implies_CDH](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDH_implies_CDH.proof) | [DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDH.game) implies [CDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/CDH.game) | +| [DDH_implies_HashedDDH](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDH_implies_HashedDDH.proof) | [DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDH.game) implies [Hashed DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/HashedDDH.game) (standard model) | +| [CDH_implies_HashedDDH](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/CDH_implies_HashedDDH.proof) | [CDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/CDH.game) implies [Hashed DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/HashedDDH.game) (random oracle model) | +| [DDHMultiChal_implies_HashedDDHMultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDHMultiChal_implies_HashedDDHMultiChal.proof) | [DDH (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDHMultiChal.game) implies [Hashed DDH (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/Group/HashedDDHMultiChal.game) (random oracle model) | -This proves [Claim 15.5](https://joyofcryptography.com/pdf/book.pdf#page=273). +--- -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/PubEnc/OTSimpliesCPA.proof). +## Public-Key Encryption -## KEM-DEM Hybrid Encryption is CPA secure +### Security notion implications -This proves CPA security of the hybrid public key encryption scheme constructed via the KEM-DEM paradigm, assuming CPA security of the KEM and one-time secrecy of the symmetric encryption scheme. +| Proof | Description | +|:------|:------------| +| [INDCPA_implies_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/INDCPA_implies_INDCPA_MultiChal.proof) | [IND-CPA](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA.game) implies [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game) for [public-key encryption](https://github.com/ProofFrog/examples/blob/main/Primitives/PubKeyEnc.primitive) | -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/PubEnc/KEMDEMCPA.proof). +### ElGamal -## Hybrid Encryption is CPA secure +| Proof | Description | +|:------|:------------| +| [ElGamal_Correctness](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/ElGamal_Correctness.proof) | [ElGamal](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/ElGamal.scheme) encryption is [correct](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/Correctness.game) | +| [ElGamal_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/ElGamal_INDCPA_MultiChal.proof) | [ElGamal](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/ElGamal.scheme) is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game) secure under [DDH (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDHMultiChal.game) | +| [HashedElGamal_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HashedElGamal_INDCPA_MultiChal.proof) | [Hashed ElGamal](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HashedElGamal.scheme) is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game) secure under [Hashed DDH (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/Group/HashedDDHMultiChal.game) (standard model) | +| [HashedElGamal_INDCPA_ROM_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HashedElGamal_INDCPA_ROM_MultiChal.proof) | [Hashed ElGamal](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HashedElGamal.scheme) is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_ROM_MultiChal.game) secure under [DDH (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDHMultiChal.game) (random oracle model) | -This proves [Claim 15.9](https://joyofcryptography.com/pdf/book.pdf#page=279). +### Hybrid public-key encryption -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/PubEnc/Hybrid.proof). +| Proof | Description | | +|:------|:------------|-| +| [HybridKEMDEM_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof) | [KEM-DEM](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HybridKEMDEM.scheme) hybrid public-key encryption is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game) secure | Rich example | +| [HybridPKEDEM_INDCPA_MultiChal](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HybridPKEDEM_INDCPA_MultiChal.proof) | [PKE+SymEnc](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HybridPKEDEM.scheme) hybrid public-key encryption is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game) secure | Rich example | +{: .table-labels } -## Encrypt-then-MAC is CCA secure +### KEM constructions -This proves [Claim 10.10](https://joyofcryptography.com/pdf/book.pdf#page=205). +The [KEMPRF](https://github.com/ProofFrog/examples/blob/main/Schemes/KEM/KEMPRF.scheme) construction derives the shared secret by applying a [PRF](https://github.com/ProofFrog/examples/blob/main/Primitives/PRF.primitive) to the underlying [KEM](https://github.com/ProofFrog/examples/blob/main/Primitives/KEM.primitive)'s shared secret and ciphertext: {% katex %}\mathit{ss'} = F(k_F, \mathit{ss} \| c){% endkatex %}. -The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/EncryptThenMACCCA.proof). +| Proof | Description | | +|:------|:------------|-| +| [KEMPRF_Correctness](https://github.com/ProofFrog/examples/blob/main/Proofs/KEM/KEMPRF_Correctness.proof) | [KEMPRF](https://github.com/ProofFrog/examples/blob/main/Schemes/KEM/KEMPRF.scheme) is [correct](https://github.com/ProofFrog/examples/blob/main/Games/KEM/Correctness.game) | +| [KEMPRF_INDCPA](https://github.com/ProofFrog/examples/blob/main/Proofs/KEM/KEMPRF_INDCPA.proof) | [KEMPRF](https://github.com/ProofFrog/examples/blob/main/Schemes/KEM/KEMPRF.scheme) is [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/KEM/INDCPA_MultiChal.game) secure | +| [KEMPRF_INDCCA](https://github.com/ProofFrog/examples/blob/main/Proofs/KEM/KEMPRF_INDCCA.proof) | [KEMPRF](https://github.com/ProofFrog/examples/blob/main/Schemes/KEM/KEMPRF.scheme) is [IND-CCA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/KEM/INDCCA_MultiChal.game) secure | Rich example | +{: .table-labels } -## Textbook Exercises +--- -The following are ProofFrog proofs of exercises from [The Joy of Cryptography](https://joyofcryptography.com/). +## Research Applications -### Exercise 2.13 +### KEM Combiner (GHP18) -One-time secrecy of the double symmetric encryption scheme. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Book/2_Exercises/2_13.proof). +A ProofFrog formalization of the [KEM combiner](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/KEMCombiner.scheme) from Giacon, Heuer, and Poettering ([PKC 2018](https://eprint.iacr.org/2018/024)). The combiner encapsulates with two KEMs independently, obtaining {% katex %}(\mathit{ss}_1, c_1){% endkatex %} and {% katex %}(\mathit{ss}_2, c_2){% endkatex %}, then derives the combined shared secret as {% katex %}\mathit{ss} = F(\mathit{ss}_1, \mathit{ss}_2, \mathit{pk}_1 \| c_1 \| \mathit{pk}_2 \| c_2){% endkatex %} using a [two-key PRF](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/TwoKeyPRF.primitive) {% katex %}F{% endkatex %}. The combined KEM is secure as long as **at least one** of the component KEMs is secure. -### Exercise 2.14 +See the [full README](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/README.md) for construction details and a list of all files. -An alternative characterization of one-time secrecy. Proved in both directions: -[forward](https://github.com/ProofFrog/examples/blob/main/Book/2_Exercises/2_14_Forward.proof) and -[backward](https://github.com/ProofFrog/examples/blob/main/Book/2_Exercises/2_14_Backward.proof). +| Proof | Description | | +|:------|:------------|-| +| [GHP18_Correctness](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/GHP18_Correctness.proof) | The [KEM combiner](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/KEMCombiner.scheme) is correct | +| [GHP18_INDCPA1](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/GHP18_INDCPA1.proof) | [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/KEM/INDCPA_MultiChal.game) security from security of the first component KEM | Rich example | +| [GHP18_INDCPA2](https://github.com/ProofFrog/examples/blob/main/applications/KEMCombiner-GHP18/GHP18_INDCPA2.proof) | [IND-CPA (multi-challenge)](https://github.com/ProofFrog/examples/blob/main/Games/KEM/INDCPA_MultiChal.game) security from security of the second component KEM | Rich example | +{: .table-labels } -### Exercise 2.15 +--- -Another alternative characterization of one-time secrecy. Proved in both directions: -[forward](https://github.com/ProofFrog/examples/blob/main/Book/2_Exercises/2_15_Forward.proof) and -[backward](https://github.com/ProofFrog/examples/blob/main/Book/2_Exercises/2_15_Backward.proof). +## Proof Ladders -### Exercise 5.8 +The [Proof Ladders project](https://proof-ladders.github.io/) includes an example showing CPA security of KEM-DEM in both [ProofFrog](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/ProofFrog) and [EasyCrypt](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/EasyCrypt), which is helpful for seeing how proofs in ProofFrog compare to proofs in more advanced formal verification tools like EasyCrypt. Note that that version of the ProofFrog KEM-DEM proof uses a slightly different formulation compared to the example linked earlier on this page. -Security of various PRG constructions. Parts: -[a](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_8_a.proof), -[b](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_8_b.proof), -[e](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_8_e.proof), -[f](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_8_f.proof). -Also, [Pseudo-OTP has OTUC](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_8_PseudoOTP_OTUC.proof) (used in part e). +--- -### Exercise 5.10 +## Old Joy of Cryptography Exercises (PDF Preview Edition) -Security of a PRG construction. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Book/5_Exercises/5_10.proof). +The [`examples/joy_old`](https://github.com/ProofFrog/examples/tree/main/joy_old) directory contains ProofFrog proofs of selected exercises from the older PDF preview edition of [The Joy of Cryptography](https://joyofcryptography.com/). These use an older syntax; for new work, prefer the examples in [`examples/joy`](https://github.com/ProofFrog/examples/tree/main/joy) above. -### Exercise 7.13 +| Exercise | Description | Proof | +|----------|-------------|-------| +| Claim 2.13 | Double one-time pad has OTUC | [2_13](https://github.com/ProofFrog/examples/blob/main/joy_old/2/2_13.proof) | +| Claim 5.4 | Pseudo one-time pad has OTUC | [5_3](https://github.com/ProofFrog/examples/blob/main/joy_old/5/5_3.proof) | +| Exercise 2.13 | One-time secrecy of the double symmetric encryption scheme | [2_13](https://github.com/ProofFrog/examples/blob/main/joy_old/2_Exercises/2_13.proof) | +| Exercise 2.14 | Alternative characterization of one-time secrecy | [forward](https://github.com/ProofFrog/examples/blob/main/joy_old/2_Exercises/2_14_Forward.proof), [backward](https://github.com/ProofFrog/examples/blob/main/joy_old/2_Exercises/2_14_Backward.proof) | +| Exercise 2.15 | Another alternative characterization of one-time secrecy | [forward](https://github.com/ProofFrog/examples/blob/main/joy_old/2_Exercises/2_15_Forward.proof), [backward](https://github.com/ProofFrog/examples/blob/main/joy_old/2_Exercises/2_15_Backward.proof) | +| Exercise 5.8 | Security of PRG constructions | [a](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_8_a.proof), [b](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_8_b.proof), [e](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_8_e.proof), [f](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_8_f.proof); also [Pseudo-OTP OTUC](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_8_PseudoOTP_OTUC.proof) | +| Exercise 5.10 | Security of a PRG construction | [5_10](https://github.com/ProofFrog/examples/blob/main/joy_old/5_Exercises/5_10.proof) | +| Exercise 7.13 | Alternative characterization of CPA security | [forward](https://github.com/ProofFrog/examples/blob/main/joy_old/7_Exercises/7_13_Forward.proof), [backward](https://github.com/ProofFrog/examples/blob/main/joy_old/7_Exercises/7_13_Backward.proof) | +| Exercise 9.6 | CCA$ security implies CCA security | [9_6](https://github.com/ProofFrog/examples/blob/main/joy_old/9_Exercises/9_6_CCA%24impliesCCA.proof) | -An alternative characterization of CPA security. Proved in both directions: -[forward](https://github.com/ProofFrog/examples/blob/main/Book/7_Exercises/7_13_Forward.proof) and -[backward](https://github.com/ProofFrog/examples/blob/main/Book/7_Exercises/7_13_Backward.proof). +--- -### Exercise 9.6 +## External Uses of ProofFrog -CCA$ security implies CCA security. The proof file can be found [here](https://github.com/ProofFrog/examples/blob/main/Book/9_Exercises/9_6_CCA%24impliesCCA.proof). +A list of external projects and papers using ProofFrog is maintained on the [external uses page]({% link researchers/external-uses.md %}). diff --git a/getting-started.md b/getting-started.md deleted file mode 100644 index bc49817..0000000 --- a/getting-started.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Getting Started -layout: default -nav_order: 2 ---- - -# Getting Started - -## Installation - -Requires **Python 3.11+**. - -### From PyPI - -```txt -python3 -m venv .venv -source .venv/bin/activate -pip install proof_frog -``` - -After installing ProofFrog via pip, you may want to download the examples repository: - -```txt -git clone https://github.com/ProofFrog/examples -``` - -### From source - -```txt -git clone https://github.com/ProofFrog/ProofFrog -cd ProofFrog -git submodule update --init -python3 -m venv .venv -source .venv/bin/activate -pip install -e . -pip install -r requirements-dev.txt -``` - -## Web Interface - -```txt -proof_frog web [directory] -``` - -Starts a local web server (default port 5173) and opens the editor in your browser. The `[directory]` argument specifies the working directory for proof files; it defaults to the current directory. - -The web interface lets you edit ProofFrog files with syntax highlighting, validate proofs from the web editor, and explore the game state at each hop. - -## Command-Line Interface - -| Command | Description | -|---------|-------------| -| `proof_frog parse ` | Parse a file and print its AST representation | -| `proof_frog check ` | Type-check a file for well-formedness | -| `proof_frog prove ` | Verify a game-hopping proof (`-v` for verbose output) | -| `proof_frog web [dir]` | Launch the browser-based editor | - -## Writing a Proof - -See the [guide to writing a proof in ProofFrog]({% link guide.md %}) for a tutorial on writing proofs in ProofFrog, covering all four file types (primitives, games, schemes, proofs) with examples, and the proof structure. - -## Examples - -The [examples repository](https://github.com/ProofFrog/examples) contains primitives, schemes, games, and proofs largely adapted from [*The Joy of Cryptography*](https://joyofcryptography.com/) by Mike Rosulek. See the [Examples]({% link examples.md %}) page for a full list. - -For example: - -```txt -git clone https://github.com/ProofFrog/examples.git -proof_frog prove 'examples/Proofs/SymEnc/OTUCimpliesOTS.proof' -``` - -## Vibe-Coding a Proof - -See the information from [HACS 2026 on vibe-coding a ProofFrog proof with Claude Code]({% link hacs-2026/vibe/index.md %}). diff --git a/guide.md b/guide.md deleted file mode 100644 index e5ca36f..0000000 --- a/guide.md +++ /dev/null @@ -1,795 +0,0 @@ ---- -title: Guide to Writing a Proof -layout: default -nav_order: 3 ---- - -# Guide to Writing and Testing Proofs in ProofFrog - -This guide walks through writing each component of a cryptographic game-hopping proof in ProofFrog's domain-specific language, **FrogLang**, and testing them with the CLI. - -Proofs in ProofFrog strictly follow the distinguishing game hopping paradigm exemplified in [Mike Rosulek's *Joy of Cryptography*](https://joyofcryptography.com/). - -## Overview - -A game-hopping proof in ProofFrog involves four file types, each building on the previous: - -1. **Primitives** (`.primitive`) -- abstract cryptographic interfaces -2. **Games** (`.game`) -- security properties defined as pairs of games -3. **Schemes** (`.scheme`) -- concrete instantiations of primitives -4. **Proofs** (`.proof`) -- game-hopping proof scripts - -All files use C-style syntax with curly braces, semicolons, and `//` line comments. Only ASCII characters are allowed. - -## FrogLang Type System - -Before diving into file types, here is a summary of the types available in FrogLang. - -### Basic types - -| Type | Description | -|------|-------------| -| `Int` | Integer | -| `Bool` | Boolean | -| `Void` | No return value (used for `Initialize` methods) | -| `BitString` | Bitstring of length `n` (where `n` is an integer expression) | -| `BitString` | Generic/unparameterized bitstring (used in primitive signatures) | - -### Composite types - -| Type | Description | -|------|-------------| -| `Set` | Set of elements of type `T` | -| `Map` | Map from keys of type `K` to values of type `V` | -| `Array` | Array of `n` elements of type `T` | -| `T1 * T2` | Product (tuple) type | -| `T?` | Optional type (nullable) | - -### Type aliases - -Primitives and schemes define named sets (e.g., `Set Key = BitString`). This can be referenced as `PrimitiveName.Key` from other files. - -### Operators - -| Operator | Description | -|----------|-------------| -| `+` | XOR (on bitstrings) or addition (on integers) | -| `||` | Concatenation | -| `[i : j]` | Slice from index `i` to `j` | -| `[i]` | Indexing | -| `<-` | Uniform random sampling | -| `==`, `!=`, `<`, `>`, `<=`, `>=` | Comparison | -| `&&`, `||`, `!` | Logical operators | -| `in` | Membership test | -| `|expr|` | Size/cardinality | - -## 1. Primitives - -A **primitive** defines an abstract cryptographic interface: the sets involved and the method signatures, with no implementations. - -### Syntax - -```prooffrog -Primitive Name(parameters) { - // Field definitions (with initialization) - Type fieldName = expression; - - // Method signatures (no bodies) - ReturnType MethodName(ParamType param, ...); -} -``` - -### Parameters - -Parameters can be: -- `Int name` -- an integer parameter (e.g., security parameter, key length) -- `Set name` -- a generic set parameter (for abstract message/key spaces) - -### Example: Pseudorandom Generator - -```prooffrog -Primitive PRG(Int lambda, Int stretch) { - Int lambda = lambda; - Int stretch = stretch; - - BitString evaluate(BitString x); -} -``` - -A PRG takes a seed of `lambda` bits and stretches it to `lambda + stretch` bits. The field definitions expose the parameters so that schemes and games can reference `G.lambda` and `G.stretch`. - -### Example: Symmetric Encryption - -```prooffrog -Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { - Set Message = MessageSpace; - Set Ciphertext = CiphertextSpace; - Set Key = KeySpace; - - Key KeyGen(); - Ciphertext Enc(Key k, Message m); - Message? Dec(Key k, Ciphertext c); -} -``` - -Note the `Message?` optional return type on `Dec` -- decryption can fail and return nothing. - -### Example: Pseudorandom Function - -```prooffrog -Primitive PRF(Int lambda, Int in, Int out) { - Int lambda = lambda; - Int in = in; - Int out = out; - - BitString evaluate(BitString seed, BitString input); -} -``` - -### Example: Message Authentication Code - -```prooffrog -Primitive MAC(Set MessageSpace, Set TagSpace, Set KeySpace) { - Set Message = MessageSpace; - Set Tag = TagSpace; - Set Key = KeySpace; - - Key KeyGen(); - Tag MAC(Key k, Message m); -} -``` - -### Testing a primitive - -```bash -proof_frog parse examples/Primitives/PRG.primitive # check syntax -proof_frog check examples/Primitives/PRG.primitive # type-check -``` - -## 2. Games - -A **game** file defines a **security property** as a pair of games. The adversary's goal is to distinguish between the two games. - -### Syntax - -```prooffrog -import 'relative/path/to/Primitive.primitive'; - -Game SideName1(parameters) { - // Optional state fields - Type fieldName; - - // Optional Initialize method (run once) - Void Initialize() { ... } - - // Oracle methods (called by the adversary) - ReturnType OracleName(ParamType param, ...) { ... } -} - -Game SideName2(parameters) { - // Same method signatures as SideName1 - ... -} - -export as SecurityPropertyName; -``` - -### Key rules - -- A game file must contain exactly **two games** with the same method signatures. -- Common naming conventions for the two sides: `Left`/`Right`, `Real`/`Random`, or `Real`/`Fake`. -- The `export as Name;` statement at the end defines the security property's name. -- Imports use **file-relative paths** (relative to the importing file's directory). - -### Statements in method bodies - -```prooffrog -Type var = expr; // declare and assign -Type var <- Type; // uniform random sampling -Type var; // declare without initializing -lvalue = expr; // assignment -return expr; // return a value -if (condition) { ... } // conditional -if (condition) { ... } else { ... } -for (Int i = start to end) { ... } // numeric for loop -for (Type x in expr) { ... } // iteration -``` - -### Example: PRG Security - -```prooffrog -import '../../Primitives/PRG.primitive'; - -Game Real(PRG G) { - BitString Query() { - BitString s <- BitString; - return G.evaluate(s); - } -} - -Game Random(PRG G) { - BitString Query() { - BitString r <- BitString; - return r; - } -} - -export as Security; -``` - -In the `Real` game, the adversary receives the output of the PRG on a random seed. In the `Random` game, the adversary receives a truly random bitstring. PRG security means these are indistinguishable. - -### Example: One-Time Secrecy - -```prooffrog -import '../../Primitives/SymEnc.primitive'; - -Game Left(SymEnc E) { - E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { - E.Key k = E.KeyGen(); - E.Ciphertext c = E.Enc(k, mL); - return c; - } -} - -Game Right(SymEnc E) { - E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { - E.Key k = E.KeyGen(); - E.Ciphertext c = E.Enc(k, mR); - return c; - } -} - -export as OneTimeSecrecy; -``` - -The adversary submits two messages and receives an encryption of either the left or the right one. One-time secrecy means the adversary cannot tell which. - -### Example: CPA Security (with state) - -```prooffrog -import '../../Primitives/SymEnc.primitive'; - -Game Left(SymEnc E) { - E.Key k; - Void Initialize() { - k = E.KeyGen(); - } - E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { - return E.Enc(k, mL); - } -} - -Game Right(SymEnc E) { - E.Key k; - Void Initialize() { - k = E.KeyGen(); - } - E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { - return E.Enc(k, mR); - } -} - -export as CPA; -``` - -CPA security is like one-time secrecy but the key persists across multiple queries (via the `Initialize` method and state field `k`). - -### Helper games - -Some games capture simple probabilistic facts rather than cryptographic security properties. These are placed in `Games/Misc/` and often appear as assumptions in proofs. - -**BitStringSampling**: Concatenating two independent random samples is the same as sampling one longer bitstring. - -```prooffrog -Game Concatenate(Int len1, Int len2) { - BitString Query() { - BitString x <- BitString; - BitString y <- BitString; - return x || y; - } -} - -Game SampleDirectly(Int len1, Int len2) { - BitString Query() { - BitString value <- BitString; - return value; - } -} - -export as BitStringSampling; -``` - -**OTPUniform**: XOR-ing a message with a random key produces a uniform random ciphertext. - -```prooffrog -Game Real(Int lambda) { - BitString CTXT(BitString m) { - BitString k <- BitString; - return k + m; - } -} - -Game Random(Int lambda) { - BitString CTXT(BitString m) { - BitString c <- BitString; - return c; - } -} - -export as OTPUniform; -``` - -### Testing a game - -```bash -proof_frog parse examples/Games/PRG/Security.game -proof_frog check examples/Games/PRG/Security.game -``` - -The `check` command verifies that both games have matching method signatures, correct types, and well-formed expressions. - -## 3. Schemes - -A **scheme** is a concrete instantiation of a primitive. It provides implementations for all methods declared in the primitive. - -### Syntax - -```prooffrog -import 'relative/path/to/Primitive.primitive'; - -Scheme Name(parameters) extends PrimitiveName { - // Optional preconditions - requires expression; - - // Field definitions (set type aliases, integer constants) - Set Key = BitString; - Int someConstant = 42; - - // Method implementations - ReturnType MethodName(ParamType param, ...) { - ... - } -} -``` - -### Key rules - -- `extends PrimitiveName` links the scheme to the primitive it instantiates. -- The scheme must implement all methods declared in the primitive. -- Parameters can include primitives (to build generic constructions), integers, and sets. -- `requires` clauses specify preconditions on parameters (e.g., matching key sizes). -- Field definitions typically assign concrete types to the abstract sets from the primitive. - -### Example: One-Time Pad - -```prooffrog -import '../../Primitives/SymEnc.primitive'; - -Scheme OTP(Int lambda) extends SymEnc { - Set Key = BitString; - Set Message = BitString; - Set Ciphertext = BitString; - - Key KeyGen() { - Key k <- Key; - return k; - } - - Ciphertext Enc(Key k, Message m) { - return k + m; - } - - Message Dec(Key k, Ciphertext c) { - return k + c; - } -} -``` - -The OTP scheme sets all three spaces to `BitString`. Key generation samples a random key, encryption and decryption are both XOR. - -### Example: TriplingPRG (generic construction) - -```prooffrog -import '../../Primitives/PRG.primitive'; - -Scheme TriplingPRG(PRG G) extends PRG { - requires G.lambda == G.stretch; - - Int lambda = G.lambda; - Int stretch = 2 * G.lambda; - - BitString evaluate(BitString s) { - BitString<2 * lambda> result1 = G.evaluate(s); - BitString x = result1[0 : lambda]; - BitString y = result1[lambda : 2*lambda]; - BitString<2 * lambda> result2 = G.evaluate(y); - - return x || result2; - } -} -``` - -This builds a PRG with stretch `2 * lambda` from a PRG with stretch `lambda`. It applies the underlying PRG twice and concatenates parts of the results. The `requires` clause ensures the underlying PRG has equal seed length and stretch. - -### Testing a scheme - -```bash -proof_frog parse examples/Schemes/SymEnc/OTP.scheme -proof_frog check examples/Schemes/SymEnc/OTP.scheme -``` - -The `check` command verifies that the scheme correctly implements the primitive's interface and that all types are consistent. - -## 4. Proofs - -A **proof** file is a game-hopping proof that a scheme satisfies a security property, possibly under assumptions about underlying primitives. - -### Structure - -A proof file has two main sections: - -1. **Proof helpers** (before the `proof:` keyword): `Reduction` and intermediate `Game` definitions -2. **Proof block** (after `proof:`): the `let:`, `assume:`, `theorem:`, and `games:` sections - -```prooffrog -import '...'; - -// Reductions and intermediate games go here (before proof:) - -Reduction R1(params) compose SecurityGame(params) - against TheoremGame(params).Adversary { - // Oracle implementations -} - -Game IntermediateGame(params) { - // Game body -} - -proof: - -let: - // Declare sets, integers, instantiate schemes - Int lambda; - PRG G = PRG(lambda, lambda); - MyScheme S = MyScheme(G); - -assume: - // Security assumptions on underlying schemes - Security(G); - -theorem: - // What we want to prove - TargetSecurity(S); - -games: - // Sequence of game hops - TargetSecurity(S).Left against TargetSecurity(S).Adversary; - ... - TargetSecurity(S).Right against TargetSecurity(S).Adversary; -``` - -### The `let:` section - -Declares the mathematical objects used in the proof: - -```prooffrog -let: - Set MessageSpace; // abstract set - Int lambda; // integer parameter - PRG G = PRG(lambda, lambda); // scheme instantiation - SymEnc E = OTP(lambda); // concrete scheme -``` - -### The `assume:` section - -Lists the security assumptions (games that are assumed to hold for underlying schemes): - -```prooffrog -assume: - Security(G); // PRG security - BitStringSampling(lambda, lambda); // helper assumption -``` - -Helper assumptions from `Games/Misc/` (like `BitStringSampling` and `OTPUniform`) capture simple probabilistic facts and can be freely added as needed. - -### The `theorem:` section - -States the security property to prove: - -```prooffrog -theorem: - OneTimeSecrecy(proofE); -``` - -### The `games:` section - -Lists a sequence of games. The first game must be one side of the theorem's security property composed with the adversary, and the last game must be the other side. - -Each adjacent pair of games must be justified as either: -- **Interchangeable**: the two games are code-equivalent (verified automatically by the ProofFrog engine) -- **An assumption hop**: one game is obtained from the other by swapping the two sides of an assumed security property - -### Game step syntax - -Each step in the `games:` list takes one of two forms: - -```prooffrog -// Direct game (no reduction) -GameProperty(params).Side against GameProperty(params).Adversary; - -// Game composed with a reduction -GameProperty(params).Side compose ReductionName(params) - against GameProperty(params).Adversary; -``` - -### Reductions - -A **reduction** is a wrapper that composes an adversary for the theorem's security property with a game for an assumed security property. It acts as an adapter between the two interfaces. - -```prooffrog -Reduction R(params) compose AssumedGame(params) - against TheoremGame(params).Adversary { - ReturnType OracleName(params) { - // Implement the theorem game's oracle interface - // using challenger.Method() to call the assumed game's oracles - return challenger.Query(...); - } -} -``` - -Inside a reduction: -- `challenger` refers to the assumed security game (the one after `compose`). -- The reduction implements the oracle methods of the theorem game. -- The parameter list must include every parameter needed to instantiate the composed game, even if not directly used in the reduction body. - -### The four-step reduction pattern - -Each use of a reduction in the games sequence follows a standard four-step pattern: - -```prooffrog -games: - ... - // Step 1: Interchangeability - G_A against Adversary; - - // Step 2: Interchangeability (with reduction composed with one side) - AssumedGame.Side1 compose R against Adversary; - - // Step 3: Assumption hop (swap Side1 for Side2) - AssumedGame.Side2 compose R against Adversary; - - // Step 4: Interchangeability (back to a direct game) - G_B against Adversary; - ... -``` - -Steps 1-2 and 3-4 are verified as interchangeable by the engine. Step 2-3 is justified by the assumption. Assumption hops are bidirectional -- you can go from `Side1` to `Side2` or from `Side2` to `Side1`. - -### Example: OTUC implies One-Time Secrecy - -This proof shows that if a symmetric encryption scheme has one-time uniform ciphertexts (OTUC), then it also has one-time secrecy (OTS). - -```prooffrog -import '../../Primitives/SymEnc.primitive'; -import '../../Games/SymEnc/OneTimeSecrecy.game'; -import '../../Games/SymEnc/OneTimeUniformCiphertexts.game'; - -// R1 forwards the left message to OTUC -Reduction R1(SymEnc se) compose OneTimeUniformCiphertexts(se) - against OneTimeSecrecy(se).Adversary { - se.Ciphertext Eavesdrop(se.Message mL, se.Message mR) { - return challenger.CTXT(mL); - } -} - -// R2 forwards the right message to OTUC -Reduction R2(SymEnc se2) compose OneTimeUniformCiphertexts(se2) - against OneTimeSecrecy(se2).Adversary { - se2.Ciphertext Eavesdrop(se2.Message mL, se2.Message mR) { - return challenger.CTXT(mR); - } -} - -proof: - -let: - Set ProofMessageSpace; - Set ProofCiphertextSpace; - Set ProofKeySpace; - SymEnc proofE = SymEnc(ProofMessageSpace, ProofCiphertextSpace, ProofKeySpace); - -assume: - OneTimeUniformCiphertexts(proofE); - -theorem: - OneTimeSecrecy(proofE); - -games: - // Start: OTS Left game - OneTimeSecrecy(proofE).Left against OneTimeSecrecy(proofE).Adversary; - - // Interchangeability: inline OTS.Left into R1 - OneTimeUniformCiphertexts(proofE).Real compose R1(proofE) - against OneTimeSecrecy(proofE).Adversary; - - // By assumption: OTUC Real -> Random - OneTimeUniformCiphertexts(proofE).Random compose R1(proofE) - against OneTimeSecrecy(proofE).Adversary; - - // Interchangeability: R1 and R2 differ only in unused argument - OneTimeUniformCiphertexts(proofE).Random compose R2(proofE) - against OneTimeSecrecy(proofE).Adversary; - - // By assumption: OTUC Random -> Real - OneTimeUniformCiphertexts(proofE).Real compose R2(proofE) - against OneTimeSecrecy(proofE).Adversary; - - // Interchangeability: inline back to OTS Right game - OneTimeSecrecy(proofE).Right against OneTimeSecrecy(proofE).Adversary; -``` - -**Proof idea**: The left and right games encrypt `mL` and `mR` respectively. By using the OTUC assumption (which says real ciphertexts are indistinguishable from random), we can replace the real ciphertext with a random one. Once the ciphertext is random, it doesn't matter whether `mL` or `mR` was "encrypted", so we can switch between them. - -### Example: TriplingPRG Security - -A more complex proof showing that the tripling PRG construction is secure if the underlying PRG is secure. - -```prooffrog -import '../../Primitives/PRG.primitive'; -import '../../Schemes/PRG/TriplingPRG.scheme'; -import '../../Games/Misc/BitStringSampling.game'; -import '../../Games/PRG/Security.game'; - -Reduction R1(PRG G, TriplingPRG T) compose Security(G) - against Security(T).Adversary { - BitString Query() { - BitString<2 * T.lambda> result1 = challenger.Query(); - BitString x = result1[0 : T.lambda]; - BitString y = result1[T.lambda : 2*T.lambda]; - BitString<2 * T.lambda> result2 = G.evaluate(y); - return x || result2; - } -} - -Reduction R2(PRG G, TriplingPRG T, Int lambda) compose BitStringSampling(lambda, lambda) - against Security(T).Adversary { - BitString Query() { - BitString<2 * T.lambda> result1 = challenger.Query(); - BitString x = result1[0 : T.lambda]; - BitString y = result1[T.lambda : 2*T.lambda]; - BitString<2 * T.lambda> result2 = G.evaluate(y); - return x || result2; - } -} - -Reduction R3(PRG G, TriplingPRG T) compose Security(G) - against Security(T).Adversary { - BitString Query() { - BitString x <- BitString; - BitString<2 * T.lambda> result2 = challenger.Query(); - return x || result2; - } -} - -Reduction R4(TriplingPRG T) compose BitStringSampling(T.lambda, 2 * T.lambda) - against Security(T).Adversary { - BitString Query() { - return challenger.Query(); - } -} - -proof: - -let: - Int lambda; - PRG G = PRG(lambda, lambda); - TriplingPRG T = TriplingPRG(G); - -assume: - Security(G); - BitStringSampling(lambda, lambda); - BitStringSampling(lambda, 2 * lambda); - -theorem: - Security(T); - -games: - Security(T).Real against Security(T).Adversary; - - // First PRG application: replace G.evaluate(s) with random - Security(G).Real compose R1(G, T) against Security(T).Adversary; - Security(G).Random compose R1(G, T) against Security(T).Adversary; - - // Sampling: concat of two random strings = one longer random string - BitStringSampling(lambda, lambda).SampleDirectly compose R2(G, T, lambda) - against Security(T).Adversary; - BitStringSampling(lambda, lambda).Concatenate compose R2(G, T, lambda) - against Security(T).Adversary; - - // Second PRG application: replace G.evaluate(y) with random - Security(G).Real compose R3(G, T) against Security(T).Adversary; - Security(G).Random compose R3(G, T) against Security(T).Adversary; - - // Final sampling: concat of random strings = one long random string - BitStringSampling(lambda, 2 * lambda).Concatenate compose R4(T) - against Security(T).Adversary; - BitStringSampling(lambda, 2 * lambda).SampleDirectly compose R4(T) - against Security(T).Adversary; - - Security(T).Random against Security(T).Adversary; -``` - -**Proof idea**: The TriplingPRG applies the underlying PRG twice. We replace each PRG call with random output one at a time (using the PRG security assumption), and use the BitStringSampling helper to simplify concatenations of independent random samples into single random samples. - -## Testing and Verification - -### CLI commands - -| Command | Description | -|---------|-------------| -| `proof_frog parse ` | Check syntax (works on any file type) | -| `proof_frog check ` | Type-check and semantic analysis | -| `proof_frog prove ` | Verify a proof (only for `.proof` files) | -| `proof_frog prove -v ` | Verbose proof output (shows canonical forms) | -| `proof_frog describe ` | Show interface summary | - -### Development workflow - -1. **Write the primitive** and test with `parse` then `check`. -2. **Write the security games** and test with `parse` then `check`. The checker verifies both games have matching signatures. -3. **Write the scheme** and test with `parse` then `check`. The checker verifies the scheme correctly implements the primitive's methods. -4. **Write the proof** incrementally: - - Start with the `let:`, `assume:`, and `theorem:` sections. - - Add the first and last game steps (the two sides of the theorem). - - Fill in intermediate steps and reductions one hop at a time. - - Run `proof_frog prove ` after each change to see which hops pass. - -### Interpreting proof output - -When you run `proof_frog prove`, each hop is reported as either valid or invalid: - -``` -Step 1 (interchangeability): VALID -Step 2 (assumption): VALID -Step 3 (interchangeability): VALID -... -``` - -If a step fails, use `proof_frog prove -v` to see the canonical forms of both games. The engine simplifies each game to a canonical form and compares them structurally. Differences in the canonical forms show what needs to change. - -### Web interface - -For an interactive experience, launch the web editor: - -```bash -proof_frog web examples/ -``` - -This opens a browser-based editor at `http://localhost:5173` with syntax highlighting, file browsing, and buttons to parse, check, and prove files directly. - -## Writing Tips - -### Import paths - -Imports are **file-relative**: the path is resolved relative to the directory containing the importing file. Examples from files in `examples/Proofs/SymEnc/`: - -```prooffrog -import '../../Primitives/SymEnc.primitive'; // up two levels, into Primitives/ -import '../../Games/SymEnc/OneTimeSecrecy.game'; // up two levels, into Games/SymEnc/ -``` - -### Reduction parameters - -A reduction's parameter list must include every parameter needed to instantiate the composed security game, even if that parameter is not referenced in the reduction body. For example, if a reduction composes with `BitStringSampling(lambda, lambda)`, it must take the `Int lambda` parameter (or a scheme whose fields provide it). - -### Common patterns - -- **Symmetric proofs**: Many proofs are symmetric around a midpoint. The first half transitions from the theorem's Left/Real game toward a "neutral" middle (often with all randomness replaced), and the second half transitions from the middle to the Right/Random game. -- **Helper assumptions**: The `Games/Misc/` directory contains helper games that capture basic probabilistic facts (e.g., `BitStringSampling`, `OTPUniform`). These can be assumed freely in proofs since they hold unconditionally. -- **Incremental development**: Build proofs one hop at a time. Write the reduction, add the corresponding game steps, and verify before moving on. - -## Further Resources - -- [Examples]({% link examples.md %}) -- many complete working examples -- [Mike Rosulek, *The Joy of Cryptography*](https://joyofcryptography.com/) -- the examples directory contains ProofFrog proofs corresponding to the textbook diff --git a/index.md b/index.md index f7f0a05..3478e66 100644 --- a/index.md +++ b/index.md @@ -12,31 +12,41 @@ nav_order: 1 **A tool for checking transitions in cryptographic game-hopping proofs.** -ProofFrog checks the validity of transitions in game-hopping proofs — the standard technique in provable security for showing that a cryptographic scheme satisfies a security property. Proofs are written in FrogLang, a domain-specific language for defining primitives, schemes, security games, and proofs. ProofFrog is [designed]({% link design.md %}) to handle introductory-level proofs, trading expressivity and power for ease of use. The ProofFrog engine checks each hop by manipulating abstract syntax trees into a canonical form, with some help from Z3 and SymPy. ProofFrog's engine does not have any formal guarantees: the soundness of its transformations has not been verified. +ProofFrog checks the validity of game hops for cryptographic game-hopping proofs in the reduction-based security paradigm: it checks that the starting and ending games match the security definition, and that each adjacent pair of games is either interchangeable (by code equivalence) or justified by a stated assumption. Proofs are written in FrogLang, a small C/Java-style domain-specific language designed to look like a pen-and-paper proof. ProofFrog can be used from the command line, a browser-based editor, or an MCP server for integration with AI coding assistants. ProofFrog is suitable for introductory level proofs, but is not as expressive for advanced concepts as other verification tools like EasyCrypt. -ProofFrog can be used via a command-line interface, a browser-based editor, or an MCP server for integration with AI coding assistants. +## Getting started -## Getting Started +**[Read the manual]({% link manual/index.md %})** for [installation]({% link manual/installation.md %}), [tutorials]({% link manual/tutorial/index.md %}), [worked examples]({% link manual/worked-examples/index.md %}), and a complete [language reference]({% link manual/language-reference/index.md %}). -ProofFrog is implemented in Python (3.11+) and can be installed using `pip`: +**[Browse the examples page]({% link examples.md %})** for a catalogue of proofs ProofFrog can currently analyze. -```txt +**Researchers**: see [the research section of this website]({% link researchers/index.md %}) for the scientific background, engine internals, soundness story, publications, and links to presentations/demos from past events. + +Checking your first proof is as easy as: + +```bash pip install proof_frog -git clone https://github.com/ProofFrog/examples # optionally download examples +git clone https://github.com/ProofFrog/examples +proof_frog prove examples/Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof ``` -See the [getting started page]({% link getting-started.md %}) for detailed installation options, the web interface, and CLI usage, or the [guide to writing proofs in ProofFrog]({% link guide.md %}). +See the [installation instructions]({% link manual/installation.md %}) for details. -A list of examples is given on the [Examples]({% link examples.md %}) page. +## Participate on GitHub -## Development +[ProofFrog's GitHub site](https://github.com/ProofFrog) is the place to go to download the ProofFrog source code and examples, [ask questions](https://github.com/orgs/ProofFrog/discussions), and contribute issues or pull requests. ProofFrog is released under the [MIT License](https://github.com/ProofFrog/ProofFrog/blob/main/LICENSE). -See the [GitHub repo](https://github.com/ProofFrog/ProofFrog) for source code and development information. ProofFrog is released under the MIT License. +## Recent updates -## Publications +- Mar. 6, 2026: [ProofFrog discussions and demos at HACS 2026](http://prooffrog.github.io/researchers/publications/hacs-2026/) +- **Mar. 5, 2026: Release of [ProofFrog version 0.3.1](https://github.com/ProofFrog/ProofFrog/releases/tag/v0.3.1)** featuring a web interface and engine updates + +## Acknowledgements ProofFrog was created by Ross Evans and Douglas Stebila, building on the pygamehop tool created by Douglas Stebila and Matthew McKague. For more information about ProofFrog's design, see [Ross Evans' master's thesis](https://uwspace.uwaterloo.ca/bitstream/handle/10012/20441/Evans_Ross.pdf) and [eprint 2025/418](https://eprint.iacr.org/2025/418). -NSERC logo +ProofFrog's syntax and approach to modelling is heavily inspired by Mike Rosulek's excellent book [*The Joy of Cryptography*](https://joyofcryptography.com/). We acknowledge the support of the Natural Sciences and Engineering Research Council of Canada (NSERC). + +NSERC logo diff --git a/manual/canonicalization.md b/manual/canonicalization.md new file mode 100644 index 0000000..9c39203 --- /dev/null +++ b/manual/canonicalization.md @@ -0,0 +1,583 @@ +--- +title: Canonicalization +layout: default +parent: Manual +nav_order: 40 +--- + +# Canonicalization +{: .no_toc } + +This page explains what the ProofFrog engine does between receiving a `.proof` file and +producing a green (valid) or red (failed) result for each hop. It is written for a +cryptographer who wants to understand why two games are being called interchangeable, or +who is diagnosing a hop that refuses to validate. + +- TOC +{:toc} + +--- + +## Overview + +`proof_frog prove` walks the `games:` list in a proof file and, for each adjacent pair of +games, does one of two things. If the pair corresponds to an assumption hop -- the two +games differ only in which side of an assumed security property is composed with a +reduction -- it checks that the assumption appears in the `assume:` section and accepts +the hop on that basis. Otherwise it treats the pair as an interchangeability hop: it +canonicalizes both games independently and compares their canonical forms. The +canonicalization step is a fixed-point loop of semantics-preserving abstract syntax tree (AST) rewrites (the +core pipeline) followed by a single-pass normalization (the standardization pipeline). +If the canonical forms are structurally identical, the hop is accepted; if not, the +engine additionally asks Z3 whether the two forms differ only in logically equivalent +branch conditions. + +The phrase "semantics-preserving" means preserving the probability distribution that the +game induces over adversary outputs, as defined by the FrogLang execution model in +[Execution Model]({% link manual/language-reference/execution-model.md %}). The engine is +deliberately limited to a fixed catalog of transformations it believes to be sound; it does +not discover or invent new algebraic moves. For a list of what the engine cannot do, see +[Limitations]({% link manual/limitations.md %}). + +--- + +## What the engine simplifies automatically + +The subsections below describe the main categories of canonicalization transforms the engine applies. +For each category a minimal before/after snippet illustrates the transform firing, a +real-proof pointer gives an example from the distribution, and a callout lists common +reasons the transform might not fire. + +### Inlining of method calls + +When a game calls a scheme method -- for example `E.Enc(k, m)` in a CPA game +instantiated with a concrete scheme -- the engine substitutes the method's body at the +call site. Local variables in the inlined body are renamed to avoid collisions, and +`this.Method(args)` references inside the method are rewritten to use the scheme +instance name before inlining, so they resolve correctly in the outer context. +Inlining runs in a fixed-point loop until no further method calls can be expanded; +this is guaranteed to terminate because FrogLang does not permit recursive methods. +After inlining, every game is expressed purely in terms of sampling statements, +arithmetic, map operations, and control flow -- no abstract method calls remain. + +```prooffrog +// Before: CPA game with scheme method call +E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + return E.Enc(k, mL); +} + +// After inlining OTP.Enc (which returns k + m): +E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + return k + mL; +} +``` + +Real-proof pointer: every proof in the distribution relies on inlining. +[`examples/Proofs/PRG/TriplingPRG_PRGSecurity.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/TriplingPRG_PRGSecurity.proof) is a clean illustration -- the +`TriplingPRG` scheme composes calls to an underlying `PRG`, and those calls are inlined +before the PRG reduction hops. + +{: .note } +**If this is not firing:** Check that the scheme is fully instantiated in the `let:` +block. If a method signature is abstract (no body), or if the `this.Method` reference +inside a scheme body is not resolved, inlining will stop at that call site. Running +`proof_frog prove -v` shows the inlined-but-not-yet-canonicalized form and will reveal +dangling call nodes. + +### Algebraic simplification on bitstrings + +The engine applies several identities for `BitString` arithmetic (XOR and +concatenation). + +**XOR cancellation:** `x + x` simplifies to `0^n`. The transform flattens the XOR chain +and removes pairs of identical terms. It guards against cancelling calls to +non-deterministic methods -- `F.Enc(k, x) + F.Enc(k, x)` is left alone unless `F.Enc` +is annotated `deterministic`. + +**XOR identity:** `x + 0^n` and `0^n + x` simplify to `x`. + +**Uniform-absorbs (one-time-pad move):** When `u` is sampled uniformly from +`BitString` and is used exactly once, the expression `u + m` (or `m + u`) simplifies +to just `u`. This formalizes the one-time-pad argument: XOR of a uniform value with any +independent value is uniform. + +```prooffrog +// Uniform-absorbs on bitstrings +BitString u <- BitString; +return u + m; +// simplifies to: +BitString u <- BitString; +return u; +``` + +**Concat/slice inverses:** `SimplifySplice` replaces `(x || y)[0 : n]` with `x` when the +slice boundaries align with the component widths, saving a round-trip through +concatenation and slicing. + +Real-proof pointer: [`examples/Proofs/SymEnc/SymEncPRF_INDCPA$_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDCPA%24_MultiChal.proof) -- the final +merge of two independent uniform samples into the CPA$ random game relies on XOR +absorption after the PRF is replaced by a random function. + +{: .note } +**If this is not firing:** The most common cause is that `u` is used more than once +(static use-count, including both branches of an `if/else`). The engine counts +statically and will not fire even if only one branch executes at runtime. Restructure so +that `u` appears in only one branch. A second cause is that the ADD is in a `ModInt` +context rather than a `BitString` context; the engine checks the type to distinguish +the two. + +### Algebraic simplification on `ModInt` + +The engine applies addition and multiplication identities for `ModInt`: additive +identity (`x + 0` -> `x`), multiplicative identity (`x * 1` -> `x`), multiplicative +zero (`x * 0` -> `0`), additive inverse (`x - x` -> `0`), and double negation. + +**Uniform-absorbs for ModInt:** When `u` is sampled uniformly from `ModInt` and is +used exactly once, the expression `u + m` or `u - m` (mod q) simplifies to `u`. This +is the modular-arithmetic analogue of the one-time-pad argument: adding a uniform +element modulo `q` with any fixed value is uniform. + +```prooffrog +// Uniform-absorbs on ModInt +ModInt u <- ModInt; +return u + m; +// simplifies to: +ModInt u <- ModInt; +return u; +``` + +Real-proof pointer: [`examples/Proofs/SymEnc/ModOTP_INDOT.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/ModOTP_INDOT.proof) uses the ModInt +one-time-pad argument directly. + +{: .note } +**If this is not firing:** Same static use-count caveat as for bitstrings. Also check +that the expression is in a `ModInt` context and not an `Int` context; symbolic +computation (SymPy) handles `Int` arithmetic separately. + +### Algebraic simplification on `GroupElem` + +The engine applies the following identities for group elements in `GroupElem`: + +- **Cancellation:** `x * m / x` simplifies to `m` in an abelian group. (Guards against + non-deterministic bases -- the bases must be structurally equal and deterministic.) +- **Exponent identities:** `g ^ 0` simplifies to `G.identity`; `g ^ 1` simplifies to + `g`. +- **Multiplicative identity:** `G.identity * g` and `g * G.identity` simplify to `g`; + `g / G.identity` simplifies to `g`. +- **Power-of-power:** `(h ^ a) ^ b` simplifies to `h ^ (a * b)`, when the exponent + types are compatible for multiplication. +- **Exponent combination:** `g ^ a * g ^ b` simplifies to `g ^ (a + b)`, and + `g ^ a / g ^ b` to `g ^ (a - b)`, when the bases are structurally identical and + deterministic. +- **Uniform-absorbs (group masking):** When `u` is sampled uniformly from `GroupElem` + and is used exactly once, the expressions `u * m`, `m * u`, `u / m`, and `m / u` + all simplify to `u`. Left and right multiplication (and their inverses under division) + are bijections on any finite group, so any of these forms preserves the uniform + distribution. + +```prooffrog +// Power-of-power and exponent combination +GroupElem h = G.generator ^ a; +return (h ^ b) * (h ^ c); +// simplifies to: +return G.generator ^ (a * b + a * c); +``` + +Real-proof pointer: [`examples/Proofs/PubKeyEnc/ElGamal_INDCPA_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/ElGamal_INDCPA_MultiChal.proof) uses the +group masking move (the DDH `Right` game replaces `pk ^ r` with a random group element `c`, +and the uniform-absorbs rule fires to simplify `mL * c` to `c`, making the ciphertext +independent of the message). + +{: .note } +**If this is not firing for uniform-absorbs:** Exponentiation (`^`) is deliberately +excluded from the group masking move -- `u ^ n` is not necessarily uniform (for example +if the group has even order and `n = 2`, squaring is not a bijection). Use only +`*` and `/` to trigger this rule. Also note the static single-use requirement: `u` +must not appear in both branches of a conditional. + +### Sample merge and sample split + +**Merge** (`MergeUniformSamples`): If two independent uniform samples `x <- BitString` +and `y <- BitString` are used only via their concatenation `x || y` and nowhere else, +they collapse to a single sample `z <- BitString`. Soundness: the concatenation +of independent uniforms is uniform of the combined length. + +**Split** (`SplitUniformSamples`): The reverse move. If a single sample `z <- +BitString` is accessed only via non-overlapping slices (for example +`z[0 : n]` and `z[n : n + m]`), it splits into independent samples for each slice. +Soundness: non-overlapping slices of a uniform bitstring are independent uniforms. + +These two moves are inverses and together allow the engine to match games that represent +the same randomness in different granularities. + +```prooffrog +// Sample merge +BitString x <- BitString; +BitString y <- BitString; +return x || y; +// simplifies to: +BitString x <- BitString; +return x; +``` + +```prooffrog +// Sample split +BitString z <- BitString; +return [z[0 : n], z[n : n + m]]; +// simplifies to: +BitString z_0 <- BitString; +BitString z_1 <- BitString; +return [z_0, z_1]; +``` + +Real-proof pointer: [`examples/Proofs/PRG/TriplingPRG_PRGSecurity.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/TriplingPRG_PRGSecurity.proof) uses sample split +to decompose a single PRG output into its two halves (each half is then used +independently as a new seed or output value, and the split enables the per-component +uniform reasoning). + +{: .note } +**If merge is not firing:** Every leaf variable in the concatenation must be used +exclusively in that concatenation -- any other reference (including assigning the +variable to a map, using it in a condition, or passing it to a method) blocks the merge. +Also, the operator is overloaded: `||` on `Bool` is logical OR, not concatenation; +the engine checks types and will not merge boolean expressions. If split is not firing, +check that all uses of the large sample are via slices with no bare references. + +### Random function on distinct inputs + +When a `Function` field is used as a random oracle (sampled via `<- Function`), and every call to it uses an argument that was sampled using `<-uniq[S]` from the +same persistent set field `S`, the engine replaces each `z = H(r)` with `z <- R` (an +independent uniform sample). Soundness: a truly random function evaluated on pairwise +distinct inputs produces independent uniform outputs -- this is a direct consequence of +the definition of a random function. + +In practice, the bookkeeping set `S` is almost always the random function's own implicit +`.domain` set -- for example `r <-uniq[RF.domain] BitString` guarantees that `r` has +not been queried before on `RF`, which is exactly the precondition this transform +requires. See [Function]({% link manual/language-reference/basics.md %}#functiond-r) +for details on `.domain`. + +A related transform, `FreshInputRFToUniform`, fires when the argument `v` to `H(v)` is +a `<-uniq`-sampled variable used solely in that single call: in that case the input is +structurally guaranteed to be fresh, and the RF call is replaced by a uniform sample. + +```prooffrog +// Before: RF on uniquely-sampled input +BitString r <-uniq[RF.domain] BitString; +BitString z = RF(r); + +// After: independent uniform sample +BitString r <-uniq[RF.domain] BitString; +BitString z <- BitString; +``` + +Real-proof pointer: [`examples/Proofs/PubKeyEnc/HashedElGamal_INDCPA_ROM_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HashedElGamal_INDCPA_ROM_MultiChal.proof) -- the proof +uses `FreshInputRFToUniform` (after the DDH hop places a uniform group element `c` in +the exclusion set) to collapse `H(c)` into a fresh uniform bitstring, which then masks +the message via XOR. + +{: .note } +**If this is not firing:** The exclusion set must be a game field, not a local variable +(a local set is re-initialized on each oracle call, losing the cross-call freshness +guarantee). All calls to the RF must use the same exclusion set. If the argument +variable appears more than once (including a second call to the same RF with the same +variable), the transform is blocked. The `<-uniq` sampling must be present; plain `<-` +does not trigger this rule. + +### `deterministic` and `injective` annotations + +**Same-method call deduplication** (`DeduplicateDeterministicCalls`): If a method +annotated `deterministic` on a primitive is called more than once with structurally +equal arguments, all occurrences are replaced with a single shared variable. This +implements common subexpression elimination for deterministic calls and eliminates the +need for explicit "IsDeterministic" assumption games that earlier versions of ProofFrog +required. + +**Cross-method field aliasing** (`CrossMethodFieldAlias`): If `Initialize` stores the +result of a `deterministic` call in a field (e.g., `pk = G.generator ^ sk`), and an +oracle method also calls the same function with the same arguments, the oracle's call is +replaced with the field reference. This connects the Initialize-time computation to the +oracle-time use. + +**Encoding-wrapper transparency** (`UniformBijectionElimination`): If a uniformly sampled +`BitString` variable is used exclusively wrapped in a single `deterministic injective` +method call with matching input and output type (`BitString` to `BitString`), the +wrapper is removed and the variable stands alone. Soundness: a deterministic injective +function on a finite set of the same cardinality is a bijection and hence preserves +uniformity. + +```prooffrog +// Before: duplicate deterministic call +BitString c1 = E.Enc(k, mL); +BitString c2 = E.Enc(k, mL); +return [c1, c2]; + +// After: shared variable +BitString v = E.Enc(k, mL); +return [v, v]; +``` + +Real-proof pointer: any proof where a deterministic scheme method is invoked twice +with identical arguments after inlining will exercise `DeduplicateDeterministicCalls` +-- typically visible in proofs where the same encryption or hash call appears on both +branches of a conditional or in two separate oracle bodies that reference the same +message. + +{: .note } +**If deduplication is not firing:** The method must be declared `deterministic` in the +primitive file. The engine checks this annotation; without it, the two calls are treated +as independent non-deterministic events that cannot be merged. If the annotation is +present but the arguments are not syntactically identical (for example, one side has +`k` and the other has a copy `k2 = k` that has not yet been inlined), run more inlining +passes or check for redundant copy variables. + +### Code simplification + +The engine applies a collection of standard program simplifications as part of the core +pipeline: + +- **Dead code elimination** (`RemoveUnnecessaryFields`, `RemoveUnreachable`): Unused + variables and fields are removed. Statements after all execution paths have returned + are dropped (Z3 assists with reachability under complex conditions). +- **Constant folding** (`SymbolicComputation`): Arithmetic sub-expressions involving + only known constant values are evaluated symbolically via SymPy. +- **Single-use variable inlining** (`InlineSingleUseVariable`, `SimplifyReturn`): + A variable declared and used exactly once is inlined at its use site. + `Type v = expr; return v;` collapses to `return expr;`. +- **Redundant copy elimination** (`RedundantCopy`): `Type v = w;` where `v` is just + a copy of `w` is eliminated and `w` used directly. +- **Branch elimination** (`BranchElimination`): `if (true) { ... }` and + `if (false) { ... }` are collapsed to their taken or dropped bodies. +- **Tuple index folding** (`FoldTupleIndex`): `[a, b][0]` folds to `a`, + `[a, b][1]` to `b`, and so on. + +These simplifications are individually simple but collectively they do a large amount of +work: after inlining a scheme into a game, the result is often cluttered with +intermediate variables, unreachable code, and trivially foldable branches. The +simplification passes clean all of that up before the final structural comparison. + +```prooffrog +// Before: redundant temporary and unreachable return +BitString v = k + m; +return v; +if (true) { return 0^n; } // unreachable after above return + +// After: +return k + m; +``` + +Real-proof pointer: dead code elimination is exercised in almost every proof. The +elimination of the random-function field after `UniqueRFSimplification` replaces all +its calls with uniform samples is a good representative case; see +[`examples/Proofs/SymEnc/SymEncPRF_INDCPA$_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDCPA%24_MultiChal.proof). + +{: .note } +**If a branch is not being eliminated:** The engine folds `if (true)` and `if (false)` +conditions, but it does not evaluate conditions that involve symbolic variables at the +structural level. If you expect a condition to simplify to a constant, check that all +relevant variables have been inlined first. If the condition involves Z3-provable facts +(rather than syntactic constants), see the next subsection. + +### SMT-assisted comparison of branch conditions + +After the core pipeline converges, if the two canonical game forms are structurally +identical except for the conditions in one or more `if` statements, the engine calls Z3 +to check whether the differing conditions are logically equivalent. If Z3 confirms +equivalence under the current proof context (including any `assume expr;` predicates +stated in the `games:` section), the hop is accepted. + +This allows the engine to validate hops where two games write the same guard condition +in syntactically different but logically equivalent forms -- for example `a != b` versus +`!(a == b)`, or a condition that simplifies under an inequality asserted in the proof +context. + +The `RemoveUnreachable` pass also uses Z3 to determine whether a statement after a +guarded return is reachable, allowing dead branches under non-trivially-false conditions +to be removed. + +Real-proof pointer: [`examples/Proofs/SymEnc/EncryptThenMAC_INDCCA_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/EncryptThenMAC_INDCCA_MultiChal.proof) uses phased +games with guard conditions that require Z3 to confirm equivalence after inlining. + +{: .note } +**If SMT comparison is not catching an expected equivalence:** Z3 reasons over the +quantifier-free fragment of linear arithmetic and equality; it does not handle +non-linear arithmetic or cryptographic assumptions by default. If the conditions involve +nonlinear constraints, you may need to introduce an intermediate game whose guard is +already in the simplified form. + +--- + +## Helper games -- doing things manually + +Some probabilistic facts cannot be derived purely from program structure: they require +an external statistical argument (typically a birthday bound or a random oracle +property). ProofFrog handles these via *helper games* -- ordinary `.game` files in +`examples/Games/Helpers/` that state a statistical equivalence as a security assumption. +The user invokes a helper game as an assumption hop in the same way as any other +assumption; the difference is that the helper game is not a hardness assumption but a +statistical argument that the proof author vouches for. + +Four helper games currently in the distribution are: + +### UniqueSampling + +**File:** [`examples/Games/Helpers/Probability/UniqueSampling.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/UniqueSampling.game) + +**What it states:** Sampling uniformly with replacement from a set `S` is +indistinguishable from sampling without replacement (exclusion sampling, `<-uniq`). +The `Replacement` game draws `val <- S`; the `NoReplacement` game draws +`val <-uniq[bookkeeping] S`. The statistical distinguishing advantage is bounded by +the guessing probability `|bookkeeping| / |S|`. + +**When to reach for it:** Whenever your proof needs to switch from plain uniform sampling +to `<-uniq` sampling (or back) so that the `UniqueRFSimplification` or +`FreshInputRFToUniform` transform can fire. The switch to `<-uniq` is the forward hop +(Replacement -> NoReplacement); after the random-function simplifications fire, the +switch back is the reverse hop. In reductions that compose with `UniqueSampling`, the +bookkeeping set is typically the random function's implicit `.domain` set -- for example, +the reduction calls `challenger.Samp(RF.domain)` to delegate sampling to the +`UniqueSampling` challenger while using `RF`'s query history as the exclusion set. + +```prooffrog +// Four-step pattern using UniqueSampling +G_before against Adversary; // interchangeability +UniqueSampling.Replacement compose R_Uniq against Adversary; // interchangeability +UniqueSampling.NoReplacement compose R_Uniq against Adversary; // by UniqueSampling +G_after against Adversary; // interchangeability +``` + +Real-proof pointer: used in [`examples/Proofs/SymEnc/SymEncPRF_INDCPA$_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDCPA%24_MultiChal.proof), +[`examples/Proofs/PubKeyEnc/HashedElGamal_INDCPA_ROM_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HashedElGamal_INDCPA_ROM_MultiChal.proof), [`examples/Proofs/Group/DDHMultiChal_implies_HashedDDHMultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDHMultiChal_implies_HashedDDHMultiChal.proof). + +### Regularity + +**File:** [`examples/Games/Hash/Regularity.game`](https://github.com/ProofFrog/examples/blob/main/Games/Hash/Regularity.game) + +**What it states:** Applying a hash function `H : D -> BitString` to a uniformly +sampled input from `D` is indistinguishable from sampling `BitString` uniformly. +The `Real` game returns `H(x)` for `x <- D`; the `Ideal` game returns `y <- BitString` +directly. This captures the standard-model randomness-extraction property of a +sufficiently regular hash function. + +**When to reach for it:** When `H` is a deterministic function (declared in the `let:` +block without sampling, so not a random oracle), and the proof requires treating the +output of `H` on a uniform input as uniformly random. This is the standard-model +counterpart to what `FreshInputRFToUniform` does automatically in the ROM. + +Real-proof pointer: used in [`examples/Proofs/Group/DDH_implies_HashedDDH.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDH_implies_HashedDDH.proof). + +### ROMProgramming + +**File:** [`examples/Games/Helpers/Probability/ROMProgramming.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/ROMProgramming.game) + +**What it states:** Programming a random function at a single target point with a fresh +uniform value is statistically equivalent to evaluating it naturally. The `Natural` game +returns `H(target)` directly; the `Programmed` game stores a fresh random `u` at +initialization time and returns `u` when queried at `target` and `H(x)` otherwise. +This equivalence is exact (perfect) when `H` is a truly random function. + +**When to reach for it:** When the proof needs to "program" a random oracle at the +challenge point -- replacing `H(target)` with an independently sampled value so that the +challenge ciphertext becomes statistically independent of the adversary's hash queries. +This is a standard technique in ROM proofs. + +Real-proof pointer: used in [`examples/Proofs/Group/CDH_implies_HashedDDH.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/CDH_implies_HashedDDH.proof). + +### RandomTargetGuessing + +**File:** [`examples/Games/Helpers/Probability/RandomTargetGuessing.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/RandomTargetGuessing.game) + +**What it states:** Comparing an adversary-supplied value against a hidden, uniformly +sampled target is indistinguishable from always returning false. The `Real` game samples +`target <- S` in `Initialize` and returns `c == target` on each `Eq` query; the `Ideal` +game always returns `false`. Any adversary distinguishes the two with advantage at most +`q / |S|`, where `q` is the number of queries. + +**When to reach for it:** When a game checks whether the adversary has guessed a secret +uniform value, and the proof argues that such a guess succeeds only with negligible +probability. + +Real-proof pointer: used in [`examples/Proofs/Group/DDH_implies_CDH.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/Group/DDH_implies_CDH.proof). + +{: .important } +Using a helper game adds to the trust base -- see the [Soundness]({% link researchers/soundness.md %}) page in the For Researchers area. + +--- + +## What the engine does not do + +The following things are outside the scope of automated canonicalization. Each item +links to [Limitations]({% link manual/limitations.md %}) for details and workarounds. + +- **No quantitative probability reasoning.** The engine does not compute or bound + advantage, security loss, or collision probabilities. Those quantities are the proof + author's responsibility and are stated as external arguments. See + [Limitations]({% link manual/limitations.md %}) for details. + +- **No nested induction.** The engine supports single-level hybrid arguments via the + [`induction` construct]({% link manual/language-reference/proofs.md %}#induction-hybrid-arguments), + but nested induction or induction with complex base cases must be decomposed into + multiple proof files via `lemma:`. See [Limitations]({% link manual/limitations.md %}) + for details. + +- **No reduction search.** When a proof hop requires a reduction to an underlying + hardness assumption, the user supplies the reduction code. The engine verifies that + the supplied reduction composed with each side of the assumption is interchangeable + with the adjacent games; it does not propose or construct reductions. See + [Limitations]({% link manual/limitations.md %}) for details. + +- **No always-recognition of stylistically different equivalent code.** Two games that + are semantically equivalent but written in stylistically different forms -- different + operand order in a `||` concatenation, different field declaration order, different + branch order in an `if/else if` -- may not be recognized as equivalent. The engine + normalizes commutative `+` and `*` chains but not `||` or `&&`. See + [Limitations]({% link manual/limitations.md %}) for details. + +--- + +## Diagnosing a failing hop + +When a proof step fails, the following recipe finds the problem in most cases. + +**Step 1: Get the canonical forms.** +Run `proof_frog prove -v` (see [CLI Reference]({% link manual/cli-reference.md %}#prove)) or, +in the browser, click the Inlined Game button for each of the two games in the failing +hop (see [Web Editor]({% link manual/web-editor.md %})). +The verbose output shows the fully canonicalized form of each game after the entire +pipeline has run. + +**Step 2: Find the first structural difference.** +Compare the two canonical forms side by side. The engine produces structurally sorted +output, so the first difference corresponds to the first point where the two games +diverge after all simplifications. Common differences include a leftover method call +that was not inlined (because a scheme field was not resolved), a variable that +was not simplified away (because the uniform-absorbs precondition was not met), or +a field that appears in one game but not the other (because dead-code elimination +fired asymmetrically). + +**Step 3: Apply one of three fixes.** + +- *Add an intermediate game.* If the two games are genuinely equivalent but the engine + cannot bridge the gap in one step, split the hop into two: write a new intermediate + game that is partway between them, so that each half-hop is within the engine's + capability. + +- *Add a helper assumption.* If the gap corresponds to a statistical fact (birthday + bound, hash-on-uniform, ROM programming), introduce the appropriate helper game from + `examples/Games/Helpers/` and use the four-step reduction pattern to cross the gap. + +- *Restructure existing code.* If the canonical forms differ only in ordering -- + operand order in a concatenation, field declaration order, branch order in an + if/else if -- adjust the intermediate game or the reduction body to match the + other side's ordering. The relevant known limitations are listed in + [Limitations]({% link manual/limitations.md %}). + +**Step 4: If you believe the engine should validate and it isn't, file an issue.** +Include the smallest proof file that reproduces the problem and the full output of +`proof_frog prove -v `. The canonical forms in the verbose output +are exactly what is compared, so they are the key evidence. Issues can be filed at +[https://github.com/ProofFrog/ProofFrog/issues](https://github.com/ProofFrog/ProofFrog/issues). + +For specific error messages and their meanings, see +[Troubleshooting]({% link manual/troubleshooting.md %}). diff --git a/manual/cli-reference.md b/manual/cli-reference.md new file mode 100644 index 0000000..f637c8c --- /dev/null +++ b/manual/cli-reference.md @@ -0,0 +1,295 @@ +--- +title: Command-Line Interface +layout: default +parent: Manual +nav_order: 60 +--- + +# CLI Reference + +The ProofFrog command-line interface (`proof_frog`) lets you parse, type-check, and verify cryptographic game-hopping proofs entirely from the terminal. Seven public commands cover the full workflow from inspecting files to running complete proof verification. If you prefer a graphical environment, a browser-based editor is also available via the `web` command described below. + +> **Activate your Python virtual environment first.** All of the commands below assume that the virtual environment in which ProofFrog was installed is activated in the current terminal session. If you opened a new terminal, re-activate it before running any `proof_frog` (or `python -m proof_frog`) command: +> +> - `source .venv/bin/activate` on macOS/Linux (bash/zsh) +> - `source .venv/bin/activate.fish` on fish +> - `.venv\Scripts\Activate.ps1` on Windows PowerShell +> +> See [Installation]({% link manual/installation.md %}) for details. A `command not found: proof_frog` error almost always means the virtual environment is not active. +{: .important } + +## Command Summary + +| Command | Description | +|---------|-------------| +| [`version`](#version) | Print the ProofFrog version. | +| [`parse`](#parse) | Parse a FrogLang file and pretty-print it. | +| [`check`](#check) | Type-check and semantically analyze a FrogLang file. | +| [`prove`](#prove) | Run proof verification on a `.proof` file. | +| [`describe`](#describe) | Print a concise interface description of a FrogLang file. | +| [`download-examples`](#download-examples) | Download the examples repository. | +| [`web`](#web) | Start the ProofFrog web interface. | + +--- + +## version + +### Synopsis + +``` +proof_frog version [OPTIONS] +``` + +### Behavior + +Prints the installed ProofFrog version string to standard output and exits. The output format is `ProofFrog `, for example `ProofFrog 0.4.0.dev0` on development builds. Use this to confirm which release is active in your environment or to include version information in bug reports. + +### Examples + +```bash +# Print the installed version +proof_frog version +``` + +Expected output (version may differ): + +``` +ProofFrog 0.4.0 +``` + +--- + +## parse + +### Synopsis + +``` +proof_frog parse [OPTIONS] FILE +``` + +### Behavior + +Parses any FrogLang source file (`.primitive`, `.scheme`, `.game`, or `.proof`) and prints a normalized source representation of the parsed file to standard output. The default output is a pretty-printed form (useful for confirming the parser saw what you intended); the `--json` flag instead emits a JSON-encoded AST suitable for tooling. This command is mainly useful for debugging grammar issues. If the file cannot be parsed, ProofFrog prints an error with the offending line and column and exits with a non-zero status. + +### Options + +| Flag | Description | +|------|-------------| +| `-j`, `--json` | Output JSON instead of the default text representation. | + +### Examples + +```bash +# Parse a primitive definition +proof_frog parse examples/Primitives/PRG.primitive + +# Parse a scheme and get JSON output +proof_frog parse --json examples/joy/Schemes/SymEnc/OTP.scheme + +# Parse a proof file +proof_frog parse examples/joy/Proofs/Ch2/OTPSecure.proof +``` + +### Common Errors + +**Parse error at line N, column M** — The file contains a syntax error. The message indicates the exact position; check for missing semicolons, unbalanced braces, or unknown keywords near that location. + +--- + +## check + +### Synopsis + +``` +proof_frog check [OPTIONS] FILE +``` + +### Behavior + +Type-checks and performs semantic analysis on any FrogLang file. This goes beyond parsing: ProofFrog verifies that types are used consistently, that scheme implementations match the signatures declared in the corresponding primitive, that method modifiers (`deterministic`, `injective`) match between the scheme and the primitive it extends, and that all referenced identifiers are in scope. Running `check` is a good first step before `prove` — it catches structural errors quickly without the overhead of full equivalence checking. On success, `check` prints ` is well-formed.` and exits with status 0. On failure it prints a diagnostic and exits with a non-zero status. + +### Options + +| Flag | Description | +|------|-------------| +| `-j`, `--json` | Output JSON instead of the default text representation. | + +### Examples + +```bash +# Type-check a symmetric encryption scheme +proof_frog check examples/joy/Schemes/SymEnc/OTP.scheme + +# Type-check a primitive definition +proof_frog check examples/Primitives/SymEnc.primitive + +# Check a proof file and emit JSON diagnostics +proof_frog check --json examples/joy/Proofs/Ch2/OTPSecure.proof +``` + +### Common Errors + +**Type error** — An expression or assignment involves incompatible types. The error message names the conflicting types and the relevant line. + +**Modifier mismatch** — A scheme method declares a `deterministic`/`injective` modifier that differs from what the primitive's signature requires. Align the modifiers between the scheme and the primitive it extends. + +--- + +## prove + +### Synopsis + +``` +proof_frog prove [OPTIONS] FILE +``` + +### Behavior + +Verifies a game-hopping proof written in a `.proof` file. ProofFrog works through each game-hop step, reduces both sides to canonical form, and checks equivalence. If every step is valid and the proof begins and ends at the required security games, the proof is accepted. + +By default ProofFrog runs equivalence checks in parallel. Pass `--sequential` (or set the environment variable `PROOFFROG_SEQUENTIAL=1`) to force single-process execution, which is useful for reproducible output in CI environments. + +Pass `-v` once to print the canonical game form after each hop, which is invaluable for diagnosing why a step fails. Pass `-vv` to additionally show which transformation rules fire during canonicalization. + +### Options + +| Flag | Description | +|------|-------------| +| `-v`, `--verbose` | Increase verbosity. Use `-v` to print each game's canonical form; use `-vv` to also show transformation activity. | +| `-j`, `--json` | Output JSON instead of the default text representation. | +| `--no-diagnose` | Suppress diagnostic analysis on failure (print summary only). | +| `--skip-lemmas` | Skip lemma proof verification (trust lemmas without re-checking). | +| `--sequential` | Disable parallel equivalence checking (use a single process). Can also be forced via the `PROOFFROG_SEQUENTIAL` environment variable. | + +{: .important } +`--skip-lemmas` bypasses verification of any lemmas referenced by the proof and trusts them unconditionally. Use this only during iterative development when you have already confirmed that the lemmas are correct and want faster turnaround on the main proof steps. Never use it as a substitute for verifying a complete proof. + +### Examples + +```bash +# Verify the OTP security proof from Joy of Cryptography examples +proof_frog prove examples/joy/Proofs/Ch2/OTPSecure.proof + +# Verbose: print canonical game forms at each hop +proof_frog prove -v examples/joy/Proofs/Ch2/OTPSecure.proof + +# Very verbose: also show transformation rule firings +proof_frog prove -vv examples/joy/Proofs/Ch2/OTPSecure.proof + +# Verify a PRG security proof, skipping lemma re-verification +proof_frog prove --skip-lemmas examples/Proofs/PRG/CounterPRG_PRGSecurity.proof +``` + +### Common Errors + +**Step N invalid** — The two game expressions at hop N do not reduce to the same canonical form. Run with `-v` to see the canonical forms of both sides and identify the discrepancy. + +**First/last game must be the security property** — The proof's opening or closing game does not match the security definition required by the proof statement. Verify that the first and last games in the proof file reference the correct security game. + +**Assumption not in assume block** — A game hop cites an assumption (e.g., a hardness assumption) that is not listed in the proof's `assume` block. Add the missing assumption to the block or correct the citation. + +--- + +## describe + +### Synopsis + +``` +proof_frog describe [OPTIONS] FILE +``` + +### Behavior + +Prints a concise, human-readable summary of any FrogLang file's interface — the type parameters, oracles, and their signatures — without showing the full implementation. This is useful for confirming that you have understood a scheme's interface before writing a proof, and for quickly reviewing what a primitive exposes without reading the full source file. + +### Options + +| Flag | Description | +|------|-------------| +| `-j`, `--json` | Output JSON instead of the default text representation. | + +### Examples + +```bash +# Describe a primitive +proof_frog describe examples/Primitives/PRG.primitive + +# Describe a scheme +proof_frog describe examples/joy/Schemes/SymEnc/OTP.scheme + +# Describe with JSON output (useful for tooling) +proof_frog describe --json examples/Primitives/SymEnc.primitive +``` + +--- + +## download-examples + +### Synopsis + +``` +proof_frog download-examples [OPTIONS] [DIRECTORY] +``` + +### Behavior + +Downloads the [ProofFrog examples repository](https://github.com/ProofFrog/examples) into the specified directory (default: `examples`). By default the command downloads the version of the examples that was pinned when your copy of ProofFrog was built, ensuring the examples are compatible with your installed version. Use `--ref` to override this and download a specific commit, tag, or branch instead. If the target directory already exists, the command exits with an error unless `--force` is passed. + +### Options + +| Flag | Description | +|------|-------------| +| `--force` | Overwrite the target directory if it already exists. | +| `--ref REF` | Git ref (commit SHA, tag, or branch) to download. Defaults to the version pinned at build time. | + +### Examples + +```bash +# Download the examples matching your version of ProofFrog into an "examples" directory +proof_frog download-examples + +# Download into a custom directory +proof_frog download-examples my-examples + +# Download the latest main branch instead of the pinned version +proof_frog download-examples --ref main + +# Overwrite an existing examples directory +proof_frog download-examples --force +``` + +--- + +## web + +### Synopsis + +``` +proof_frog web [OPTIONS] [DIRECTORY] +``` + +### Behavior + +Starts the ProofFrog browser-based editor, which provides an in-browser environment for editing and running proofs. The optional `DIRECTORY` argument sets the working directory that the editor will use as its file root; it defaults to the current working directory if omitted. The server starts on port 5173 if it is free, otherwise it scans upward for the next available port; the actual URL is printed to the terminal at startup. ProofFrog also opens that URL in your default browser automatically. + +{: .note } +The web interface provides the same verification engine as the CLI. It is particularly useful for exploring examples and for interactive proof development where you want to see game hops rendered graphically. + +### Examples + +```bash +# Start the editor using the current directory as the file root +proof_frog web + +# Start the editor rooted at the bundled examples directory +proof_frog web examples/ + +# Start the editor rooted at a specific project directory +proof_frog web /path/to/my/proofs +``` + +--- + +## Advanced Commands + +ProofFrog also includes several engine-introspection commands (`step-detail`, `inlined-game`, `canonicalization-trace`, `step-after-transform`, `lsp`, `mcp`) that expose internal engine state and protocol servers. These are intended for researchers and tool authors who need low-level access to the proof engine. They are documented separately in the researcher-facing [Engine Internals]({% link researchers/engine-internals.md %}) page. diff --git a/manual/editor-plugins.md b/manual/editor-plugins.md new file mode 100644 index 0000000..5e8b0b4 --- /dev/null +++ b/manual/editor-plugins.md @@ -0,0 +1,92 @@ +--- +title: Editor Plugins +layout: default +parent: Manual +nav_order: 80 +--- + +# Editor Plugins +{: .no_toc } + +ProofFrog ships a plugin for Visual Studio Code that provides rich editing support for FrogLang files. The plugin connects to an LSP server bundled with ProofFrog (`proof_frog lsp`); any other editor that supports the Language Server Protocol can be wired up to the same server without a dedicated plugin. Additional first-party plugins may be added in the future. See the [Engine Internals]({% link researchers/engine-internals.md %}) page for details on the LSP protocol, the language IDs, and the workspace configuration the server expects. + +- TOC +{:toc} + +--- + +## VSCode + +### Installation + +The extension is not currently published on the VSCode Marketplace. It ships as a `.vsix` package that you install manually. + +**From a pre-built package.** If you have a `.vsix` file (distributed separately or built by a colleague), install it from within VSCode: open the Extensions view, click the `...` menu in its header, and choose "Install from VSIX...". Select the `.vsix` file and reload the window when prompted. + +**Building from source.** From the repository root, run: + +```bash +# Compile the extension +make vscode-extension + +# Package as .vsix +make vscode-vsix +``` + +Then install the resulting `.vsix` via the Extensions view as described above. + +**Requirements.** The extension requires VSCode 1.85 or later and Python 3.11 or later with ProofFrog installed in the Python environment it uses. By default the extension looks for `python3` on your PATH. If you use a virtual environment, set the `prooffrog.pythonPath` setting to the full path of that environment's interpreter: + +```json +{ + "prooffrog.pythonPath": "/path/to/ProofFrog/.venv/bin/python" +} +``` + +See [Installation]({% link manual/installation.md %}) for instructions on setting up ProofFrog itself. + +--- + +### Features + +The features described below are implemented in the LSP server (`proof_frog/lsp/`) and surfaced through the VSCode extension client. + +**Syntax highlighting.** The extension registers a single language ID (`prooffrog`, also aliased as `ProofFrog` and `FrogLang`) for all four FrogLang file extensions: `.primitive`, `.scheme`, `.game`, and `.proof`. A TextMate grammar provides token-based highlighting for keywords, types, literals, comments, and import paths. + +**Diagnostics.** Parse errors are reported as squiggle underlines on every keystroke, without waiting for a save. On save, the server runs semantic analysis (type checking) and reports any type errors inline. For `.proof` files, saving also triggers full proof verification: each game hop is checked, and failed hops appear as error diagnostics on the relevant line in the `games:` list. All diagnostics come from `proof_frog/lsp/diagnostics.py` and `proof_frog/lsp/proof_features.py`. + +**Go-to-definition.** Pressing F12 (or Cmd/Ctrl+click) on an import path navigates to the imported file. On a dotted name such as `E.KeyGen`, the extension jumps to the declaration of that field or method within the imported file. Implemented in `proof_frog/lsp/navigation.py`. + +**Hover information.** Hovering over an import name or a dotted member reference shows a Markdown popup with the interface description for that primitive, scheme, or game, or the signature of the specific method. Implemented in `proof_frog/lsp/navigation.py`. + +**Outline panel and document symbols.** The Explorer Outline panel is populated with the structural elements of the active file: the primitive or scheme name with its fields and methods, game or reduction definitions with their oracle methods, the `theorem:` declaration in a proof file, and the `games:` list with each step as a child entry. This uses `proof_frog/lsp/symbols.py`. + +**Code folding.** Method bodies, game and reduction bodies, and the `games:` list in a proof file can be collapsed with the standard fold controls. Blocks of three or more consecutive comment lines are also foldable as a comment region. Implemented in `proof_frog/lsp/folding.py`. + +**Rename (F2).** Pressing F2 on an identifier renames all whole-word occurrences of that identifier throughout the current file, skipping occurrences inside comments and string literals. Language keywords and built-in type names (`Initialize`, `Finalize`, `BitString`, etc.) cannot be renamed. Implemented in `proof_frog/lsp/rename.py`. + +**Completion and signature help.** The extension provides two forms of IntelliSense. Keyword completion offers context-sensitive keywords for each file type (different sets for `.primitive`, `.scheme`, `.game`, and `.proof` files). Member completion triggers after a dot (`.`) and lists the fields and methods of the named import or `let:` binding. Signature help appears when you open a parenthesis on a method call and highlights the active parameter as you type commas. Completion also resolves `let:` bindings in proof files so that aliases like `E2.KeyGen` are completed correctly. Implemented in `proof_frog/lsp/completion.py`. + +**Code lens for proof hops.** In `.proof` files, after a save triggers proof verification, each line in the `games:` list receives an inline annotation showing whether that hop passed (`interchangeability`), failed (`interchangeability -- FAILED`), or was an assumption hop (`assumption`). Failed hops are also reported as error diagnostics. Implemented in `proof_frog/lsp/proof_features.py`. + +**Proof hops tree view.** When a `.proof` file is active, a "ProofFrog: Proof Hops" panel appears in the Explorer sidebar. It lists every game hop with its pass/fail status and the descriptions of the two games being compared. Clicking an entry navigates to the corresponding line in the proof file. This view is updated automatically each time proof verification completes. Implemented in `proof_frog/lsp/proof_features.py` (server side) and `vscode-extension/src/proof_tree.ts` (client side). + + + +--- + +## (Future) Emacs + +ProofFrog does not yet ship a dedicated Emacs plugin. The LSP server (`proof_frog lsp`) can be used directly with `eglot` or `lsp-mode` for syntax highlighting, diagnostics, and the basic LSP feature set. You would configure the server command as `python3 -m proof_frog lsp` (or the equivalent path for your environment) and associate it with the `.primitive`, `.scheme`, `.game`, and `.proof` extensions. See the [Engine Internals]({% link researchers/engine-internals.md %}) page for LSP protocol details. + +--- + +## JetBrains + +There's a plugin available for JetBrains IDE-s which provides syntax validation and highlighting, custom color settings, import statement file path references, context-menu actions and other features for the ProofFrog language. You can obtain the plugin from the JetBrains Marketplace inside the IDE. The project is hosted in [this GitHub repository](https://github.com/aabmets/proof-frog-ide-plugin). + +--- + +## Adding a new editor + +Any editor that supports the Language Server Protocol can be connected to ProofFrog's LSP server. The server is started with `proof_frog lsp` (or `python3 -m proof_frog lsp`) and communicates over stdio using the standard JSON-RPC wire protocol. It uses full document synchronisation (`TextDocumentSyncKind.Full`). The language ID for all four file types is `prooffrog`. The server expects the working directory to match the directory from which the proof files are being edited, so that import paths resolve correctly. See the [Engine Internals]({% link researchers/engine-internals.md %}) page for a full description of the protocol surface, the supported LSP methods, and the custom notifications (`prooffrog/verificationDone`, `prooffrog/proofSteps`) used by the proof hops tree view. diff --git a/manual/index.md b/manual/index.md new file mode 100644 index 0000000..82db882 --- /dev/null +++ b/manual/index.md @@ -0,0 +1,32 @@ +--- +title: Manual +layout: default +nav_order: 2 +has_children: true +permalink: /manual/ +has_toc: false +--- + +# Manual + +ProofFrog is a tool for verifying transitions in cryptographic game-hopping proofs. This manual is the place to start if you want to *use* it: install it, write your first proof, and look up language constructs as you go. + +## Getting Started + +If you are new to ProofFrog, work through the pages below in order: + +1. **[Installation]({% link manual/installation.md %})**: Set up Python, install via pip, and verify. +2. **[Tutorial]({% link manual/tutorial/index.md %})**: A two-part hands-on introduction — [Part 1: Hello Frog]({% link manual/tutorial/hello-frog.md %}) runs an existing proof, breaks it on purpose, and fixes it; [Part 2: OTP has one-time secrecy]({% link manual/tutorial/otp-ots.md %}) walks through writing your first complete four-file proof from scratch, about the security of the one-time pad. +3. **[Worked Examples]({% link manual/worked-examples/index.md %})**: Read fully-explained walkthroughs of real proofs, starting with [chained symmetric encryption]({% link manual/worked-examples/chained-encryption.md %}) (the first reduction proof) and then [chosen-plaintext attack security of hybrid public key encryption (the KEM-DEM construction)]({% link manual/worked-examples/kemdem-cpa.md %}) (a multi-primitive proof). + +After that, treat the manual as a reference and use the navigation to look up what you need. + +## Reference + +- **[Language Reference]({% link manual/language-reference/index.md %})**: Types, operators, sampling, statements, the four file types (primitive, scheme, game, proof), and the execution model. +- **[Canonicalization]({% link manual/canonicalization.md %})**: How ProofFrog tries to check that two games are equivalent: what transformations it applies automatically, how to use helper games, and how to diagnose a failing hop. +- **[Limitations]({% link manual/limitations.md %})**: Capability limits of ProofFrog's language and canonicalization engine. ProofFrog's [soundness]({% link researchers/soundness.md %}) is discussed in the "For Researchers" section. +- **[Command-Line Interface Reference]({% link manual/cli-reference.md %})**: `proof_frog` on the command-line: `version`, `parse`, `check`, `prove`, `describe`, `web`. +- **[Web Editor]({% link manual/web-editor.md %})**: Using ProofFrog's web-based editor environment via `proof_frog web`. +- **[Editor Plugins]({% link manual/editor-plugins.md %})**: How to add ProofFrog extensions to VSCode and other editors using the LSP server. +- **[Troubleshooting]({% link manual/troubleshooting.md %})**: How to diagnose common errors. diff --git a/manual/installation.md b/manual/installation.md new file mode 100644 index 0000000..e3cbff6 --- /dev/null +++ b/manual/installation.md @@ -0,0 +1,176 @@ +--- +title: Installation +layout: linear +parent: Manual +nav_order: 1 +redirect_from: + - /getting-started/ + - /getting-started.html +--- + +# Installation +{: .no_toc } + +This page walks you through installing ProofFrog on your computer, verifying the installation, and obtaining the example files. No prior command-line experience is assumed. + +--- + +- TOC +{:toc} + +--- + +## Prerequisites + +ProofFrog requires **Python 3.11 or newer**. + +To check whether Python is already installed and which version you have, open a terminal and run: + +```bash +python3 --version +``` + +You should see output like `Python 3.11.9` or `Python 3.12.3`. Any version 3.11 or higher is fine. If the command is not found, or the version is older than 3.11, install Python using the instructions for your operating system below. + +### macOS + +The recommended approach is to use [Homebrew](https://brew.sh/). If you have Homebrew installed, run: + +```bash +brew install python3 +# If you want to install a specific version of Python: +# brew install python@3.11 +``` + +Alternatively, download the macOS installer from [python.org/downloads](https://www.python.org/downloads/) and follow the prompts. After installing, open a new terminal window and run `python3 --version` to confirm. + +### Windows + +Download the installer from [python.org/downloads](https://www.python.org/downloads/). Run it, and on the first screen of the installer, make sure to check the box labelled **"Add Python to PATH"** before clicking Install Now. This step is easy to miss and is the most common cause of `python3` not being recognized in a new terminal after installation. + +After the installer finishes, open a new Command Prompt or PowerShell window and run `python3 --version` to confirm. + +### Linux + +Use your distribution's package manager: + +- **Debian/Ubuntu:** + ```bash + sudo apt install python3 python3-venv python3-pip + ``` +- **Fedora:** + ```bash + sudo dnf install python3 python3-pip + ``` +- **Arch Linux:** + ```bash + sudo pacman -S python python-pip + ``` + +After installing, run `python3 --version` to confirm. + +## Install ProofFrog with pip + +The recommended way to install ProofFrog is from [PyPI](https://pypi.org/project/proof-frog/) using `pip`. It is best practice to install it inside a **virtual environment**, which keeps ProofFrog and its dependencies isolated from other Python projects on your system. + +**Step 1.** Create a virtual environment in a directory of your choice. The command below creates one called `.venv` in your current directory: + +```bash +python3 -m venv .venv +``` + +**Step 2.** Activate the virtual environment. The command depends on your shell: + +- bash or zsh (macOS/Linux default): + ```bash + source .venv/bin/activate + ``` +- fish: + ```fish + source .venv/bin/activate.fish + ``` +- Windows PowerShell: + ```powershell + .venv\Scripts\Activate.ps1 + ``` + +> On a fresh Windows install, PowerShell's default execution policy blocks running `.ps1` scripts. To allow it, run the following once in PowerShell — this only needs to be done once per user account and does not require administrator privileges: +> +> ~~~powershell +> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +> ~~~ +{: .warning } + +Once activated, your prompt will show the name of the environment (e.g., `(.venv)`). + +{: .important } +**You must activate the virtual environment every time you open a new terminal before using ProofFrog.** If you see `command not found: proof_frog` later on, this is almost always the reason — just re-run the activation command above for your shell. + +**Step 3.** Install ProofFrog: + +```bash +pip install proof_frog +``` + +{: .note } +The virtual environment ensures that ProofFrog's dependencies do not conflict with other Python packages you have installed, and makes it straightforward to uninstall ProofFrog completely by simply deleting the `.venv` directory. + +## Upgrade ProofFrog + +To upgrade to the latest version of ProofFrog, activate your virtual environment and run: + +```bash +pip install --upgrade proof_frog +``` + +After upgrading, you can re-run `proof_frog download-examples` to get the examples matching the new version. + +## Verify your ProofFrog installation + +To confirm that ProofFrog installed correctly, run: + +```bash +proof_frog version +``` + +You should see a version number printed to the terminal, such as `ProofFrog 0.4.0` or `ProofFrog 0.4.0.dev0` on development builds. + +### Troubleshooting: "command not found" + +If you see a `command not found` error (or similar), the most likely causes are: + +- **The virtual environment is not activated.** Run the `source .venv/bin/activate` command (or the equivalent for your shell, as shown above) and try again. +- **`pip install` used a different Python.** If you have multiple Python installations, `pip` may have installed `proof_frog` into one that is not on your PATH. Make sure you created and activated the virtual environment using the same `python3` that meets the version requirement, then re-run `pip install proof_frog` inside the activated environment. +- **PATH is not configured correctly on Windows.** If you did not check "Add Python to PATH" during installation, Python's scripts directory will not be on your PATH. Re-running the installer and checking that box, or adding the scripts directory to your PATH manually, will resolve this. + +{: .warning } +Do not use `sudo pip install` to work around a `command not found` error. Installing packages system-wide with `sudo` can interfere with your operating system's own Python packages. Use a virtual environment instead. + +## Get the examples + +ProofFrog has a companion repository of example proof files. The easiest way to get them is with the built-in `download-examples` command: + +```bash +proof_frog download-examples +``` + +This creates an `examples/` directory in your current location containing primitives, schemes, games, and proofs from introductory cryptography. The command downloads the exact version of the examples that matches your installed ProofFrog release. + +## First run + +You are ready for [Tutorial Part 1: Hello Frog]({% link manual/tutorial/hello-frog.md %}). + +## For developers and advanced users: installing from source + +If you want to contribute to ProofFrog or work with the latest development version, install from source: + +```bash +git clone https://github.com/ProofFrog/ProofFrog +cd ProofFrog +git submodule update --init +python3 -m venv .venv +source .venv/bin/activate +pip install -e ".[dev]" +``` + +The `-e` flag installs the package in editable mode, so changes to the source files take effect immediately without reinstalling. See the [GitHub README](https://github.com/ProofFrog/ProofFrog#readme) for the full development setup guide, including how to run the test suite. diff --git a/manual/language-reference/basics.md b/manual/language-reference/basics.md new file mode 100644 index 0000000..e742104 --- /dev/null +++ b/manual/language-reference/basics.md @@ -0,0 +1,347 @@ +--- +title: Basics +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 1 +--- + +# FrogLang Basics +{: .no_toc } + +This page describes the syntactic and semantic foundations shared by all FrogLang file types: lexical conventions, types, expressions and operators, sampling, statements, and imports. + +- TOC +{:toc} + +--- + +## Lexical + +**Character set.** FrogLang files must be ASCII only. Non-ASCII characters (including Unicode letters, accents, and non-breaking spaces) are rejected by the parser. + +**Comments.** `//` begins a comment that runs to the end of the line. Multi-line or block comments use `/* ... */`. + +**Identifiers.** An identifier is a sequence of letters, digits, and underscores that does not begin with a digit. Identifiers are case-sensitive. + +**Reserved keywords.** The following words are reserved and may not be used as identifiers: + +``` +Primitive Scheme Game Reduction proof +let assume lemma theorem games +compose against import export as +if else for to in +return extends requires this challenger +deterministic injective None true false +``` + +**File extensions** + +| Extension | File type | +|---|---| +| `.primitive` | Cryptographic primitive | +| `.scheme` | Cryptographic scheme | +| `.game` | Security game (pair) | +| `.proof` | Game-hopping proof | + +--- + +## Types + +### Primitive types + +| Type | Description | +|---|---| +| `Int` | Unbounded integer. Used for security parameters, lengths, loop bounds, and arithmetic. | +| `Bool` | Boolean. Literals: `true`, `false`. | +| `Void` | Unit type. Only valid as a method return type (typically for `Initialize`). | + +### Parameterized types + +**`BitString`** — the set of all bit strings of length `n`, where `n` is an `Int` expression. Cardinality: `2^n`. An unparameterized `BitString` (no angle brackets) may appear in primitive signatures as a placeholder to be resolved when the primitive is instantiated. + +**`ModInt`** — integers modulo `q`, i.e., the set `{0, 1, ..., q-1}`. Cardinality: `q`. Arithmetic on `ModInt` is performed mod `q`. + +**`Group`** — a declaration type used to introduce a group parameter. The model is a finite cyclic group; all finite cyclic groups are abelian, so the group operation is commutative. Groups may have prime or composite order. + +A group identifier `G` provides three built-in accessors: + +| Accessor | Type | Description | +|---|---|---| +| `G.order` | `Int` | Number of elements in the group | +| `G.generator` | `GroupElem` | A designated generator whose powers enumerate every group element | +| `G.identity` | `GroupElem` | Identity element; `G.generator ^ 0 == G.identity` | + +**`GroupElem`** — the set of elements of group `G`. Parameterized by group identifier, not order, so elements from different groups are type-incompatible even when the groups have the same order. Cardinality: `G.order`. + +### Collection types + +**`Array`** — fixed-size array of `n` elements of type `T`, indexed from `0` to `n-1`. + +**`Map`** — finite partial function from keys of type `K` to values of type `V`. A map starts empty (no keys are mapped). Accessing a key not in the map's domain is undefined behavior. + +**`Set`** — finite set of elements of type `T`. An unparameterized `Set` in a primitive signature is an abstract placeholder. + +### `Function` + +The type of a function from domain `D` to range `R`. Its meaning depends on how it is introduced: + +- **Declared** (`Function H;`): a known deterministic function in the standard model. The adversary can compute it; the engine treats calls to it as deterministic. +- **Sampled** (`Function H <- Function;`): a truly random function (random oracle model). Each distinct input maps independently to a uniform random output; repeated queries on the same input return the same result. + +Only sampled `Function` values receive random-function simplifications during proof verification. This distinction matters: declaring `H` without sampling gives the adversary free access to a fixed function, not a random one. + +Every sampled `Function` field automatically maintains an implicit **`.domain`** set that tracks which inputs have been queried. For example, if a game has a field `Function, BitString> RF`, then `RF.domain` is a `Set>` containing every input on which `RF` has been evaluated. This set can be used as the bookkeeping set for `<-uniq` sampling (e.g., `r <-uniq[RF.domain] BitString`) or passed to a helper game's `Samp` method (e.g., `challenger.Samp(RF.domain)`). See [Canonicalization]({% link manual/canonicalization.md %}#random-function-on-distinct-inputs) for how the engine uses this to simplify random-function calls on distinct inputs. + +### Optional type + +**`T?`** — either a value of type `T` or `None`. Commonly used for operations that may fail, such as decryption (`Message? Dec(Key k, Ciphertext c);`). + +### Tuple types + +**`[T1, T2, ..., Tn]`** — ordered heterogeneous collection. Tuple literals are written `[e1, e2, ..., en]` and elements are accessed by **constant** integer index: `t[0]`, `t[1]`, etc. The index must be a compile-time constant, not a runtime expression. + +Note: the current syntax for tuple types uses bracket notation `[A, B]`. An older product-type notation `A * B` is not accepted by the current engine. + +Note: `...` cannot be used in FrogLang tuples: when writing a tuple type or tuple literals in FrogLang, a concrete size must be used. In other words, you can write a 4-tuple `[T1, T2, T3, T4]` but not an `n`-tuple literal `[T1, T2, ..., Tn]`. + +### Type aliases + +Primitives and schemes declare named `Set` fields that become type aliases: + +```prooffrog +Set Key = BitString; +``` + +From another file, after importing, a scheme or primitive instance `E` exposes this as `E.Key`. When a scheme is instantiated in a proof's `let:` block, the alias resolves to its concrete type. + +--- + +## Expressions and Operators + +### Literals + +| Form | Type | Description | +|---|---|---| +| `0`, `42` | `Int` | Integer literal | +| `0b101` | `BitString<3>` | Binary literal; length equals digit count after `0b` | +| `0^n` | `BitString` | The all-zeros bitstring of length `n` | +| `1^n` | `BitString` | The all-ones bitstring of length `n` | +| `true`, `false` | `Bool` | Boolean literals | +| `None` | `T?` | The null value, for methods with an optional return type | +| `{e1, e2}` | `Set` | Set literals (no empty set notation needed; all sets are initialized to empty) | +| `[e1, e2]` | `[T1, T2]` | Tuple literal | + +### Operator table + +| Operator | Operand types | Result | Notes | +|---|---|---|---| +| `+` | `Int`, `Int` | `Int` | Addition | +| `+` | `ModInt`, `ModInt` | `ModInt` | Addition mod `q` | +| `+` | `BitString`, `BitString` | `BitString` | **XOR** — not addition | +| `-` | `Int`, `Int` | `Int` | Subtraction | +| `-` | `ModInt`, `ModInt` | `ModInt` | Subtraction mod `q` | +| `*` | `Int`, `Int` | `Int` | Multiplication | +| `*` | `ModInt`, `ModInt` | `ModInt` | Multiplication mod `q` | +| `*` | `GroupElem`, `GroupElem` | `GroupElem` | Group operation (abelian) | +| `/` | `Int`, `Int` | `Int` | Integer division | +| `/` | `ModInt`, `ModInt` | `ModInt` | Modular division | +| `/` | `GroupElem`, `GroupElem` | `GroupElem` | `a * b^(-1)` | +| `^` | `Int`, `Int` | `Int` | Exponentiation (right-associative) | +| `^` | `ModInt`, `Int` | `ModInt` | Modular exponentiation (right-associative) | +| `^` | `GroupElem`, `ModInt` or `Int` | `GroupElem` | Scalar power (right-associative) | +| `-` (unary) | `Int` | `Int` | Negation | +| `||` | `Bool`, `Bool` | `Bool` | Logical OR | +| `||` | `BitString`, `BitString` | `BitString` | Concatenation | +| `&&` | `Bool`, `Bool` | `Bool` | Logical AND | +| `!` | `Bool` | `Bool` | Logical NOT | +| `==`, `!=` | any comparable | `Bool` | Equality / inequality | +| `<`, `>`, `<=`, `>=` | `Int`, `ModInt` | `Bool` | Ordered comparison | +| `in` | `T`, `Set` | `Bool` | Membership test | +| `subsets` | `Set`, `Set` | `Bool` | Subset test | +| `union` | `Set`, `Set` | `Set` | Set union | +| `\` | `Set`, `Set` | `Set` | Set difference | +| `|x|` | `Set`, `Map`, `BitString`, `Array` | `Int` | Cardinality / length | +| `a[i]` | `Array`, index `Int` | `T` | Array element at index `i` | +| `a[i]` | `BitString`, index `Int` | single bit | Bit at position `i` | +| `a[i : j]` | `BitString` | `BitString` | Slice from `i` (inclusive) to `j` (exclusive) | + +**Highlights:** + +- `+` on `BitString` is **XOR**, not arithmetic addition. This is a common source of confusion: `k + m` in FrogLang XORs `k` and `m` when both are bitstrings. The OTP encryption `return k + m;` is XOR. +- `||` is overloaded: logical OR on `Bool` and concatenation on `BitString`. The type of both operands determines which operation is performed. +- `^` is **right-associative** exponentiation, not XOR. XOR is `+`. +- Bitstring slice bounds: `a[i : j]` is **inclusive on the left, exclusive on the right**, yielding a bitstring of length `j - i`. + +### Operator precedence + +Precedence from highest (binds tightest) to lowest: + +| Level | Operators | +|---|---| +| 1 (highest) | `^` (right-associative) | +| 2 | `*`, `/` | +| 3 | `+`, `-` | +| 4 | `==`, `!=`, `<`, `>`, `<=`, `>=`, `in`, `subsets` | +| 5 | `&&` | +| 6 (lowest) | `||`, `union`, `\` | + +### Algebraic properties + +| Operator | Types | Commutative | Associative | Identity | +|---|---|---|---|---| +| `+` | `Int`, `ModInt` | Yes | Yes | `0` | +| `+` | `BitString` | Yes | Yes | `0^n` | +| `*` | `Int`, `ModInt` | Yes | Yes | `1` | +| `*` | `GroupElem` | Yes | Yes | `G.identity` | +| `&&` | `Bool` | Yes | Yes | `true` | +| `||` | `Bool` | Yes | Yes | `false` | +| `-` | any | No | No | — | +| `/` | any | No | No | — | +| `^` | any | No | No | — | +| `||` | `BitString` | No | Yes | — | + +--- + +## Sampling + +FrogLang uses the `<-` operator for uniform random sampling. + +**Uniform sample from a type:** + +```prooffrog +BitString r <- BitString; +ModInt x <- ModInt; +GroupElem u <- GroupElem; +``` + +Draws a value uniformly at random from the full domain of the named type. + +**Unique sampling (rejection sampling):** + +```prooffrog +BitString x <-uniq[S] BitString; +``` + +This is shorthand notation for sampling uniformly without replacement, with bookkeeping handled by the set `S`. In other words, it's equivalent to initializing `S = {}`, sampling `x <- BitString \ S`, and then updating `S <- S union {x}`. While the expanded form is also valid FrogLang, using the shorthand notation enables the ProofFrog engine to recognize this pattern and apply certain transformations. + +A common pattern is to use a random function's implicit `.domain` set as the bookkeeping set: `r <-uniq[RF.domain] BitString` ensures `r` is distinct from all inputs on which `RF` has previously been evaluated. See [`Function`](#functiond-r) for details on `.domain`. + +**Sample into a map entry:** + +```prooffrog +M[k] <- BitString; +``` + +Samples a value uniformly at random and stores it at key `k` of map `M`. + +**Sample a random function (ROM):** + +```prooffrog +Function H <- Function; +``` + +Instantiates a fresh random function. Each distinct input independently maps to a uniform random output in `R`; repeated queries on the same input return the same value. This is the standard way to model a random oracle. + +**Non-determinism by default.** Scheme method calls such as `F.evaluate(k, x)` are **non-deterministic by default**: each invocation may return a different value even with the same arguments, unless the primitive method is declared with the `deterministic` modifier. The engine is conservative and will not assume two calls with the same inputs produce the same result unless determinism is annotated. For more on how the engine uses this annotation, see the [Execution Model]({% link manual/language-reference/execution-model.md %}) page. + +--- + +## Statements + +### Declaration + +```prooffrog +Type x; // declare uninitialized +Type x = expr; // declare and initialize +``` + +An uninitialized variable has an undefined value until assigned. It is valid to declare a variable and assign it in a later statement. + +### Assignment + +```prooffrog +x = expr; // assign to a variable +a[i] = expr; // assign to an array or map element +``` + +### Sampling + +Sampling is a statement form (see the Sampling section above): + +```prooffrog +Type x <- Type; // sample variable x uniformly at random from set Type +Type x <-uniq[S] Type; // sample variable x uniformly at random from Type \ S + // and implicitly update bookkeeping set S +M[k] <- Type; // sample uniformly at random and assign to a map value +``` + +### Conditional + +```prooffrog +if (condition) { + ... +} + +if (condition1) { + ... +} else if (condition2) { + ... +} else { + ... +} +``` + +Conditions must be `Bool` expressions. + +### Numeric for loop + +```prooffrog +for (Int i = start to end) { + ... +} +``` + +Iterates `i` from `start` (inclusive) to `end` (exclusive), incrementing by 1 each iteration. The loop body executes `end - start` times when `end > start`; zero times otherwise. + +### Iteration for loop + +```prooffrog +for (Type x in collection) { + ... +} +``` + +Iterates over all elements of a `Set`, all elements of an `Array`, or the keys of a `Map`. For sets, the iteration order is unspecified. + +### Return + +```prooffrog +return expr; +``` + +Exits the current method and returns `expr`. The type of `expr` must match the method's declared return type. + +--- + +## Imports + +Files import other files using a relative path: + +```prooffrog +import 'relative/path/to/File.primitive'; +``` + +**Paths are file-relative**: the path is resolved relative to the directory containing the importing file, not relative to the directory where the CLI is invoked. + +**Example.** The proof `examples/Proofs/SymEnc/INDOT$_implies_INDOT.proof` imports: + +```prooffrog +import '../../Primitives/SymEnc.primitive'; +import '../../Games/SymEnc/INDOT.game'; +import '../../Games/SymEnc/INDOT$.game'; +``` + +From `examples/Proofs/SymEnc/`, `../..` navigates up to `examples/`, and the paths then descend into `Primitives/` and `Games/SymEnc/`. + +Any file type (`.primitive`, `.scheme`, `.game`, `.proof`) can be imported. diff --git a/manual/language-reference/execution-model.md b/manual/language-reference/execution-model.md new file mode 100644 index 0000000..e18e356 --- /dev/null +++ b/manual/language-reference/execution-model.md @@ -0,0 +1,173 @@ +--- +title: Execution Model +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 2 +--- + +# Execution Model +{: .no_toc } + +[Basics]({% link manual/language-reference/basics.md %}) covers what FrogLang syntax looks like: types, operators, statements, and sampling syntax. This page covers what it *means* to execute a game: how state is initialized and persisted, what an adversary can observe, how sampling and non-determinism are modeled, and how games are composed with schemes and reductions. The four file-type pages (Primitives, Schemes, Games, Proofs) cover what is syntactically and semantically legal in each file kind. + +- TOC +{:toc} + +--- + +## The adversary model + +An **adversary** in FrogLang is an implicit external entity. It is not written in FrogLang — it is the abstract party that the game is designed to challenge. The adversary: + +- Has access to the game's oracle methods (all methods other than `Initialize`). +- May call any available oracle in any order, any number of times (including zero times). +- May choose arguments to each oracle call *adaptively* — that is, based on all values returned by prior oracle calls. +- Has no direct access to the game's internal state (its fields). The only information the adversary can obtain is what the game explicitly returns through oracle responses. +- Eventually halts and produces an output value. In most security definitions, this output is a single bit ("guess"). + +The security model is **left/right indistinguishability**: a scheme is considered secure if no efficient adversary can distinguish between interacting with the left-side game and the right-side game with more than negligible probability. FrogLang uses this formulation exclusively; win/lose games (such as unforgeability) can be reformulated as left/right games when needed. + +--- + +## Game execution model + +One execution of a game `G` with an adversary `A` proceeds in four stages: + +1. **Field initialization.** All state fields are set up. Fields declared with an explicit initializer (`Type x = expr;`) are assigned the value of `expr`. Fields declared without an initializer (`Type x;`) are left in an undefined state until the first assignment. + +2. **`Initialize` execution.** If the game defines an `Initialize` method, it is executed exactly once before the adversary sees any oracle. This is where games typically perform setup: sampling cryptographic keys, setting counters, preparing tables. Any values returned from `Initialize` are given to the adversary. + +3. **Oracle interaction phase.** The adversary calls the game's oracles freely, in any order, any number of times. Each call executes the oracle's body, which may read and write state fields, sample fresh randomness, and return a value. The adversary observes only the return values. + +4. **Adversary output.** The adversary halts and produces its output. + +--- + +## State semantics + +**Persistence within an execution.** A game's state fields persist across all oracle calls within a single execution. If an oracle writes to a field, subsequent oracle calls in the same execution see the updated value. This is how games track state like "which messages have been queried" or "what key was generated." + +**Isolation across executions.** Each execution is independent. There is no leakage of state from one execution to another. When the engine checks whether two games are interchangeable, the execution of each game is an independent, fresh execution. + +**Local variable scope.** Local variables declared inside an oracle method are scoped to that single invocation. Each call to the same oracle gets fresh local variables; there is no implicit sharing between calls except through the game's state fields. + +**Adversary access.** The adversary cannot access game state or local variables directly; the adversary only receives values returned to them from oracle calls. + +--- + +## Non-determinism and the `deterministic`/`injective` annotations + +{: .important } +**Algorithms in ProofFrog are non-deterministic by default.** When a game or scheme calls a method like `F.evaluate(k, x)`, the engine does not assume this call is a pure function. Two calls with identical arguments may, in principle, return different values. The engine is conservative: unless told otherwise, it treats every scheme method call as potentially non-deterministic. + +This default exists because FrogLang does not look inside a primitive's method bodies — primitives declare *interface*, not behavior. The engine cannot tell, without explicit annotation, whether a primitive method does internal sampling, maintains hidden state, or is a pure computation. + +Two annotations on primitive method declarations override the default: + +| Annotation | Meaning | +|---|---| +| `deterministic` | This method always returns the same output for the same inputs. Every call with identical arguments is equivalent to the first call. | +| `injective` | This method maps distinct inputs to distinct outputs. | + +Example: + +```prooffrog +deterministic injective BitString Encode(GroupElem g); +``` + +The `deterministic` annotation enables the engine to: +- Treat calls as pure expressions (safe to inline, alias, hoist, and deduplicate). +- Replace a duplicate call `[F.eval(k, x), F.eval(k, x)]` with `v = F.eval(k, x); [v, v]`. +- Propagate a field initialized with a deterministic call into later oracles that make the same call. + +The `injective` annotation allows the engine to see through encoding wrappers when deciding whether two random function inputs are structurally distinct. + +Both annotations are *semantic claims*. The typechecker enforces that when a scheme extends a primitive, the scheme's implementation of each method carries exactly the same `deterministic`/`injective` modifiers as the primitive declared. The engine uses these claims to enable certain canonicalization transforms — see the [Canonicalization]({% link manual/canonicalization.md %}) page for details. + +{: .warning } +ProofFrog's typechecker will not check that a scheme method's implementation satisfies a `deterministic` annotation: if you label a scheme's method as `deterministic`, but then do a sampling operation in it, the result is not well-defined. + +--- + +## Sampling, randomness, and freshness + +FrogLang uses the `<-` operator for all explicit randomness. Every sampling statement produces an **independent**, **uniform** draw from the specified domain. + +**Basic uniform sampling.** Each statement like + +```prooffrog +BitString r <- BitString; +``` + +draws a value uniformly at random from the full domain. Crucially, each such statement is independent of every other sampling statement — including other executions of the *same* statement in different oracle calls. + +**Unique (rejection) sampling.** The statement + +```prooffrog +BitString x <-uniq[S] BitString; +``` + +samples uniformly from `BitString \ S`, where `S` is a set expression, then implicitly update `S` with the newly drawn value. Semantically this is sampling without replacement: draw from `BitString`, repeat if the result is already in `S`. The result is guaranteed to be fresh with respect to `S`. This is used when a nonce or challenge must be distinct from previously used values. + +**Sampling into a map entry.** The statement `M[k] <- BitString;` samples a fresh value into the entry of map `M` at key `k`. This is the imperative analogue of the random-function lazy evaluation described next: a `Map` together with `M[k] <-` sampling on the first query to each key implements exactly the same lazily-evaluated truly random function semantics that `Function` makes a primitive type. + +**Random functions (the random oracle model).** The statement + +```prooffrog +Function H <- Function; +``` + +instantiates a *truly random function*. Its semantics: + +- Calling `H(x)` returns a value drawn uniformly from `R`. +- Repeated calls to `H` on the *same* input `x` always return the *same* value (consistency). +- Calls on *distinct* inputs are independent. +- The function is evaluated lazily: the first query to a new input draws a fresh uniform value; later queries on that input return the stored result. + +This is the standard formal model for a random oracle. It arises on the "Random" side of PRF security games and in proofs that use the random oracle methodology. + +Every sampled `Function` field automatically maintains an implicit **`.domain`** set (of type `Set`) that records every input on which the function has been evaluated. For example, if `H` is a sampled `Function, BitString>`, then `H.domain` is a `Set>` containing all queried inputs. This set is commonly used as the bookkeeping set for `<-uniq` sampling — writing `c <-uniq[H.domain] GroupElem` guarantees `c` is fresh with respect to all prior `H` queries, which is the precondition for the engine's [random-function simplifications]({% link manual/canonicalization.md %}#random-function-on-distinct-inputs). + +{: .important } +**Declared vs. sampled `Function` values have different semantics.** In a proof's `let:` block, writing `Function H;` (no `<-`) declares a *known deterministic function in the standard model*. The adversary can compute it; calls to `H(x)` are treated as deterministic (same input, same output, always). Writing `Function H <- Function;` places the proof in the random oracle model, where `H` is chosen uniformly at random from all functions `D -> R`. The engine applies random-function simplifications only to *sampled* `Function` values. Confusing the two forms is a common source of subtle proof errors. + +--- + +## Composition + +### Game composed with a scheme + +Games are parameterized over primitives. For example, `Game Left(SymEnc E)` accepts any scheme that extends `SymEnc`. When a proof's `let:` block instantiates a scheme (e.g., `SymEnc E = OTP(lambda)`) and uses it in a game step, the scheme's method bodies become callable from the game's oracles. + +During proof verification, the engine **inlines** these calls: each call `E.Enc(k, m)` in the game's body is replaced by the body of `OTP.Enc`, with formal parameters substituted for actual arguments. Local variables in the inlined body are renamed to avoid collisions. Inlining repeats in a fixed-point loop until all calls have been expanded (recursion is not allowed in FrogLang, so this always terminates). + +The result is a flat, self-contained game body with no remaining calls to scheme methods. This flat form is what the engine canonicalizes and compares. For the full details of the canonicalization pipeline, see the [Canonicalization]({% link manual/canonicalization.md %}) page. + +### Game composed with a reduction + +`compose` produces a new game from a security game and a reduction. The composition syntax appears as a *game step* inside the `games:` block of a `.proof` file (it is not a free-standing expression you can write elsewhere). For example, a single line in a `games:` block might read: + +```prooffrog +// inside a games: block of a .proof file +Security(G).Real compose R(G, T) against Security(T).Adversary; +``` + +This produces a composed game where: +- The adversary calls `R`'s oracle methods (which implement the interface of `Security(T)`). +- Inside `R`'s methods, the keyword `challenger` refers to `Security(G).Real`'s oracles; `challenger.Query()` calls that game's `Query` oracle. +- The combined state includes both `R`'s fields and `Security(G).Real`'s fields. + +The `Initialize` methods are merged: if both the reduction and the composed game define `Initialize`, the reduction's `Initialize` may call `challenger.Initialize()` to trigger the composed game's initialization. Both run as part of the single initialization step of the composed game. + +After composition, the engine inlines the reduction's calls to `challenger.*` just as it inlines scheme method calls — everything is flattened before canonicalization. + +--- + +## What is not on this page + +- **Algebraic identities and other semantic equivalences** — XOR cancellation, group element identities, sample merge and split, uniform masking, and other transforms the engine applies during canonicalization are described on the [Canonicalization]({% link manual/canonicalization.md %}) page. + +- **File-type rules** — what is syntactically and semantically legal in each kind of file (`.primitive`, `.scheme`, `.game`, `.proof`) is covered on the Primitives, Schemes, Games, and Proofs pages respectively. + +- **What FrogLang does not model** — computational complexity (the engine works with exact equality, not asymptotic bounds), side channels, concurrency, and abort semantics are outside the scope of FrogLang's model. See the [Limitations]({% link manual/limitations.md %}) page for a list of out-of-scope concerns and known engine gaps. diff --git a/manual/language-reference/games.md b/manual/language-reference/games.md new file mode 100644 index 0000000..8d81a76 --- /dev/null +++ b/manual/language-reference/games.md @@ -0,0 +1,248 @@ +--- +title: Games +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 5 +--- + +# Games +{: .no_toc } + +A `.game` file defines a *security property* as a pair of games. The adversary interacts with one of the two games — without knowing which — and tries to distinguish them. If no efficient adversary can tell the two games apart (except with small probability), the scheme satisfies the security property. For a precise description of what "interact with a game" means at runtime — how `Initialize` is called, how oracle calls are sequenced, how state is managed — see the [Execution Model]({% link manual/language-reference/execution-model.md %}) page. + +- TOC +{:toc} + +--- + +## The two-game requirement + +Every `.game` file must contain *exactly two* `Game` blocks. The two games must expose the same set of oracle methods with matching signatures: each method must appear in both games under the same name, with the same parameter types and the same return type. The engine rejects mismatched signatures during type-checking. + +{: .important } +If the two games differ in any method name, parameter type, or return type, `proof_frog check` reports a type error. Both games must present an identical interface to the adversary — only the implementations may differ. + +There is no enforced naming convention for the two sides. Common choices from the literature include: + +| Convention | Typical use | +|---|---| +| `Left` / `Right` | General indistinguishability | +| `Real` / `Random` | PRG, PRF security | +| `Real` / `Ideal` | Simulation-based security, correctness | + +Pick names that make sense for the property in question, and consider how the security property is stated in the relevant literature. Neither side is "preferred": your game hopping proof can go from left to right or right to left; all that matters is that in a proof's `games:` list, the sequence must start on one side and end on the other. + +### Distinguishing games versus win/lose and hidden bit games + +Following the conventions of Joy of Cryptography, security properties in ProofFrog are always about distinguishing a pair of games. + +Conventional cryptographic literature naturally phrases some security properties as distinguishing a pair of games, but naturally phrases many security properties in terms of single games, where the adversary has to guess a hidden bit (e.g., a traditional formulation of IND-CPA security for encryption) or produce an output satisfying a condition (e.g., hash function collision resistance or unforgeability of a message authentication code). + +In order to model such properties in ProofFrog, they must be phrased in terms of distinguishing a pair of games. This may feel unnatural at times, but is usually doable. + +For example, [unforgeability of a MAC](https://github.com/ProofFrog/examples/blob/main/Games/MAC/SUFCMA.game) can be formulated with a game that provides a `GetTag(m)` oracle and a `CheckTag(m, t)` oracle. In the real world, `CheckTag` returns the boolean result testing `t == E.MAC(k, m)`, whereas in the ideal world, `CheckTag` only accepts tags that were produced by `GetTag`. An adversary who produces a forgery (that was never queried to the `GetTag` oracle) will indeed be able to distinguish the two games. + +--- + +## The `Game` blocks + +The general form of a single game is: + +```prooffrog +Game SideName(parameters) { + // Optional state fields (persist across oracle calls) + Type fieldName; + + // Optional Initialize method (called once before any oracle) + Void Initialize() { + // set up state + } + + // Oracle methods (called by the adversary) + ReturnType Oracle1(ParamType param, ...) { ... } + ReturnType Oracle2(ParamType param, ...) { ... } +} +``` + +A game may take any number of parameters. Parameters are typically primitive or scheme instances (e.g., `SymEnc E`, `PRG G`) or integers (e.g., `Int lambda`). The parameter list is the same for both games in the file. + +Both `Initialize` and all oracle methods may use the full statement language described on the [Basics]({% link manual/language-reference/basics.md %}) page: variable declarations, random sampling, conditionals, loops, and return statements. + +--- + +## `Initialize` and state fields + +State fields are declared at the top of a game body, before any methods: + +```prooffrog +Game Left(SymEnc E) { + E.Key k; // state field — persists across oracle calls + Int callCount; // another state field + + Void Initialize() { + k = E.KeyGen(); // set up state before any oracle is called + callCount = 0; + } + + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + callCount = callCount + 1; + return E.Enc(k, mL); + } +} +``` + +`Initialize` is run exactly once, before the adversary makes any oracle call. It is the right place to perform setup that must happen once per execution (sampling a key, initializing a counter, populating a table). + +`Initialize` can have a non-`Void` return type; the semantics of the execution model are that any value returned from `Initialize` is provided to the adversary. For further details on the semantics, see the [Execution Model]({% link manual/language-reference/execution-model.md %}) page. + +State fields persist across oracle calls for the entire duration of a game execution. A value written in `Initialize` is visible in every subsequent oracle call; a value written by one oracle call is visible in later oracle calls. Each game execution starts with fresh state — there is no sharing of state between different executions. + +`Initialize` is optional. If it is absent, state fields begin in an undefined state (unless their declaration includes an initializer expression). Games with no state fields at all typically need no `Initialize`. + + + +--- + +## Oracle methods + +Oracle methods are the interface the adversary uses to interact with the game. The adversary can call any oracle method in any order, any number of times, with adaptively chosen arguments. The adversary cannot directly inspect any state field; it can only observe the values that oracles return. + +Return types follow the same rules as the [Basics]({% link manual/language-reference/basics.md %}) type system: + +- Concrete types: `E.Ciphertext`, `BitString`, `Bool` +- Optional types `T?`: the oracle may return `None` (e.g., a decryption oracle that rejects invalid ciphertexts returns `E.Message?`) +- Tuple types `[T1, T2]`: the oracle returns multiple values at once + +Inside an oracle body, calls to scheme and primitive methods (e.g., `E.Enc(k, m)`) are the only way the game exercises the cryptographic construction being studied. The adversary never calls scheme methods directly — it goes through the game's oracles (though by Kerckhoff's principle, the adversary does know which scheme is being used). + +{: .warning } +ProofFrog's convention, following Joy of Cryptography, allows all game oracles (other than `Initialize`) to be called arbitrarily many times by the adversary. The language does not natively support restricting an oracle to be called only once, though you can try to encode such a condition using counters. This means that security notions in ProofFrog are often inherently multi-instance. For an example, see the two different formulations of decisional Diffie–Hellman in the examples repository: [multi-challenge DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDHMultiChal.game) versus [single-challenge DDH](https://github.com/ProofFrog/examples/blob/main/Games/Group/DDH.game). + +--- + +## `export as` + +The last line of every `.game` file is an `export as` statement that assigns a name to the security property: + +```prooffrog +export as INDOT; +``` + +This name is how the rest of the tool chain refers to the security property. In a proof file, after importing the game file, you write: + +- `INDOT(E).Left` — the left game instantiated with scheme `E` +- `INDOT(E).Right` — the right game instantiated with `E` +- `INDOT(E).Adversary` — the type of adversary for this property + +The `export as` name is also what appears in the proof's `theorem:` and `assume:` sections. The name must be a valid identifier; by convention it matches the file name (e.g., `INDOT.game` exports `INDOT`). + +--- + +## Helper games as a special case + +Not every `.game` file has to define a *cryptographic security property*. Game files can be used to capture facts that ProofFrog's engine is not able to reason about, such as mathematical properties, statistical claims, or additional program equivalence properties. The [`Games/Helpers/`](https://github.com/ProofFrog/examples/tree/main/Games/Helpers) directory contains several *helper games*. + +Here are some helper games that capture mathematical facts: + +- **`UniqueSampling`** ([`UniqueSampling.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/UniqueSampling.game)): sampling uniformly from a set `S` is indistinguishable from sampling from `S` with exclusion of a bookkeeping set (rejection sampling). +- **`Regularity`** ([`Regularity.game`](https://github.com/ProofFrog/examples/blob/main/Games/Hash/Regularity.game)): applying a hash to a uniformly random input yields a uniform output. +- **`RandomTargetGuessing`** ([`RandomTargetGuessing.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/RandomTargetGuessing.game)): guessing a random target is no easier than guessing any fixed value. +- **`ROMProgramming`** ([`ROMProgramming.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/ROMProgramming.game)): facts about programming random oracles. + +Helper games are structurally identical to security-property games — they are pairs of games with `export as` — but they appear in a proof's `assume:` block rather than the `theorem:` block. They can be assumed freely because they hold unconditionally or statistically, not by reduction to a computational hardness assumption. For the full catalog of available helper games and when to use each, see the [Canonicalization]({% link manual/canonicalization.md %}) page. + +When a mathematical fact is encoded via a helper game and used to bridge a step in a game hopping proof, ProofFrog will still check that the claimed fact was applied properly in the game hopping proof (via an appropriate reduction). However, ProofFrog does not itself check the *validity* of the fact stated in a helper game. It is up to the proof author and the proof reader to confirm the validity of these facts. It is also up to the proof author and reader to include the appropriate probabilistic bound in the analysis, for example a {% katex %}|S| / |D|{% endkatex %} term when applying a unique sampling hop that replaces sampling {% katex %}x \stackrel{\$}\leftarrow D{% endkatex %} with {% katex %}x \stackrel{\$}\leftarrow D \setminus S{% endkatex %}. + +--- + +## Examples + +### One-time secrecy + +[`Games/SymEnc/INDOT.game`](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT.game) + +```prooffrog +import '../../Primitives/SymEnc.primitive'; + +Game Left(SymEnc E) { + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, mL); + return c; + } +} + +Game Right(SymEnc E) { + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, mR); + return c; + } +} + +export as INDOT; +``` + +The adversary submits two equal-length messages and receives an encryption of either the left or the right one. A fresh key is sampled per query, so no key reuse is implied. One-time secrecy holds if the adversary cannot tell which message was encrypted. + +### CPA security (stateful game) + +[`Games/SymEnc/INDCPA_MultiChal.game`](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDCPA_MultiChal.game) + +```prooffrog +import '../../Primitives/SymEnc.primitive'; + +Game Left(SymEnc E) { + E.Key k; + Void Initialize() { + k = E.KeyGen(); + } + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + return E.Enc(k, mL); + } +} + +Game Right(SymEnc E) { + E.Key k; + Void Initialize() { + k = E.KeyGen(); + } + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + return E.Enc(k, mR); + } +} + +export as INDCPA_MultiChal; +``` + +Like one-time secrecy, but the key is sampled once in `Initialize` and reused across all oracle calls. The state field `k` persists from one `Eavesdrop` call to the next, modelling the chosen-plaintext attack setting where the adversary may request many encryptions under the same key. + +### A helper game + +[`Games/Helpers/Probability/UniqueSampling.game`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/UniqueSampling.game) + +```prooffrog +// Assumption: sampling uniformly from a set S is indistinguishable from +// sampling from S \ bookkeeping. + +Game Replacement(Set S) { + S Samp(Set bookkeeping) { + S val <- S; + return val; + } +} + +Game NoReplacement(Set S) { + S Samp(Set bookkeeping) { + // <-uniq[bookkeeping] implicitly adds val to bookkeeping, so + // repeated calls accumulate all prior outputs automatically. + S val <-uniq[bookkeeping] S; + return val; + } +} + +export as UniqueSampling; +``` + +This game captures the fact that sampling with replacement (`Replacement`) is indistinguishable from sampling without replacement (`NoReplacement`) when the bookkeeping set is small relative to the sample space. It takes no cryptographic scheme as a parameter — it is a self-contained probabilistic fact. In a proof, it appears under `assume:` rather than `theorem:`. It is up to the proof author and proof reader to mathematically compute and interpret the probabilistic loss incurred by such a step. diff --git a/manual/language-reference/index.md b/manual/language-reference/index.md new file mode 100644 index 0000000..ffb2b63 --- /dev/null +++ b/manual/language-reference/index.md @@ -0,0 +1,31 @@ +--- +title: Language Reference +layout: default +parent: Manual +nav_order: 30 +has_children: true +has_toc: false +permalink: /manual/language-reference/ +redirect_from: + - /guide/ + - /guide.html +--- + +# Language Reference + +FrogLang has four file types — primitives, schemes, games, and proofs — and a shared syntactic and semantic layer beneath them. This section is organized to match that structure. + +- [Basics]({% link manual/language-reference/basics.md %}) covers the syntactic layer that every file type uses: types, expressions and operators, sampling, statements, and imports. +- [Execution Model]({% link manual/language-reference/execution-model.md %}) covers the operational layer — what it means for an adversary to interact with a game, how state persists, what interchangeability means formally, and how composition works. +- The four file-type pages below cover what is specific to each kind of file. + +The [tutorials]({% link manual/tutorial/index.md %}) and [worked examples]({% link manual/worked-examples/index.md %}) are a good starting place if you are new to ProofFrog. + +| Reference Page | Topic | +|---|---| +| [Basics]({% link manual/language-reference/basics.md %}) | Types, operators, sampling forms, statements, imports | +| [Execution Model]({% link manual/language-reference/execution-model.md %}) | Adversary model, game lifecycle, state, non-determinism, composition, interchangeability | +| [Primitives]({% link manual/language-reference/primitives.md %}) | The `.primitive` file type: `Primitive` block, parameters, set fields, method signatures, `deterministic`/`injective` modifiers | +| [Schemes]({% link manual/language-reference/schemes.md %}) | The `.scheme` file type: `extends`, parameter forms, `requires`, field assignments, method bodies, the `this` keyword, matching-modifier rule | +| [Games]({% link manual/language-reference/games.md %}) | The `.game` file type: two-game requirement, `Initialize` and state, oracle methods, `export as`, helper games | +| [Proofs]({% link manual/language-reference/proofs.md %}) | The `.proof` file type: `let:`, `assume:`, `lemma:`, `theorem:`, `games:` blocks, reductions, the reduction parameter rule, the four-step reduction pattern | diff --git a/manual/language-reference/primitives.md b/manual/language-reference/primitives.md new file mode 100644 index 0000000..c93439f --- /dev/null +++ b/manual/language-reference/primitives.md @@ -0,0 +1,239 @@ +--- +title: Primitives +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 3 +--- + +# Primitives +{: .no_toc } + +A `.primitive` file defines an *abstract cryptographic interface*: the named sets and method signatures that characterize a cryptographic operation, with no implementations. Primitives capture a class of schemes — its types and its calling contract — but say nothing about how it works internally. Concrete instantiations are provided by schemes (covered on the [Schemes]({% link manual/language-reference/schemes.md %}) page). For the full type system available in primitives, see the [Basics]({% link manual/language-reference/basics.md %}) page. For an explanation of how oracle calls made to primitive methods behave at runtime during game evaluation, see the [Execution Model]({% link manual/language-reference/execution-model.md %}) page. + +- TOC +{:toc} + +--- + +## The `Primitive` block + +A primitive is declared with the `Primitive` keyword, a name, a parameter list, and a body containing field declarations and method signatures: + +```prooffrog +Primitive Name(param1, param2, ...) { + // Field declarations (type aliases exposed to the outside world) + Set FieldName = paramOrExpr; + Int intField = paramOrExpr; + + // Method signatures (no bodies) + ReturnType MethodName(ParamType param, ...); + deterministic ReturnType DetMethod(ParamType param, ...); + deterministic injective ReturnType InjMethod(ParamType param, ...); +} +``` + +The parameter list specifies what must be provided to instantiate the primitive. The body consists of field declarations (which bind names to types or values) and method signatures (which declare what methods exist and their types, without any implementation). Method bodies are absent by design: a primitive is an interface, not code. + +--- + +## Parameter forms + +A primitive's parameter list may contain any combination of the following: + +**`Int name`** — an integer parameter, used for security parameters, key lengths, or output lengths. + +```prooffrog +Primitive PRG(Int lambda, Int stretch) { ... } +``` + +Here `lambda` is the seed length and `stretch` is the additional output length. + +**`Set name`** — an abstract set parameter, used for message spaces, key spaces, ciphertext spaces, or any other abstract type that schemes will later make concrete. + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { ... } +``` + +**`Group name`** — a finite cyclic group parameter, used by primitives that operate over a group (e.g., a Diffie–Hellman key-exchange or ElGamal-style construction). Once a `Group G` parameter is in scope, the primitive can refer to `G.order`, `G.generator`, and `G.identity`, and to the element type `GroupElem`. See [Basics]({% link manual/language-reference/basics.md %}) for the full group accessor list. + +```prooffrog +Primitive DH(Group G) { ... } +``` + +Multiple parameters of any of these kinds may be combined in a single comma-separated list. Schemes and games that instantiate the primitive supply concrete arguments in the same order. + +--- + +## Set field declarations + +Inside a primitive body, a `Set` field declaration binds a name to one of the abstract set parameters (or a derived type expression). The declared name becomes a *type alias* that the rest of the file system can use to refer to that type through the primitive instance. + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; + + ... +} +``` + +Once an instance `E` of `SymEnc` is in scope (in a game or proof), you can write `E.Message`, `E.Ciphertext`, and `E.Key` as type names. This indirection is what lets a security game be written generically — the game refers to `E.Message` rather than to a specific bitstring length, so it works for any scheme that instantiates the primitive. + +`Int` fields work the same way for integer constants: + +```prooffrog +Primitive PRG(Int lambda, Int stretch) { + Int lambda = lambda; + Int stretch = stretch; + + ... +} +``` + +After this, a game or scheme holding an instance `G` of `PRG` can write `G.lambda` and `G.stretch` wherever an integer expression is expected. + +--- + +## Method signatures + +Method signatures in a primitive declare the return type, method name, and parameter types — but no body. The return type may be: + +- Any concrete or parameterized type: `Key`, `BitString`, `Bool` +- An optional type `T?` when the method may fail to produce a value (e.g., decryption returning `Message?` to signal a decryption failure) +- A tuple type `[T1, T2]` when the method returns multiple values + +Parameters follow the same type rules. + +**Unparameterized `BitString`** is a special placeholder available in primitive signatures for methods whose input or output length is not determined by the primitive itself. The `Hash` primitive illustrates this: the input length is not fixed by the primitive, so it appears as bare `BitString`: + +```prooffrog +deterministic BitString evaluate(BitString seed, BitString input); +``` + +A scheme extending this primitive will supply a concrete type for that parameter. + +Primitives do not contain method bodies. Any method declared in a primitive must be fully implemented by every scheme that `extends` the primitive. + +--- + +## The `deterministic` and `injective` modifiers + +Primitive method signatures may carry one or both of two modifiers that communicate behavioral guarantees to the ProofFrog engine. + +### `deterministic` + +A method marked `deterministic` always returns the same output when called on the same inputs. Without this annotation, the engine treats every method call as potentially non-deterministic — two calls with identical arguments might return different values, and the engine will not assume otherwise. + +Marking a method `deterministic` enables several engine optimizations during proof verification: it may deduplicate calls to the same method with identical arguments, propagate value aliases across method calls, hoist repeated calls out of contexts where they appear multiple times, and fold tuples through the call. These transformations are sound only when the method is genuinely deterministic, which is why the annotation must be explicitly provided rather than inferred. + +### `injective` + +A method marked `injective` maps distinct inputs to distinct outputs. The engine uses this to see through encoding wrappers when applying certain transforms. This allows the engine to realize, for example, that sampling distinct preimages under an injective function yields distinct images, enabling simplifications that would not be sound otherwise. + +### Combining the modifiers + +The two modifiers may appear together: + +```prooffrog +deterministic injective BitString evaluate(BitString seed, BitString input); +``` + +Both claims must hold: the method is deterministic (same inputs give the same output) and injective (distinct inputs give distinct outputs). The PRP primitive uses both, since a pseudorandom permutation is a keyed bijection. + +For details on exactly which engine transforms are unlocked by each modifier, see the [Canonicalization]({% link manual/canonicalization.md %}) page. + +### Matching-modifier rule + +When a scheme `extends` a primitive, the typechecker requires that the scheme's implementation of each method carries *exactly* the same `deterministic` and `injective` modifiers as the primitive's declaration. Omitting a modifier or adding an extra one is a type error. This rule ensures that the engine's assumptions about modifier semantics are consistent across the interface boundary. + +--- + +## Examples + +The following examples are drawn directly from the [Primitives](https://github.com/ProofFrog/examples/tree/main/Primitives) directory in the ProofFrog examples repository. + +### PRG — Pseudorandom Generator + +[`Primitives/PRG.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/PRG.primitive) + +```prooffrog +Primitive PRG(Int lambda, Int stretch) { + Int lambda = lambda; + Int stretch = stretch; + + deterministic BitString evaluate(BitString x); +} +``` + +Takes a seed of `lambda` bits and stretches it to `lambda + stretch` bits. The `evaluate` method is `deterministic` because a PRG is a keyed function: same seed always yields the same output. Integer fields expose `lambda` and `stretch` for use in game and scheme code. + +### PRF — Pseudorandom Function + +[`Primitives/PRF.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/PRF.primitive) + +```prooffrog +Primitive PRF(Int lambda, Int in, Int out) { + Int lambda = lambda; + Int in = in; + Int out = out; + + deterministic BitString evaluate(BitString seed, BitString input); +} +``` + +A keyed function mapping `in`-bit inputs to `out`-bit outputs under a `lambda`-bit key. Like the PRG, `evaluate` is `deterministic` because the same key and input always produce the same output. + +### SymEnc — Symmetric Encryption + +[`Primitives/SymEnc.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/SymEnc.primitive) + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; + + Key KeyGen(); + Ciphertext Enc(Key k, Message m); + deterministic Message? Dec(Key k, Ciphertext c); +} +``` + +An encryption scheme over abstract message, ciphertext, and key spaces. `KeyGen` and `Enc` are non-deterministic by default (key generation and encryption may use randomness). `Dec` is `deterministic` because decryption is a pure function of the key and ciphertext. The return type `Message?` captures that decryption can fail (returning `None` for an invalid ciphertext). + +### MAC — Message Authentication Code + +[`Primitives/MAC.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/MAC.primitive) + +```prooffrog +Primitive MAC(Set MessageSpace, Set TagSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Tag = TagSpace; + Set Key = KeySpace; + + Key KeyGen(); + + deterministic Tag MAC(Key k, Message m); +} +``` + +A tagging scheme over abstract message, tag, and key spaces. Tag computation is `deterministic`: given a fixed key and message, the tag is always the same value. Key generation is non-deterministic. + +### PRP — Pseudorandom Permutation + +[`Primitives/PRP.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/PRP.primitive) + +```prooffrog +Primitive PRP(Int lambda, Int blen) { + Int lambda = lambda; + Int blen = blen; + + deterministic injective BitString evaluate(BitString seed, BitString input); + + deterministic injective BitString evaluateInverse(BitString seed, BitString input); +} +``` + +A keyed permutation on `blen`-bit strings with a `lambda`-bit key. Both the forward evaluation and its inverse are `deterministic` and `injective`: a permutation is by definition a bijection, so distinct inputs always yield distinct outputs. The inverse method allows schemes to implement block-cipher-based constructions that require decryption. diff --git a/manual/language-reference/proofs.md b/manual/language-reference/proofs.md new file mode 100644 index 0000000..a53206f --- /dev/null +++ b/manual/language-reference/proofs.md @@ -0,0 +1,489 @@ +--- +title: Proofs +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 6 +--- + +# Proofs +{: .no_toc } + +A `.proof` file is the central artifact in ProofFrog. It proves that a scheme satisfies a security property under stated assumptions by exhibiting a sequence of games that walks from one side of the theorem to the other. Every adjacent pair of games in the sequence must be either interchangeable (verified automatically by the ProofFrog engine) or justified by an assumed security property. The engine checks every hop and reports the result. + +- TOC +{:toc} + +--- + +## File structure + +A `.proof` file has two parts separated by the `proof:` keyword. + +**Helpers** (above `proof:`): zero or more `Reduction` definitions and intermediate `Game` definitions. These are used inside the `games:` block below. + +**Proof block** (below `proof:`): four sections in order: + +| Section | Required | Purpose | +|---|---|---| +| `let:` | yes | Declares sets, integers, primitive instances, and scheme instantiations | +| `assume:` | yes (may be empty) | Lists security properties assumed to hold for underlying primitives or schemes | +| `lemma:` | no | References other proof files whose theorems become available as assumptions | +| `theorem:` | yes | The security property to be proved | +| `games:` | yes | The ordered sequence of game steps | + +The overall skeleton of a `.proof` file is: + +```prooffrog +import 'relative/path/to/Scheme.scheme'; +import 'relative/path/to/Security.game'; + +// Helpers: Reduction and intermediate Game definitions + +Game G_Mid(params) { + // state variables and oracle methods for an intermediate proof step +} + +Reduction R(params) compose AssumedGame(params) against TheoremGame(params).Adversary { + // oracle implementations +} + +proof: + +let: + Int lambda; + MyScheme S = MyScheme(lambda); + +assume: + AssumedGame(someParam); + +theorem: + TheoremGame(S); + +games: + TheoremGame(S).Side1 against TheoremGame(S).Adversary; + // ... intermediate steps ... + TheoremGame(S).Side2 against TheoremGame(S).Adversary; +``` + +--- + +## Helpers section + +The region above `proof:` (after any `import` statements) holds: + +- **`Reduction` definitions** — adapters that translate between the theorem game's adversary interface and an assumed security game's interface. Detailed in the Reductions section below. +- **Intermediate `Game` definitions** — explicit game definitions that appear as steps in the `games:` sequence but are not already defined in an imported `.game` file. + +Helpers are only meaningful when referenced from the `games:` block. They do not affect the `let:`, `assume:`, or `theorem:` sections. Reductions are documented later on this page; intermediate games are described in the next subsection. + +### Intermediate games + +An intermediate game is a `Game` definition written directly in the `.proof` file's helpers section (above `proof:`), rather than imported from a `.game` file. Intermediate games represent explicit proof states that appear as steps in the `games:` sequence. They are useful when a transition between two games is too large for the engine to verify in a single hop, so you introduce one or more named waypoints that break the transition into smaller, verifiable steps. + +Because an intermediate game is a standalone game (not a game pair), it is referenced in the `games:` block by name and parameters alone — there is no `.Side` suffix: + +```prooffrog +IntermediateGame(params) against TheoremGame(params).Adversary; +``` + +Here is a simplified example. Suppose a proof needs to transition from a game that encrypts using a PRF to one that encrypts using a truly random function. An intermediate game makes the boundary explicit: + +```prooffrog +// Helpers section: define the intermediate game +Game G_RF(PRF F, SymEnc se) { + Map T; + + se.Ciphertext Enc(se.Message m) { + se.Key k = F.inp.uniform(); + if (T[k] == bottom) { + T[k] = F.out.uniform(); + } + return se.Enc(T[k], m); + } +} + +proof: +// ... +games: + INDCPA_MultiChal(S).Real against INDCPA_MultiChal(S).Adversary; + G_RF(F, se) against INDCPA_MultiChal(S).Adversary; // intermediate step + INDCPA_MultiChal(S).Random against INDCPA_MultiChal(S).Adversary; +``` + +The engine checks that each adjacent pair of games is interchangeable or justified by an assumption, just as it would for any other game step. + +Intermediate games can take parameters from the proof's `let:` block. Common naming conventions include `G_` prefixes (e.g., `G_RF`, `G_RandKey`), `Intermediate` names (e.g., `Intermediate1`, `Intermediate2`), or descriptive names like `Hyb`. For real-world examples, see [`SymEncPRF_INDOT$.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDOT%24.proof) (which defines `G0`, `G_RF`, and `G_Uniq`) and [`HybridPKEDEM_INDCPA_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HybridPKEDEM_INDCPA_MultiChal.proof) (which defines `Intermediate1` and `Intermediate2`). + +--- + +## The `let:` block + +The `let:` block declares the mathematical objects used throughout the proof. Declarations can be: + +- **`Int name;`** — a free integer variable (e.g., a security parameter). The proof holds for all values. +- **`Set name;`** — an abstract set (no internal structure). +- **`PrimitiveType name = PrimitiveType(params);`** — a primitive instance. +- **`SchemeType name = SchemeConstructor(params);`** — a scheme instance. + +Examples drawn from real proofs: + +```prooffrog +// OTPSecure.proof: a single integer parameter, one scheme instance +let: + Int lambda; + OTP E = OTP(lambda); +``` + +```prooffrog +// INDOT$_implies_INDOT.proof: abstract sets, then a primitive instance built from them +let: + Set ProofMessageSpace; + Set ProofCiphertextSpace; + Set ProofKeySpace; + SymEnc proofE = SymEnc(ProofMessageSpace, ProofCiphertextSpace, ProofKeySpace); +``` + +```prooffrog +// TriplingPRG_PRGSecurity.proof: one integer, two chained scheme instances +let: + Int lambda; + PRG G = PRG(lambda, lambda); + TriplingPRG T = TriplingPRG(G); +``` + +Once `E = OTP(lambda)` is in `let:`, every subsequent reference to `E` — including `OneTimeSecrecy(E).Real`, `E.Key`, and so on — is resolved through that binding. Names introduced in `let:` are in scope for the entire proof block. + +For a full description of FrogLang types, see the [Basics]({% link manual/language-reference/basics.md %}) language reference page. + +--- + +## The `assume:` block + +The `assume:` block lists the security properties that the proof takes as given. Each entry names a security game and instantiates it with parameters from `let:`: + +```prooffrog +assume: + PRFSecurity(F); + UniqueSampling(BitString); +``` + +An assumption `Prop(params)` means: the two sides of the game pair `Prop` are computationally indistinguishable when instantiated with `params`. The proof is valid *conditional on* all stated assumptions being true. + +**Empty `assume:` blocks.** If the proof holds unconditionally (information-theoretically), the `assume:` block is left empty. The OTP one-time-secrecy proof is the canonical example: + +```prooffrog +assume: +``` + +**Helper games from `Games/Helpers/`.** The [`Games/Helpers/`](https://github.com/ProofFrog/examples/tree/main/Games/Helpers) directory contains game pairs that capture simple probabilistic facts rather than cryptographic hardness assumptions — for example, [`UniqueSampling`](https://github.com/ProofFrog/examples/blob/main/Games/Helpers/Probability/UniqueSampling.game) (sampling uniformly from a set is indistinguishable from sampling with exclusion of a bookkeeping set) and [`Regularity`](https://github.com/ProofFrog/examples/blob/main/Games/Hash/Regularity.game) (applying a hash to a uniformly random input yields a uniform output). These hold unconditionally and can be listed in `assume:` freely to enable certain game hops. + +An assumption entry can be used in the `games:` sequence as a hop justification as many times as needed. + +--- + +## The `lemma:` block + +The optional `lemma:` block appears between `assume:` and `theorem:`. Each entry references another `.proof` file and adds that proof's theorem to the pool of available assumptions: + +```prooffrog +lemma: + INDOT$_implies_INDOT(proofE) by 'path/to/INDOT$_implies_INDOT.proof'; +``` + +The engine verifies the referenced proof file (recursively), checks that all of its `assume:` entries are satisfied by the current proof's own assumptions, and then treats the lemma's theorem as an additional assumption. This allows large proofs to be decomposed into smaller verified pieces. + +{: .important } +**`--skip-lemmas` flag.** During iterative proof development, lemma verification can be slow. Pass `--skip-lemmas` to `proof_frog prove` to bypass lemma verification and treat lemma theorems as unverified assumptions. See the [CLI reference]({% link manual/cli-reference.md %}) page for details. + +--- + +## The `theorem:` block + +The `theorem:` block states the single security property being proved: + +```prooffrog +theorem: + OneTimeSecrecy(E); +``` + +The parameter to the theorem must be a scheme instance declared in `let:` that satisfies the primitive expected by the security game. The engine checks at the start of proof verification that the first game step is one side of this theorem and the last game step is the other side. + +--- + +## The `games:` block + +The `games:` block is the sequence of game steps that constitutes the proof. Adjacent steps must be justified as either interchangeable (code-equivalent, checked automatically) or an assumption hop (justified by an entry in `assume:` or `lemma:`). + +**Constraints:** +- The first step must be one side of the theorem's security property instantiated with the scheme from `let:`. +- The last step must be the other side. +- Every intermediate transition must be justifiable. + +The sequence for the OTP proof has a single hop: + +```prooffrog +games: + OneTimeSecrecy(E).Real against OneTimeSecrecy(E).Adversary; + OneTimeSecrecy(E).Random against OneTimeSecrecy(E).Adversary; +``` + +The engine inlines the OTP scheme into both games, canonicalizes the resulting code, and checks equivalence. Because XOR with a uniform random bit string that is used exactly once produces a uniform result, the two games canonicalize identically without any additional effort by the proof author. + +--- + +### Game step syntax + +Each entry in the `games:` block takes one of two forms. + +**Direct step** — the game is used without a reduction: + +```prooffrog +GameProperty(params).Side against GameProperty(params).Adversary; +``` + +Direct steps are used for the starting and ending games (which use the `.Side` suffix of a game pair) and for intermediate games defined in the helpers section (which are standalone games referenced by name alone, without a `.Side` suffix). + +**Composed step** — the game is applied through a reduction: + +```prooffrog +GameProperty(params).Side compose ReductionName(params) + against TheoremGameProperty(params).Adversary; +``` + +In a composed step, `GameProperty(params).Side` is the assumed game (the challenger the adversary inside the reduction talks to), and `ReductionName(params)` is a `Reduction` defined in the helpers section. The adversary in `against ...` is the adversary for the theorem game. + +The full six-step sequence from [`INDOT$_implies_INDOT.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/INDOT%24_implies_INDOT.proof) illustrates both forms: + +```prooffrog +games: + INDOT(proofE).Left against INDOT(proofE).Adversary; + + INDOT$(proofE).Real compose R1(proofE) + against INDOT(proofE).Adversary; + + INDOT$(proofE).Random compose R1(proofE) + against INDOT(proofE).Adversary; + + INDOT$(proofE).Random compose R2(proofE) + against INDOT(proofE).Adversary; + + INDOT$(proofE).Real compose R2(proofE) + against INDOT(proofE).Adversary; + + INDOT(proofE).Right against INDOT(proofE).Adversary; +``` + +Steps 1 and 6 are direct; steps 2 through 5 are composed with reductions `R1` and `R2`. + +--- + +## Reductions in detail + +A `Reduction` is a wrapper that adapts the theorem game's adversary interface to an assumed security game's interface. Syntactically: + +```prooffrog +Reduction R(params) compose AssumedGame(params) against TheoremGame(params).Adversary { + // method bodies +} +``` + +Inside a reduction body: + +- **`challenger`** refers to the composed assumed game. Calling `challenger.Method(args)` invokes an oracle of `AssumedGame`. +- The reduction must **implement the oracle interface of the theorem game** — the same method names, parameter types, and return types that the theorem game's adversary expects. +- The two games' `Initialize` methods are merged during inlining: if either game has an `Initialize` method, the engine combines their state setup automatically. + +The reduction acts as a simulator: from the theorem-game adversary's point of view, it is interacting with the theorem game; in reality, it is forwarding calls to the assumed game and translating inputs and outputs as needed. + +Here is `R1` from [`INDOT$_implies_INDOT.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/INDOT%24_implies_INDOT.proof), a complete reduction: + +```prooffrog +// R1 forwards the left message to the INDOT$ oracle +Reduction R1(SymEnc se) compose INDOT$(se) + against INDOT(se).Adversary { + se.Ciphertext Eavesdrop(se.Message mL, se.Message mR) { + return challenger.CTXT(mL); + } +} +``` + +`R1` receives two messages from the `INDOT` adversary (the `Eavesdrop` oracle), forwards only `mL` to the `INDOT$` challenger's `CTXT` oracle, and returns the result. When `INDOT$.Real` is composed with `R1`, the result is interchangeable with `INDOT.Left` (which encrypts `mL`). When `INDOT$.Random` is composed with `R1`, the result is interchangeable with `INDOT$.Random compose R2` (because neither `R1` nor `R2` uses the message when the ciphertext is random). + +--- + +### The reduction parameter rule + +{: .important } +> **A reduction's parameter list must include every parameter needed to instantiate the composed security game, even if that parameter is not referenced anywhere in the reduction body.** +> +> If a parameter required to instantiate `AssumedGame(params)` is missing from the reduction's parameter list, you will get a confusing instantiation error at the game step that uses the reduction — not at the reduction definition itself. The error message may not point clearly to the missing parameter. +> +> Example: a reduction that composes with `UniqueSampling(BitString)` must take a parameter that exposes `F.in` (such as a `PRF F` instance), even if `F` is not otherwise referenced in the reduction body. See `R_Uniq` in [`SymEncPRF_INDCPA$_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/SymEnc/SymEncPRF_INDCPA%24_MultiChal.proof) for an example. + +--- + +### The four-step reduction pattern + +The standard way to invoke an assumption in a game-hopping proof is the four-step reduction pattern. Suppose the proof is at some game {% katex %}G_A{% endkatex %} and wants to transition to a game {% katex %}G_B{% endkatex %} by appealing to a security assumption on some underlying primitive `Security` (whose two sides are `Security.Real` and `Security.Random`), via a reduction `R`. The pattern occupies four consecutive entries in the `games:` list: + +1. {% katex %}G_A{% endkatex %} — some starting game. +2. `Security.Real compose R` — the engine verifies, by code equivalence, that this is interchangeable with {% katex %}G_A{% endkatex %}. +3. `Security.Random compose R` — the **assumption hop**. The engine accepts this transition without checking equivalence because `Security` is in the proof's `assume:` block, so its `Real` and `Random` sides are indistinguishable by hypothesis. +4. {% katex %}G_B{% endkatex %} — the engine verifies, by code equivalence, that this is interchangeable with `Security.Random compose R`. + +So the assumption hop (steps 2 → 3) is sandwiched between two engine-verified interchangeability hops (1 → 2 and 3 → 4). The role of `R` is to bridge between the "high-level" games {% katex %}G_A{% endkatex %} and {% katex %}G_B{% endkatex %} and the lower-level `Security` game whose assumption we want to use. + +**Assumption hops are bidirectional.** A hop from `Side1` to `Side2` and a hop from `Side2` to `Side1` are both valid — indistinguishability is symmetric. In a symmetric proof, the forward half often uses `Real -> Random` hops and the reverse half uses `Random -> Real` hops. + +**Merging adjacent patterns.** When two four-step patterns are chained — one assumption hop followed by another — {% katex %}G_B{% endkatex %} of the first pattern often doubles as {% katex %}G_A{% endkatex %} of the second, compressing eight steps down to seven (or fewer if additional boundary games coincide). The [`TriplingPRG_PRGSecurity.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/TriplingPRG_PRGSecurity.proof) example shows the four-step pattern applied twice (once per application of the underlying PRG), with the shared boundary compressed: + +``` +games: + PRGSecurity(T).Real against PRGSecurity(T).Adversary; // } + // } 1st four-step + PRGSecurity(G).Real compose R1(G, T) against PRGSecurity(T).Adversary; // } pattern + PRGSecurity(G).Random compose R1(G, T) against PRGSecurity(T).Adversary; // } ] + // } ] + PRGSecurity(G).Real compose R3(G, T) against PRGSecurity(T).Adversary; // } ] 2nd + PRGSecurity(G).Random compose R3(G, T) against PRGSecurity(T).Adversary; // ] four-step + // ] pattern + PRGSecurity(T).Random against PRGSecurity(T).Adversary; // ] +``` + +For a detailed walkthrough of the four-step pattern applied to a concrete proof, see the [Chained Encryption worked example]({% link manual/worked-examples/chained-encryption.md %}). + +--- + +## Induction (hybrid arguments) + +Many cryptographic proofs use a *hybrid argument*: a sequence of games {% katex %}G_0, G_1, \ldots, G_q{% endkatex %} where each adjacent pair differs in the treatment of a single oracle call, and the total number of steps {% katex %}q{% endkatex %} depends on the adversary's query budget. FrogLang supports this pattern with the `induction` construct, `calls <= q` query bounds, and `assume` step assumptions. + +### Query bounds + +A proof can declare a bound on the number of oracle calls the adversary makes by adding a `calls <= expr;` declaration in the `assume:` block: + +```prooffrog +assume: + PRFSecurity(F); + calls <= q; +``` + +The variable `q` must be an `Int` declared in `let:`. This tells the engine that the adversary makes at most `q` calls to each oracle method per game execution. The bound is used during induction verification: the engine treats `q` as a symbolic integer and reasons about game equivalence under that constraint. + +### The `induction` block + +The `induction` block replaces a linear sequence of game hops with a parameterized loop. Its syntax is: + +```prooffrog +induction(i from start to end) { + // game steps parameterized by i +} +``` + +Here `i` is an `Int` variable (scoped to the block) that ranges from `start` to `end` (both expressions of type `Int`, typically `1` and `q`). The body contains game steps — the same syntax as the `games:` block — that are parameterized by `i`. + +The engine verifies an induction block in two phases: + +1. **Internal hops.** Each adjacent pair of steps inside the block is checked for interchangeability or assumption validity, treating `i` as a symbolic integer. +2. **Rollover.** The engine checks that the last step of iteration `i` is interchangeable with the first step of iteration `i + 1` (substituting `i + 1` for `i` in the first step). This ensures the iterations chain together correctly. + +The step immediately before the `induction` block is checked against the first step of the block (at `i = start`), and the step immediately after is checked against the last step (at `i = end`). + +### Step assumptions (`assume`) + +Some hops inside an induction block (or adjacent to it) require facts about game state that the engine cannot derive structurally — typically bounds on a counter variable. The `assume expr;` directive asserts that `expr` holds at that point in the game sequence, making it available to the engine's Z3-based comparison: + +```prooffrog +assume R_Hybrid(F, 1).count >= 1; + +induction(i from 1 to q) { + PRFSecurity(F).Real compose R_Hybrid(F, i) against PRFSecurity_MultiKey(F).Adversary; + PRFSecurity(F).Random compose R_Hybrid(F, i) against PRFSecurity_MultiKey(F).Adversary; +} + +assume R_Hybrid(F, q).count < q + 1; + +PRFSecurity_MultiKey(F).Random against PRFSecurity_MultiKey(F).Adversary; +``` + +The expression in `assume` can reference game or reduction fields using dot notation (e.g., `R_Hybrid(F, 1).count`). The engine resolves these field references against the games in the adjacent hop and passes the assertion to Z3 as an additional constraint. Step assumptions placed immediately before an `induction` block apply to the entry hop; those placed at the end of the block (after the last game step) apply to the rollover check. + +{: .important } +Step assumptions are **not verified** by the engine — they are trusted assertions that the proof author provides. Using an incorrect step assumption can make an invalid proof appear to verify. Use them only for facts that follow from the query bound and the structure of the reduction (such as counter range constraints). + +### Example: multi-key PRF security + +The proof that multi-key PRF security follows from single-key PRF security ([`PRFSecurity_implies_PRFSecurity_MultiKey.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PRF/PRFSecurity_implies_PRFSecurity_MultiKey.proof)) is a clean example of induction. The reduction `R_Hybrid` uses a counter to select the `i`-th call for delegation to the PRF challenger: + +```prooffrog +games: + PRFSecurity_MultiKey(F).Real against PRFSecurity_MultiKey(F).Adversary; + + assume R_Hybrid(F, 1).count >= 1; + + induction(i from 1 to q) { + PRFSecurity(F).Real compose R_Hybrid(F, i) + against PRFSecurity_MultiKey(F).Adversary; + PRFSecurity(F).Random compose R_Hybrid(F, i) + against PRFSecurity_MultiKey(F).Adversary; + } + + assume R_Hybrid(F, q).count < q + 1; + + PRFSecurity_MultiKey(F).Random against PRFSecurity_MultiKey(F).Adversary; +``` + +Each iteration replaces one PRF call with a random output via the standard assumption hop pattern. The rollover check ensures that iteration `i`'s final game (where calls `1` through `i` are random) matches iteration `i + 1`'s first game (where calls `1` through `i` are also random). + +For a more complex example with intermediate games inside the induction body, see [`CounterPRG_PRGSecurity.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PRG/CounterPRG_PRGSecurity.proof). + +--- + +## Verification and development workflow + +ProofFrog offers two ways to verify proofs: the command-line interface (CLI) and the browser-based web editor. Both use the same underlying engine; choose whichever fits your workflow. + +### CLI workflow + +To verify a proof from the terminal: + +```bash +proof_frog prove examples/Proofs/PRG/TriplingPRG_PRGSecurity.proof +``` + +Use `-v` for verbose output showing canonical forms of each game, or `-vv` for very verbose output including detailed canonicalization information: + +```bash +proof_frog prove -v examples/Proofs/PRG/TriplingPRG_PRGSecurity.proof +``` + +The engine reports each hop as `ok` or failing and prints the step type (`equivalence` or `assumption`). When a hop fails, the verbose output shows the canonical form of both sides so you can see where they diverge. + +You can also type-check a proof file without running the full proof verification: + +```bash +proof_frog check examples/Proofs/PRG/TriplingPRG_PRGSecurity.proof +``` + +See the [CLI reference]({% link manual/cli-reference.md %}) for the full set of commands. + +### Web editor workflow + +Start the web editor with `proof_frog web [directory]` and open a `.proof` file from the file tree. The toolbar provides **Parse**, **Type Check**, and **Run Proof** buttons that correspond to the CLI's `parse`, `check`, and `prove` commands. A verbosity selector next to the **Run Proof** button lets you choose Quiet, Verbose, or Very Verbose output (matching the CLI's no-flag, `-v`, and `-vv` modes). + +When a proof runs, the **Game Hops** panel in the left sidebar lists each step of the `games:` block, color-coded by result. Clicking a step opens a detail view showing the canonical forms of the games on each side of the hop — this is the fastest way to diagnose why a hop succeeds or fails. + +See the [Web Editor]({% link manual/web-editor.md %}) page for details on layout, editing features, and limitations. + +### Recommended incremental approach + +Regardless of which environment you use: + +1. Write the `let:`, `assume:`, and `theorem:` blocks first. +2. Add only the first and last game steps (the two sides of the theorem). +3. **Sketch the proof with intermediate games.** Before writing any reductions, it can be helpful to define intermediate games in the helpers section that represent the key waypoints in your proof strategy. Even before these games are connected by verified hops, writing them out forces you to be precise about what each step of the proof looks like and gives you a concrete target for each reduction. +4. Write one reduction and add its corresponding four-step pattern to `games:`. +5. Verify after each addition — run `proof_frog prove` on the CLI or click **Run Proof** in the web editor. Address failures one hop at a time before adding more steps. + +For a guided walkthrough of writing a complete proof from scratch, see [Tutorial Part 2]({% link manual/tutorial/otp-ots.md %}). diff --git a/manual/language-reference/schemes.md b/manual/language-reference/schemes.md new file mode 100644 index 0000000..16a8c16 --- /dev/null +++ b/manual/language-reference/schemes.md @@ -0,0 +1,311 @@ +--- +title: Schemes +layout: default +parent: Language Reference +grand_parent: Manual +nav_order: 4 +--- + +# Schemes +{: .no_toc } + +A `.scheme` file defines a *concrete instantiation* of a primitive: it provides implementations for every method declared in the primitive, with matching signatures. Where a [primitive]({% link manual/language-reference/primitives.md %}) is an abstract interface — declaring types and method signatures with no code — a scheme fills in the method bodies and binds abstract sets to concrete types. Schemes can themselves be parameterized over other primitives, which is how generic constructions such as KEMDEM, EncryptThenMAC, and TriplingPRG are expressed. For the full type system and statement forms available in scheme bodies, see the [Basics]({% link manual/language-reference/basics.md %}) page. + +- TOC +{:toc} + +--- + +## The `Scheme ... extends` block + +A scheme is declared with the `Scheme` keyword, a name, a parameter list, the `extends` keyword naming the primitive it instantiates, and a body: + +```prooffrog +import 'relative/path/to/Primitive.primitive'; + +Scheme Name(parameters) extends PrimitiveName { + requires expression; // optional precondition + + Set Field = SomeType; // type alias (concrete set) + Int field = expression; // integer field + + ReturnType MethodName(ParamType param, ...) { + // method body + } +} +``` + +`extends PrimitiveName` links the scheme to the primitive it implements. The scheme must provide a method body for every method declared in that primitive. The `requires` clause and field assignments are optional, and method bodies use the same statement forms described on the [Basics]({% link manual/language-reference/basics.md %}) page. + +Imports use paths relative to the directory of the importing file. + +--- + +## Parameter forms + +A scheme's parameter list accepts three kinds of parameters, which may be mixed freely in a comma-separated list. + +**`Int name`** — an integer parameter, such as a security parameter or bitstring length. + +```prooffrog +Scheme OTP(Int lambda) extends SymEnc { ... } +``` + +**`Set name`** — an abstract set parameter, used when the scheme should remain generic over some type space. + +```prooffrog +Scheme SomeScheme(Set MessageSpace) extends SomeInterface { ... } +``` + +**A primitive instance** — the most powerful form. A scheme can take another primitive as a parameter and call methods on it inside its own method bodies. This is how generic constructions are written. + +```prooffrog +Scheme TriplingPRG(PRG G) extends PRG { ... } +``` + +Here `TriplingPRG` takes a PRG named `G` and itself extends PRG, meaning it is a new PRG built from an underlying one. Inside the scheme's method bodies, calls like `G.evaluate(s)` invoke the underlying PRG. The parameter name (`G`) acts as the instance through which the underlying primitive's methods and fields are accessed. + +Multiple parameters of any combination may appear together: + +```prooffrog +Scheme KEMDEM(KEM K, SymEnc E) extends PubKeyEnc { ... } +Scheme EncryptThenMAC(SymEnc E, MAC M) extends SymEnc { ... } +``` + +--- + +## `requires` clauses + +A `requires` clause states a precondition that must hold among the scheme's parameters. It appears immediately after the opening brace, before any field or method declarations: + +```prooffrog +Scheme TriplingPRG(PRG G) extends PRG { + requires G.lambda == G.stretch; + ... +} +``` + +The expression may reference any integer fields exposed by the parameter primitives. In the example above, `G.lambda` and `G.stretch` come from the PRG primitive's integer field declarations; the clause asserts that the underlying PRG has equal seed length and stretch length, which the TriplingPRG construction requires in order to apply the underlying PRG twice as intended. + +A scheme may have zero or more `requires` clauses. Each clause is checked when the scheme is instantiated in a proof's `let:` block: if the condition does not hold for the supplied parameter values, the proof is rejected at type-checking time. + +--- + +## Field assignments + +Inside the scheme body, field declarations bind names to concrete types or integer expressions. These are the same field forms that appear in primitives, but here they are assigned concrete values rather than abstract parameters. + +**Set type aliases** bind an abstract set name from the primitive's interface to a concrete type: + +```prooffrog +Set Key = BitString; +Set Message = BitString; +Set Ciphertext = BitString; +``` + +Once defined, `Key`, `Message`, and `Ciphertext` can be used as type names inside the scheme's method bodies. Games and proofs that hold an instance `S` of the scheme can access these as `S.Key`, `S.Message`, and `S.Ciphertext`. + +**Integer fields** expose computed quantities derived from parameters: + +```prooffrog +Int lambda = G.lambda; +Int stretch = 2 * G.lambda; +``` + +These are accessible from outside the scheme as `S.lambda` and `S.stretch`, making it possible for games and proofs to reference the scheme's numeric properties without hard-coding them. + +For the full range of types available for field assignments (bitstrings, tuples, optional types, and more), see the [Basics]({% link manual/language-reference/basics.md %}) page. + +--- + +## Method bodies + +A scheme must implement every method declared in the primitive it extends. Method bodies use ordinary FrogLang statements: variable declarations and assignments, uniform random sampling, conditionals, return statements, and calls to other primitives or other methods on the same scheme. See the [Basics]({% link manual/language-reference/basics.md %}) page for the complete statement reference. + +### Calling primitive parameters + +Methods on primitive parameters are called with dot notation: + +```prooffrog +BitString<2 * lambda> r = G.evaluate(s); +K.SharedSecret ks = K.Encaps(pk)[0]; +``` + +### The `this` keyword + +A scheme can call its own other methods using `this.MethodName(args)`. This is useful when one method's implementation builds on another method of the same scheme: + +```prooffrog +Scheme Example(Int lambda) extends SomePrimitive { + SomeType Helper(SomeType x) { ... } + + SomeType Main(SomeType input) { + SomeType intermediate = this.Helper(input); + return intermediate; + } +} +``` + +During proof verification, `this` references are automatically rewritten to the scheme's instance name (for example, `S.Helper(...)` when the scheme is instantiated as `S` in a proof), so the inliner can resolve the call correctly. You do not need to handle this rewriting manually; it is performed transparently by the engine. The [Canonicalization]({% link manual/canonicalization.md %}) page describes the inlining process in more detail. + +--- + +## The matching-modifier rule + +{: .important } +When a scheme `extends` a primitive, the typechecker requires that each method implementation carries *exactly* the same `deterministic` and `injective` modifiers as the corresponding method declaration in the primitive. Adding a modifier that the primitive does not declare, or omitting one that the primitive does declare, is a type error. Return types must also match exactly: `T?` is not accepted in place of `T` or vice versa. + +This is a common source of type errors when writing schemes. If the primitive declares: + +```prooffrog +deterministic Message? Dec(Key k, Ciphertext c); +``` + +then the scheme must declare: + +```prooffrog +deterministic Message? Dec(Key k, Ciphertext c) { ... } +``` + +Writing `Message? Dec(...)` (missing `deterministic`) or `deterministic Message Dec(...)` (missing `?`) are both type errors, even though the difference may seem minor. The reason is that the engine relies on these annotations to determine which proof-verification transforms it may apply; a mismatch would make the scheme's behavior inconsistent with what the security games assume about the primitive interface. + +See the [Primitives]({% link manual/language-reference/primitives.md %}) page for a full description of the `deterministic` and `injective` modifiers. + +--- + +## Examples + +The following examples are drawn from the [Schemes](https://github.com/ProofFrog/examples/tree/main/Schemes) directory in the ProofFrog examples repository. + +### OTP — One-Time Pad + +[`Schemes/SymEnc/OTP.scheme`](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/OTP.scheme) — the simplest possible symmetric encryption scheme: all three spaces are `BitString`, key generation samples a uniform random key, and encryption and decryption are both XOR. + +```prooffrog +import '../../Primitives/SymEnc.primitive'; + +Scheme OTP(Int lambda) extends SymEnc { + Set Key = BitString; + Set Message = BitString; + Set Ciphertext = BitString; + + Key KeyGen() { + Key k <- Key; + return k; + } + + Ciphertext Enc(Key k, Message m) { + return k + m; + } + + deterministic Message? Dec(Key k, Ciphertext c) { + return k + c; + } +} +``` + +Note that `Dec` carries the `deterministic` modifier and returns `Message?`, matching the SymEnc primitive's declaration exactly. + +### TriplingPRG — Generic PRG Construction + +[`Schemes/PRG/TriplingPRG.scheme`](https://github.com/ProofFrog/examples/blob/main/Schemes/PRG/TriplingPRG.scheme) — a generic construction that takes a PRG as a parameter and produces a PRG with twice the stretch. It uses a `requires` clause, integer field assignments, and calls the underlying PRG twice. + +```prooffrog +import '../../Primitives/PRG.primitive'; + +Scheme TriplingPRG(PRG G) extends PRG { + requires G.lambda == G.stretch; + + Int lambda = G.lambda; + Int stretch = 2 * G.lambda; + + deterministic BitString evaluate(BitString s) { + BitString<2 * lambda> result1 = G.evaluate(s); + BitString x = result1[0 : lambda]; + BitString y = result1[lambda : 2*lambda]; + BitString<2 * lambda> result2 = G.evaluate(y); + + return x || result2; + } +} +``` + +The `requires` clause enforces that the underlying PRG's seed length equals its stretch, which the construction relies on to feed the second half of the first PRG output back as a seed to the second PRG call. Integer fields `lambda` and `stretch` expose the new PRG's parameters so that games and proofs can reference `T.lambda` and `T.stretch` for a `TriplingPRG` instance `T`. + +### EncryptThenMAC — Two-Primitive Generic Construction + +[`Schemes/SymEnc/EncryptThenMAC.scheme`](https://github.com/ProofFrog/examples/blob/main/Schemes/SymEnc/EncryptThenMAC.scheme) — a generic authenticated encryption scheme that takes a symmetric encryption scheme and a MAC as parameters. It shows a `requires` clause relating sets from two different primitive parameters, tuple types for key and ciphertext, and method bodies that call into two distinct primitive instances. + +```prooffrog +import '../../Primitives/SymEnc.primitive'; +import '../../Primitives/MAC.primitive'; + +Scheme EncryptThenMAC(SymEnc E, MAC M) extends SymEnc { + requires E.Ciphertext subsets M.Message; + + Set Key = [E.Key, M.Key]; + Set Message = E.Message; + Set Ciphertext = [E.Ciphertext, M.Tag]; + + Key KeyGen() { + E.Key ke = E.KeyGen(); + M.Key me = M.KeyGen(); + return [ke, me]; + } + + Ciphertext Enc(Key k, Message m) { + E.Ciphertext c = E.Enc(k[0], m); + M.Tag t = M.MAC(k[1], c); + return [c, t]; + } + + deterministic Message? Dec(Key k, Ciphertext c) { + if (c[1] != M.MAC(k[1], c[0])) { + return None; + } + return E.Dec(k[0], c[0]); + } +} +``` + +The key is a tuple `[E.Key, M.Key]` and the ciphertext is a tuple `[E.Ciphertext, M.Tag]`. The `requires` clause ensures that the symmetric ciphertext space is a subset of the MAC message space, as required by the Encrypt-then-MAC construction. Tuple element access (`k[0]`, `k[1]`, `c[0]`, `c[1]`) is used throughout to unpack the compound types. + +### KEM-DEM — Hybrid Public-Key Encryption + +[`Schemes/PubKeyEnc/HybridKEMDEM.scheme`](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HybridKEMDEM.scheme) — a hybrid public-key encryption scheme that combines a key encapsulation mechanism with a symmetric encryption scheme. + +```prooffrog +import '../../Primitives/KEM.primitive'; +import '../../Primitives/NonNullableSymEnc.primitive'; +import '../../Primitives/PubKeyEnc.primitive'; + +Scheme KEMDEM(KEM K, SymEnc E) extends PubKeyEnc { + requires K.SharedSecret subsets E.Key; + + Set Message = E.Message; + Set Ciphertext = [K.Ciphertext, E.Ciphertext]; + Set PublicKey = K.PublicKey; + Set SecretKey = K.SecretKey; + + [PublicKey, SecretKey] KeyGen() { + return K.KeyGen(); + } + + Ciphertext Enc(PublicKey pk, Message m) { + [K.SharedSecret, K.Ciphertext] x = K.Encaps(pk); + E.Key k_sym = x[0]; + K.Ciphertext c_kem = x[1]; + E.Ciphertext c_sym = E.Enc(k_sym, m); + return [c_kem, c_sym]; + } + + deterministic Message? Dec(SecretKey sk, Ciphertext c) { + K.Ciphertext c_kem = c[0]; + E.Ciphertext c_sym = c[1]; + K.SharedSecret k_sym = K.Decaps(sk, c_kem); + return E.Dec(k_sym, c_sym); + } +} +``` + +The `requires` clause checks that the KEM's shared secret space is a subset of the symmetric encryption key space, ensuring the shared secret can be used directly as a symmetric key. The `KeyGen` method simply delegates to the KEM's key generation. `Enc` calls `K.Encaps` to obtain a shared secret and a KEM ciphertext, then uses the shared secret as the symmetric key to encrypt the message. diff --git a/manual/limitations.md b/manual/limitations.md new file mode 100644 index 0000000..6e12445 --- /dev/null +++ b/manual/limitations.md @@ -0,0 +1,237 @@ +--- +title: Limitations +layout: default +parent: Manual +nav_order: 50 +--- + +# Limitations +{: .no_toc } + +ProofFrog is a mechanized proof assistant for a specific class of cryptographic arguments. +This page describes where the tool stops: what it cannot express, what it deliberately +chooses not to do, and where its current implementation is weak. +If a proof step fails unexpectedly, this page is the right place to start diagnosing why. + +- TOC +{:toc} + +--- + +## Soundness vs. capability -- read this first + +This page is about **capability limits**: things the engine cannot do or has not yet been +built to do. When you hit a capability limit you see a failed proof step, and the goal is +to understand why and find a workaround. The sections below catalogue those situations. + +A separate concern is **trust limits**: even when the engine validates a proof, how +confident should you be in the validation? That question is about soundness -- does the +engine's acceptance of a proof actually guarantee what the proof claims? The answer +involves the formal semantics of FrogLang, the correctness of the canonicalization +pipeline, and the scope of the reduction paradigm itself. That story lives in the For Researchers area at [Soundness]({% link researchers/soundness.md %}). Both kinds of +limits are real and important. They are documented separately because they require +different mental models: a capability limit is a practical obstacle to finishing a proof; +a trust limit is a question about what a finished proof means. + +--- + +## Things ProofFrog does not model + +FrogLang is a domain-specific language for game-hopping proofs in the reduction security +paradigm. Several aspects of real cryptographic systems are deliberately outside its scope. +These are not bugs or oversights; they are the boundary the language was designed around. + +**Computational complexity.** FrogLang does not model the running time of adversaries or +scheme algorithms. The notion of "efficient adversary" is present as a conceptual +assumption but is not tracked in the language. ProofFrog proofs establish qualitative +reductions -- if the underlying assumption holds for all efficient adversaries, so does the +theorem -- but do not bound how the advantage or running time scales with security +parameters. + +**Quantitative bounds.** Security loss and advantage bounds are not tracked. A successful +proof says that a reduction exists; it does not say how tight the reduction is. If your +proof requires a concrete tightness argument (for example, hybrid counting over +polynomially many independent samples), that argument lives outside what ProofFrog +verifies. + +**Recursive computation.** FrogLang methods cannot call themselves recursively, and +loops are bounded (numeric `for` loops have a fixed range and generic `for` loops iterate +over a finite collection; there is no general while loop or unbounded iteration). The +language is not Turing-complete. + +**Side channels.** Timing, power, cache, and other physical side-channel attacks are not +modeled. All games are defined solely by the sequence of return values their oracles +produce. An adversary who learns something from the time it takes an oracle to respond +is outside the model. + +**Concurrency.** All oracle calls are sequential. There is no model of concurrent +adversaries, parallel oracle queries, or adaptive ordering of queries that depends on +partial results arriving simultaneously. + +**Abort semantics.** FrogLang has no `abort` statement. Failure conditions are +represented through control flow: returning `None` via optional types, conditional +early returns, or implicitly through collision probabilities in unique sampling +(`<-uniq`). A proof that relies on a game aborting with a certain probability requires +modeling that probability as a distinguishing event within the reduction, not as a +built-in abort primitive. + +--- + +## Capabilities the engine deliberately lacks + +Some things the engine does not do are intentional design choices, not missing features. + +**Lemma synthesis.** The engine does not search for or invent lemmas. When a proof step +requires a non-trivial algebraic identity or a helper reduction, the user states the +lemma explicitly using the `lemma:` block and provides the corresponding proof file. +The engine verifies the lemma proof and adds its conclusion to the available assumptions, +but the creative step of identifying what lemma is needed belongs to the user. + +**Reduction search.** The engine does not search for reductions. When a proof hop +invokes an underlying hardness assumption, the user supplies the reduction -- the code +that translates a challenge from the underlying game into the context of the proof. +The engine verifies that the supplied reduction composed with each side of the assumption +is interchangeable with the adjacent games in the proof sequence, but it does not propose +or construct reductions automatically. + +**Probability bookkeeping.** The engine does not compute or track numerical advantage +bounds. It verifies that a chain of interchangeable-or-reduction-justified hops connects +the two sides of the security property, which establishes that the theorem follows from +the stated assumptions. Quantifying the concrete advantage (for example, the probability +of a collision in a birthday argument) is the user's responsibility and is stated as an +external comment or separate argument outside the proof file. + +--- + +## Capabilities the engine aspires to but is currently weak at + +The following limitations are registered in the engine's diagnostic system and will +be flagged explicitly in the `prove -v` output when a failing step looks like one of +these cases. + +### `||` and `&&` commutativity +{: .no_toc } + +The engine normalizes `+` and `*` via commutative chain normalization, but it does not +normalize the operand order of `||` (logical OR on Bool, or concatenation on BitString) +or `&&`. As a result, `a || b` and `b || a` will not be recognized as identical during +canonicalization. + +**Workaround.** Reorder the operands manually in your intermediate game so that both +sides of the hop list them in the same order. + +### Field declaration ordering +{: .no_toc } + +The engine does not reorder field declarations (state variables or local variable +declarations) within a game. If two games have the same set of declarations but in a +different order, they will not be recognized as equivalent. + +**Workaround.** Match the declaration order between adjacent games manually. When writing +an intermediate game, inspect the canonical form produced by `prove -v` on the preceding +step and copy the field order from there. + +### `||` and `&&` associativity +{: .no_toc } + +Just as the engine does not normalize commutativity for `||` and `&&`, it does not +normalize associativity. The expressions `(a || b) || c` and `a || (b || c)` are +treated as structurally different and will not be recognized as identical. + +**Workaround.** Reparenthesize the expression in your intermediate game so that the +parenthesization matches the previous step. Running `prove -v` shows the canonical form +on both sides of a failing hop, which makes it straightforward to identify where the +grouping differs. + +### Extra temporary variable form +{: .no_toc } + +The engine's `SimplifyReturn` transform inlines single-use temporary variables that are +immediately returned. If this transform fires on one game but not on a structurally +similar game written differently, the two canonical forms will not match. Concretely: + +``` +// Form A -- SimplifyReturn inlines this +Type v = f(); +return v; + +// Form B -- already in direct form +return f(); +``` + +When one game uses Form A and the other uses Form B, the engine may or may not unify +them depending on how the surrounding code looks. + +**Workaround.** Pick one form consistently across all intermediate games in a proof. +The direct-return form (`return f();`) is the canonical target, so preferring it avoids +the discrepancy. + +### `if`/`else if` branch reordering +{: .no_toc } + +The engine does not normalize the order of `if`/`else if` branches. Two games that +test the same conditions in a different order will not be recognized as equivalent, even +if the branch bodies are identical. + +**Workaround.** Match the branch order from the previous game. When writing an +intermediate game, check the canonical form from `prove -v` and copy the branch order +from there. + +### Higher-level soft spots +{: .no_toc } + +Beyond the registered limitations above, the following areas are known to be +challenging for the current engine, even though the diagnostic system may not always +produce a specific error message for them. + +**Group-theoretic reasoning beyond the engine's listed identities.** The engine knows +a fixed set of algebraic identities (XOR cancellation, modular arithmetic rules, and +similar). If a proof step requires an identity outside that set -- for example, a +non-trivial group law or an algebraic manipulation involving exponents -- the engine +will not find it automatically. The user must introduce an intermediate game that +already has the result of the identity applied. + +**Nested induction.** FrogLang supports single-level hybrid arguments via the `induction` construct (see [Proofs: Induction]({% link manual/language-reference/proofs.md %}#induction-hybrid-arguments)), but nested induction or induction with complex base cases must be handled by breaking the argument into multiple proof files composed via the `lemma:` mechanism. + +**Probabilistic reasoning beyond the algebraic identities listed in [Canonicalization]({% link manual/canonicalization.md %}).** +The engine knows that XOR with a uniform value produces a uniform value, that unique +sampling gives independent uniform outputs, and that random functions on fresh inputs +give uniform outputs. Beyond these specific patterns, general probabilistic arguments -- +such as the probability of a collision under a specific distribution, or the independence +of two events that happen to be statistically independent -- are not mechanized. Such +arguments must be made externally and reflected in the intermediate game structure. + +--- + +## What kinds of proofs do work well + +ProofFrog is well matched to a recognizable class of proofs: + +- **Textbook game-hopping proofs** at the level of Joy of Cryptography and similar + introductory graduate texts. The worked examples in the ProofFrog distribution cover + one-time pad security, pseudorandom generator security, and similar foundational + results. + +- **Proofs that are primarily inlining plus a small number of reductions.** The engine's + core strength is automated interchangeability checking via canonicalization. Proofs + that consist of a sequence of definitional expansions (inlining a scheme into a game), + followed by one or two reduction hops, map directly onto what the engine does best. + +- **Proofs whose structure mirrors a standard pen-and-paper argument.** If you can + describe the proof as "game G1 is identical to game G2 by inspection, then we hop via + the PRG assumption to game G3, then G3 is identical to the right side by inspection," + the engine is likely to validate each of those steps. + +See the [Worked Examples]({% link manual/worked-examples/index.md %}) section of the manual for annotated proof walkthroughs, and the [Examples Gallery]({% link examples.md %}) for a browsable index of all proof files in the distribution. + +--- + +## Reporting a limitation + +If you encounter a case where the engine rejects a proof step that you believe is +mathematically valid, please open an issue at +[https://github.com/ProofFrog/ProofFrog/issues](https://github.com/ProofFrog/ProofFrog/issues). +Include the smallest proof file that reproduces the problem, the full output of +`proof_frog prove -v ` (which shows the canonical form of +each game and the point of failure), and a brief description of what you expected the +engine to accept and why. diff --git a/manual/troubleshooting.md b/manual/troubleshooting.md new file mode 100644 index 0000000..9bfa808 --- /dev/null +++ b/manual/troubleshooting.md @@ -0,0 +1,414 @@ +--- +title: Troubleshooting +layout: default +parent: Manual +nav_order: 90 +--- + +# Troubleshooting +{: .no_toc } + +This page is keyed by symptom. Find the error message or behavior you are +seeing, then follow the **Fix** to resolve it. Where a more detailed +explanation exists elsewhere in the manual, a **See also** link is provided. + +- TOC +{:toc} + +--- + +## Install problems + +**Symptom:** `proof_frog: command not found` + +**Likely cause:** The virtual environment where ProofFrog was installed is not +activated in the current shell session, or you installed ProofFrog into a +different Python installation than the one your shell resolves. + +**Fix:** Activate the virtual environment (`source .venv/bin/activate` on +macOS/Linux, `source .venv/bin/activate.fish` on fish, `.venv\Scripts\Activate.ps1` on Windows PowerShell) before +running `proof_frog`. If you are not using a virtual environment, verify with +`which proof_frog` (macOS/Linux) or `where proof_frog` (Windows) that the +command is on your `PATH`. + +**See also:** [Installation]({% link manual/installation.md %}) + +--- + +**Symptom:** `python3: command not found` + +**Likely cause:** Python is not installed, or the installer did not add it to +the system `PATH`. + +**Fix:** Install Python 3.11 or newer from python.org or your operating +system's package manager, then open a new terminal. On macOS, `brew install +python@3.11` is the recommended path; on Windows, the installer checkbox +"Add Python to PATH" must be checked. + +**See also:** [Installation]({% link manual/installation.md %}) + +--- + +**Symptom:** `pip install proof_frog` completes but then says "no matching +distribution found" or installs a very old version unexpectedly. + +**Likely cause:** The active `pip` belongs to a Python version older than +3.11. ProofFrog requires Python 3.11 or newer; packages published for older +Python versions are not compatible. + +**Fix:** Run `python3 --version` to confirm which interpreter is active. If +the version is below 3.11, create your virtual environment with an explicit +path, for example `python3.11 -m venv .venv`, then activate and install again. + +**See also:** [Installation]({% link manual/installation.md %}) + +--- + +**Symptom:** On Windows PowerShell, running `.venv\Scripts\Activate.ps1` +prints `cannot be loaded because running scripts is disabled on this system`. + +**Likely cause:** PowerShell's default execution policy on a fresh Windows +install blocks running `.ps1` scripts. + +**Fix:** Run the following command once per user account (no administrator +privileges required), then try activating again: +```powershell +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +``` + +**See also:** The execution-policy warning in [Installation]({% link manual/installation.md %}) + +--- + +## Parse errors + +Parse errors use the format `::: parse error: `, +followed by the offending source line and a caret pointing to the problem +character. + +**Symptom:** `::: parse error: missing ';' before ''` +(where `` is typically `}` or the first token of the next statement) + +**Likely cause:** A statement in a method body is missing its terminating +semicolon. + +**Fix:** Add `;` at the end of the statement on the indicated line. + +--- + +**Symptom:** `::: parse error: unexpected 'export'; a closing +'}' is missing for a Game or method definition above` + +**Likely cause:** A `Game` block or method body is missing its closing brace. +The parser does not detect the problem until it reaches the next top-level +`export` keyword. + +**Fix:** Check the Game or method that ends just before line `` for a +missing `}`. + +--- + +**Symptom:** `::: parse error: a .game security property +file must contain exactly two Game definitions (e.g. Left and Right), but only +one was found` + +**Likely cause:** A `.game` file contains only one `Game` block instead of +the required pair (such as `Real` and `Ideal`, or `Left` and `Right`). + +**Fix:** Add the second `Game` definition to the file. + +**See also:** [Games]({% link manual/language-reference/games.md %}) + +--- + +**Symptom:** `::: parse error: import paths must use single +quotes, not double quotes (e.g. import '../path/to/file';)` + +**Likely cause:** An `import` statement used double-quoted path strings, which +FrogLang does not allow. + +**Fix:** Replace `"..."` with `'...'` in all `import` statements. + +--- + +**Symptom:** `:: imported file not found: ''` + +**Likely cause:** The path in an `import` statement does not resolve to an +existing file relative to the importing file's directory. + +**Fix:** Confirm the file exists at the given path and that the import is +relative to the directory containing the source file that has the `import` +statement, not relative to the directory where the CLI is invoked. Import +paths in FrogLang are resolved relative to the importing file's directory. + +**See also:** [Language Reference: Basics]({% link manual/language-reference/basics.md %}) + +--- + +## Type errors + +Type errors use the format `::: error: `, with the +offending source line and a caret on the next two lines. + +**Symptom:** +`::: error: Method '' has different signatures in and : vs ` + +**Likely cause:** The two games in a `.game` security-property file declare +the same method name but with different parameter types or return types. The +two games must have exactly matching method signatures. + +**Fix:** Align the method signatures in both games so they agree on parameter +types, return type, and order. + +**See also:** [Games]({% link manual/language-reference/games.md %}) + +--- + +**Symptom:** +`::: error: Scheme does not correctly implement primitive

: method '' is missing the 'deterministic' modifier required by primitive

` + +(Variants exist for `'deterministic' modifier not declared by primitive`, +`'injective' modifier required`, and `'injective' modifier not declared`.) + +**Likely cause:** The primitive declares a method with a `deterministic` or +`injective` annotation, but the scheme's implementation of that method omits +(or adds) the annotation, or vice versa. The typechecker requires the +annotation set to match exactly. + +**Fix:** Add or remove the `deterministic` / `injective` modifier on the +scheme method so it matches the corresponding primitive method declaration. + +**See also:** [Schemes]({% link manual/language-reference/schemes.md %}) + +--- + +**Symptom:** +`::: error: Scheme does not correctly implement primitive

: method '' return type does not match primitive

return type ` + +**Likely cause:** The scheme's method return type is `T?` (optional) where +the primitive declares `T` (non-optional), or the concrete type does not match +what the primitive specifies. The typechecker applies strict optional checking +for scheme-vs-primitive conformance. + +**Fix:** Make the scheme method's return type agree exactly with the primitive, +including the presence or absence of `?`. If the method legitimately returns +`None` in some cases, the primitive itself must declare the optional type. + +**See also:** [Language Reference: Basics]({% link manual/language-reference/basics.md %}) + +--- + +**Symptom:** +`::: error: Could not determine type of ` + +**Likely cause:** An identifier is used before it is declared, or a name that +should refer to an imported primitive, scheme, or game is not visible in the +current scope. This can also occur if a `let:` binding in a proof file is +missing. + +**Fix:** Check that every name referenced in the expression is declared (in +scope) before its first use and that the containing file imports all necessary +primitives, schemes, and games. + +--- + +**Symptom:** +`::: error: not found in imports` + +**Likely cause:** A reduction, game step, or proof `assume:` block references +a name that does not appear in any `import` statement in the file. + +**Fix:** Add an `import` statement for the file that defines the missing name, +and ensure the path is correct relative to the directory where the CLI is +invoked. + +--- + +## Proof errors + +Proof errors are printed to standard output (not standard error) and are +prefixed with a colored status line. + +**Symptom:** +`Proof Succeeded, but is incomplete: first and last steps use the same side ()` + +**Likely cause:** All individual hops verified, but the first and last game +steps both use the same side (e.g., both `Real`) of the security game, so the +chain of games does not connect the two sides of the theorem. A complete proof +must begin on one side and end on the other. + +**Fix:** Add the missing intermediate hops so that the game sequence starts on +one side of the security property and ends on the other. Check that the final +game step uses the opposite side from the first step. + +--- + +**Symptom:** +`Proof Failed! ( step(s) failed: )` + +together with a per-step line like `Step 3/8 -> equivalence FAILED` + +**Likely cause:** One or more interchangeability hops could not be verified +because the canonical forms of the two adjacent games differ in a way the +engine cannot reconcile. A diagnostic summary line (indented below the step) +describes the first detected difference. + +**Fix:** Run `proof_frog prove -v` to see full canonical-form diagnostics. +If the diagnostic reports a known engine limitation (see the section below), +apply the listed workaround. Otherwise, inspect the canonical forms of the +two games and ensure they are genuinely equivalent under ProofFrog's semantics. + +**See also:** [CLI Reference]({% link manual/cli-reference.md %}) + +--- + +**Symptom:** +`::: error: lemma proof file not found: ''` + +**Likely cause:** A `lemma:` block in a proof file references a `.proof` path +that does not exist relative to the directory of the proof file. + +**Fix:** Correct the path in the `lemma:` entry, or use `--skip-lemmas` on +the CLI to bypass lemma verification temporarily while developing the proof. + +**See also:** [CLI Reference]({% link manual/cli-reference.md %}) + +--- + +**Symptom:** The engine prints `Proof must start with a game matching the +theorem's security game` followed by lines showing `Theorem expects:` and +`First step uses:`. + +**Likely cause:** The first game step in the `games:` list does not match the +security game named in the `theorem:` line. + +**Fix:** Change the first step so it uses a side of the game named in +`theorem:`, or correct the `theorem:` declaration to match the game used in +the first step. + +--- + +**Symptom:** An assumption hop is listed in the `games:` sequence but the +engine treats it as an equivalence check (which then fails) rather than +accepting it by assumption. + +**Likely cause:** The pair of game steps does not match any entry in the +`assume:` block. The engine recognizes an assumption hop only when the two +adjacent steps compose with the two sides of an assumed security game. + +**Fix:** Verify that the game named in `assume:` exactly matches (same name, +same parameters) the game used in the two steps. Any parameter mismatch causes +the engine to fall through to an equivalence check. + +**See also:** [Language Reference: Proofs]({% link manual/language-reference/proofs.md %}) + +--- + +## Web editor problems + +**Symptom:** Running `proof_frog web` prints an address but immediately exits, +or you see a message like `Could not find a free port`. + +**Likely cause:** All 100 ports starting from 5173 are already in use, which +is extremely unlikely in practice. The more common scenario is that a previous +`proof_frog web` process is still running in another terminal. + +**Fix:** Stop any existing `proof_frog web` process. The server automatically +scans from port 5173 upward for the next free port and prints the actual URL +it bound to; you do not need to free a specific port. + +**See also:** [Web Editor]({% link manual/web-editor.md %}) + +--- + +**Symptom:** `proof_frog web` starts and prints a URL but no browser window +opens. + +**Likely cause:** The `webbrowser.open` call in the server failed silently +because there is no default browser configured in the environment (common in +headless or remote SSH sessions). + +**Fix:** Copy the URL printed to the terminal (for example +`http://127.0.0.1:5173`) and open it manually in any browser. + +**See also:** [Web Editor]({% link manual/web-editor.md %}) + +--- + +**Symptom:** The web editor output panel shows a red "Failed" status but the +body of the output contains the text "Succeeded". + +**Likely cause:** This matches the `Proof Succeeded, but is incomplete` case +described in the Proof errors section above. All hops verified individually, +but the game sequence does not span both sides of the theorem. + +**Fix:** See the "Proof Succeeded, but is incomplete" entry above under Proof +errors. + +--- + +## "My proof should work but doesn't" + +If all of your proof steps look logically correct but one or more +interchangeability hops still fail, the steps below help narrow down the +cause. + +**Step 1: Run with verbose output.** + +```bash +proof_frog prove -v your_proof.proof +``` + +The `-v` flag enables Level 3 diagnostics: the canonical form of both games +at each failing hop, a near-miss report, and a notice when the failure matches +a known engine limitation. + +**See also:** [CLI Reference]({% link manual/cli-reference.md %}) + +**Step 2: Check for known engine limitations.** + +The ProofFrog engine currently does not normalize the following patterns when +comparing canonical game forms. If the verbose output shows a diff that matches +one of these descriptions, apply the listed workaround in your intermediate +game or reduction. + +1. **Operand order for `||` and `&&` is not normalized.** + The engine normalizes `+` and `*` via commutative chains but does not + reorder the operands of `||` (logical OR on `Bool`) or `&&`. + *Workaround:* Reorder operands manually in your intermediate game to match + the order used in the adjacent game. + +2. **Field (statement) declaration order within a method is not reordered.** + Two games that are identical except that their field assignments appear in a + different order will not be recognized as equivalent. + *Workaround:* Match the statement order from the previous game in your + intermediate game. + +3. **Associativity of `||` and `&&` is not normalized.** + `(a || b) || c` and `a || (b || c)` are not recognized as equivalent, even + though they are semantically identical. + *Workaround:* Reparenthesize the expression in your intermediate game to + match the parenthesization used in the adjacent game. + +4. **Extra temporary variable vs. direct return.** + If one game writes `Type v = expr; return v;` and the adjacent game writes + `return expr;` directly, the `SimplifyReturn` transform may fire on one + but not the other, leaving a residual difference. + *Workaround:* Either inline the temporary (use `return expr;` directly in + both games) or add the temporary to both games so they match. + +5. **`if`/`else-if` branch order is not normalized.** + Two games with the same set of conditional branches listed in a different + order will not be recognized as equivalent. + *Workaround:* Match the branch order from the previous game in your + intermediate game. + +**Step 3: Check for deeper engine issues.** + +If none of the limitations above apply and the diff still looks semantically +equivalent, there may be an engine bug or an unimplemented transformation. +The [Canonicalization]({% link manual/canonicalization.md %}) and +[Limitations]({% link manual/limitations.md %}) pages document the full +canonicalization pipeline and its current gaps. If the gap you are seeing is +not covered there, consult the ProofFrog issue tracker or ask on the +[GitHub discussions forum](https://github.com/orgs/ProofFrog/discussions). diff --git a/manual/tutorial/blank-editor.png b/manual/tutorial/blank-editor.png new file mode 100644 index 0000000..d0b680e Binary files /dev/null and b/manual/tutorial/blank-editor.png differ diff --git a/manual/tutorial/hello-frog.md b/manual/tutorial/hello-frog.md new file mode 100644 index 0000000..4175124 --- /dev/null +++ b/manual/tutorial/hello-frog.md @@ -0,0 +1,94 @@ +--- +title: "Part 1: Hello Frog" +layout: linear +parent: Tutorial +grand_parent: Manual +nav_order: 1 +--- + +# Tutorial Part 1 — Hello Frog +{: .no_toc } + +In this tutorial you will run your first ProofFrog proof, break it on purpose to see what a failing proof looks like, and fix it again. You will not write any FrogLang yourself — the goal is simply to get comfortable with the tool before writing anything from scratch. + +--- + +- TOC +{:toc} + +--- + +## Prerequisites + +Make sure you've [installed ProofFrog]({% link manual/installation.md %}). + +## Get the examples + +Download the ProofFrog examples: + +```bash +proof_frog download-examples +``` + +This creates an `examples/` directory containing primitives, schemes, games, and proofs from introductory cryptography. The rest of this tutorial assumes you ran this command in your current working directory, so that the path `examples/joy/` exists. + +## Launch the web editor + +{: .important } +**Activate your virtual environment first.** If you followed the [installation instructions]({% link manual/installation.md %}), ProofFrog is installed inside a Python virtual environment and you need to activate it in every new terminal before running `proof_frog`. On macOS/Linux with bash or zsh, that is `source .venv/bin/activate`; on fish, `source .venv/bin/activate.fish`; on Windows PowerShell, `.venv\Scripts\Activate.ps1`. Your prompt should show `(.venv)` once it is active. + +Start the web editor pointed at the `joy` example folder: + +```bash +proof_frog web examples/joy +``` + +ProofFrog starts a local web server on port 5173 and opens your browser automatically. You should see a file tree on the left and an editor pane on the right. + +![Web editor on launch](blank-editor.png){: .lightbox } + +## Run a successful proof + +In the file tree, open `Proofs/Ch2/OTPSecure.proof`. Click the **Run Proof** button in the toolbar. After a moment the output panel should turn green and report that the proof succeeded. + +![Successful proof of OTPSecure.proof](otpsecure-success.png){: .lightbox } + +> **CLI alternative.** You can prove the same file from the command line: +> ```bash +> proof_frog prove examples/joy/Proofs/Ch2/OTPSecure.proof +> ``` +> The web editor and the CLI call the same underlying engine; the two paths produce identical results. +{: .note } + +## Break it on purpose + +Open `Proofs/Ch2/OTPSecure.proof` in the editor. In the `games:` block, comment out the second line — the one that reads: + +``` +OneTimeSecrecy(E).Random against OneTimeSecrecy(E).Adversary; +``` + +Add a `//` at the start so it becomes: + +``` +// OneTimeSecrecy(E).Random against OneTimeSecrecy(E).Adversary; +``` + +Click **Run Proof** again. This time the status badge in the output panel turns red and shows **✗ Failed**, while the body text reports something like: + +``` +Proof Failed! Individual hops verified, but the proof is incomplete: +first and last steps use the same side (Real) +``` + +ProofFrog is telling you that both endpoints of the game sequence are on the `Real` side of the security property, which means the proof never actually reaches the `Random` side — the chain is broken. + +![Failed proof of OTPSecure.proof with a line commented out](otpsecure-failed.png){: .lightbox } + +## Fix it + +Remove the `//` to restore the line, then click **Run Proof** one more time. The output turns green again. + +## What happened when you clicked "Run Proof" + +ProofFrog checked that each adjacent pair of games in the `games:` sequence is interchangeable — meaning the two games are equivalent under the semantics of FrogLang, as verified by the engine's canonicalization and equivalence-checking machinery. The proof of `OTPSecure` succeeds because the single hop from `Real` to `Random` is one such equivalence: the ProofFrog engine knows XORing a uniformly random key (used only once) with a message produces a uniformly random ciphertext, so the real and random games are indistinguishable. In Part 2 of the tutorial, you will write this exact proof from scratch. diff --git a/manual/tutorial/index.md b/manual/tutorial/index.md new file mode 100644 index 0000000..8332511 --- /dev/null +++ b/manual/tutorial/index.md @@ -0,0 +1,18 @@ +--- +title: Tutorial +layout: linear +parent: Manual +nav_order: 2 +has_children: true +permalink: /manual/tutorial/ +has_toc: false +--- + +# Tutorial + +This section of the manual contains a two-part hands-on introduction to ProofFrog. Work through the parts in order: + +1. **[Part 1: Hello Frog]({% link manual/tutorial/hello-frog.md %})**: Run an existing proof, break it on purpose, fix it. No proof-writing just yet. +2. **[Part 2: OTP has one-time secrecy]({% link manual/tutorial/otp-ots.md %})**: Write your first complete four-file proof from scratch, showing the security of the one-time pad. + +After finishing the tutorial, continue with the [Worked Examples]({% link manual/worked-examples/index.md %}) for fully-explained walkthroughs of larger proofs. diff --git a/manual/tutorial/onetimesecrecy-typecheck.png b/manual/tutorial/onetimesecrecy-typecheck.png new file mode 100644 index 0000000..b8ff91e Binary files /dev/null and b/manual/tutorial/onetimesecrecy-typecheck.png differ diff --git a/manual/tutorial/otp-ots.md b/manual/tutorial/otp-ots.md new file mode 100644 index 0000000..5ba5114 --- /dev/null +++ b/manual/tutorial/otp-ots.md @@ -0,0 +1,608 @@ +--- +title: "Part 2: OTP has one-time secrecy" +layout: linear +parent: Tutorial +grand_parent: Manual +nav_order: 2 +--- + +# Tutorial Part 2 — One-time pad has one-time secrecy +{: .no_toc } + +In Tutorial Part 1, you ran an existing proof that the one-time pad has one-time secrecy, broke it on purpose, and fixed it again. Now you will write that proof from scratch — all four files of it. By the end of this tutorial you will have defined a cryptographic primitive, a security game, a concrete scheme, and a game-hopping proof file, and you will have seen each one type-check or prove green as you finish it. We follow [Joy of Cryptography Section 2.5](https://joyofcryptography.com/provsec/#sec.abstract-defs) closely; if you have Rosulek's textbook handy, keep it open. Everything you write here is already in the `examples/joy/` directory, so you can always peek at the finished version if you get stuck. + +{: .important } +**Activate your virtual environment first.** Before running any `proof_frog` command in a fresh terminal, activate the Python virtual environment you created during [installation]({% link manual/installation.md %}): `source .venv/bin/activate` on macOS/Linux (bash/zsh), `source .venv/bin/activate.fish` on fish, or `.venv\Scripts\Activate.ps1` on Windows PowerShell. Your prompt should show `(.venv)` once it is active. + +--- + +- TOC +{:toc} + +--- + +## Step 1: Define the SymEnc primitive + +*Joy of Cryptography parallel:* This step encodes Definition 2.5.1 from [Joy of Cryptography](https://joyofcryptography.com/provsec/#sec.abstract-defs:~:text=Definition%202%2E5%2E1%20%28Syntax%20of%20symmetric%2Dkey%20encryption), which defines what a symmetric encryption scheme is: a key space, a message space, a ciphertext space, and three algorithms (KeyGen, Enc, Dec). + +### What a primitive is + +A **primitive** in FrogLang is a named interface: it declares what sets and methods a cryptographic scheme must provide, without saying anything about how those methods work. Think of it as the abstract type signature that any concrete scheme (like OTP) must satisfy. The one-time pad scheme is a symmetric encryption scheme, so we have to define the primitive of "symmetric encryption". + +Mathematically, a symmetric encryption scheme is a triple of algorithms over a key space {% katex %}\mathcal{K}{% endkatex %}, message space {% katex %}\mathcal{M}{% endkatex %}, and ciphertext space {% katex %}\mathcal{C}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\mathsf{KeyGen}: () \to \mathcal{K} \\ +\mathsf{Enc}: \mathcal{K} \times \mathcal{M} \to \mathcal{C} \\ +\mathsf{Dec}: \mathcal{K} \times \mathcal{C} \to \mathcal{M} \cup \{\bot\} \quad \text{(deterministic)} +\end{array} +{% endkatex %} + +We will now translate this signature into a FrogLang `Primitive` declaration. + +### Building the file, line by line + +In the web editor, use the "New File" button to create a new file called `SymEnc.primitive`. + +Start with the opening line: + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { +``` + +The keyword `Primitive` introduces a primitive definition. The name `SymEnc` is what other files will use to refer to it. The parenthesized list `(Set MessageSpace, Set CiphertextSpace, Set KeySpace)` declares three **parameters** of kind `Set` — later, you will have to supply the three spaces when instantiating a scheme. Parameters of kind `Set` represent abstract sets; they have no internal structure until a scheme binds them to concrete types. + +Next, declare the three named fields: + +```prooffrog + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; +``` + +These `Set X = Y;` lines create named slots that can be referred to later: if we eventually have a symmetric encryption scheme `E`, then we will be able to refer to `E.Message`, `E.Ciphertext`, and `E.Key`. The right-hand side binds the slot to the corresponding parameter. Without these lines, the three parameter names would not be accessible outside the primitive block. + +Now declare the three method signatures: + +```prooffrog + Key KeyGen(); + Ciphertext Enc(Key k, Message m); + deterministic Message? Dec(Key k, Ciphertext c); +``` + +Primitives declare **method signatures only** — there are no method bodies here. Every scheme that extends `SymEnc` must implement all three methods with exactly these signatures. + +Two new language features appear here: + +- `Message?` — the `?` suffix denotes a **nullable** (optional) type. In a symmetric encryption scheme, the decryption algorithm `Dec` returns either a `Message` or `None`; decryption is allowed to fail if the ciphertext is invalid. If we later create a scheme returning just plain `Message` where the primitive declared `Message?`, we will get a type mismatch that the engine will catch. +- `deterministic` — this modifier on `Dec` tells the engine that `Dec` is a deterministic algorithm that always returns the same output for the same inputs. In general, algorithms in FrogLang are assumed to be probabilistic, unless explicitly declared to be deterministic. We will come back to why this matters in Step 4 when the proof engine uses it to justify certain algebraic simplifications. + +Finally, add a brace to close the block: + +```prooffrog +} +``` + +### The complete file + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; + + Key KeyGen(); + Ciphertext Enc(Key k, Message m); + deterministic Message? Dec(Key k, Ciphertext c); +} +``` + +### Check it + +Click **Type Check** in the toolbar. You should see: + +``` +SymEnc.primitive is well-formed. +``` + +![SymEnc.primitive type checks](symenc-typecheck.png){: .lightbox } + +> **CLI alternative.** From your working directory: +> ```bash +> proof_frog check SymEnc.primitive +> ``` +{: .note } + +#### What if it fails +{: .no_toc } + +**Parse error / unexpected token.** The most common cause is a missing semicolon. Every field declaration and every method signature must end with `;`. The error message will cite a line number; look there first. + +**`Message` cannot be used where `Message?` is expected.** If you wrote `Message Dec(...)` instead of `Message? Dec(...)`, the type-checker will flag a mismatch when you later try to run the proof. Add the `?`. + +--- + +## Step 2 — Define the one-time secrecy security property + +*Joy of Cryptography parallel:* This step encodes Definition 2.5.3 from [Joy of Cryptography](https://joyofcryptography.com/provsec/#sec.abstract-defs:~:text=Definition%202%2E5%2E3%20%28One%2Dtime%20secrecy), which defines one-time secrecy as the indistinguishability of an encryption oracle from a random-ciphertext oracle. + +### What a security property is + +A **security property** in FrogLang is always a *pair* of games exported under a single name. The adversary is given access to one of the two games but not told which one; security means the adversary cannot reliably distinguish them. This is the left/right (or Real/Random) formulation of indistinguishability used throughout Joy of Cryptography. + +The engine requires both games in a pair to expose the exact same method signatures — same names, same parameter types, same return types. The adversary interacts with both games through the same interface; only the internals differ. + +For one-time secrecy of a symmetric encryption scheme {% katex %}E{% endkatex %}, the two games each expose a single oracle {% katex %}\mathsf{ENC}(m){% endkatex %}. In the {% katex %}\mathsf{Real}{% endkatex %} game, {% katex %}\mathsf{ENC}{% endkatex %} returns a genuine encryption of the adversary's message under a freshly generated key. In the {% katex %}\mathsf{Random}{% endkatex %} game, {% katex %}\mathsf{ENC}{% endkatex %} ignores the message and returns a uniformly random ciphertext: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Real}_E.\mathsf{ENC}(m)} \\ +k \gets E.\mathsf{KeyGen}() \\ +c \gets E.\mathsf{Enc}(k, m) \\ +\text{return } c +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Random}_E.\mathsf{ENC}(m)} \\ +c \stackrel{\$}{\leftarrow} E.\mathcal{C} \\ +\text{return } c +\end{array} +{% endkatex %} + +{% katex %}E{% endkatex %} satisfies one-time secrecy if no adversary making a single query to {% katex %}\mathsf{ENC}(m){% endkatex %} can distinguish the two games. Notice the adversary has no access to the decryption algorithm {% katex %}\mathsf{Dec}{% endkatex %} — otherwise they could trivially decrypt and win. + +### Building the file, line by line + +Create `OneTimeSecrecy.game`. + +Start with the import: + +```prooffrog +import 'SymEnc.primitive'; +``` + +The `import` keyword loads another file and makes its definitions available in the current file. The path is **relative to the directory containing the importing file**. For now we've put all the files in the same folder, but eventually you might have to reference files from different folders, like `../../Primitives/SymEnc.primitive`. + +Now write the first game: + +```prooffrog +Game Real(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, m); + return c; + } +} +``` + +The `Game` keyword introduces a game definition. `Real` is its name. The parameter `(SymEnc E)` says this game is parameterized by a scheme `E` that implements the `SymEnc` primitive. Inside the game there is one oracle method, `ENC`, which generates a fresh key, encrypts the adversary's message under that key, and returns the ciphertext. + +Now write the second game: + +```prooffrog +Game Random(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Ciphertext c <- E.Ciphertext; + return c; + } +} +``` + +The `<-` operator is **uniform random sampling**. The line `E.Ciphertext c <- E.Ciphertext;` samples a uniformly random element from `E.Ciphertext` and assigns it to variable `c`. The adversary's message `m` is accepted but not used — the ciphertext is chosen at random regardless of the plaintext. This captures the intuition that a secure encryption scheme's ciphertexts look random. + +Notice that both `Real` and `Random` expose an oracle named `ENC` with the same signature: `E.Ciphertext ENC(E.Message m)`. This is mandatory — the engine will reject a game pair with mismatched oracle signatures. + +Finally, export the pair under a single name: + +```prooffrog +export as OneTimeSecrecy; +``` + +The `export as` line gives the two-game pair a single name — `OneTimeSecrecy` — that proof files use when stating a theorem. After this line, `OneTimeSecrecy(E).Real` and `OneTimeSecrecy(E).Random` refer to the two games. + +### The complete file + +```prooffrog +// Definition 2.5.3: One-time secrecy +// A scheme has one-time secrecy if encrypting any message produces +// a ciphertext indistinguishable from a uniformly random ciphertext. + +import 'SymEnc.primitive'; + +Game Real(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, m); + return c; + } +} + +Game Random(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Ciphertext c <- E.Ciphertext; + return c; + } +} + +export as OneTimeSecrecy; +``` + +### Check it + +Click **Type Check** in the web editor, or on the command-line run: + +```bash +proof_frog check OneTimeSecrecy.game +``` + +Expected output: + +``` +OneTimeSecrecy.game is well-formed. +``` + +![OneTimeSecrecy.game type checks](onetimesecrecy-typecheck.png){: .lightbox } + + +#### What if it fails: mismatched oracle signatures +{: .no_toc } + +The most instructive mistake here is giving the two games oracles with different signatures. For example, suppose you accidentally added an extra parameter to `Random.ENC`: + +```prooffrog +// Wrong: extra parameter that Real.ENC does not have +E.Ciphertext ENC(E.Message m, E.Message extra) { +``` + +The engine rejects this at the type-checking stage with an error like: + +``` +OneTimeSecrecy.game:8:4: error: Method 'ENC' has different signatures in Real and Random: E.Ciphertext ENC(E.Message m) vs E.Ciphertext ENC(E.Message m, E.Message extra) + E.Ciphertext ENC(E.Message m) { + ^ +``` + +The fix is to make both oracles identical: same name, same parameters, same return type. The engine cares only about signatures here, not bodies. + +A second common mistake is forgetting the `export as OneTimeSecrecy;` line at the bottom. Without it the file is a valid collection of games but not a named security property, and any proof that tries to reference `OneTimeSecrecy` will fail to find it. + +--- + +## Step 3 — Define the OTP scheme + +*Joy of Cryptography parallel:* This step implements Construction 1.2.1 from [Joy of Cryptography](https://joyofcryptography.com/otp/#sec.otp:~:text=Construction%201%2E2%2E1%20%28One%2Dtime%20pad), the One-Time Pad. + +### What a scheme is + +A **scheme** is a concrete instantiation of a primitive. Where the primitive declared method *signatures*, the scheme provides *bodies*. A scheme must implement every method the primitive declares, with exactly the same modifiers and types. For example, the one-time pad scheme is an instantiation of the symmetric encryption primitive. + +Parameterized by a security parameter {% katex %}\lambda{% endkatex %}, the one-time pad fixes all three sets to {% katex %}\lambda{% endkatex %}-bit strings and defines encryption and decryption by XOR: + +{% katex display %} +\mathcal{K} = \mathcal{M} = \mathcal{C} = \{0, 1\}^{\lambda} +{% endkatex %} + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{KeyGen}()} \\ +k \stackrel{\$}{\leftarrow} \{0, 1\}^{\lambda} \\ +\text{return } k +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Enc}(k, m)} \\ +\text{return } k \oplus m +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Dec}(k, c)} \\ +\text{return } k \oplus c +\end{array} +{% endkatex %} + +Correctness is immediate: {% katex %}\mathsf{Dec}(k, \mathsf{Enc}(k, m)) = k \oplus (k \oplus m) = m{% endkatex %}. (Although it is possible to formulate and prove correctness in ProofFrog.) + +We will now encode this scheme in FrogLang. + +### Building the file, line by line + +Create `OTP.scheme`. + +Start with the import: + +```prooffrog +import 'SymEnc.primitive'; +``` + +Declare the scheme: + +```prooffrog +Scheme OTP(Int lambda) extends SymEnc { +``` + +`Scheme OTP(Int lambda)` says this is a scheme named `OTP` parameterized by a single integer `lambda`. The **security parameter** `lambda` is the bit length of the key (and message and ciphertext for the one-time pad). In a real proof, `lambda` appears as a free variable; the scheme is instantiated for a specific value when the proof's `let:` block binds it. + +`extends SymEnc` links this scheme to the `SymEnc` primitive. The type checker will verify that `OTP` satisfies every declaration in `SymEnc`. + +Now bind the three set slots: + +```prooffrog + Set Key = BitString; + Set Message = BitString; + Set Ciphertext = BitString; +``` + +`BitString` is the built-in type of bit strings of length exactly `lambda`. For the one-time pad, keys, messages, and ciphertexts are all `lambda`-bit strings. These assignments satisfy the three `Set` slots declared in the primitive — the engine maps `E.Key` to `BitString`, and so on. + +Write the `KeyGen` method: + +```prooffrog + Key KeyGen() { + Key k <- Key; + return k; + } +``` + +`Key k <- Key;` samples a uniformly random element of `Key` (which is `BitString`). The `<-` operator always means uniform random sampling; there is no other form of randomness in FrogLang. + +Write the `Enc` method: + +```prooffrog + Ciphertext Enc(Key k, Message m) { + return k + m; + } +``` + +{: .note } +**Heads-up: `+` on bit strings is XOR, not addition.** When applied to two values of type `BitString`, the `+` operator computes their bitwise exclusive-or. Integer addition on `Int` values uses `+` in the usual sense, but whenever both operands are bit strings, `+` means XOR. The OTP ciphertext `k + m` is therefore `k XOR m`. + +Write the `Dec` method: + +```prooffrog + deterministic Message? Dec(Key k, Ciphertext c) { + return k + c; + } +``` + +Two things to note: + +- The `deterministic` modifier must appear here because the primitive declared `Dec` with `deterministic`. The type checker requires the scheme to declare exactly the same modifiers as the primitive; you cannot add or remove them. One-time pad decryption is indeed `deterministic` because XOR of two fixed bit strings always gives the same result. +- The return type is `Message?`, matching the primitive. One-time pad decryption never actually fails (XOR is always defined on bitstrings of the same length), but the primitive contract requires the *possibility* of failure to be expressed in the type. + +Finally, add a brace to close the block: + +```prooffrog +} +``` + +### The complete file + +```prooffrog +// Construction 1.2.1: One-Time Pad +// Encryption: C = K xor M +// Decryption: M = K xor C + +import '../../Primitives/SymEnc.primitive'; + +Scheme OTP(Int lambda) extends SymEnc { + Set Key = BitString; + Set Message = BitString; + Set Ciphertext = BitString; + + Key KeyGen() { + Key k <- Key; + return k; + } + + Ciphertext Enc(Key k, Message m) { + return k + m; + } + + deterministic Message? Dec(Key k, Ciphertext c) { + return k + c; + } +} +``` + +### Check it + +Click **Type Check** in the web editor, or run: + +```bash +proof_frog check OTP.scheme +``` + +Expected output: + +``` +OTP.scheme is well-formed. +``` + +![OTP.scheme type checks](otp-typecheck.png){: .lightbox } + + +#### What if it fails +{: .no_toc } + +**Scheme does not correctly implement primitive: ...** If you omit one of the three `Set X = ...;` lines, or use the wrong type for a method parameter, the engine reports which part of the primitive is not satisfied. The error names both the scheme and the primitive so you can compare them side by side. + +**Type mismatch on `+`.** If you write `return k + m;` where `k` or `m` has type `Int` instead of `BitString`, the engine will complain about a type mismatch. Check that your `Set Key = BitString;` lines are present and spelled correctly. + +**Missing `deterministic` modifier.** If you write `Message? Dec(...)` without `deterministic`, the type checker will report that the modifier does not match the primitive's declaration. Copy the modifier exactly. + +--- + +## Step 4 — Write the proof + +*Joy of Cryptography parallel:* This step carries out Example 2.5.4 from [Joy of Cryptography](https://joyofcryptography.com/provsec/#sec.abstract-defs:~:text=Example%202%2E5%2E4%20%28One%2Dtime%20secrecy%20of%20OTP%29), which proves that OTP has one-time secrecy via a single-hop game sequence. + +### What a proof file is + +A **proof file** assembles the three files you just wrote into a game-hopping argument. It declares the concrete scheme being studied, states the security theorem, and lists a sequence of games that walks from one side of the theorem to the other. The engine checks that each adjacent pair of games in the sequence is interchangeable — that is, equivalent under the semantics of FrogLang. + +### Building the file, line by line + +Create `OTPSecure.proof`. + +Start with the imports: + +```prooffrog +import 'OTP.scheme'; +import 'OneTimeSecrecy.game'; +``` + +Proof files import schemes and games. They do not need to import the primitive directly — the scheme already imports it. + +Now write the `proof:` section marker: + +```prooffrog +proof: +``` + +The `proof:` keyword is a section divider that separates the imports and top-level declarations from the proof body. Everything after it describes the proof itself. + +Write the `let:` block: + +```prooffrog +let: + Int lambda; + OTP E = OTP(lambda); +``` + +The `let:` block declares the variables and scheme instances used in the proof. + +- `Int lambda;` declares `lambda` as a free integer variable — the security parameter. It is not assigned a concrete value; the proof holds for all values of `lambda`. +- `OTP E = OTP(lambda);` instantiates the OTP scheme with parameter `lambda` and names the resulting instance `E`. From this point on, `E` refers to OTP, and expressions like `E.Key`, `E.Message`, `OneTimeSecrecy(E).Real` all expand through this binding. + +Write the `assume:` block: + +```prooffrog +assume: +``` + +The `assume:` block lists security assumptions that the proof relies on — for example, "PRF security of F" in a proof that uses a pseudorandom function. For the one-time pad, the block is **empty**. The one-time pad's one-time secrecy is **information-theoretically secure**: the proof holds unconditionally, with no computational assumptions. This is unusual and worth noticing. Most proofs of real-world schemes have non-trivial `assume:` blocks; OTP does not. + +Write the `theorem:` block: + +```prooffrog +theorem: + OneTimeSecrecy(E); +``` + +The `theorem:` block states what we are proving: that the scheme `E` (our OTP instance) satisfies the security property `OneTimeSecrecy`. The engine will check that the `games:` sequence below starts at one side of `OneTimeSecrecy(E)` and ends at the other. + +Write the `games:` block: + +```prooffrog +games: + OneTimeSecrecy(E).Real against OneTimeSecrecy(E).Adversary; + + OneTimeSecrecy(E).Random against OneTimeSecrecy(E).Adversary; +``` + +The `games:` block is the heart of the proof. Each line is a **game step** of the form ` against ;`. The engine processes adjacent pairs and checks that each transition is valid. + +- The first step must be one side of the theorem (`OneTimeSecrecy(E).Real` here), and the last step must be the other side (`OneTimeSecrecy(E).Random`). +- `OneTimeSecrecy(E).Adversary` is the adversary interface — it is automatically derived from the oracle signatures declared in the game pair. +- There is a single hop here, from `Real` to `Random`. The engine verifies this hop by inlining the OTP scheme into both games and checking that the resulting code is equivalent. After inlining, both games reduce to sampling a uniform random bit string and returning it: in `Real`, `k` is uniform and `k + m` is uniform; in `Random`, the ciphertext is sampled directly as uniform. These are interchangeable, so the hop passes. + +### The complete file + +```prooffrog +// Example 2.5.4: OTP has one-time secrecy. +// The ciphertext k + m is uniformly distributed when k is uniform, +// so the Real and Random games are interchangeable in one step. + +import 'OTP.scheme'; +import 'OneTimeSecrecy.game'; + +proof: + +let: + Int lambda; + OTP E = OTP(lambda); + +assume: + +theorem: + OneTimeSecrecy(E); + +games: + OneTimeSecrecy(E).Real against OneTimeSecrecy(E).Adversary; + + OneTimeSecrecy(E).Random against OneTimeSecrecy(E).Adversary; +``` + +### Prove it + +Click **Run Proof** in the web editor. The output panel should turn green and report: + +``` +Theorem: OneTimeSecrecy(E) + + Step 1/1 OneTimeSecrecy(E).Real -> OneTimeSecrecy(E).Random ... ok + + Step Hop Type Result + ---- -------------------------------------------------- ----------- ------ + 1 OneTimeSecrecy(E).Real -> OneTimeSecrecy(E).Random equivalence ok + +Proof Succeeded! +``` + +![Successful OTPSecure.proof](otpsecure-proof.png){: .lightbox } + +> **CLI alternative.** Run +> ```bash +> proof_frog prove OTPSecure.proof +> ``` +{: .note } + +### Why this single hop works + +After the engine inlines `OTP` into `OneTimeSecrecy(E).Real`, the game body becomes: + +``` +Key k <- BitString; +Ciphertext c = k + m; +return c; +``` + +Because `k` is sampled uniformly from `BitString` and used exactly once in `k + m`, the result `c` is also uniformly distributed over `BitString` — independent of `m`. The engine recognizes this pattern (XOR with a uniform random bit string that is used once) as an equivalence transformation: it can replace `k <- BitString; c = k + m;` with `c <- BitString;`. After that replacement, the inlined `Real` game and the `Random` game are syntactically identical, and the hop is verified. This is precisely the reasoning in Joy of Cryptography Example 2.5.4. + +### Diving into the canonical forms + +ProofFrog's engine works by trying to convert a game into a "canonical form", by renaming variables to have standard names, removing unused statements, sorting the lines into a canonical order, and applying other mathematical and logical transformations. It then compares the canonical forms of the two games that are being checked for interchangeability. + +In the web editor, you can drill down into the canonical forms derived at each step. When you clicked "Run Proof" above, the Game Hop panel in the bottom left should have been updated to have one line highlighted in green: `OneTimeSecrecy(E).Random ✅`. Click on this green line, and you will see four different chunks of source code appear: + +![Details of canonicalization of one hop of OTPSecure.proof](otpsecure-hopdetail.png){: .lightbox } + +- Top left: The previous game (`OneTimeSecrecy(E).Real`), with the code of the one-time pad scheme `E` inlined. Notice that the variables from the OTP scheme have a prefix to avoid any collisions between variable names when inserted. +- Middle left: The current game (`OneTimeSecrecy(E).Random`), with the code of the one-time pad scheme `E` inlined. +- Top right: The canonicalized form of the previous game, `OneTimeSecrecy(E).Real`. Notice that the variable names have been replaced with canonical versions (`v1`) and that the engine has simplified the program by applying a mathematical identity it knows: that the XOR of a uniform random bit string with any bit string is equivalent to just sampling a uniform random bit string. +- Bottom right: The canonicalized form of the current game, `OneTimeSecrecy(E).Random`. The canonicalization here is more obvious, and no extra transforms were applied. + +As you can see, the two canonicalized forms are exactly the same: this is why ProofFrog concludes that the hop is valid. + +If you really want to dive into the details of every transformation ProofFrog applies during canonicalization, you can "Run Proof" again with the output detail changed from "Quiet" to "Very Verbose". (On the command-line: `proof_frog prove -vv OTPSecure.proof`.) + +#### What if it fails +{: .no_toc :} + +**Cannot find imported file.** If you wrote a path with the wrong number of `../` steps or a misspelled file name, the engine reports that the file cannot be found. Count the directory levels carefully. + +**Theorem mismatch.** If the theorem names a security property or scheme that was not imported or not declared in `let:`, the engine reports that the name is undefined. Check that `OneTimeSecrecy` is imported and that `E` is declared in `let:`. + +**Proof Failed! Individual hops verified, but the proof is incomplete.** If the first and last game steps use the same side of the theorem (both `Real`, or both `Random`), the engine accepts all individual hops but reports that the overall sequence is incomplete. This is the error you saw in Tutorial Part 1 when you commented out the second game step. Make sure the sequence starts at `Real` and ends at `Random` (or vice versa). + +**A hop fails.** If the engine cannot verify a hop as an equivalence, it will report which step failed and show a diagnostic. For a proof as small as this one, a failing hop usually means the scheme file has a typo — for example, `k * m` instead of `k + m` in `Enc`. See the [troubleshooting page]({% link manual/troubleshooting.md %}) for a deeper guide to diagnosing failing steps. + +--- + +## What you did not learn yet + +Congratulations — you have written a complete game-hopping proof from scratch! Security of the one-time pad is the simplest possible case: one primitive, one scheme, one game pair, one hop, no assumptions. Most real proofs are more complex. There are three directions to explore next: + +- **Reductions and the four-step pattern.** When a scheme relies on an underlying primitive (like a PRF or a hash function), the proof uses *reductions* to hop via an assumption rather than an equivalence. We'll see this in the next worked example: [security of chained symmetric encryption]({% link manual/worked-examples/chained-encryption.md %}). + +- **The rest of the language.** Everything else FrogLang offers — tuples, maps, arrays, random functions, injective annotations, induction — is documented in the [Language Reference]({% link manual/language-reference/index.md %}). + +- **What `prove` does under the hood.** You can learn about the engine's canonicalization pipeline and the equivalence-checking algorithm on the [canonicalization page]({% link manual/canonicalization.md %}). diff --git a/manual/tutorial/otp-typecheck.png b/manual/tutorial/otp-typecheck.png new file mode 100644 index 0000000..f8a3873 Binary files /dev/null and b/manual/tutorial/otp-typecheck.png differ diff --git a/manual/tutorial/otpsecure-failed.png b/manual/tutorial/otpsecure-failed.png new file mode 100644 index 0000000..4293705 Binary files /dev/null and b/manual/tutorial/otpsecure-failed.png differ diff --git a/manual/tutorial/otpsecure-hopdetail.png b/manual/tutorial/otpsecure-hopdetail.png new file mode 100644 index 0000000..ea12c8f Binary files /dev/null and b/manual/tutorial/otpsecure-hopdetail.png differ diff --git a/manual/tutorial/otpsecure-proof.png b/manual/tutorial/otpsecure-proof.png new file mode 100644 index 0000000..281e67a Binary files /dev/null and b/manual/tutorial/otpsecure-proof.png differ diff --git a/manual/tutorial/otpsecure-success.png b/manual/tutorial/otpsecure-success.png new file mode 100644 index 0000000..c4b550c Binary files /dev/null and b/manual/tutorial/otpsecure-success.png differ diff --git a/manual/tutorial/symenc-typecheck.png b/manual/tutorial/symenc-typecheck.png new file mode 100644 index 0000000..dbb0794 Binary files /dev/null and b/manual/tutorial/symenc-typecheck.png differ diff --git a/manual/web-editor.md b/manual/web-editor.md new file mode 100644 index 0000000..f9c2fe0 --- /dev/null +++ b/manual/web-editor.md @@ -0,0 +1,87 @@ +--- +title: Web Editor +layout: default +parent: Manual +nav_order: 70 +--- + +# Web Editor +{: .no_toc } + +ProofFrog includes a browser-based editor that gives you a graphical environment for writing and verifying FrogLang files. It runs entirely on your local machine; nothing leaves your computer. The web editor is well-suited to interactive proof development and to exploring the bundled examples, because it keeps the file tree, source code, and proof output in a single window. + +- TOC +{:toc} + +--- + +## Launching + +{: .important } +**Activate your Python virtual environment first.** In every new terminal, run `source .venv/bin/activate` (macOS/Linux bash/zsh), `source .venv/bin/activate.fish` (fish), or `.venv\Scripts\Activate.ps1` (Windows PowerShell) before invoking `proof_frog`. See [Installation]({% link manual/installation.md %}). + +```bash +proof_frog web [directory] +``` + +The optional `[directory]` argument sets the working directory the editor treats as its file root; everything you open or verify is resolved relative to that path. Without an argument, the current working directory is used. + +The server listens on port **5173** if free, otherwise scans upward for the next available port; the actual URL is printed to the terminal. If the browser does not open automatically, copy that URL by hand. Press **Ctrl-C** in the terminal to stop the server. + +--- + +## Layout + +The editor has four regions. + +![Web editor](tutorial/otpsecure-proof.png){: .lightbox } + +**Toolbar (top).** File actions (New File, Save, Save All), the Insert wizard dropdown, validation buttons (Parse, Type Check, Run Proof), the Describe button, the current working directory, and a light/dark theme toggle. + +**File tree (left sidebar).** The directory tree rooted at the working directory; click a file to open it. Below the tree, a collapsible **Game Hops** panel appears when a `.proof` file is active (see [Inspecting hops](#inspecting-hops) below). + +**Editor pane (center).** A multi-tab editor with FrogLang syntax highlighting for `.primitive`, `.scheme`, `.game`, and `.proof` files. Unsaved changes are flagged on the tab. **Cmd-S** / **Ctrl-S** saves the active file. + +**Output pane (bottom).** Results from Parse, Type Check, Run Proof, and Describe operations. Closes with the X button and reopens automatically on the next operation. + +--- + +## Editing + +The **Insert** dropdown lists wizards applicable to the active file's type; choosing one opens a modal that inserts a correctly-structured fragment at the right location. Four creation wizards (Create new primitive / scheme / game / proof) appear only on an empty file. The remaining wizards are file-type-filtered and shown when a matching file is open: Add import, Add primitive method, Add scheme method, Add game oracle method, Insert reduction hop, New reduction stub, New intermediate game stub, Add assumption, Add lemma. + +The **New File** button takes a folder, base name, and extension and creates an empty file. **Save** writes the active tab; **Save All** writes every modified tab. + +--- + +## Validation buttons + +Three toolbar buttons verify the active file at increasing levels of depth: + +- **Parse** — checks that the file is syntactically valid FrogLang, and shows the parse-error position if not. +- **Type Check** — runs semantic analysis and reports whether the file is well-formed or shows the first type error. +- **Run Proof** — available only for `.proof` files. Verifies every hop in the `games:` sequence. A verbosity selector next to the button picks Quiet, Verbose, or Very Verbose output. + +Each button corresponds to a CLI command (`parse`, `check`, `prove`); see the [CLI Reference]({% link manual/cli-reference.md %}) for details on the equivalent commands. + +--- + +## Inspecting hops + +After **Run Proof** completes, the **Game Hops** panel in the left sidebar lists every step in the proof's `games:` block, color-coded by result (green for passing, red for failing). Clicking a hop opens a detail view that shows: + +- The two game steps on each side of the hop. +- The **canonical form** of each game after the full canonicalization pipeline has run — this is exactly what the engine compares when checking interchangeability. +- For assumption hops, the assumption that justifies the transition. + +This is the fastest way to diagnose a failing hop: compare the two canonical forms side by side and look for the first structural difference. + +![The game hop detail inspector](tutorial/otpsecure-hopdetail.png){: .lightbox } + +**Describe** shows a concise interface summary of the active file (type parameters, oracle names and signatures). Useful for confirming what a primitive or game exposes before writing a proof. + +--- + +## Limitations of the web editor + +The web editor is constrained to a single working directory chosen at startup; file access outside it and to dot-files is denied. Some CLI-only diagnostic commands (`step-detail`, `inlined-game`, `canonicalization-trace`, `step-after-transform`) have no web-editor equivalent; see [Engine Internals]({% link researchers/engine-internals.md %}) for details on those tools. diff --git a/manual/worked-examples/chained-encryption.md b/manual/worked-examples/chained-encryption.md new file mode 100644 index 0000000..3e19099 --- /dev/null +++ b/manual/worked-examples/chained-encryption.md @@ -0,0 +1,451 @@ +--- +title: Chained Encryption +layout: linear +parent: Worked Examples +grand_parent: Manual +nav_order: 1 +--- + +# Chained Symmetric Encryption +{: .no_toc } + +In Tutorial Part 2 you proved that the one-time pad has one-time secrecy — a one-hop interchangeability proof with an empty `assume:` block. Real cryptographic proofs usually look more like this: they build a scheme out of another scheme and reduce the security of the construction to the security of its building block. + +In this worked example we examine **chained symmetric encryption**, which composes two symmetric encryption schemes {% katex %}E_1{% endkatex %} and {% katex %}E_2{% endkatex %} by sampling a one-time {% katex %}E_2{% endkatex %}-key {% katex %}k'{% endkatex %}, encrypting it using the main key {% katex %}k{% endkatex %}, and then encrypting the message using {% katex %}k'{% endkatex %}: + +{% katex display %} +(c_1, c_2) \quad \text{ where } \quad k' \stackrel{\$}\leftarrow E_2.\mathcal{K}, \quad c_1 \gets E_1.\mathsf{Enc}(k, k'), \quad \ c_2 \gets E_2.\mathsf{Enc}(k', m) +{% endkatex %} + +We'll give a proof that chained symmetric encryption satisfies one-time secrecy, under the assumption that both {% katex %}E_1{% endkatex %} and {% katex %}E_2{% endkatex %} are one-time secret. + +This is the first proof in the manual that uses a reduction, and the first place you will see the standard four-step reduction pattern — the organizing principle behind almost every game-hopping proof you will write. + +The full proof is available in the examples repository at [`joy/Proofs/Ch2/ChainedEncryptionSecure.proof`](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch2/ChainedEncryptionSecure.proof). + +--- + +- TOC +{:toc} + +--- + +## 1. What this proves + +**Chained symmetric encryption** is Construction 2.6.1 from [Joy of Cryptography](https://joyofcryptography.com/provsec/#sec.modular). It composes two independent symmetric encryption schemes {% katex %}E_1{% endkatex %} and {% katex %}E_2{% endkatex %} into a new symmetric encryption scheme {% katex %}\mathsf{CE} = \mathsf{ChainedEncryption}(E_1, E_2){% endkatex %}. To encrypt a message {% katex %}m{% endkatex %} under a key {% katex %}k{% endkatex %}, the scheme samples a fresh {% katex %}E_2{% endkatex %}-key {% katex %}k'{% endkatex %}, encrypts {% katex %}k'{% endkatex %} under {% katex %}E_1{% endkatex %} with {% katex %}k{% endkatex %} (producing {% katex %}c_1{% endkatex %}), and then encrypts {% katex %}m{% endkatex %} under {% katex %}k'{% endkatex %} with {% katex %}E_2{% endkatex %} (producing {% katex %}c_2{% endkatex %}); the ciphertext is the pair {% katex %}(c_1, c_2){% endkatex %}. + +Its key space is {% katex %}\mathsf{CE}.\mathcal{K} = E_1.\mathcal{K}{% endkatex %} and its message space is {% katex %}\mathsf{CE}.\mathcal{M} = E_2.\mathcal{M}{% endkatex %}. The critical structural requirement is {% katex %}E_2.\mathcal{K} \subseteq E_1.\mathcal{M}{% endkatex %} — so that a freshly generated {% katex %}E_2{% endkatex %}-key can itself be treated as a message for {% katex %}E_1{% endkatex %}. + +The theorem we prove is: if both {% katex %}E_1{% endkatex %} and {% katex %}E_2{% endkatex %} satisfy one-time secrecy, then {% katex %}\mathsf{CE}{% endkatex %} also satisfies one-time secrecy. + +**Joy of Cryptography parallel.** This mirrors Claim 2.6.2 in *Joy of Cryptography* by Mike Rosulek, which proves the same result. It's helpful to get a good mental model of Rosulek's proof: replace the {% katex %}E_1{% endkatex %} encryption of {% katex %}k'{% endkatex %} with a random ciphertext (using {% katex %}E_1{% endkatex %}'s one-time secrecy), then replace the {% katex %}E_2{% endkatex %} encryption of {% katex %}m{% endkatex %} with a random ciphertext (using {% katex %}E_2{% endkatex %}'s one-time secrecy). At that point both components of the ciphertext are uniformly random and independent of {% katex %}m{% endkatex %}, so the combined ciphertext is uniformly random — which is exactly the random side of one-time secrecy for {% katex %}\mathsf{CE}{% endkatex %}. The FrogLang proof encodes this argument precisely and has the engine verify each step. + +--- + +## 2. The primitive and the security game + +Before looking at the construction, let us recall the objects the proof is built on: the **symmetric encryption primitive** that both {% katex %}E_1{% endkatex %}, {% katex %}E_2{% endkatex %}, and the composed {% katex %}\mathsf{CE}{% endkatex %} implement, and the **one-time secrecy** game we are proving about the composed scheme. + +### Symmetric encryption primitive + +Recall that a symmetric encryption scheme is a triple of algorithms over a key space {% katex %}\mathcal{K}{% endkatex %}, message space {% katex %}\mathcal{M}{% endkatex %}, and ciphertext space {% katex %}\mathcal{C}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\mathsf{KeyGen}: () \to \mathcal{K} \\ +\mathsf{Enc}: \mathcal{K} \times \mathcal{M} \to \mathcal{C} \\ +\mathsf{Dec}: \mathcal{K} \times \mathcal{C} \to \mathcal{M} \cup \{\bot\} \quad \text{(deterministic)} +\end{array} +{% endkatex %} + +The FrogLang primitive file is [`examples/joy/Primitives/SymEnc.primitive`](https://github.com/ProofFrog/examples/blob/main/joy/Primitives/SymEnc.primitive): + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; + + Key KeyGen(); + Ciphertext Enc(Key k, Message m); + deterministic Message? Dec(Key k, Ciphertext c); +} +``` + +{: .note } +**Try it.** From the `examples/joy/` directory, run `proof_frog check Primitives/SymEnc.primitive` to type-check the primitive file. In the web editor, open the file and click **Type Check**. + +The `deterministic` modifier on `Dec` matters to the engine: it lets the canonicalizer treat repeated `Dec` calls with the same arguments as the same value. `KeyGen` and `Enc` are non-deterministic: they may internally sample randomness and thus be probabilistic. + +### One-time secrecy game + +The security property we are proving — one-time secrecy ([*Joy of Cryptography* Definition 2.5.3](https://joyofcryptography.com/provsec/#sec.abstract-defs:~:text=Definition%202%2E5%2E3%20%28One%2Dtime%20secrecy)) — is phrased as a pair of games, {% katex %}\mathsf{Real}{% endkatex %} and {% katex %}\mathsf{Random}{% endkatex %}, over a single oracle {% katex %}\mathsf{ENC}{% endkatex %}. In the {% katex %}\mathsf{Real}{% endkatex %} game, {% katex %}\mathsf{ENC}(m){% endkatex %} generates a fresh key and returns a genuine encryption of {% katex %}m{% endkatex %}. In the {% katex %}\mathsf{Random}{% endkatex %} game, {% katex %}\mathsf{ENC}(m){% endkatex %} ignores {% katex %}m{% endkatex %} and returns a uniformly random ciphertext. A scheme has one-time secrecy if no adversary can distinguish these two games: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Real}_E.\mathsf{ENC}(m)} \\ +k \gets E.\mathsf{KeyGen}() \\ +c \gets E.\mathsf{Enc}(k, m) \\ +\text{return } c +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Random}_E.\mathsf{ENC}(m)} \\ +c \stackrel{\$}{\leftarrow} E.\mathcal{C} \\ +\text{return } c +\end{array} +{% endkatex %} + +The FrogLang security game file is [`examples/joy/Games/SymEnc/OneTimeSecrecy.game`](https://github.com/ProofFrog/examples/blob/main/joy/Games/SymEnc/OneTimeSecrecy.game): + +```prooffrog +// Definition 2.5.3: One-time secrecy +// A scheme has one-time secrecy if encrypting any message produces +// a ciphertext indistinguishable from a uniformly random ciphertext. + +import '../../Primitives/SymEnc.primitive'; + +Game Real(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, m); + return c; + } +} + +Game Random(SymEnc E) { + E.Ciphertext ENC(E.Message m) { + E.Ciphertext c <- E.Ciphertext; + return c; + } +} + +export as OneTimeSecrecy; +``` + +{: .note } +**Try it.** From the `examples/joy/` directory, run `proof_frog check Games/SymEnc/OneTimeSecrecy.game` to type-check the game file, or open it in the web editor and click **Type Check**. + +The `<- E.Ciphertext` syntax denotes uniform sampling from the set `E.Ciphertext` (corresponding to {% katex %}\stackrel{\$}{\leftarrow}{% endkatex %} in the math). Notice that the games only give the adversary oracle access to `Enc` — not `Dec`, since we don't allow chosen ciphertext attacks. + +One subtlety to remember is that the semantics of security experiments in Joy of Cryptography, and thus ProofFrog, let the adversary call game oracles multiple times. So how can we say that this game models "one-time" secrecy? Because the `ENC` oracle samples a fresh key every time, each encryption key is used only once. This is what makes it *one-time* secrecy rather than CPA security. + +--- + +## 3. The scheme + +{% katex %}\mathsf{ChainedEncryption}{% endkatex %} takes two symmetric encryption schemes {% katex %}E_1, E_2{% endkatex %} and produces a new symmetric encryption scheme whose keys come from {% katex %}E_1{% endkatex %}, whose messages come from {% katex %}E_2{% endkatex %}, and whose ciphertexts are pairs. It requires {% katex %}E_2.\mathcal{K} \subseteq E_1.\mathcal{M}{% endkatex %} so that a freshly sampled {% katex %}E_2{% endkatex %} key can be encrypted as a message under {% katex %}E_1{% endkatex %}: + +The sets needed for the scheme definition are: + +{% katex display %} +\begin{array}{l} +\mathcal{K} = E_1.\mathcal{K}, \qquad \mathcal{M} = E_2.\mathcal{M}, \qquad \mathcal{C} = E_1.\mathcal{C} \times E_2.\mathcal{C} +\end{array} +{% endkatex %} + +The algorithms comprising the scheme are: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{KeyGen}()} \\ +k \gets E_1.\mathsf{KeyGen}() \\ +\text{return } k +\end{array} +\quad +\begin{array}{l} +\underline{\mathsf{Enc}(k, m)} \\ +k' \gets E_2.\mathsf{KeyGen}() \\ +c_1 \gets E_1.\mathsf{Enc}(k, k') \\ +c_2 \gets E_2.\mathsf{Enc}(k', m) \\ +\text{return } (c_1, c_2) +\end{array} +\quad +\begin{array}{l} +\underline{\mathsf{Dec}(k, (c_1, c_2))} \\ +k' \gets E_1.\mathsf{Dec}(k, c_1) \\ +\text{if } k' = \bot: \text{ return } \bot \\ +\text{return } E_2.\mathsf{Dec}(k', c_2) +\end{array} +{% endkatex %} + +The FrogLang scheme file is [`examples/joy/Schemes/SymEnc/ChainedEncryption.scheme`](https://github.com/ProofFrog/examples/blob/main/joy/Schemes/SymEnc/ChainedEncryption.scheme): + +```prooffrog +// Construction 2.6.1: Chained encryption +// Combines two encryption schemes E1, E2 by encrypting a fresh +// key for E2 under E1, then encrypting the message under E2. +// Requires E2's keys to be encryptable by E1. + +import '../../Primitives/SymEnc.primitive'; + +Scheme ChainedEncryption(SymEnc E1, SymEnc E2) extends SymEnc { + requires E2.Key subsets E1.Message; + + Set Key = E1.Key; + Set Message = E2.Message; + Set Ciphertext = [E1.Ciphertext, E2.Ciphertext]; + + Key KeyGen() { + E1.Key k = E1.KeyGen(); + return k; + } + + Ciphertext Enc(Key k, Message m) { + E2.Key kprime = E2.KeyGen(); + E1.Ciphertext c1 = E1.Enc(k, kprime); + E2.Ciphertext c2 = E2.Enc(kprime, m); + return [c1, c2]; + } + + deterministic Message? Dec(Key k, Ciphertext c) { + E2.Key? kprime = E1.Dec(k, c[0]); + if (kprime == None) { + return None; + } + return E2.Dec(kprime, c[1]); + } +} +``` + +{: .note } +**Try it.** From the `examples/joy/` directory, run `proof_frog check Schemes/SymEnc/ChainedEncryption.scheme` to type-check the scheme file, or open it in the web editor and click **Type Check**. + +The `requires` clause is a structural constraint: `E2.Key subsets E1.Message`. Without this, the scheme cannot instantiate `E1.Enc(k, kprime)` because `kprime` has type `E2.Key` but `E1.Enc` expects an `E1.Message`. The `requires` clause makes this subtype relationship explicit and lets the type checker accept the call. + +The `Ciphertext` type is a **tuple**: `[E1.Ciphertext, E2.Ciphertext]`. In FrogLang, `[T1, T2]` is the two-component product type; the components are accessed by constant index: `c[0]` and `c[1]`. The `Dec` method extracts `c[0]` (the `E1` ciphertext), tries to decrypt it with `E1.Dec`, and uses the recovered `kprime` to decrypt `c[1]` with `E2.Dec`. Either decryption step can fail, hence the `E2.Key?` intermediate type and the `None` check. + +For the full syntax of `Scheme`, the `requires` clause, tuple types, and method modifiers, see the [Schemes section of the language reference]({% link manual/language-reference/schemes.md %}). + +--- + +## 4. The proof structure + +The `games:` block lists six game steps, producing five hops. The overall shape: + +- **Step 1**: `OneTimeSecrecy(CE).Real against OneTimeSecrecy(CE).Adversary` + The starting point: the Real side of the `OneTimeSecrecy` experiment for `CE`, composed with a generic adversary. + +- **Step 2**: `OneTimeSecrecy(E1).Real compose R1(CE, E1, E2) against OneTimeSecrecy(CE).Adversary` + Interchangeability hop. The game from step 1 is rewritten in terms of reduction `R1` composed with the Real side of `E1`'s one-time secrecy game. The engine verifies that these two representations are code-equivalent. + +- **Step 3**: `OneTimeSecrecy(E1).Random compose R1(CE, E1, E2) against OneTimeSecrecy(CE).Adversary` + Assumption hop (Real to Random for `E1`). Justified by `OneTimeSecrecy(E1)` in `assume:`. The engine accepts this without code-equivalence checking since it's an assumption listed in the theorem preconditions. + +- **Step 4**: `OneTimeSecrecy(E2).Real compose R2(CE, E1, E2) against OneTimeSecrecy(CE).Adversary` + Interchangeability hop. After `E1`'s encryption has been replaced with random, the code is reorganized through reduction `R2`, which hands `E2`'s encryption off to the `E2` challenger. Again engine-verified by code equivalence. + +- **Step 5**: `OneTimeSecrecy(E2).Random compose R2(CE, E1, E2) against OneTimeSecrecy(CE).Adversary` + Assumption hop (Real to Random for `E2`). Justified by `OneTimeSecrecy(E2)` in `assume:`. + +- **Step 6**: `OneTimeSecrecy(CE).Random against OneTimeSecrecy(CE).Adversary` + The ending point: the Random side of the `OneTimeSecrecy` experiment for `CE`. Once both components `c1` and `c2` are uniformly random (component-wise), the joint pair is also uniformly random, which equals a direct sample from `CE.Ciphertext`. The engine verifies this final equivalence. + +The proof proceeds in two phases. In the first phase, hops 1 and 2 handle `E1`'s encryption (replacing `c1` with a random ciphertext) by reducing to `OneTimeSecrecy(E1)` via reduction `R1`. In the second phase, hops 4 and 5 handle `E2`'s encryption (replacing `c2` with a random ciphertext) by reducing to `OneTimeSecrecy(E2)` via reduction `R2`. Hop 3 is the pivot interchangeability hop that rewrites the halfway state from the `R1`/`E1.Random` form into the `R2`/`E2.Real` form. Steps 1 through 4 are the first four-step reduction pattern (for `E1`, via `R1`); steps 3 through 6 are the second (for `E2`, via `R2`). The two patterns share step 4 (which serves as both the exit step `G_B` of the first pattern and the entry step `G_A` of the second), so the six game steps cover two four-step patterns with one shared boundary. The two halves of the proof reduce to one-time secrecy of the two underlying schemes, but they do so in different ways. + +--- + +## 5. The four-step reduction pattern, walked through + +The standard way to invoke an assumption in a game-hopping proof is the four-step reduction pattern. Suppose the proof is at some game {% katex %}G_A{% endkatex %} and wants to transition to a game {% katex %}G_B{% endkatex %} by appealing to a security assumption on some underlying primitive `Security` (whose two sides are `Security.Real` and `Security.Random`), via a reduction `R`. The pattern occupies four consecutive entries in the `games:` list: + +1. {% katex %}G_A{% endkatex %} — some starting game. +2. `Security.Real compose R` — the engine verifies, by code equivalence, that this is interchangeable with {% katex %}G_A{% endkatex %}. +3. `Security.Random compose R` — the **assumption hop**. The engine accepts this transition without checking equivalence because `Security` is in the proof's `assume:` block, so its `Real` and `Random` sides are indistinguishable by hypothesis. +4. {% katex %}G_B{% endkatex %} — the engine verifies, by code equivalence, that this is interchangeable with `Security.Random compose R`. + +So the assumption hop (steps 2 → 3) is sandwiched between two engine-verified interchangeability hops (1 → 2 and 3 → 4). The role of `R` is to bridge between the "high-level" games {% katex %}G_A{% endkatex %} and {% katex %}G_B{% endkatex %} and the lower-level `Security` game whose assumption we want to use. + +Hops 1 to 3 in this proof are the first complete instance of the pattern, applied to `E1`. Here are the four lines exactly as they appear in [`examples/joy/Proofs/Ch2/ChainedEncryptionSecure.proof`](https://github.com/ProofFrog/examples/blob/main/joy/Proofs/Ch2/ChainedEncryptionSecure.proof): + + +```prooffrog + OneTimeSecrecy(CE).Real against OneTimeSecrecy(CE).Adversary; + + // Factor out E1's encryption into reduction R1 + OneTimeSecrecy(E1).Real compose R1(CE, E1, E2) against OneTimeSecrecy(CE).Adversary; + + // By assumption: E1 has one-time secrecy + OneTimeSecrecy(E1).Random compose R1(CE, E1, E2) against OneTimeSecrecy(CE).Adversary; + + // Factor out E2's encryption into reduction R2 + OneTimeSecrecy(E2).Real compose R2(CE, E1, E2) against OneTimeSecrecy(CE).Adversary; +``` + +Reading these four steps: + +1. **`OneTimeSecrecy(CE).Real against ...`** — This is {% katex %}G_A{% endkatex %}. It is the starting game for the theorem. The engine will verify that it is code-equivalent to step 2. + +2. **`OneTimeSecrecy(E1).Real compose R1(CE, E1, E2) against ...`** — This is `Security.Real compose R`. The engine inlines `R1` into `OneTimeSecrecy(E1).Real` and verifies that the result is code-equivalent to step 1. This hop is an *interchangeability hop*: the engine checks it automatically. + +3. **`OneTimeSecrecy(E1).Random compose R1(CE, E1, E2) against ...`** — This is `Security.Random compose R`. The transition from step 2 to step 3 is the **assumption hop**: the engine accepts it because `OneTimeSecrecy(E1)` appears in the `assume:` block. No code equivalence is checked; the indistinguishability of `E1.Real` and `E1.Random` is taken as given. + +4. **`OneTimeSecrecy(E2).Real compose R2(CE, E1, E2) against ...`** — This is {% katex %}G_B{% endkatex %}, and simultaneously the start of the second four-step pattern. The engine verifies that `OneTimeSecrecy(E1).Random compose R1` is code-equivalent to `OneTimeSecrecy(E2).Real compose R2` — two composed forms that both represent the "halfway" state where `c1` is random and `c2` is still real. + +The structure is therefore: + +- Lines 1 and 2: engine-verified interchangeability (step into R1 via E1.Real). +- Lines 2 and 3: assumption hop (E1.Real to E1.Random, justified by `OneTimeSecrecy(E1)`). +- Lines 3 and 4: engine-verified interchangeability (transition from R1/E1.Random to R2/E2.Real). + +For the full details on the syntax of ProofFrog proofs, see the [proofs section of the language reference]({% link manual/language-reference/proofs.md %}). The [canonicalization]({% link manual/canonicalization.md %}) page explains the canonicalization steps the engine uses to verify code equivalence. + +--- + +## 6. The first reduction in detail + +Reduction {% katex %}R_1{% endkatex %} adapts a {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}){% endkatex %} adversary so it can play against a {% katex %}\mathsf{OneTimeSecrecy}(E_1){% endkatex %} challenger. In math, it exposes a single {% katex %}\mathsf{ENC}{% endkatex %} oracle that generates its own {% katex %}E_2{% endkatex %}-key, delegates the {% katex %}E_1{% endkatex %} encryption of {% katex %}k'{% endkatex %} to its external challenger's $\mathsf{ENC}$ oracle, and finishes the {% katex %}E_2{% endkatex %} layer itself: + +{% katex display %} +\begin{array}{l} +\underline{R_1.\mathsf{ENC}(m)} \\ +k' \gets E_2.\mathsf{KeyGen}() \\ +c_1 \gets \mathsf{challenger}.\mathsf{ENC}(k') \\ +c_2 \gets E_2.\mathsf{Enc}(k', m) \\ +\text{return } (c_1, c_2) +\end{array} +{% endkatex %} + +When the external challenger is {% katex %}\mathsf{Real}_{E_1}{% endkatex %}, the inlined body computes {% katex %}c_1 = E_1.\mathsf{Enc}(k, k'){% endkatex %} for a fresh {% katex %}k{% endkatex %} — matching `ChainedEncryption.Enc` exactly. When the challenger is {% katex %}\mathsf{Random}_{E_1}{% endkatex %}, {% katex %}c_1{% endkatex %} becomes a uniform sample from {% katex %}E_1.\mathcal{C}{% endkatex %}. + +In FrogLang: + +```prooffrog +// R1: delegates E1 encryption to the challenger, handles E2 encryption itself +Reduction R1(ChainedEncryption CE, SymEnc E1, SymEnc E2) compose OneTimeSecrecy(E1) against OneTimeSecrecy(CE).Adversary { + CE.Ciphertext ENC(CE.Message m) { + E2.Key kprime = E2.KeyGen(); + E1.Ciphertext c1 = challenger.ENC(kprime); + E2.Ciphertext c2 = E2.Enc(kprime, m); + return [c1, c2]; + } +} +``` + +{: .note } +**Try it.** {% katex %}R_1{% endkatex %} lives inside `Proofs/Ch2/ChainedEncryptionSecure.proof` along with the rest of the proof. From the `examples/joy/` directory, run `proof_frog prove Proofs/Ch2/ChainedEncryptionSecure.proof` to verify the whole proof, or open the file in the web editor and click **Run Proof**. See [§9 Verifying](#9-verifying) for the expected output. + +**The parameter list: `(ChainedEncryption CE, SymEnc E1, SymEnc E2)`** + +The reduction takes three parameters even though the body references only `E1`, `E2`, and `CE.Ciphertext`. `CE` is required because the composed security game is `OneTimeSecrecy(E1)`, and the outer adversary interface is `OneTimeSecrecy(CE).Adversary` — instantiating `CE.Ciphertext` as the return type requires `CE` to be in scope. In general, the parameter list must include every parameter needed to instantiate both the composed game and the theorem-game adversary, even if a parameter is not used inside any oracle body. This is the *reduction parameter rule* (documented in the [Proofs language reference]({% link manual/language-reference/proofs.md %})). Omitting a required parameter produces a confusing instantiation error at the game step that uses the reduction, not at the reduction definition itself. + +**The header: `compose OneTimeSecrecy(E1) against OneTimeSecrecy(CE).Adversary`** + +This says: the reduction plays as the challenger for `OneTimeSecrecy(CE)` (the outer theorem game), and internally it communicates with a `OneTimeSecrecy(E1)` challenger, which it accesses through the built-in name `challenger`. When this reduction is composed with `OneTimeSecrecy(E1).Real`, the `challenger.ENC(kprime)` call resolves to the Real game's oracle: it generates a fresh `E1` key and returns `E1.Enc(k, kprime)`. When composed with `OneTimeSecrecy(E1).Random`, the call resolves to the Random game's oracle: it returns a uniformly random `E1.Ciphertext`. + +**The oracle body** + +The `ENC` oracle receives a message `m` of type `CE.Message` (which equals `E2.Message`). It: + +1. Generates a fresh `E2` key `kprime` locally. +2. Asks the `E1` challenger to encrypt `kprime` as a message: `challenger.ENC(kprime)`. This is the only call to the external challenger. Because `E2.Key subsets E1.Message`, `kprime` is a valid input. +3. Encrypts the original message `m` directly using `E2.Enc(kprime, m)`, handling `E2`'s layer itself. +4. Returns the pair `[c1, c2]` as a `CE.Ciphertext`. + +When the `E1` challenger is in Real mode, the body of `R1 compose OneTimeSecrecy(E1).Real` is code-equivalent to the direct `ChainedEncryption.Enc` logic inside `OneTimeSecrecy(CE).Real`. This is why hop 1-to-2 passes. When the `E1` challenger switches to Random mode (returning a uniform `c1` instead), the body of `R1 compose OneTimeSecrecy(E1).Random` is the halfway game where `c1` is random — described in the next section. + +--- + +## 7. The intermediate halfway game + +After the assumption hop on {% katex %}E_1{% endkatex %} (the transition from step 2 to step 3 of the games list), the proof has reached a halfway state. Conceptually, this is an *intermediate game* {% katex %}G_{\mathsf{mid}}{% endkatex %} sitting between {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}).\mathsf{Real}{% endkatex %} and {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}).\mathsf{Random}{% endkatex %}, in which {% katex %}c_1{% endkatex %} has already been replaced by a uniform random sample but {% katex %}c_2{% endkatex %} is still a real {% katex %}E_2{% endkatex %} encryption of {% katex %}m{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\underline{G_{\mathsf{mid}}.\mathsf{ENC}(m)} \\ +c_1 \stackrel{\$}{\leftarrow} E_1.\mathcal{C} \\ +k' \gets E_2.\mathsf{KeyGen}() \\ +c_2 \gets E_2.\mathsf{Enc}(k', m) \\ +\text{return } (c_1, c_2) +\end{array} +{% endkatex %} + +This game doesn't need to be written down explicitly in `ChainedEncryptionSecure.proof`, although ProofFrog will let you write it down if you want it as a helpful reminder. Regardless, it shows up implicitly *twice*, in two different syntactic forms: + +- as **{% katex %}R_1 \circ \mathsf{OneTimeSecrecy}(E_1).\mathsf{Random}{% endkatex %}** (step 3): {% katex %}R_1{% endkatex %} samples its own {% katex %}k'{% endkatex %}, calls `challenger.ENC(k')`, and the random-side challenger returns a uniform {% katex %}E_1{% endkatex %}-ciphertext, ignoring {% katex %}k'{% endkatex %}; +- as **{% katex %}R_2 \circ \mathsf{OneTimeSecrecy}(E_2).\mathsf{Real}{% endkatex %}** (step 4): {% katex %}R_2{% endkatex %} samples {% katex %}c_1{% endkatex %} directly from {% katex %}E_1.\mathcal{C}{% endkatex %}, then asks the real-side {% katex %}E_2{% endkatex %} challenger to encrypt {% katex %}m{% endkatex %}. + +Both inline to the same {% katex %}G_{\mathsf{mid}}{% endkatex %} after canonicalization. That is exactly what makes the pivot hop (step 3 → step 4) an interchangeability hop the engine can verify automatically — and it is the technical reason a single proof can chain two assumptions, on {% katex %}E_1{% endkatex %} and on {% katex %}E_2{% endkatex %}, without ever introducing an explicit intermediate game. + +It is also instructive to see what {% katex %}G_{\mathsf{mid}}{% endkatex %} *looks like* compared to the two endpoints of the proof. The starting game {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}).\mathsf{Real}{% endkatex %} samples a fresh {% katex %}E_1{% endkatex %}-key {% katex %}k{% endkatex %} and computes {% katex %}c_1 = E_1.\mathsf{Enc}(k, k'){% endkatex %}; in {% katex %}G_{\mathsf{mid}}{% endkatex %} that whole computation has collapsed into the single uniform sample {% katex %}c_1 \stackrel{\$}{\leftarrow} E_1.\mathcal{C}{% endkatex %}. The ending game {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}).\mathsf{Random}{% endkatex %} will further collapse the {% katex %}c_2{% endkatex %} branch the same way (using the {% katex %}E_2{% endkatex %} assumption in the second four-step pattern). So the proof's overall trajectory is: replace {% katex %}c_1{% endkatex %} with a uniform sample (via {% katex %}E_1{% endkatex %}'s assumption), then replace {% katex %}c_2{% endkatex %} with a uniform sample (via {% katex %}E_2{% endkatex %}'s assumption), at which point both components of the ciphertext are uniform and the result is the random side of one-time secrecy for {% katex %}\mathsf{CE}{% endkatex %}. + +--- + +## 8. The second reduction in detail + +Reduction {% katex %}R_2{% endkatex %} starts from the intermediate halfway game {% katex %}G_{\mathsf{mid}}{% endkatex %} of §7 and uses the assumption on {% katex %}E_2{% endkatex %} to collapse {% katex %}c_2{% endkatex %} into a uniform sample as well. To do that it must play against a {% katex %}\mathsf{OneTimeSecrecy}(E_2){% endkatex %} challenger; note that, unlike {% katex %}R_1{% endkatex %}, it has no {% katex %}E_1{% endkatex %} challenger available — the {% katex %}\mathsf{OneTimeSecrecy}(E_1){% endkatex %} assumption has already been consumed in the earlier assumption hop — and so it must sample {% katex %}c_1{% endkatex %} directly from {% katex %}E_1.\mathcal{C}{% endkatex %} itself: + +{% katex display %} +\begin{array}{l} +\underline{R_2.\mathsf{ENC}(m)} \\ +c_1 \stackrel{\$}{\leftarrow} E_1.\mathcal{C} \\ +c_2 \gets \mathsf{challenger}.\mathsf{ENC}(m) \\ +\text{return } (c_1, c_2) +\end{array} +{% endkatex %} + +When the external challenger is {% katex %}\mathsf{Real}_{E_2}{% endkatex %}, the body of {% katex %}R_2{% endkatex %} is exactly the intermediate game {% katex %}G_{\mathsf{mid}}{% endkatex %} from §7 — uniform {% katex %}c_1{% endkatex %} and a real {% katex %}E_2{% endkatex %} encryption of {% katex %}m{% endkatex %}. This is what makes the pivot interchangeability hop (step 3 → step 4) go through. When the challenger then switches to {% katex %}\mathsf{Random}_{E_2}{% endkatex %} (the assumption hop, step 4 → step 5), {% katex %}c_2{% endkatex %} becomes a uniform sample as well, and {% katex %}R_2 \circ \mathsf{OneTimeSecrecy}(E_2).\mathsf{Random}{% endkatex %} matches {% katex %}\mathsf{OneTimeSecrecy}(\mathsf{CE}).\mathsf{Random}{% endkatex %}, finishing the proof. + +In FrogLang: + +```prooffrog +// R2: c1 is already random; delegates E2 encryption to the challenger +Reduction R2(ChainedEncryption CE, SymEnc E1, SymEnc E2) compose OneTimeSecrecy(E2) against OneTimeSecrecy(CE).Adversary { + CE.Ciphertext ENC(CE.Message m) { + E1.Ciphertext c1 <- E1.Ciphertext; + E2.Ciphertext c2 = challenger.ENC(m); + E2.Ciphertext c2prime <- E2.Ciphertext; + return [c1, c2]; + } +} +``` + +Unlike `R1`, `R2` does not have any `E1` challenger in scope — the proof has already used up `OneTimeSecrecy(E1)` in the earlier assumption hop. The reduction must therefore produce `c1` by itself: it samples `c1` uniformly from `E1.Ciphertext` directly. This is why the halfway state between hops 3 and 4 (the step 4 entry point) canonicalizes to "c1 is a fresh uniform sample" — exactly the state `R1 compose OneTimeSecrecy(E1).Random` leaves us in at step 3. The engine verifies that the two forms agree. + +The body then asks the `E2` challenger to encrypt `m` (via `challenger.ENC(m)`) and returns `[c1, c2]`. When the `E2` challenger is in Real mode, `c2 = E2.Enc(k_fresh, m)` for a freshly sampled key, so the overall output matches the halfway state where `c1` is random and `c2` is a real encryption of `m`. When the `E2` challenger switches to Random mode, `c2` becomes a uniform `E2.Ciphertext`, and the output matches the final game `OneTimeSecrecy(CE).Random` where both components are uniformly random. + +**A note on `c2prime`.** Line 28 of the proof file samples `E2.Ciphertext c2prime <- E2.Ciphertext;` but never uses the result — `c2prime` is not returned, and no subsequent statement reads it. This is dead code left over from an earlier draft of this example and has no effect on the proof; the canonicalization pipeline eliminates unused samples when comparing the reduction's output to the surrounding games. You can mentally ignore the line when reading the reduction. + +--- + +## 9. Verifying the proof in ProofFrog + +{: .important } +**Activate your Python virtual environment first** if it is not already active in this terminal: `source .venv/bin/activate` on macOS/Linux (bash/zsh), `source .venv/bin/activate.fish` on fish, or `.venv\Scripts\Activate.ps1` on Windows PowerShell. See [Installation]({% link manual/installation.md %}). + +From the `examples/joy/` directory (or the repository root), run: + +```bash +proof_frog prove Proofs/Ch2/ChainedEncryptionSecure.proof +``` + +Expected output: + +``` +Type checking... + +Theorem: OneTimeSecrecy(CE) + + Step 1/5 OneTimeSecrecy(CE).Real + -> OneTimeSecrecy(E1).Real compose R1(CE, E1, E2) ... ok + Step 2/5 OneTimeSecrecy(E1).Real compose R1(CE, E1, E2) + -> OneTimeSecrecy(E1).Random compose R1(CE, E1, E2) ... by assumption + Step 3/5 OneTimeSecrecy(E1).Random compose R1(CE, E1, E2) + -> OneTimeSecrecy(E2).Real compose R2(CE, E1, E2) ... ok + Step 4/5 OneTimeSecrecy(E2).Real compose R2(CE, E1, E2) + -> OneTimeSecrecy(E2).Random compose R2(CE, E1, E2) ... by assumption + Step 5/5 OneTimeSecrecy(E2).Random compose R2(CE, E1, E2) + -> OneTimeSecrecy(CE).Random ... ok + +Proof Succeeded! +``` + +Steps 1, 3, and 5 are equivalence hops verified by the engine. Steps 2 and 4 are assumption hops, one for each of the two `OneTimeSecrecy` assumptions in `assume:`. + +In the web editor, open `Proofs/Ch2/ChainedEncryptionSecure.proof` and click the **Run Proof** button. The output panel turns green and shows the same step-by-step report. + +--- + +## What comes next +{: .no_toc } + +The next worked example, [chosen-plaintext attack security of hybrid public key encryption (the KEM-DEM construction)]({% link manual/worked-examples/kemdem-cpa.md %}), takes us further into game-hopping proofs and ProofFrog. It uses two independent primitives (a KEM and a symmetric cipher), and two reductions that operate in opposite directions of the game sequence. After seeing ChainedEncryption you have all the conceptual tools needed to read it; the KEM-DEM example shows how those tools combine at a scale closer to what real-world proof engineering looks like. diff --git a/manual/worked-examples/index.md b/manual/worked-examples/index.md new file mode 100644 index 0000000..9ca091b --- /dev/null +++ b/manual/worked-examples/index.md @@ -0,0 +1,29 @@ +--- +title: Worked Examples +layout: linear +parent: Manual +nav_order: 20 +has_children: true +permalink: /manual/worked-examples/ +--- + +# Worked Examples + +This page contains walkthroughs of complete ProofFrog proofs from the `examples/` directory, organized by complexity. Each example introduces ideas that the next one builds on. + +If you are unfamiliar with cryptographic game hopping proofs or the basics of ProofFrog, you should start with the [installation instructions]({% link manual/installation.md %}) and then the [tutorial]({% link manual/tutorial/index.md %}). Once you've completed the tutorial, continue on with the examples below. + +## General strategies + +Before diving into specific examples, here are some practical strategies that apply to most ProofFrog proofs. + +**Build proofs incrementally.** Start with the `let:`, `assume:`, and `theorem:` sections. Then add the first and last game steps (the two sides of the theorem's security property). Fill in intermediate hops and reductions one at a time, running `proof_frog prove` after each change to see which hops pass. This makes it much easier to isolate problems. + +**Exploit symmetric proof structure.** Many proofs are symmetric around a midpoint. The first half transitions from the theorem's Left (or Real) game toward a "neutral" middle where all cryptographic operations have been replaced by randomness. The second half mirrors the same steps in reverse, transitioning from the middle to the Right (or Random) game. When planning a proof, sketch the midpoint game first and then work outward in both directions. + +**Use helper assumptions and cryptographic assumptions.** Cryptographic game hopping proofs naturally use hardness assumptions about specific primitives (such as PRG security or the decisional Diffie-Hellman assumption). You may also find it helpful to use helper assumptions (available in the [`Games/Helpers/`](https://github.com/ProofFrog/examples/tree/main/Games/Helpers/) directory) that capture basic probabilistic facts (for example, about sampling with replacement versus without) or enable other proof strategies (such as ROM programming). + +## Examples + +1. **[Chained symmetric encryption]({% link manual/worked-examples/chained-encryption.md %})**: A first proof involving reductions, showing the one-time secrecy of chained symmetric encryption: {% katex %}c_1 \leftarrow \mathsf{Enc}(k, k'); c_2 \leftarrow \mathsf{Enc}(k', m){% endkatex %}. This proof introduces the "four-step reduction pattern" for game-hopping proofs. +2. **[Chosen-plaintext attack security of hybrid public key encryption (the KEM-DEM construction)]({% link manual/worked-examples/kemdem-cpa.md %})**: This construction involves multiple primitives (symmetric encryption and public key encryption), and multiple reductions — three reductions, two of them to the same assumption in opposite directions. diff --git a/manual/worked-examples/kemdem-cpa.md b/manual/worked-examples/kemdem-cpa.md new file mode 100644 index 0000000..401f253 --- /dev/null +++ b/manual/worked-examples/kemdem-cpa.md @@ -0,0 +1,790 @@ +--- +title: KEM-DEM CPA +layout: linear +parent: Worked Examples +grand_parent: Manual +nav_order: 2 +--- + +# KEM-DEM Hybrid Public Key Encryption +{: .no_toc } + +The [Chained Symmetric Encryption]({% link manual/worked-examples/chained-encryption.md %}) worked example used a single primitive twice, with two reductions both aimed at the same assumption. + +In this worked example we examine **KEM-DEM hybrid public key encryption**, which combines a key encapsulation mechanism (KEM) {% katex %}K{% endkatex %} and a symmetric encryption scheme {% katex %}E{% endkatex %} into a public key encryption scheme {% katex %}\mathsf{KEMDEM}(K, E){% endkatex %}. To encrypt a message {% katex %}m{% endkatex %} under a public key {% katex %}\mathit{pk}{% endkatex %}, the construction encapsulates a fresh shared secret {% katex %}\mathit{ss}{% endkatex %} under {% katex %}\mathit{pk}{% endkatex %} and then uses {% katex %}\mathit{ss}{% endkatex %} as a one-time symmetric key to encrypt {% katex %}m{% endkatex %}. The ciphertext is: + +{% katex display %} +(c_{\mathsf{kem}}, c_{\mathsf{sym}}) \quad \text{ where } \quad (\mathit{ss}, c_{\mathsf{kem}}) \gets K.\mathsf{Encaps}(\mathit{pk}), \quad c_{\mathsf{sym}} \gets E.\mathsf{Enc}(\mathit{ss}, m) +{% endkatex %} + +We prove that {% katex %}\mathsf{KEMDEM}(K, E){% endkatex %} satisfies CPA security, assuming that {% katex %}K{% endkatex %} is CPA-secure, that {% katex %}E{% endkatex %} satisfies one-time secrecy, and that {% katex %}E{% endkatex %}'s key generation algorithm produces keys uniformly distributed over its key space. The proof uses five reductions: two to KEM CPA security (one forward, one back), two to key uniformity of {% katex %}E{% endkatex %} (one forward, one back), and one to one-time secrecy of {% katex %}E{% endkatex %} in the middle. + +This worked example introduces three patterns beyond chained encryption: **multi-primitive composition** (a KEM and a SymEnc combine into a PKE), **reductions in opposite directions** (the same assumption is invoked twice, once forward and once backward), and **bridging between key distributions with a key-uniformity assumption**. + +The full proof is available at [`Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof). + +--- + +- TOC +{:toc} + +--- + +## 1. What this proves + +**KEM-DEM hybrid public key encryption** is the standard construction that turns a key encapsulation mechanism (KEM) {% katex %}K{% endkatex %} and a symmetric encryption scheme {% katex %}E{% endkatex %} into a public key encryption (PKE) scheme {% katex %}\mathsf{KEMDEM}(K, E){% endkatex %}. The KEM is used to establish a per-message shared secret under the recipient's public key, and the symmetric scheme is then used to encrypt the actual message under that shared secret. The ciphertext is the pair {% katex %}(c_{\mathsf{kem}}, c_{\mathsf{sym}}){% endkatex %}. Decryption reverses this: decapsulate {% katex %}c_{\mathsf{kem}}{% endkatex %} to recover the shared secret, then symmetrically decrypt {% katex %}c_{\mathsf{sym}}{% endkatex %}. The structural requirement is {% katex %}K.\mathcal{S} \subseteq E.\mathcal{K}{% endkatex %} — the KEM's shared secret space must be a subset of the symmetric encryption scheme's key space — so that the shared secret can be used directly as a symmetric key. + +The theorem proved here is: if {% katex %}K{% endkatex %} is CPA-secure, {% katex %}E{% endkatex %} has one-time secrecy (OTS), and {% katex %}E{% endkatex %}'s key generation algorithm produces uniformly distributed keys, then {% katex %}\mathsf{KEMDEM}(K,E){% endkatex %} is CPA-secure as a public key encryption scheme. + +**Why the key-uniformity assumption is needed.** The `KEMDEM` scheme uses the KEM's shared secret directly as the symmetric key — it never calls `E.KeyGen()`. But the one-time secrecy game for {% katex %}E{% endkatex %} *does* call `E.KeyGen()` to sample its one-time key. So at the point where the proof wants to invoke one-time secrecy, the "symmetric key" in the current game is a uniform sample from the KEM's shared secret space (which equals {% katex %}E.\mathcal{K}{% endkatex %}), while the OTS challenger samples its key via `E.KeyGen()`. These two distributions need to be lined up before the OTS assumption can be applied. The proof does this with a **key-uniformity assumption**: a small left/right game asserting that `E.KeyGen()` is indistinguishable from uniform sampling over `E.Key`. Two extra hops — one forward, one back — bridge the gap around the one-time secrecy hop. + +**Joy of Cryptography / Boneh-Shoup parallel.** The KEM-DEM construction and its CPA-security proof appear in Mike Rosulek's [Joy of Cryptography](https://joyofcryptography.com/) and as Exercise 11.9 in Boneh and Shoup's [A Graduate Course in Applied Cryptography](https://toc.cryptobook.us/). The proof here follows the same game-hopping argument described in those references: replace the real KEM shared secret with a random key (using KEM CPA), switch the ciphertext from encrypting the left message to encrypting the right message (using SymEnc one-time secrecy), and then restore the real KEM shared secret (using KEM CPA again, in the reverse direction). ProofFrog mechanically verifies each hop of this argument. + +--- + +## 2. The primitives and the security games + +This proof involves three different cryptographic primitives — symmetric encryption {% katex %}E{% endkatex %}, key encapsulation {% katex %}K{% endkatex %}, and public key encryption {% katex %}\mathsf{KEMDEM}(K,E){% endkatex %} — each with its own security game, plus a small key-uniformity game on {% katex %}E{% endkatex %}. We recall all of them before looking at the construction. + +### Symmetric encryption primitive + +A symmetric encryption scheme is, as in the [chained encryption worked example]({% link manual/worked-examples/chained-encryption.md %}), a triple of algorithms over a key space {% katex %}\mathcal{K}{% endkatex %}, message space {% katex %}\mathcal{M}{% endkatex %}, and ciphertext space {% katex %}\mathcal{C}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\mathsf{KeyGen}: () \to \mathcal{K} \\ +\mathsf{Enc}: \mathcal{K} \times \mathcal{M} \to \mathcal{C} \\ +\mathsf{Dec}: \mathcal{K} \times \mathcal{C} \to \mathcal{M} \quad \text{(deterministic)} +\end{array} +{% endkatex %} + +Notice that `Dec` here is total ({% katex %}\mathcal{M}{% endkatex %}, not {% katex %}\mathcal{M} \cup \{\bot\}{% endkatex %}), reflecting the simplifying assumption that decryption never fails. This is a different primitive from the one used in the chained encryption example, so it has its own file. The FrogLang primitive file is [`Primitives/NonNullableSymEnc.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/NonNullableSymEnc.primitive): + +```prooffrog +Primitive SymEnc(Set MessageSpace, Set CiphertextSpace, Set KeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set Key = KeySpace; + + Key KeyGen(); + Ciphertext Enc(Key k, Message m); + deterministic Message Dec(Key k, Ciphertext c); +} +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Primitives/NonNullableSymEnc.primitive` to type-check the primitive file, or open it in the web editor and click **Type Check**. + +### One-time secrecy game (left/right) + +The one-time secrecy game used in this proof is phrased as a **left/right** indistinguishability game, in contrast to the **real/random** formulation used in the chained encryption worked example. The adversary submits two messages {% katex %}m_L{% endkatex %} and {% katex %}m_R{% endkatex %}, and the challenger encrypts either {% katex %}m_L{% endkatex %} (Left game) or {% katex %}m_R{% endkatex %} (Right game) under a freshly sampled key. The adversary must guess which side it is interacting with. As in any "one-time" experiment, the key is sampled fresh on every oracle call via `E.KeyGen()`: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Left}_E.\mathsf{Eavesdrop}(m_L, m_R)} \\ +k \gets E.\mathsf{KeyGen}() \\ +c \gets E.\mathsf{Enc}(k, m_L) \\ +\text{return } c +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Right}_E.\mathsf{Eavesdrop}(m_L, m_R)} \\ +k \gets E.\mathsf{KeyGen}() \\ +c \gets E.\mathsf{Enc}(k, m_R) \\ +\text{return } c +\end{array} +{% endkatex %} + +The FrogLang security game file is [`Games/SymEnc/INDOT.game`](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/INDOT.game): + +```prooffrog +import '../../Primitives/SymEnc.primitive'; + +Game Left(SymEnc E) { + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, mL); + return c; + } +} + +Game Right(SymEnc E) { + E.Ciphertext Eavesdrop(E.Message mL, E.Message mR) { + E.Key k = E.KeyGen(); + E.Ciphertext c = E.Enc(k, mR); + return c; + } +} + +export as INDOT; +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Games/SymEnc/INDOT.game`, or open the file in the web editor and click **Type Check**. + +### Key uniformity game + +The second assumption on {% katex %}E{% endkatex %} is **key uniformity**: the distribution of keys produced by `E.KeyGen()` is indistinguishable from a uniform sample over `E.Key`. This is also phrased as a left/right game, with the two sides using the two sampling methods: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Real}_E.\mathsf{Challenge}()} \\ +k \gets E.\mathsf{KeyGen}() \\ +\text{return } k +\end{array} \qquad +\begin{array}{l} +\underline{\mathsf{Random}_E.\mathsf{Challenge}()} \\ +k \stackrel{\$}{\leftarrow} E.\mathcal{K} \\ +\text{return } k +\end{array} +{% endkatex %} + +This assumption is the bridge that lets the proof cross from a "symmetric key sampled uniformly from {% katex %}E.\mathcal{K}{% endkatex %}" world to a "symmetric key generated by `E.KeyGen()`" world (which is what the OTS game uses). It will be invoked twice: once on each side of the OTS hop. The FrogLang security game file is [`Games/SymEnc/KeyUniformity.game`](https://github.com/ProofFrog/examples/blob/main/Games/SymEnc/KeyUniformity.game): + +```prooffrog +import '../../Primitives/SymEnc.primitive'; + +Game Real(SymEnc E) { + E.Key Challenge() { + E.Key k = E.KeyGen(); + return k; + } +} + +Game Random(SymEnc E) { + E.Key Challenge() { + E.Key k <- E.Key; + return k; + } +} + +export as KeyUniformity; +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Games/SymEnc/KeyUniformity.game`, or open the file in the web editor and click **Type Check**. + +### KEM primitive + +A **key encapsulation mechanism** is a triple of algorithms over a public key space {% katex %}\mathcal{PK}{% endkatex %}, secret key space {% katex %}\mathcal{SK}{% endkatex %}, ciphertext space {% katex %}\mathcal{C}{% endkatex %}, and shared secret space {% katex %}\mathcal{S}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\mathsf{KeyGen}: () \to \mathcal{PK} \times \mathcal{SK} \\ +\mathsf{Encaps}: \mathcal{PK} \to \mathcal{S} \times \mathcal{C} \\ +\mathsf{Decaps}: \mathcal{SK} \times \mathcal{C} \to \mathcal{S} \quad \text{(deterministic)} +\end{array} +{% endkatex %} + +`Encaps` produces a pair: a shared secret plus a ciphertext that travels to the recipient. `Decaps` recovers the shared secret given the ciphertext and the recipient's secret key. This version assumes that decapsulation never fails. The FrogLang primitive file is [`Primitives/KEM.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/KEM.primitive): + +```prooffrog +Primitive KEM(Set SharedSecretSpace, Set CiphertextSpace, Set PKeySpace, Set SKeySpace) { + Set SharedSecret = SharedSecretSpace; + Set Ciphertext = CiphertextSpace; + Set PublicKey = PKeySpace; + Set SecretKey = SKeySpace; + + [PublicKey, SecretKey] KeyGen(); + [SharedSecret, Ciphertext] Encaps(PublicKey pk); + deterministic SharedSecret Decaps(SecretKey sk, Ciphertext m); +} +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Primitives/KEM.primitive`, or open the file in the web editor and click **Type Check**. + +### KEM CPA game + +CPA security for a KEM captures the indistinguishability of real and random shared secrets, given the corresponding KEM ciphertext. The adversary's `Initialize` call returns the public key, and it may then call a `Challenge` oracle that returns a {% katex %}(\mathit{ss}, c_{\mathsf{kem}}){% endkatex %} pair. In the `Real` game, {% katex %}\mathit{ss}{% endkatex %} is the genuine shared secret produced by encapsulation. In the `Random` game, the ciphertext is still a genuine encapsulation but the shared secret is replaced by a fresh uniform sample, completely independent of {% katex %}c_{\mathsf{kem}}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Initialize}()} \\ +(\mathit{pk}, \mathit{sk}) \gets K.\mathsf{KeyGen}() \\ +\text{return } \mathit{pk} +\end{array} +\qquad +\begin{array}{l} +\underline{\mathsf{Real}_K.\mathsf{Challenge}()} \\ +(\mathit{ss}, c) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +\text{return } (\mathit{ss}, c) +\end{array} +\qquad +\begin{array}{l} +\underline{\mathsf{Random}_K.\mathsf{Challenge}()} \\ +(\_, c) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +\mathit{ss} \stackrel{\$}{\leftarrow} K.\mathcal{S} \\ +\text{return } (\mathit{ss}, c) +\end{array} +{% endkatex %} + +(`Initialize` is shared between both sides.) The FrogLang security game file is [`Games/KEM/INDCPA_MultiChal.game`](https://github.com/ProofFrog/examples/blob/main/Games/KEM/INDCPA_MultiChal.game): + +```prooffrog +import '../../Primitives/KEM.primitive'; + +Game Real(KEM K) { + K.PublicKey pk; + K.SecretKey sk; + + K.PublicKey Initialize() { + [K.PublicKey, K.SecretKey] k = K.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + [K.SharedSecret, K.Ciphertext] Challenge() { + [K.SharedSecret, K.Ciphertext] rsp = K.Encaps(pk); + K.SharedSecret ss = rsp[0]; + K.Ciphertext ctxt = rsp[1]; + return [ss, ctxt]; + } +} + +Game Random(KEM K) { + K.PublicKey pk; + K.SecretKey sk; + + K.PublicKey Initialize() { + [K.PublicKey, K.SecretKey] k = K.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + [K.SharedSecret, K.Ciphertext] Challenge() { + [K.SharedSecret, K.Ciphertext] rsp = K.Encaps(pk); + K.SharedSecret ss <- K.SharedSecret; + K.Ciphertext ctxt = rsp[1]; + return [ss, ctxt]; + } +} + +export as KEM_INDCPA_MultiChal; +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Games/KEM/INDCPA_MultiChal.game`, or open the file in the web editor and click **Type Check**. + +### PKE primitive + +The **public key encryption** primitive is the goal of the construction: a triple of algorithms over a message space {% katex %}\mathcal{M}{% endkatex %}, ciphertext space {% katex %}\mathcal{C}{% endkatex %}, public key space {% katex %}\mathcal{PK}{% endkatex %}, and secret key space {% katex %}\mathcal{SK}{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\mathsf{KeyGen}: () \to \mathcal{PK} \times \mathcal{SK} \\ +\mathsf{Enc}: \mathcal{PK} \times \mathcal{M} \to \mathcal{C} \\ +\mathsf{Dec}: \mathcal{SK} \times \mathcal{C} \to \mathcal{M} \cup \{\bot\} \quad \text{(deterministic)} +\end{array} +{% endkatex %} + +The FrogLang primitive file is [`Primitives/PubKeyEnc.primitive`](https://github.com/ProofFrog/examples/blob/main/Primitives/PubKeyEnc.primitive): + +```prooffrog +Primitive PubKeyEnc(Set MessageSpace, Set CiphertextSpace, Set PKeySpace, Set SKeySpace) { + Set Message = MessageSpace; + Set Ciphertext = CiphertextSpace; + Set PublicKey = PKeySpace; + Set SecretKey = SKeySpace; + + [PublicKey, SecretKey] KeyGen(); + Ciphertext Enc(PublicKey pk, Message m); + deterministic Message? Dec(SecretKey sk, Ciphertext m); +} +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Primitives/PubKeyEnc.primitive`, or open the file in the web editor and click **Type Check**. + +### PKE CPA game + +The PKE CPA game is, like SymEnc one-time secrecy here, a left/right indistinguishability game. The adversary obtains the public key from `Initialize` and may call the `Challenge(mL, mR)` oracle multiple times — multi-message CPA — and each call returns either an encryption of {% katex %}m_L{% endkatex %} or an encryption of {% katex %}m_R{% endkatex %} under the long-term public key: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{Initialize}()} \\ +(\mathit{pk}, \mathit{sk}) \gets E.\mathsf{KeyGen}() \\ +\text{return } \mathit{pk} +\end{array} +\qquad +\begin{array}{l} +\underline{\mathsf{Left}_E.\mathsf{Challenge}(m_L, m_R)} \\ +\text{return } E.\mathsf{Enc}(\mathit{pk}, m_L) +\end{array} +\qquad +\begin{array}{l} +\underline{\mathsf{Right}_E.\mathsf{Challenge}(m_L, m_R)} \\ +\text{return } E.\mathsf{Enc}(\mathit{pk}, m_R) +\end{array} +{% endkatex %} + +The FrogLang security game file is [`Games/PubKeyEnc/INDCPA_MultiChal.game`](https://github.com/ProofFrog/examples/blob/main/Games/PubKeyEnc/INDCPA_MultiChal.game): + +```prooffrog +import '../../Primitives/PubKeyEnc.primitive'; + +Game Left(PubKeyEnc E) { + E.PublicKey pk; + E.SecretKey sk; + + E.PublicKey Initialize() { + [E.PublicKey, E.SecretKey] k = E.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + E.Ciphertext Challenge(E.Message mL, E.Message mR) { + return E.Enc(pk, mL); + } +} + +Game Right(PubKeyEnc E) { + E.PublicKey pk; + E.SecretKey sk; + + E.PublicKey Initialize() { + [E.PublicKey, E.SecretKey] k = E.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + E.Ciphertext Challenge(E.Message mL, E.Message mR) { + return E.Enc(pk, mR); + } +} + +export as INDCPA_MultiChal; +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Games/PubKeyEnc/INDCPA_MultiChal.game`, or open the file in the web editor and click **Type Check**. + +--- + +## 3. The KEMDEM scheme + +{% katex %}\mathsf{KEMDEM}(K, E){% endkatex %} takes a KEM {% katex %}K{% endkatex %} and a symmetric encryption scheme {% katex %}E{% endkatex %} and produces a public key encryption scheme. Its key pair is {% katex %}K{% endkatex %}'s key pair, its messages come from {% katex %}E{% endkatex %}, and its ciphertexts are pairs of a KEM ciphertext and a symmetric ciphertext. It requires {% katex %}K.\mathcal{S} \subseteq E.\mathcal{K}{% endkatex %} so that the KEM's shared secret can serve directly as a symmetric key. In the proof we will further impose equality {% katex %}K.\mathcal{S} = E.\mathcal{K}{% endkatex %} by binding both sets to the same `Set` variable in the proof's `let:` block; see §4 below. + +The sets needed for the scheme definition are: + +{% katex display %} +\begin{array}{l} +\mathcal{PK} = K.\mathcal{PK}, \quad \mathcal{SK} = K.\mathcal{SK}, \quad \mathcal{M} = E.\mathcal{M}, \quad \mathcal{C} = K.\mathcal{C} \times E.\mathcal{C} +\end{array} +{% endkatex %} + +The algorithms comprising the scheme are: + +{% katex display %} +\begin{array}{l} +\underline{\mathsf{KeyGen}()} \\ +\text{return } K.\mathsf{KeyGen}() +\end{array} +\quad +\begin{array}{l} +\underline{\mathsf{Enc}(\mathit{pk}, m)} \\ +(k_{\mathsf{sym}}, c_{\mathsf{kem}}) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +c_{\mathsf{sym}} \gets E.\mathsf{Enc}(k_{\mathsf{sym}}, m) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +\quad +\begin{array}{l} +\underline{\mathsf{Dec}(\mathit{sk}, (c_{\mathsf{kem}}, c_{\mathsf{sym}}))} \\ +k_{\mathsf{sym}} \gets K.\mathsf{Decaps}(\mathit{sk}, c_{\mathsf{kem}}) \\ +\text{return } E.\mathsf{Dec}(k_{\mathsf{sym}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +`KeyGen` simply delegates to the KEM. `Enc` encapsulates under the public key to produce the shared secret {% katex %}k_{\mathsf{sym}}{% endkatex %} and KEM ciphertext {% katex %}c_{\mathsf{kem}}{% endkatex %}, then uses that shared secret directly as the DEM key to encrypt the message. `Dec` reverses the process. The FrogLang scheme file is [`Schemes/PubKeyEnc/HybridKEMDEM.scheme`](https://github.com/ProofFrog/examples/blob/main/Schemes/PubKeyEnc/HybridKEMDEM.scheme): + +```prooffrog +import '../../Primitives/KEM.primitive'; +import '../../Primitives/NonNullableSymEnc.primitive'; +import '../../Primitives/PubKeyEnc.primitive'; + +Scheme KEMDEM(KEM K, SymEnc E) extends PubKeyEnc { + requires K.SharedSecret subsets E.Key; + + Set Message = E.Message; + Set Ciphertext = [K.Ciphertext, E.Ciphertext]; + Set PublicKey = K.PublicKey; + Set SecretKey = K.SecretKey; + + [PublicKey, SecretKey] KeyGen() { + return K.KeyGen(); + } + + Ciphertext Enc(PublicKey pk, Message m) { + [K.SharedSecret, K.Ciphertext] x = K.Encaps(pk); + E.Key k_sym = x[0]; + K.Ciphertext c_kem = x[1]; + E.Ciphertext c_sym = E.Enc(k_sym, m); + return [c_kem, c_sym]; + } + + deterministic Message? Dec(SecretKey sk, Ciphertext c) { + K.Ciphertext c_kem = c[0]; + E.Ciphertext c_sym = c[1]; + K.SharedSecret k_sym = K.Decaps(sk, c_kem); + return E.Dec(k_sym, c_sym); + } +} +``` + +{: .note } +**Try it.** From the `examples/` directory, run `proof_frog check Schemes/PubKeyEnc/HybridKEMDEM.scheme`, or open the file in the web editor and click **Type Check**. + +Note that `Dec` returns `Message?` (nullable) because `KEMDEM` extends `PubKeyEnc`, whose primitive declares `Dec` as returning an optional type. The underlying `NonNullableSymEnc` primitive's `Dec` returns plain `Message` (non-nullable), but the KEMDEM scheme's `Dec` signature must match the `PubKeyEnc` primitive it extends. + +The `requires K.SharedSecret subsets E.Key;` clause is an essential part of the entire construction. It states that the KEM's shared secret space must be a subset of the SymEnc's key space. Without it, the assignment `E.Key k_sym = x[0];` in `Enc` would be a type error — `x[0]` has type `K.SharedSecret`, not `E.Key`. The `requires` clause makes the subtype relationship explicit, so the type checker can accept the code. + +The ciphertext type `[K.Ciphertext, E.Ciphertext]` is a tuple. In FrogLang, `[T1, T2]` is the two-component product type, and components are accessed by constant index: `c[0]` is the KEM ciphertext, `c[1]` is the symmetric ciphertext. + +--- + +## 4. The proof structure + +The `games:` block lists twelve game steps, producing eleven hops. Five of those hops are assumption hops (one per reduction); the other six are interchangeability hops verified by the engine. Conceptually the proof moves through six games, which we'll call {% katex %}\mathsf{Game}_0{% endkatex %} through {% katex %}\mathsf{Game}_5{% endkatex %}: + +- {% katex %}\mathsf{Game}_0{% endkatex %} — the {% katex %}\mathsf{KEMDEM}{% endkatex %} scheme encrypting the *left* message {% katex %}m_L{% endkatex %} under the *real* KEM shared secret. This is `INDCPA_MultiChal(KD).Left` with the scheme inlined. +- {% katex %}\mathsf{Game}_1{% endkatex %} — encrypting {% katex %}m_L{% endkatex %} under a DEM key sampled *uniformly* from {% katex %}K.\mathcal{S} = E.\mathcal{K}{% endkatex %}, while the KEM ciphertext is still produced by a real `Encaps` call. +- {% katex %}\mathsf{Game}_2{% endkatex %} — encrypting {% katex %}m_L{% endkatex %} under a DEM key produced by `E.KeyGen()`. +- {% katex %}\mathsf{Game}_3{% endkatex %} — encrypting the *right* message {% katex %}m_R{% endkatex %} under a DEM key produced by `E.KeyGen()`. +- {% katex %}\mathsf{Game}_4{% endkatex %} — encrypting {% katex %}m_R{% endkatex %} under a DEM key sampled uniformly from {% katex %}K.\mathcal{S}{% endkatex %}. +- {% katex %}\mathsf{Game}_5{% endkatex %} — encrypting {% katex %}m_R{% endkatex %} under the *real* KEM shared secret. This is `INDCPA_MultiChal(KD).Right` with the scheme inlined. + +Five reductions bridge these six games: + +- {% katex %}R_1{% endkatex %} (KEM CPA, {% katex %}\mathsf{Game}_0 \to \mathsf{Game}_1{% endkatex %}): reduces to KEM CPA security in the **Real → Random** direction. The shared secret starts as the genuine output of `Encaps` and becomes a fresh uniform sample. +- {% katex %}R_2{% endkatex %} (KeyUniformity, {% katex %}\mathsf{Game}_1 \to \mathsf{Game}_2{% endkatex %}): reduces to key uniformity of {% katex %}E{% endkatex %} in the **Random → Real** direction. The symmetric key distribution moves from uniform over {% katex %}E.\mathcal{K}{% endkatex %} to the output of `E.KeyGen()`. +- {% katex %}R_3{% endkatex %} (OneTimeSecrecy, {% katex %}\mathsf{Game}_2 \to \mathsf{Game}_3{% endkatex %}): reduces to one-time secrecy of {% katex %}E{% endkatex %}. The encrypted message switches from {% katex %}m_L{% endkatex %} to {% katex %}m_R{% endkatex %} under a `KeyGen`-sampled key. +- {% katex %}R_4{% endkatex %} (KeyUniformity, {% katex %}\mathsf{Game}_3 \to \mathsf{Game}_4{% endkatex %}): structurally identical to {% katex %}R_2{% endkatex %} but encrypts {% katex %}m_R{% endkatex %}, and invokes key uniformity in the **Real → Random** direction. +- {% katex %}R_5{% endkatex %} (KEM CPA, {% katex %}\mathsf{Game}_4 \to \mathsf{Game}_5{% endkatex %}): structurally identical to {% katex %}R_1{% endkatex %} but encrypts {% katex %}m_R{% endkatex %}, and invokes KEM CPA in the **Random → Real** direction. + +Reductions {% katex %}R_1{% endkatex %} and {% katex %}R_5{% endkatex %} both reduce to KEM CPA security, but they invoke it in opposite directions: {% katex %}R_1{% endkatex %} goes Real → Random, {% katex %}R_5{% endkatex %} goes Random → Real. Similarly {% katex %}R_2{% endkatex %} and {% katex %}R_4{% endkatex %} invoke key uniformity in opposite directions. Both directions of each assumption are valid because indistinguishability is symmetric: if no adversary can distinguish the two sides in one direction, no adversary can distinguish them in the other. + +This proof file does **not** write out explicit intermediate game definitions. Each conceptual {% katex %}\mathsf{Game}_i{% endkatex %} appears in the `games:` list only as a composed form — either `AssumptionGame compose Reduction` or as an inlined side of `INDCPA_MultiChal(KD)`. The engine verifies the interchangeability hops by canonicalizing the adjacent composed forms directly. You can add these intermediate game definitions if you find it helpful; ProofFrog will check they match their neighbouring games. + +The proof's `let:` block binds {% katex %}K.\mathcal{S}{% endkatex %} and {% katex %}E.\mathcal{K}{% endkatex %} to the *same* `Set` variable (`KEMSharedSecretSpace`), imposing the equality {% katex %}K.\mathcal{S} = E.\mathcal{K}{% endkatex %} that the proof relies on whenever a uniform sample from one side must match a uniform sample from the other. + +--- + +## 5. Reduction {% katex %}R_1{% endkatex %}: Game 0 → Game 1 + +The first reduction adapts a {% katex %}\mathsf{CPA}(\mathsf{KEMDEM}(K,E)){% endkatex %} adversary so that it can play against a {% katex %}\mathsf{CPAKEM}(K){% endkatex %} challenger. It has no key pair of its own — all key material comes from the challenger. Its `Challenge` oracle delegates to `challenger.Challenge()` to obtain a {% katex %}(k_{\mathsf{sym}}, c_{\mathsf{kem}}){% endkatex %} pair, then uses {% katex %}k_{\mathsf{sym}}{% endkatex %} as the symmetric key to encrypt the **left** message: + +{% katex display %} +\begin{array}{l} +\underline{R_1.\mathsf{Challenge}(m_L, m_R)} \\ +(k_{\mathsf{sym}}, c_{\mathsf{kem}}) \gets \mathsf{challenger}.\mathsf{Challenge}() \\ +c_{\mathsf{sym}} \gets E.\mathsf{Enc}(k_{\mathsf{sym}}, m_L) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +Note that {% katex %}R_1{% endkatex %} has no `Initialize` method of its own: the compose machinery automatically forwards `Initialize` from the outer game to the inner `KEM_INDCPA_MultiChal(K)` challenger, so the challenger's `Initialize` — which generates the KEM key pair and returns `pk` — becomes the `Initialize` of the composed game. + +When the external challenger is {% katex %}\mathsf{Real}_K{% endkatex %}, {% katex %}k_{\mathsf{sym}}{% endkatex %} is the genuine shared secret produced by `Encaps`, and {% katex %}R_1 \circ \mathsf{Real}_K{% endkatex %} produces the same distribution as {% katex %}\mathsf{Game}_0{% endkatex %}: the {% katex %}\mathsf{KEMDEM}{% endkatex %} scheme encrypting {% katex %}m_L{% endkatex %} under a real shared secret. When the challenger switches to {% katex %}\mathsf{Random}_K{% endkatex %}, {% katex %}k_{\mathsf{sym}}{% endkatex %} becomes a uniformly random sample from {% katex %}K.\mathcal{S}{% endkatex %}, independent of {% katex %}c_{\mathsf{kem}}{% endkatex %}, and {% katex %}R_1 \circ \mathsf{Random}_K{% endkatex %} produces the same distribution as {% katex %}\mathsf{Game}_1{% endkatex %}. + +In FrogLang: + +```prooffrog +// Reduction for hop from Game 0 to Game 1 +// - Reduction to CPA security of the KEM. The reduction uses the shared secret +// from the KEM CPA challenger, which is either real (= Game 0) or random (= Game 1). +Reduction R1(SymEnc E, KEM K, KEMDEM KD) compose KEM_INDCPA_MultiChal(K) against INDCPA_MultiChal(KD).Adversary { + KD.Ciphertext Challenge(KD.Message mL, KD.Message mR) { + [K.SharedSecret, K.Ciphertext] y = challenger.Challenge(); + K.SharedSecret k_sym = y[0]; + K.Ciphertext c_kem = y[1]; + E.Ciphertext c_sym = E.Enc(k_sym, mL); + return [c_kem, c_sym]; + } +} +``` + +{: .note } +**Try it.** {% katex %}R_1{% endkatex %} lives inside `HybridKEMDEM_INDCPA_MultiChal.proof` along with the rest of the proof. From the `examples/` directory, run `proof_frog prove Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof` to verify the whole proof, or open the file in the web editor and click **Run Proof**. See [§11 Verifying](#11-verifying) for the expected output. + +Notice that {% katex %}R_1{% endkatex %} has no state fields: all key material lives inside the {% katex %}\mathsf{CPAKEM}(K){% endkatex %} challenger that {% katex %}R_1{% endkatex %} composes against. This contrasts with {% katex %}R_2{% endkatex %} through {% katex %}R_4{% endkatex %} below, which have to manage their own KEM key pair because they compose against a different challenger. + +--- + +## 6. Reduction {% katex %}R_2{% endkatex %}: Game 1 → Game 2 + +After the assumption hop on KEM CPA, the proof is in a state where the symmetric key is a uniform sample from {% katex %}K.\mathcal{S} = E.\mathcal{K}{% endkatex %}. But the next hop — one-time secrecy of {% katex %}E{% endkatex %} — will want the symmetric key to be the output of `E.KeyGen()` instead (because that's what the OTS game's challenger does internally). So we first bridge the two distributions with a **key-uniformity** hop. + +{% katex %}R_2{% endkatex %} composes against a {% katex %}\mathsf{KeyUniformity}(E){% endkatex %} challenger. It has no KEM challenger available — the {% katex %}\mathsf{CPAKEM}(K){% endkatex %} assumption was consumed in the previous hop — and so {% katex %}R_2{% endkatex %} must generate the KEM key pair itself and answer `Initialize` queries from its own state. Its `Challenge` oracle encapsulates under its own public key to produce {% katex %}c_{\mathsf{kem}}{% endkatex %} (discarding the shared secret), asks the `KeyUniformity(E)` challenger for a symmetric key, and uses that key to encrypt {% katex %}m_L{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\underline{R_2.\mathsf{Initialize}()} \\ +(\mathit{pk}, \mathit{sk}) \gets K.\mathsf{KeyGen}() \\ +\text{return } \mathit{pk} +\end{array} +\qquad +\begin{array}{l} +\underline{R_2.\mathsf{Challenge}(m_L, m_R)} \\ +(\_, c_{\mathsf{kem}}) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +k_{\mathsf{sym}} \gets \mathsf{challenger}.\mathsf{Challenge}() \\ +c_{\mathsf{sym}} \gets E.\mathsf{Enc}(k_{\mathsf{sym}}, m_L) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +When the `KeyUniformity(E)` challenger is in {% katex %}\mathsf{Random}{% endkatex %} mode, {% katex %}k_{\mathsf{sym}}{% endkatex %} is a uniform sample from {% katex %}E.\mathcal{K}{% endkatex %}, which equals {% katex %}K.\mathcal{S}{% endkatex %} (imposed by the proof's `let:` block). So {% katex %}R_2 \circ \mathsf{Random}{% endkatex %} matches {% katex %}\mathsf{Game}_1{% endkatex %}, which in turn matches {% katex %}R_1 \circ \mathsf{Random}_K{% endkatex %} (the closing step of the previous pattern) under canonicalization. When the challenger switches to {% katex %}\mathsf{Real}{% endkatex %} mode, {% katex %}k_{\mathsf{sym}}{% endkatex %} is the output of `E.KeyGen()`, and {% katex %}R_2 \circ \mathsf{Real}{% endkatex %} matches {% katex %}\mathsf{Game}_2{% endkatex %}. + +In FrogLang: + +```prooffrog +// Reduction for hop from Game 1 to Game 2 +// - Reduction to key uniformity of the symmetric encryption scheme. The reduction uses +// the symmetric key from the key uniformity challenger, which is either real (= Game 2) +// or random (= Game 1). +Reduction R2(SymEnc E, KEM K, KEMDEM KD) compose KeyUniformity(E) against INDCPA_MultiChal(KD).Adversary { + K.PublicKey pk; + K.SecretKey sk; + + K.PublicKey Initialize() { + [K.PublicKey, K.SecretKey] k = K.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + KD.Ciphertext Challenge(KD.Message mL, KD.Message mR) { + [K.SharedSecret, K.Ciphertext] x = K.Encaps(pk); + K.SharedSecret k_sym = challenger.Challenge(); + K.Ciphertext c_kem = x[1]; + E.Ciphertext c_sym = E.Enc(k_sym, mL); + return [c_kem, c_sym]; + } +} +``` + +This is a recurring pattern in multi-primitive proofs: a reduction that targets one assumption must internally simulate every other primitive that the surrounding game depends on. {% katex %}R_2{% endkatex %} plays against a `KeyUniformity(E)` challenger but still needs a real KEM key pair — because the scheme under attack uses `K.KeyGen()` and `K.Encaps()` as part of its `Enc` algorithm — so {% katex %}R_2{% endkatex %} generates that KEM key pair in its own `Initialize`. + +--- + +## 7. Reduction {% katex %}R_3{% endkatex %}: Game 2 → Game 3 + +With the DEM key now distributed as `E.KeyGen()`, the proof is ready to invoke one-time secrecy of {% katex %}E{% endkatex %}. {% katex %}R_3{% endkatex %} composes against an {% katex %}\mathsf{OneTimeSecrecy}(E){% endkatex %} challenger. Like {% katex %}R_2{% endkatex %}, it generates the KEM key pair in its own `Initialize`. Its `Challenge` oracle encapsulates to produce {% katex %}c_{\mathsf{kem}}{% endkatex %} (discarding the shared secret), and asks the `OTS(E)` challenger to produce the DEM ciphertext on either {% katex %}m_L{% endkatex %} or {% katex %}m_R{% endkatex %} under a fresh key: + +{% katex display %} +\begin{array}{l} +\underline{R_3.\mathsf{Initialize}()} \\ +(\mathit{pk}, \mathit{sk}) \gets K.\mathsf{KeyGen}() \\ +\text{return } \mathit{pk} +\end{array} +\qquad +\begin{array}{l} +\underline{R_3.\mathsf{Challenge}(m_L, m_R)} \\ +(\_, c_{\mathsf{kem}}) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +c_{\mathsf{sym}} \gets \mathsf{challenger}.\mathsf{Eavesdrop}(m_L, m_R) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +Crucially, {% katex %}R_3{% endkatex %} discards the shared secret produced by `Encaps` and lets the OTS challenger sample its own fresh DEM key via `E.KeyGen()`. When the OTS challenger is in {% katex %}\mathsf{Left}{% endkatex %} mode, the DEM ciphertext is an encryption of {% katex %}m_L{% endkatex %} under a `KeyGen`-sampled key, matching {% katex %}\mathsf{Game}_2{% endkatex %}. When the OTS challenger is in {% katex %}\mathsf{Right}{% endkatex %} mode, the DEM ciphertext is an encryption of {% katex %}m_R{% endkatex %}, matching {% katex %}\mathsf{Game}_3{% endkatex %}. This is the central hop of the whole proof — the point where the "which message is encrypted" bit actually flips. + +In FrogLang: + +```prooffrog +// Reduction for hop from Game 2 to Game 3 +// - Reduction to one-time secrecy of the symmetric encryption scheme. The reduction uses the +// challenger to encrypt either mL (= Game 2) or mR (= Game 3). +Reduction R3(SymEnc E, KEM K, KEMDEM KD) compose INDOT(E) against INDCPA_MultiChal(KD).Adversary { + K.PublicKey pk; + K.SecretKey sk; + + K.PublicKey Initialize() { + [K.PublicKey, K.SecretKey] k = K.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + KD.Ciphertext Challenge(KD.Message mL, KD.Message mR) { + [K.SharedSecret, K.Ciphertext] x = K.Encaps(pk); + K.Ciphertext c_kem = x[1]; + E.Ciphertext c_sym = challenger.Eavesdrop(mL, mR); + return [c_kem, c_sym]; + } +} +``` + +--- + +## 8. Reduction {% katex %}R_4{% endkatex %}: Game 3 → Game 4 + +The rest of the proof "undoes" the earlier distributional bridges, but on the right-message side. {% katex %}R_4{% endkatex %} is structurally identical to {% katex %}R_2{% endkatex %} but encrypts {% katex %}m_R{% endkatex %} instead of {% katex %}m_L{% endkatex %}, and the direction of the assumption flips: now the proof uses key uniformity to move *back* from "key from `E.KeyGen()`" to "key sampled uniformly from {% katex %}E.\mathcal{K}{% endkatex %}". + +{% katex display %} +\begin{array}{l} +\underline{R_4.\mathsf{Initialize}()} \\ +(\mathit{pk}, \mathit{sk}) \gets K.\mathsf{KeyGen}() \\ +\text{return } \mathit{pk} +\end{array} +\qquad +\begin{array}{l} +\underline{R_4.\mathsf{Challenge}(m_L, m_R)} \\ +(\_, c_{\mathsf{kem}}) \gets K.\mathsf{Encaps}(\mathit{pk}) \\ +k_{\mathsf{sym}} \gets \mathsf{challenger}.\mathsf{Challenge}() \\ +c_{\mathsf{sym}} \gets E.\mathsf{Enc}(k_{\mathsf{sym}}, m_R) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +When the `KeyUniformity(E)` challenger is in {% katex %}\mathsf{Real}{% endkatex %} mode, {% katex %}R_4 \circ \mathsf{Real}{% endkatex %} matches {% katex %}\mathsf{Game}_3{% endkatex %} (closing the OTS pattern). When the challenger switches to {% katex %}\mathsf{Random}{% endkatex %} mode, {% katex %}R_4 \circ \mathsf{Random}{% endkatex %} matches {% katex %}\mathsf{Game}_4{% endkatex %} (opening the closing KEM CPA pattern). + +In FrogLang: + +```prooffrog +// Reduction for hop from Game 3 to Game 4 +// - Reduction to key uniformity of the symmetric encryption scheme. The reduction uses +// the symmetric key from the key uniformity challenger, which is either real (= Game 3) +// or random (= Game 4). +Reduction R4(SymEnc E, KEM K, KEMDEM KD) compose KeyUniformity(E) against INDCPA_MultiChal(KD).Adversary { + K.PublicKey pk; + K.SecretKey sk; + + K.PublicKey Initialize() { + [K.PublicKey, K.SecretKey] k = K.KeyGen(); + pk = k[0]; + sk = k[1]; + return pk; + } + + KD.Ciphertext Challenge(KD.Message mL, KD.Message mR) { + [K.SharedSecret, K.Ciphertext] x = K.Encaps(pk); + K.SharedSecret k_sym = challenger.Challenge(); + K.Ciphertext c_kem = x[1]; + E.Ciphertext c_sym = E.Enc(k_sym, mR); + return [c_kem, c_sym]; + } +} +``` + +--- + +## 9. Reduction {% katex %}R_5{% endkatex %}: Game 4 → Game 5 + +The fifth and final reduction is structurally identical to {% katex %}R_1{% endkatex %} — it lets its composed {% katex %}\mathsf{CPAKEM}(K){% endkatex %} challenger supply the key pair and the {% katex %}(k_{\mathsf{sym}}, c_{\mathsf{kem}}){% endkatex %} pair — but it encrypts {% katex %}m_R{% endkatex %} instead of {% katex %}m_L{% endkatex %}: + +{% katex display %} +\begin{array}{l} +\underline{R_5.\mathsf{Challenge}(m_L, m_R)} \\ +(k_{\mathsf{sym}}, c_{\mathsf{kem}}) \gets \mathsf{challenger}.\mathsf{Challenge}() \\ +c_{\mathsf{sym}} \gets E.\mathsf{Enc}(k_{\mathsf{sym}}, m_R) \\ +\text{return } (c_{\mathsf{kem}}, c_{\mathsf{sym}}) +\end{array} +{% endkatex %} + +When the KEM challenger is in {% katex %}\mathsf{Random}{% endkatex %} mode, {% katex %}k_{\mathsf{sym}}{% endkatex %} is a random shared secret and {% katex %}R_5 \circ \mathsf{Random}_K{% endkatex %} matches {% katex %}\mathsf{Game}_4{% endkatex %}. When the challenger switches to {% katex %}\mathsf{Real}_K{% endkatex %}, {% katex %}k_{\mathsf{sym}}{% endkatex %} is the genuine shared secret and {% katex %}R_5 \circ \mathsf{Real}_K{% endkatex %} matches {% katex %}\mathsf{Game}_5{% endkatex %}, which is in turn interchangeable with `INDCPA_MultiChal(KD).Right`. + +This is the **Random → Real direction** of the KEM CPA assumption: the proof has finished using the random-key intermediate world and is restoring the real KEM shared secret, but on the right-message side. Both directions of the assumption are valid because indistinguishability is symmetric. + +In FrogLang: + +```prooffrog +// Reduction for hop from Game 4 to Game 5 +// - Reduction to CPA security of the KEM. The reduction uses the shared secret +// from the KEM CPA challenger, which is either real (= Game 5) or random (= Game 4). +Reduction R5(SymEnc E, KEM K, KEMDEM KD) compose KEM_INDCPA_MultiChal(K) against INDCPA_MultiChal(KD).Adversary { + KD.Ciphertext Challenge(KD.Message mL, KD.Message mR) { + [K.SharedSecret, K.Ciphertext] y = challenger.Challenge(); + K.SharedSecret k_sym = y[0]; + K.Ciphertext c_kem = y[1]; + E.Ciphertext c_sym = E.Enc(k_sym, mR); + return [c_kem, c_sym]; + } +} +``` + +--- + +## 10. The full games block + +Putting all twelve game steps together, the `games:` section of `HybridKEMDEM_INDCPA_MultiChal.proof` reads: + +```prooffrog +games: + // Game 0 + INDCPA_MultiChal(KD).Left against INDCPA_MultiChal(KD).Adversary; + KEM_INDCPA_MultiChal(K).Real compose R1(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + // Game 1 + KEM_INDCPA_MultiChal(K).Random compose R1(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + KeyUniformity(E).Random compose R2(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + // Game 2 + KeyUniformity(E).Real compose R2(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + INDOT(E).Left compose R3(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + // Game 3 + INDOT(E).Right compose R3(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + KeyUniformity(E).Real compose R4(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + // Game 4 + KeyUniformity(E).Random compose R4(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + KEM_INDCPA_MultiChal(K).Random compose R5(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + // Game 5 + KEM_INDCPA_MultiChal(K).Real compose R5(E, K, KD) against INDCPA_MultiChal(KD).Adversary; + INDCPA_MultiChal(KD).Right against INDCPA_MultiChal(KD).Adversary; +``` + +The twelve entries produce eleven hops. Five are assumption hops — one per reduction — at positions 2 → 3 ({% katex %}R_1{% endkatex %}, CPAKEM Real → Random), 4 → 5 ({% katex %}R_2{% endkatex %}, KeyUniformity Random → Real), 6 → 7 ({% katex %}R_3{% endkatex %}, OneTimeSecrecy Left → Right), 8 → 9 ({% katex %}R_4{% endkatex %}, KeyUniformity Real → Random), and 10 → 11 ({% katex %}R_5{% endkatex %}, CPAKEM Random → Real). The other six transitions (1 → 2, 3 → 4, 5 → 6, 7 → 8, 9 → 10, 11 → 12) are interchangeability hops verified by the engine. + +Notice how each reduction occupies two consecutive entries in the games list — one for each side of the composed assumption game — and interchangeability hops connect adjacent reductions at their conceptual boundary {% katex %}\mathsf{Game}_i{% endkatex %}. The interchangeability hop at positions 3 → 4, for example, is the engine verifying that `KEM_INDCPA_MultiChal(K).Random compose R1` and `KeyUniformity(E).Random compose R2` both canonicalize to {% katex %}\mathsf{Game}_1{% endkatex %}, even though they get there via very different-looking source programs. + +The full `let:`, `assume:`, and `theorem:` blocks, together with the five reductions above, complete the proof file, which you can find at [`Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof`](https://github.com/ProofFrog/examples/blob/main/Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof). + +--- + +## 11. Verifying + +{: .important } +**Activate your Python virtual environment first** if it is not already active in this terminal: `source .venv/bin/activate` on macOS/Linux (bash/zsh), `source .venv/bin/activate.fish` on fish, or `.venv\Scripts\Activate.ps1` on Windows PowerShell. See [Installation]({% link manual/installation.md %}). + +From the `examples/` directory: + +```bash +proof_frog prove Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof +``` + +Expected output: + +``` +Proof Succeeded! +``` + +The full step-by-step output shows 11 hops over 12 game steps: 6 interchangeability hops (verified by code canonicalization) and 5 assumption hops (one for each of {% katex %}R_1{% endkatex %} through {% katex %}R_5{% endkatex %}). + +In the web editor, open `Proofs/PubKeyEnc/HybridKEMDEM_INDCPA_MultiChal.proof` and click **Run Proof**. The output panel turns green with the same step-by-step report. + +--- + +## 12. Lessons learned + +- **Multi-primitive composition.** The proof reasons about three distinct primitives simultaneously — a KEM, a SymEnc, and a PKE — each with its own security game and its own type namespace. Reductions must carefully distinguish which primitive's methods and sets they are invoking. The `requires` clause on the scheme is what makes the types align across primitive boundaries. + +- **Reductions in opposite directions.** {% katex %}R_1{% endkatex %} and {% katex %}R_5{% endkatex %} both reduce to the same underlying assumption ({% katex %}\mathsf{CPAKEM}(K){% endkatex %}), but {% katex %}R_1{% endkatex %} invokes it in the Real → Random direction and {% katex %}R_5{% endkatex %} invokes it in the Random → Real direction. Similarly, {% katex %}R_2{% endkatex %} and {% katex %}R_4{% endkatex %} invoke key uniformity in opposite directions. Both directions are valid because indistinguishability is symmetric. This "go-and-come-back" pattern often comes up in security proofs: here we use the KEM assumption to move into a world with a random key, do the message-switching argument, then use the same KEM assumption to move back out. Neither direction is privileged; the same security game serves both roles. + +- **Bridging distributions with a key-uniformity assumption.** The `KEMDEM` scheme never calls `E.KeyGen()` — it uses the KEM's shared secret directly as the symmetric key — while the one-time secrecy game for {% katex %}E{% endkatex %} *does* call `E.KeyGen()`. These two key distributions aren't always identical, so the proof needs a key-uniformity assumption (that "`E.KeyGen()` *is* indistinguishable from uniform sampling over `E.Key`") to bridge them. The bridge is invoked symmetrically — once on each side of the OTS hop — which is why there are five reductions instead of three. + +- **Generic construction parameter handling.** The {% katex %}\mathsf{KEMDEM}{% endkatex %} scheme is parameterized by {% katex %}(K, E){% endkatex %}, and the proof is parameterized by the same values in its `let:` block. Every reduction carries `(E, K, KD)` as parameters. This is how ProofFrog proves theorems about generic constructions rather than concrete instantiations: the proof holds for any choice of {% katex %}K{% endkatex %} and {% katex %}E{% endkatex %} satisfying the stated assumptions and the {% katex %}K.\mathcal{S} = E.\mathcal{K}{% endkatex %} constraint. + +- **Proofs without explicit intermediate games.** Unlike proofs that write out each {% katex %}\mathsf{Game}_i{% endkatex %} as a standalone `Game` definition, this proof keeps its intermediate games implicit — every entry in the `games:` list is either a side of `INDCPA_MultiChal(KD)` or a `compose` expression. The engine verifies the interchangeability hops by canonicalizing adjacent composed forms directly. This keeps the proof file shorter, at the cost of making each conceptual game only visible in the reader's head (or in the line comments that label them). You can add them if you want, or you can use the web editor's game hop detail view to see what the intermediate games are. + +--- + +### 13. Next steps + +This concludes the tutorial and worked examples section of the ProofFrog manual. From here you have a few options: + +- Build your own proof! You now have the tools to start modelling your own ideas in ProofFrog. +- Explore the [**examples catalogue**]({% link examples.md %}) to see other examples of cryptographic schemes and proofs modelled in ProofFrog. +- Read the [**language reference**]({% link manual/language-reference/index.md %}) to dive deeper into FrogLang. +- Since ProofFrog is a new tool and is targeted at introduction proofs, it's important to understand the [**limitations**]({% link manual/limitations.md %}). There's also information about [**troubleshooting**]({% link manual/troubleshooting.md %}) when you get stuck. You can ask questions on the [ProofFrog discussion boards on GitHub](https://github.com/orgs/ProofFrog/discussions). +- See how proofs in ProofFrog compare to proofs in more advanced formal verification tools like EasyCrypt via the [Proof Ladders project](https://proof-ladders.github.io/), which includes an example showing CPA security of KEM-DEM in both [ProofFrog](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/ProofFrog) and [EasyCrypt](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/EasyCrypt). That version of the ProofFrog proof uses a slightly different formulation of the one-time secrecy game — one phrased with uniform sampling `E.Key k <- E.Key;` instead of `E.KeyGen()` — which lets the proof sidestep the key-uniformity assumption entirely and collapses the five reductions on this page down to three. +- Learn more about the technical background of ProofFrog in the [**for researchers section**]({% link researchers/index.md %}). +- **Get in touch!** We'd love to hear about your use of ProofFrog. Contributions of examples or improvements to the ProofFrog software are also welcome. The best way to reach us is on the [ProofFrog GitHub repositories](https://github.com/ProofFrog). diff --git a/researchers/engine-internals.md b/researchers/engine-internals.md new file mode 100644 index 0000000..cfeccb5 --- /dev/null +++ b/researchers/engine-internals.md @@ -0,0 +1,360 @@ +--- +title: Engine Internals +layout: default +parent: For Researchers +nav_order: 2 +--- + +# Engine Internals +{: .no_toc } + +This page orients researchers and contributors to ProofFrog's internals: the overall +pipeline, the canonicalization mechanism, the introspection commands for diagnosing +failing hops, and the LSP/MCP interfaces. It complements the user-facing +[Canonicalization]({% link manual/canonicalization.md %}) page, which describes *what* +the engine does from a proof-author's perspective; this page describes *how* it does +it. For the broader design of ProofFrog and its canonicalization approach, see the +ProofFrog [paper](https://eprint.iacr.org/2025/418) and +[thesis](https://hdl.handle.net/10012/20441), both listed on the +[Publications]({% link researchers/publications/index.md %}) page. + +- TOC +{:toc} + +--- + +## High-level architecture + +ProofFrog is a pipeline: source files are parsed from FrogLang syntax into abstract +syntax trees (ASTs), those ASTs are type-checked by the semantic analyzer, and +verified proofs are checked step by step by the proof engine. For each adjacent pair +of games in the `games:` block of a `.proof` file, the engine (a) instantiates both +game expressions against the proof's `let:` bindings, (b) inlines the scheme methods +named in those expressions, and (c) runs the canonicalization pipeline on each +resulting game AST independently. If the two canonical forms are structurally +identical the interchangeability hop is accepted; if they differ only in logically +equivalent branch conditions the engine also queries Z3. Reduction-based hops are +accepted by checking that the stated assumption appears in the proof's `assume:` +block. The canonicalization step is itself a two-stage process: a fixed-point loop +(`CORE_PIPELINE`) that iterates until the game AST stops changing, followed by a +single-pass normalization (`STANDARDIZATION_PIPELINE`) that renames variables and +fields to canonical identifiers. + +![ProofFrog pipeline]({{ site.baseurl }}/assets/diagram.svg) + +--- + +## The transformation pipeline + +For the user-facing model of what these transforms achieve, see the +[Canonicalization]({% link manual/canonicalization.md %}) page. + +### Pipeline assembly + +Canonicalization runs in two stages. The first stage is a fixed-point loop: an +ordered list of transformations is applied in sequence, then the entire sequence is +repeated from the beginning, until a full sweep produces no change. If the loop does +not converge within a generous iteration bound, a warning is emitted and processing +continues with the last state reached. + +The second stage is a single-pass normalization that runs once after the first +stage converges. It renames local variables and fields to canonical identifiers, +sorts field assignments, and stabilizes independent statements. Because it runs +only once, its passes must be idempotent. + +### Transform categories + +The transforms that make up the fixed-point core stage are grouped by the class of +simplification they perform. Each source file under +[`proof_frog/transforms/`](https://github.com/ProofFrog/ProofFrog/tree/main/proof_frog/transforms) +in the ProofFrog repository defines one or more transforms in a given category. The +appendix at the bottom of this page gives a one-line summary of every individual +transform. + +- **[Algebraic identities](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/algebraic.py)** — arithmetic and bitstring identities: XOR cancellation and identity, boolean simplification, commutative-chain normalization, modular arithmetic folding, group-element exponent arithmetic. +- **[Uniform sampling](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/sampling.py)** — uniform random sampling and splice normalization: merging and splitting samples, propagating slices through concatenation, sinking samples later in a block, and converting init-only or single-use fields into local variables. +- **[Random functions](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/random_functions.py)** — `Function` random-function elimination: lifting random-function calls into named temporaries, recognizing fresh/distinct/unique inputs, and replacing calls with uniform samples when the inputs are provably distinct (including through injective encoding wrappers). +- **[Inlining and common-subexpression elimination](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/inlining.py)** — variable, field, and expression inlining; common-subexpression elimination; cross-method field aliasing. +- **[Control flow](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/control_flow.py)** — branch and dead-code elimination; conditional merging; return-statement simplification; Z3-backed unreachability analysis. +- **[Structural ordering](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/structural.py)** — field and statement ordering: topological sort with dead-code elimination, field unification and pruning, uniform-bijection elimination. +- **[Symbolic integer arithmetic](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/symbolic.py)** — integer sub-expression simplification via SymPy. +- **[Type-driven simplifications](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/types.py)** — null-guard elimination and subset-type normalization. +- **[Tuples](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/tuples.py)** — tuple index folding, expansion, collapsing, and `[a[0], a[1], ..., a[n-1]]` reconstruction. + +Two further groups of transforms run outside the fixed-point core stage. The +[standardization passes](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/standardization.py) +form the single-pass second stage described above, renaming locals and fields to +canonical identifiers, sorting field assignments, and stabilizing independent +statements. The [assumption-application pass](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/assumptions.py) +is invoked by the proof engine only when an explicit assumption annotation appears +between two games in a proof, and substitutes the user-supplied equivalence pairs +between variables. + +### Reporting failures to the user + +The engine has two complementary mechanisms for explaining why an +interchangeability hop failed. + +The first is **near-miss reporting**. At any point where a transform *almost* +fires but cannot because some syntactic or semantic precondition does not hold, +it records a near-miss: a note carrying the transform's name, a human-readable +reason, an optional source location, an optional suggestion for the proof author, +and optional variable and oracle-method identifiers. After the pipeline completes, +the engine joins collected near-misses against the diff between the two canonical +forms by method name and variable presence, so that it can tell the user "this +simplification would have fired here except for X." + +The second mechanism handles failures in regions where no transform ever attempted +to simplify. There the engine falls back to a small set of detectors for known +gaps in the canonicalization pipeline — cosmetic patterns the algorithm does not +currently normalize, such as commutativity and associativity of boolean operators +or reordering of independent if/else-if branches. When one of these detectors +matches the diff, the failure is reported as an engine limitation with a +suggested workaround rather than being left unexplained. The full list of known +limitations, together with their user-facing workarounds, is on the +[Limitations]({% link manual/limitations.md %}) page. + +--- + +## Engine introspection commands + +Four CLI commands are intentionally excluded from the +[CLI Reference]({% link manual/cli-reference.md %}) because they target contributors +and MCP/LSP clients rather than end users. All four output JSON and are the CLI +equivalents of the corresponding MCP tools documented in +[`CLAUDE_MCP.md`](https://github.com/ProofFrog/ProofFrog/blob/main/CLAUDE_MCP.md) +in the ProofFrog repository. + +### `step-detail ` + +Loads the proof file, instantiates and inlines game step `step_index` (zero-based), +runs the full canonicalization pipeline, and prints a JSON object whose key fields +are: + +- `canonical` — the fully simplified game AST as a string. This is the field to + read when writing a matching intermediate `Game` definition. +- `output` — the raw (pre-simplification) game AST with mangled internal variable + names; ignore this field. +- `success` — `false` if the step index is out of range or an error occurred. +- `has_reduction`, `reduction`, `challenger`, `scheme` — metadata about whether the + step involves a reduction and what its components are. + +Comparing the `canonical` output of step `i` against step `i+1` directly shows any +remaining difference. + +```bash +proof_frog step-detail examples/joy/Proofs/Ch2/OTPSecure.proof 0 | python -m json.tool +``` + +### `inlined-game ` + +Reads the `let:`, `assume:`, and `theorem:` blocks from the proof file and +constructs a minimal scratch proof in which `step_text` is the only game in the +`games:` list, then evaluates that scratch proof's step 0. Unlike `step-detail`, the +step does not need to appear in the actual proof file, and stub reductions that +prevent parsing do not interfere. This makes it the primary tool for writing +intermediate games: you can explore arbitrary game expressions against the proof's +`let:` context before committing any of them to the file. The returned JSON has the +same `canonical`, `output`, and `success` fields as `step-detail`. + +```bash +proof_frog inlined-game examples/joy/Proofs/Ch2/OTPSecure.proof \ + "OneTimeSecrecy(E).Left against OneTimeSecrecy(E).Adversary" | python -m json.tool +``` + +### `canonicalization-trace ` + +Runs the canonicalization pipeline for the given proof step using +`run_pipeline_with_trace` and returns a JSON object containing `success`, +`iterations` (an array of `{iteration, transforms_applied}` objects, one per +iteration in which at least one transform changed the AST), `total_iterations`, +and `converged` (whether the pipeline converged before the 200-iteration limit). + +This is the first diagnostic to reach for when a hop is failing and near-miss +output is not enough: it shows the exact sequence of transforms that fire and the +order in which they reduce the game, making it straightforward to identify which +simplification is missing. + +```bash +proof_frog canonicalization-trace examples/joy/Proofs/Ch2/OTPSecure.proof 1 \ + | python -m json.tool +``` + +### `step-after-transform ` + +Applies the canonicalization pipeline up to and including the named transform +(first iteration only, using `run_pipeline_until`) and returns the game AST at that +point. The response JSON contains `success`, `output` (the AST as a string after the +named transform), `transform_applied` (whether the named transform actually changed +the AST), and `available_transforms` (the full list of valid names, useful when the +supplied name is not recognized). + +This enables single-step debugging: apply transforms one by one and inspect the +intermediate AST to determine exactly where a simplification should have fired but +did not. + +```bash +proof_frog step-after-transform examples/joy/Proofs/Ch2/OTPSecure.proof 1 \ + "UniformXorSimplification" | python -m json.tool +``` + +--- + +## LSP and MCP servers + +### LSP server + +ProofFrog ships with a Language Server Protocol implementation, started by +`proof_frog lsp`. It listens on stdio and is compatible with any LSP-aware editor; +see [Editor Plugins]({% link manual/editor-plugins.md %}) for the VSCode plugin +configuration. Other editors need only a thin client pointing at `proof_frog lsp` +as the server command. + +The server implements the standard LSP features over FrogLang source: parse and +semantic diagnostics, go-to-definition, hover, context-sensitive completion and +signature help, document symbols for the Outline panel, local-symbol rename, and +folding ranges. The server caches the most recently successfully parsed AST for +each open document, so that completion, hover, and symbols continue to work even +while the document currently has syntax errors. + +On save, the server runs the full proof engine against the file and surfaces the +results back to the editor as inline code lenses — a green check or red cross next +to each game in the `games:` block. The VSCode extension additionally uses a +custom notification to populate its proof-hops tree view with per-step results; +other clients can ignore that notification and rely on the code lenses alone. + +### MCP server + +ProofFrog also exposes its capabilities as +[Model Context Protocol](https://modelcontextprotocol.io/) tools for LLM-based +proof authoring. Start the MCP server with `proof_frog mcp [directory]`, where the +optional directory argument sets the working root from which all file paths are +resolved; a path-safety check prevents tools from reading or writing files outside +that directory or inside hidden subdirectories. The server communicates over stdio +and requires the optional MCP extra (`pip install 'proof_frog[mcp]'`). + +The server exposes the following tools: + +| Tool | Description | +|------|-------------| +| `version` | Report the installed ProofFrog version | +| `list_files` | Browse `.primitive`, `.game`, `.scheme`, `.proof` files as a nested tree | +| `read_file` | Read a ProofFrog file by path | +| `write_file` | Create or overwrite a file; parent directories are created automatically | +| `describe` | Compact interface summary (parameters, fields, signatures, no bodies) | +| `parse` | Syntax check; returns `{output, success}` | +| `check` | Semantic type-check; returns `{output, success}` | +| `prove` | Full proof verification; returns `{output, success, hop_results}` | +| `get_step_detail` | Canonical form of a proof step (use `canonical` field, not `output`) | +| `get_inlined_game` | Canonical form of an arbitrary step expression against the proof's `let:` context | +| `get_canonicalization_trace` | Which transforms fired at each fixed-point iteration | +| `get_step_after_transform` | Game AST after transforms up to a named pass | + +It also exposes a `prooffrog://language-reference` MCP resource containing a concise +FrogLang syntax reference that an LLM can fetch without reading through example +files. + +The MCP-oriented tool-usage guide and setup instructions are in +[`CLAUDE_MCP.md`](https://github.com/ProofFrog/ProofFrog/blob/main/CLAUDE_MCP.md) +in the ProofFrog repository. For a practical introduction to using the MCP server +to iteratively write and debug proofs with Claude Code, see the +[Gen AI & Proving page]({% link researchers/gen-ai.md %}). + +--- + +## Appendix: transform catalog + +This appendix contains a one-line summary of every transform in +[`proof_frog/transforms/`](https://github.com/ProofFrog/ProofFrog/tree/main/proof_frog/transforms), +grouped by source file. The authoritative ordering is in +[`pipelines.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/pipelines.py). + +**[`algebraic.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/algebraic.py)** + +- `UniformXorSimplification` — uniform `BitString` XOR'd with an expression used only in that one XOR becomes a fresh uniform sample (one-time pad). +- `UniformModIntSimplification` — the `ModInt` addition analogue. +- `UniformGroupElemSimplification` — the group-element scalar-multiplication analogue. +- `XorCancellation` — `x + x` becomes the zero bitstring. +- `XorIdentity` — `x + 0^n` becomes `x`. +- `ModIntSimplification` — folds constant modular arithmetic via SymPy. +- `NormalizeCommutativeChains` — sorts XOR, `+`, and `*` operand chains into a canonical order. +- `ReflexiveComparison` — `x == x` → `true`, `x != x` → `false`. +- `BooleanIdentity` — boolean AND/OR with literal `true`/`false`. +- `SimplifyNotPass` — `!(a == b)` → `a != b`, `!(a != b)` → `a == b`. +- `GroupElemSimplification`, `GroupElemCancellation`, `GroupElemExponentCombination` — group-element exponent arithmetic. + +**[`sampling.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/sampling.py)** + +- `SimplifySplice` — propagates slices through concatenation, collapsing `(a || b)[0:n]` when boundaries are static. +- `MergeUniformSamples` — merges `BitString` and `BitString` samples used only via concatenation into one `BitString`. +- `MergeProductSamples` — the product (tuple) type analogue. +- `SplitUniformSamples` — the inverse: splits a `BitString` into independent samples per non-overlapping slice. +- `SingleCallFieldToLocal` — converts an init-written, single-oracle-read field into a local variable. +- `CounterGuardedFieldToLocal` — converts counter-guarded (write-once) fields into local variables. +- `SinkUniformSample` — moves uniform samples as late as possible within a block to expose further simplifications. +- `LocalizeInitOnlyFieldSample` — converts a field sampled only in `Initialize` and never written again into a local there. + +**[`random_functions.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/random_functions.py)** + +- `ExtractRFCalls` — lifts random-function calls out of expressions into named intermediate variables. +- `UniqueRFSimplification` — calls on `<-uniq`-sampled inputs each return an independent uniform sample. +- `ChallengeExclusionRFToUniform` — recognizes challenge-exclusion even through an injective encoding wrapper. +- `LocalRFToUniform` — a single-oracle-local random function called on a fresh input becomes a uniform sample. +- `DistinctConstRFToUniform` — statically distinct constant inputs yield independent uniform samples. +- `FreshInputRFToUniform` — a `<-uniq` input used only in one RF call becomes a uniform sample. + +**[`inlining.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/inlining.py)** + +- `RedundantCopy` — removes `x = y` when `y` is not later modified. +- `InlineSingleUseVariable` — substitutes at the single use site and drops the binding. +- `DeduplicateDeterministicCalls` — introduces a common temporary for duplicate calls to a `deterministic` method. +- `ForwardExpressionAlias` — forward-propagates a never-reassigned pure RHS to all uses. +- `HoistFieldPureAlias` — replaces reads of a field always equal to a pure expression with the expression. +- `InlineSingleUseField` — inlines the write into the read site when a field is written and read exactly once. +- `InlineMultiUsePureExpression` — inlines a pure expression at multiple uses when it does not duplicate non-determinism. +- `CollapseAssignment` — removes trivial `x = x`. +- `RedundantFieldCopy` — removes field assignments that duplicate a field already holding the same value. +- `CrossMethodFieldAlias` — propagates a `deterministic` call result stored in a field across oracle boundaries. + +**[`control_flow.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/control_flow.py)** + +- `BranchElimination` — eliminates branches whose conditions are literal `true`/`false`. +- `SimplifyIf` — merges adjacent if/else-if branches with identical bodies under an OR condition. +- `SimplifyReturn` — collapses `Type v = expr; return v;` to `return expr;`. +- `RemoveUnreachable` — Z3-backed dead-statement elimination after unconditional returns. +- `IfConditionAliasSubstitution` — substitutes a known alias inside an if condition. +- `RedundantConditionalReturn` — drops the else branch when the then branch unconditionally returns. + +**[`structural.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/structural.py)** + +- `TopologicalSort` — reorders statements by data dependency, with dead-code elimination for no-effect statements. +- `RemoveDuplicateFields` — unifies two same-type fields that provably share the same value. +- `RemoveUnnecessaryFields` — deletes fields not contributing to any oracle's return value. +- `UniformBijectionElimination` — a uniform sample composed with an injective function becomes a fresh sample of the output type. + +**[`symbolic.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/symbolic.py)** + +- `SymbolicComputation` — integer sub-expressions round-tripped through SymPy for algebraic simplification. + +**[`types.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/types.py)** + +- `DeadNullGuardElimination` — removes `if x != None` guards when the type system already guarantees non-null. +- `SubsetTypeNormalization` — normalizes subset-type expressions to the underlying base type where semantics allow. + +**[`tuples.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/tuples.py)** + +- `FoldTupleIndex` — `[a, b, c][1]` → `b` when the index is a compile-time constant. +- `ExpandTuple` — rewrites a tuple into per-index variables when all accesses use constant indices. +- `SimplifyTuple` — collapses `[a[0], a[1], ..., a[n-1]]` into `a`. +- `CollapseSingleIndexTuple` — scalarizes a tuple accessed only at one constant index. + +**[`standardization.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/standardization.py)** + +- `VariableStandardize` — renames locals to `v1`, `v2`, ... in order of first appearance. +- `StandardizeFieldNames` — renames game fields to `field1`, `field2`, ... in order of first appearance. +- `BubbleSortFieldAssignments` — sorts the field assignment block into a stable canonical order. +- `StabilizeIndependentStatements` — reorders independent statements canonically by stable string comparison. + +**[`assumptions.py`](https://github.com/ProofFrog/ProofFrog/blob/main/proof_frog/transforms/assumptions.py)** + +- `ApplyAssumptions` — applies user-supplied equivalence pairs between variables. Not in the core pipeline; invoked by the engine when an assumption annotation appears between two games. diff --git a/researchers/external-uses.md b/researchers/external-uses.md new file mode 100644 index 0000000..0c26e3b --- /dev/null +++ b/researchers/external-uses.md @@ -0,0 +1,20 @@ +--- +title: External Uses +layout: default +parent: For Researchers +nav_order: 5 +--- + +# External Uses + +This page lists external projects that use ProofFrog. See the [Publications & More]({% link researchers/publications/index.md %}) for outputs from the ProofFrog team. + +If you are using ProofFrog in your own project and would like to be listed here, please file an issue at the [ProofFrog GitHub repository](https://github.com/ProofFrog/ProofFrog/issues). + +--- + +### [StarFortress](https://github.com/dconnolly/starfortress) + +A hybrid post-quantum key encapsulation mechanism construction combining a static-key Diffie–Hellman component with an IND-CCA-secure KEM. The project includes ProofFrog formalisations of the construction and several supporting reductions. + +**ProofFrog files to look at:** the proofs and supporting scheme/game/primitive files live under [`proof/prooffrog/`](https://github.com/dconnolly/starfortress/tree/main/proof/prooffrog) in the repository. diff --git a/researchers/gen-ai.md b/researchers/gen-ai.md new file mode 100644 index 0000000..2039a9b --- /dev/null +++ b/researchers/gen-ai.md @@ -0,0 +1,128 @@ +--- +title: "Gen AI & Proving" +layout: default +parent: For Researchers +nav_order: 4 +--- + +# Gen AI & Proving +{: .no_toc } + +It is possible to have an LLM-based coding agent, such as Claude Code, interact with ProofFrog to draft, debug, and iterate on ProofFrog proofs. + +If you are new to game-hopping proofs, please first focus on the [tutorial]({% link manual/tutorial/index.md %}) and [worked examples]({% link manual/worked-examples/index.md %}) and then try writing a proof on your own. Come back to this page once you have a baseline intuition for what a valid reduction looks like. + +- TOC +{:toc} + +--- + +## Setup + +The key tool for a coding agent to interact with a tool like ProofFrog is an MCP (Model Context Protocol) server. + +### Installing the MCP server + +The MCP server ships as part of ProofFrog. Install ProofFrog with the MCP extra: + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install proof_frog[mcp] +``` + +(The rest of this document assumes that you ran the above commands in the directory `/path/to/working/directory`) + +To give the server a useful set of examples to draw on, clone the examples repository into the same working directory before continuing: + +``` +git clone https://github.com/ProofFrog/examples +``` + +Download the following guidance file from the ProofFrog repository into your working directory so that your coding agent can read it at the start of a session to get some hints on how to use the MCP server: + +``` +curl -O https://raw.githubusercontent.com/ProofFrog/ProofFrog/refs/heads/main/CLAUDE_MCP.md +``` + +### Configuring Claude Code + +Next you need to register the ProofFrog MCP server with your coding agent. For Claude Code: + +```bash +claude mcp add prooffrog /path/to/working/directory/.venv/bin/python -- -m proof_frog mcp /path/to/working/directory/examples +``` + +Alternatively, add the server directly to `.claude/settings.json` in your working directory: + +```json +{ + "mcpServers": { + "prooffrog": { + "command": "python", + "args": ["-m", "proof_frog", "mcp", "/path/to/working/directory/examples/"], + "cwd": "/path/to/working/directory" + } + } +} +``` + +After registering, type `/mcp` inside Claude Code to confirm the server appears as connected. If it does not appear, reload the window (in VS Code: `CMD-Shift-P`, then `Developer: Reload Window`). + +### What the MCP server exposes + +The MCP server exposes the main ProofFrog commands (`parse`, `check`, `prove`) to the coding agent, along with several extra commands that allow it to inspect the game hop canonicalization in greater detail to diagnose bugs. The full list of commands is available in [`CLAUDE_MCP.md` in the ProofFrog repository](https://github.com/ProofFrog/ProofFrog/blob/main/CLAUDE_MCP.md). Some of the extra commands available include: + +- **`get_step_detail`** -- returns the canonical (fully simplified) form of one proof step by index. This is the primary diagnostic tool for a failing hop: compare the canonical forms of two adjacent steps to see exactly what differs. Read the `canonical` field, not `output` (which contains mangled internal names). +- **`get_inlined_game`** -- returns the canonical form of an arbitrary game step expression without requiring the step to appear in the proof's `games:` list, and robust to stub reductions that would otherwise block verification. Use this when writing intermediate games: it shows exactly what a game looks like after inlining against the proof's `let:`/`assume:` context, so you can write a matching `Game` definition. +- **`get_canonicalization_trace`** -- returns a trace of which transforms fired at each fixed-point iteration. Use this to understand how the engine simplifies a specific step. +- **`get_step_after_transform`** -- returns the game AST after applying transforms up to a named transform. Useful for inspecting intermediate states in the canonicalization pipeline. + +--- + +## What works well + +Take a look at the [HACS 2026 demo]({% link researchers/publications/hacs-2026/vibe/index.md %}) to see an example: from an English language prompt outlining a basic scheme (symmetric encryption build from a PRG), Claude Code was able to produce a working scheme and proof in roughly five minutes of wall time. That is a useful data point, but the task was chosen to be representative of examples already in the repository -- not a stress test. + +With that context, here are some things that can work okay: + +**Drafting primitives, schemes, and games from a natural-language specification.** A plain-English description of a scheme -- "encrypt by XOR with a keystream derived from a PRG applied to the key XOR'd with a fresh nonce" -- can produce a usable first draft in one request. The LLM can learn the FrogLang syntax well enough (from `CLAUDE_MCP.md` and the examples directory) to get the types, method signatures, and import paths approximately right. Its use of the MCP server to check its work lets it correct mistakes. Expect to correct minor issues (wrong field name, off-by-one slice boundary) but not to rewrite from scratch. + +**Writing reductions when the game structure is explicit.** Tell the model which assumption to reduce to and what the intermediate game looks like -- "this reduction should compose with `OTPUniform(lambda)` and its `Eavesdrop` oracle should call `challenger.CTXT(r)` to get either `k + r` or a uniform string" -- and it can usually produce a syntactically valid reduction. Sometimes it can figure out the hops on its own, but the more concrete the description of the game hop, the better the output. + +**Short iteration loops using `get_step_detail` and `get_inlined_game`.** The effective pattern is: ask the model to draft a game or reduction, call `prove` to check which steps fail, call `get_step_detail` (or `get_inlined_game` if the proof is not yet parseable) to retrieve the canonical form, feed that canonical form back into the conversation, and ask the model to adjust. Each iteration is fast, and the model is good at reading a canonical form and identifying what changed. + +**Symmetric proofs.** The HACS 2026 proof has a left half and a right half that mirror each other. Once the left half is working, you can ask the model to mirror it for the right side. + +--- + +## What does not + +**The LLM will produce reductions that almost work.** Same structure, wrong detail -- a `mL` where there should be a `mR`, a missing oracle parameter, a slice with the wrong bounds. The model cannot tell whether a reduction is correct without running the engine. Do not accept a reduction as complete until `prove` returns `"success": true` for that step. The model may claim success based on the shape of the code without having run the engine; verify independently. + +**Without iteration, the model drifts out of scope.** In a single long prompt with no intermediate engine feedback, the LLM may introduce primitives it was not asked for, invent security definitions that do not match the one specified, or write a proof structure that does not correspond to the game sequence described. Short requests with tight scope -- one game, one reduction at a time -- produce better results than large upfront requests. + +**Hallucinated helper games.** The LLM may confidently cite `Games/Helpers/BitStringSampling.game` or similar file paths for helper assumptions that do not exist in the current repository, or may invent its own assumptions. Before accepting a proof that imports an unusual game file, check that these exist and that they are suitable from a mathematical perspective. + +**The LLM will occasionally invoke the engine, see a failure, and announce success anyway.** This is the most dangerous failure mode. The model may misread the `hop_results` list from `prove`, report the wrong step as passing, or summarize a partial success as a full one. Always check the raw engine output the model is quoting. If you are running the session yourself, verify the final state by calling `prove` directly and reading `success` from the response, not from the model's summary of it. + +--- + +## Soundness considerations + +The observations in the [Soundness]({% link researchers/soundness.md %}) page apply to any proof that ProofFrog checks, regardless of whether the proof was developed manually or with generative AI tools. + +In some sense, LLM-generated proofs warrant *more* manual inspection, because the author (the LLM) cannot explain its own reasoning when asked. A human who wrote a proof can describe what changed in each hop and why it is valid; an LLM that generated a proof can narrate the hop but does not have the same reasoning as a human. + +When reviewing ProofFrog files generated by an LLM, pay extra attention to the scheme specification and the security properties. Is this the scheme you actually wanted to analyze? Is the security property actually the security property you care about? If the LLM generated additional security assumptions to bridge a step, are those assumptions reasonable? + +--- + +## Pointers + +- [HACS 2026 vibe-coding demo]({% link researchers/publications/hacs-2026/vibe/index.md %}) -- the original event handout this page is derived from, including full configuration instructions and the recorded transcript. + - [Prompt]({% link researchers/publications/hacs-2026/vibe/prompt.md %}) -- the exact prompt used in the HACS 2026 demo. The scheme is `FunkyPRGSymEnc`, a PRG-based symmetric encryption scheme with a nonce; the proof establishes `OneTimeSecrecy` using `PRG.Security` and `OTPUniform`. (Note that engine improvements since then have rendered the `OTPUniform` assumption unnecessary.) + - [Generated scheme]({% link researchers/publications/hacs-2026/vibe/scheme.md %}) -- the scheme file the LLM produced. + - [Generated proof]({% link researchers/publications/hacs-2026/vibe/proof.md %}) -- the proof file the LLM produced. Seven game hops; the proof is symmetric, with the left and right halves mirroring each other. + - [Session transcript]({% link researchers/publications/hacs-2026/vibe/transcript.md %}) -- the full Claude Code session from the HACS 2026 demo. +- [`CLAUDE_MCP.md`](https://github.com/ProofFrog/ProofFrog/blob/main/CLAUDE_MCP.md) in the ProofFrog repository -- the current best-practice guides for LLM clients. \ No newline at end of file diff --git a/researchers/index.md b/researchers/index.md new file mode 100644 index 0000000..0bc373a --- /dev/null +++ b/researchers/index.md @@ -0,0 +1,25 @@ +--- +title: For Researchers +layout: default +nav_order: 4 +has_children: true +has_toc: false +permalink: /researchers/ +--- + +# For Researchers + +This area of the site is for people who want to *understand* ProofFrog's design, primarily from a scientific perspective. + +If you are learning provable security or are new to ProofFrog, we recommend starting with the [manual]({% link manual/index.md %}). + +- [Scientific Background]({% link researchers/scientific-background.md %}) — ProofFrog's design choices and positioning relative to other tools. +- [Engine Internals]({% link researchers/engine-internals.md %}) — high-level architecture, core modules, the transformation pipeline by category, diagnostics and near-miss matching, the engine-introspection CLI commands, and the LSP and MCP servers. +- [Soundness]({% link researchers/soundness.md %}) — what ProofFrog claims, what is in the trust base, what is *not* claimed, mitigations a careful user can apply, and the comparison framing relative to EasyCrypt and CryptoVerif. +- [GenAI & Proving]({% link researchers/gen-ai.md %}) — using LLM-based coding assistants to draft and iterate on proofs (a research-and-experimentation tool, not a recommended student workflow). +- [External Uses]({% link researchers/external-uses.md %}) — curated case studies of external projects using ProofFrog. +- [Publications]({% link researchers/publications/index.md %}) — papers, theses, talks, and how to cite ProofFrog. + +## Lineage + +ProofFrog was built by Ross Evans, Matthew McKague, and Douglas Stebila at the University of Waterloo, building on the earlier `pygamehop` tool by Douglas Stebila and Matthew McKague at Queensland University of Technology. Full bibliographic details are on the [Publications]({% link researchers/publications/index.md %}) page. Development is supported by the Natural Sciences and Engineering Research Council of Canada (NSERC) Discovery grant RGPIN-2022-03187. diff --git a/caps-2025.md b/researchers/publications/caps-2025.md similarity index 65% rename from caps-2025.md rename to researchers/publications/caps-2025.md index b6bf42f..14bcb06 100644 --- a/caps-2025.md +++ b/researchers/publications/caps-2025.md @@ -1,15 +1,20 @@ --- title: CAPS 2025 Tutorial layout: default -nav_order: 7 +parent: Publications & More +grand_parent: For Researchers +nav_order: 2 +redirect_from: + - /caps-2025 + - /caps-2025.html --- # CAPS 2025 Tutorial ProofFrog was one of the examples presented at the [Workshop on Computer-Aided Proofs of Security (CAPS)](https://caps-workshop.com/) at Eurocrypt 2025. -The [presentation](assets/caps-2025-presentation-prooffrog.pdf) walked through the pen-and-paper proof that the KEM-DEM construction is IND-CPA-secure, then showed the analogous proof in ProofFrog. +The [presentation]({{ '/assets/caps-2025-presentation-prooffrog.pdf' | relative_url }}) walked through the pen-and-paper proof that the KEM-DEM construction is IND-CPA-secure, then showed the analogous proof in ProofFrog. -A tutorial file walking through the pen-and-paper and ProofFrog proofs side-by-side, as well as executable ProofFrog files, can be found in the [proof-ladders asymmetric ladder repository on Github](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/ProofFrog). +A tutorial file walking through the pen-and-paper and ProofFrog proofs side-by-side, as well as executable ProofFrog files, can be found in the [proof-ladders asymmetric ladder repository on Github](https://github.com/proof-ladders/asymmetric-ladder/tree/main/kemdem/ProofFrog). The Joy of Cryptography-style proof for KEM-DEM IND-CPA security by Mike Rosulek (with awesome animations) can be found at [https://garbledcircus.com/kemdem/left-right](https://garbledcircus.com/kemdem/left-right). diff --git a/hacs-2024.md b/researchers/publications/hacs-2024.md similarity index 63% rename from hacs-2024.md rename to researchers/publications/hacs-2024.md index d0bd649..eaaac25 100644 --- a/hacs-2024.md +++ b/researchers/publications/hacs-2024.md @@ -1,15 +1,23 @@ --- title: HACS 2024 Exercise layout: default -nav_order: 6 +parent: Publications & More +grand_parent: For Researchers +nav_order: 1 +redirect_from: + - /hacs-2024 + - /hacs-2024.html --- # HACS 2024 Exercises +{: .warning } +This exercise was prepared for a very early early version of ProofFrog, and may no longer be compatible with the current version of ProofFrog. + In [Lúcás C. Meier's blog post](https://cronokirby.com/posts/2022/05/state-separable-proofs-for-the-curious-cryptographer/) on state separable proofs, he presents a section on hybrid arguments for state separable proofs. One can encode this proof in EasyCrypt. First, he states that "an adversary for IND-CPA-1 can obviously break IND-CPA, since the latter allows them to make the one query they need." As a first exercise, demonstrate the contrapositive in ProofFrog. If a symmetric encryption scheme is CPA secure for multiple challenge queries, then it is also CPA secure for one challenge query. Second, he demonstrates a hybrid argument using a reduction **R**. This is to show that "If we make Q queries in the IND-CPA game, then our advantage is only larger by a factor of Q compared to the IND-CPA-1 game". As a second exercise, demonstrate the contrapositive in ProofFrog. If a symmetric encryption scheme is CPA secure for a single challenge query, then it is also CPA secure for multiple challenge queries. -[Solutions are found here](assets/hacs.zip). I have written a SymEnc primitive, two security definitions that align with the blog post, and two proofs for each of the exercises above. You can consider using the primitives and security definitions I have already provided if you just wish to try writing a proof file. +[Solutions are found here]({{ '/assets/hacs.zip' | relative_url }}). I have written a SymEnc primitive, two security definitions that align with the blog post, and two proofs for each of the exercises above. You can consider using the primitives and security definitions I have already provided if you just wish to try writing a proof file. diff --git a/hacs-2026/index.md b/researchers/publications/hacs-2026/index.md similarity index 55% rename from hacs-2026/index.md rename to researchers/publications/hacs-2026/index.md index 3760b73..7219fbc 100644 --- a/hacs-2026/index.md +++ b/researchers/publications/hacs-2026/index.md @@ -1,9 +1,15 @@ --- title: HACS 2026 Updates layout: default -nav_order: 8 +parent: Publications & More +grand_parent: For Researchers has_children: true +nav_order: 3 has_toc: false +redirect_from: + - /hacs-2026 + - /hacs-2026/ + - /hacs-2026/index.html --- # HACS 2026 Updates @@ -11,4 +17,3 @@ has_toc: false - [Vibe coding a proof](vibe/index.html) - [Web interface](web.html) - diff --git a/hacs-2026/vibe/index.md b/researchers/publications/hacs-2026/vibe/index.md similarity index 87% rename from hacs-2026/vibe/index.md rename to researchers/publications/hacs-2026/vibe/index.md index 929f122..c548e85 100644 --- a/hacs-2026/vibe/index.md +++ b/researchers/publications/hacs-2026/vibe/index.md @@ -1,16 +1,24 @@ --- title: Vibe-coding a proof -parent: HACS 2026 Updates layout: default -nav_order: 1 +parent: HACS 2026 Updates +grand_parent: Publications & More has_children: true has_toc: false +nav_order: 1 +redirect_from: + - /hacs-2026/vibe + - /hacs-2026/vibe/ + - /hacs-2026/vibe/index.html --- # Vibe-coding a proof Claude Code (Opus 4.6) was able to construct a valid ProofFrog scheme file and proof from a natural language input describing the scheme and the idea of each game hop. The runtime was about 5 minutes. +{: .note } +This example relied on [ProofFrog version 0.3.1](https://github.com/ProofFrog/ProofFrog/releases/tag/v0.3.1) with [examples repository commit 668a9b9](https://github.com/ProofFrog/examples/tree/668a9b97b1475aba1dbefe998b8b2fa252d3bd88). + **Input:** - [Prompt](prompt.html) diff --git a/hacs-2026/vibe/prompt.md b/researchers/publications/hacs-2026/vibe/prompt.md similarity index 83% rename from hacs-2026/vibe/prompt.md rename to researchers/publications/hacs-2026/vibe/prompt.md index 97d107e..98221b2 100644 --- a/hacs-2026/vibe/prompt.md +++ b/researchers/publications/hacs-2026/vibe/prompt.md @@ -1,13 +1,18 @@ --- title: "Input: Prompt" -parent: Vibe-coding a proof -grand_parent: HACS 2026 Updates layout: default +parent: Vibe-coding a proof nav_order: 1 +redirect_from: + - /hacs-2026/vibe/prompt + - /hacs-2026/vibe/prompt.html --- # Input: Prompt +{: .note } +This example relied on [ProofFrog version 0.3.1](https://github.com/ProofFrog/ProofFrog/releases/tag/v0.3.1) with [examples repository commit 668a9b9](https://github.com/ProofFrog/examples/tree/668a9b97b1475aba1dbefe998b8b2fa252d3bd88). + Here is the prompt that was input to Claude Code (Opus 4.6): ```txt diff --git a/hacs-2026/vibe/proof.md b/researchers/publications/hacs-2026/vibe/proof.md similarity index 95% rename from hacs-2026/vibe/proof.md rename to researchers/publications/hacs-2026/vibe/proof.md index 535d825..182aa54 100644 --- a/hacs-2026/vibe/proof.md +++ b/researchers/publications/hacs-2026/vibe/proof.md @@ -1,13 +1,18 @@ --- title: "Output: Proof" -parent: Vibe-coding a proof -grand_parent: HACS 2026 Updates layout: default +parent: Vibe-coding a proof nav_order: 3 +redirect_from: + - /hacs-2026/vibe/proof + - /hacs-2026/vibe/proof.html --- # Output proof: FunkyPRGSymEncOTS.proof +{: .note } +This example relied on [ProofFrog version 0.3.1](https://github.com/ProofFrog/ProofFrog/releases/tag/v0.3.1) with [examples repository commit 668a9b9](https://github.com/ProofFrog/examples/tree/668a9b97b1475aba1dbefe998b8b2fa252d3bd88). + Here is the proof file that was generated by Claude Code (Opus 4.6): ```prooffrog diff --git a/hacs-2026/vibe/scheme.md b/researchers/publications/hacs-2026/vibe/scheme.md similarity index 75% rename from hacs-2026/vibe/scheme.md rename to researchers/publications/hacs-2026/vibe/scheme.md index 7771afd..ed8db35 100644 --- a/hacs-2026/vibe/scheme.md +++ b/researchers/publications/hacs-2026/vibe/scheme.md @@ -1,13 +1,18 @@ --- title: "Output: Scheme" -parent: Vibe-coding a proof -grand_parent: HACS 2026 Updates layout: default +parent: Vibe-coding a proof nav_order: 2 +redirect_from: + - /hacs-2026/vibe/scheme + - /hacs-2026/vibe/scheme.html --- # Output scheme: FunkyPRGSymEnc.scheme +{: .note } +This example relied on [ProofFrog version 0.3.1](https://github.com/ProofFrog/ProofFrog/releases/tag/v0.3.1) with [examples repository commit 668a9b9](https://github.com/ProofFrog/examples/tree/668a9b97b1475aba1dbefe998b8b2fa252d3bd88). + Here is the scheme file that was generated by Claude Code (Opus 4.6): ```prooffrog diff --git a/hacs-2026/vibe/transcript.md b/researchers/publications/hacs-2026/vibe/transcript.md similarity index 71% rename from hacs-2026/vibe/transcript.md rename to researchers/publications/hacs-2026/vibe/transcript.md index 5b2a2ee..009a0ad 100644 --- a/hacs-2026/vibe/transcript.md +++ b/researchers/publications/hacs-2026/vibe/transcript.md @@ -1,9 +1,11 @@ --- title: Claude Code Transcript -parent: Vibe-coding a proof -grand_parent: HACS 2026 Updates layout: default +parent: Vibe-coding a proof nav_order: 4 +redirect_from: + - /hacs-2026/vibe/transcript + - /hacs-2026/vibe/transcript.html --- diff --git a/hacs-2026/vibe/transcript/index.html b/researchers/publications/hacs-2026/vibe/transcript/index.html similarity index 100% rename from hacs-2026/vibe/transcript/index.html rename to researchers/publications/hacs-2026/vibe/transcript/index.html diff --git a/hacs-2026/vibe/transcript/page-001.html b/researchers/publications/hacs-2026/vibe/transcript/page-001.html similarity index 100% rename from hacs-2026/vibe/transcript/page-001.html rename to researchers/publications/hacs-2026/vibe/transcript/page-001.html diff --git a/hacs-2026/web.md b/researchers/publications/hacs-2026/web.md similarity index 79% rename from hacs-2026/web.md rename to researchers/publications/hacs-2026/web.md index 3e0338d..e8a32f7 100644 --- a/hacs-2026/web.md +++ b/researchers/publications/hacs-2026/web.md @@ -1,8 +1,12 @@ --- title: Web interface -parent: HACS 2026 Updates layout: default +parent: HACS 2026 Updates +grand_parent: Publications & More nav_order: 2 +redirect_from: + - /hacs-2026/web + - /hacs-2026/web.html --- # Web interface @@ -21,4 +25,4 @@ This starts a local web server (default port 5173) and opens the editor in your The web interface is a work in progress, and there are many features we would like to add. -![ProofFrog web interface](../../assets/screenshot-web.png) +![ProofFrog web interface]({{ '/assets/screenshot-web.png' | relative_url }}) diff --git a/researchers/publications/index.md b/researchers/publications/index.md new file mode 100644 index 0000000..bc0f5a8 --- /dev/null +++ b/researchers/publications/index.md @@ -0,0 +1,56 @@ +--- +title: Publications & More +layout: default +parent: For Researchers +has_children: true +nav_order: 6 +--- + +# Publications & More + +This page lists papers, theses, and event material from the ProofFrog team. For curated case studies of external projects using ProofFrog, see [External Uses]({% link researchers/external-uses.md %}). + +## Papers and theses + +- **Ross Evans.** *ProofFrog: A Tool for Verifying Game-Hopping Cryptographic Proofs.* Master's thesis, University of Waterloo, 2024. [UWSpace record](https://hdl.handle.net/10012/20441) ([PDF](https://uwspace.uwaterloo.ca/bitstream/handle/10012/20441/Evans_Ross.pdf)). + +- **Ross Evans, Matthew McKague, Douglas Stebila.** *ProofFrog: A Tool For Verifying Transitions in Game-Hopping Proofs.* Cryptology ePrint Archive, Paper 2025/418, 2025. [eprint.iacr.org/2025/418](https://eprint.iacr.org/2025/418). + +## Talks and workshops demos + +- [**HACS 2024**]({% link researchers/publications/hacs-2024.md %}) — Exercises prepared for the *High Assurance Cryptographic Software 2024* workshop. +- [**CAPS 2025**]({% link researchers/publications/caps-2025.md %}) — Presentation and related tutorial (on KEM-DEM IND-CPA security) at the *Computer-Aided Proofs of Security* workshop at Eurocrypt 2025. +- [**HACS 2026**]({% link researchers/publications/hacs-2026/index.md %}) — Demos prepared for the *High Assurance Cryptographic Software 2026* workshop. + +## How to cite ProofFrog + +Until a peer-reviewed venue version appears, the recommended form is to cite the eprint: + +{% raw %} +```bibtex +@misc{cryptoeprint:2025/418, + author = {Ross Evans and Matthew McKague and Douglas Stebila}, + title = {{ProofFrog}: A Tool For Verifying Transitions in Game-Hopping Proofs}, + howpublished = {Cryptology {ePrint} Archive, Paper 2025/418}, + year = {2025}, + url = {https://eprint.iacr.org/2025/418} +} +``` +{% endraw %} + +The Evans thesis can be cited as: + +{% raw %} +```bibtex +@mastersthesis{evans2024prooffrog, + author = {Ross Evans}, + title = {{ProofFrog}: A Tool for Verifying Game-Hopping Cryptographic Proofs}, + school = {University of Waterloo}, + year = {2024}, + type = {Master's thesis}, + url = {https://hdl.handle.net/10012/20441} +} +``` +{% endraw %} + +When citing the implementation directly, also link the GitHub repository: [`https://github.com/ProofFrog/ProofFrog`](https://github.com/ProofFrog/ProofFrog). diff --git a/researchers/scientific-background.md b/researchers/scientific-background.md new file mode 100644 index 0000000..1273345 --- /dev/null +++ b/researchers/scientific-background.md @@ -0,0 +1,81 @@ +--- +title: Scientific Background +layout: default +parent: For Researchers +nav_order: 1 +redirect_from: + - /design/ + - /design.html +--- + +# Scientific Background +{: .no_toc } + +This page explains where ProofFrog fits within the context of game-hopping proofs, reductionist security, and computer-aided cryptography; ProofFrog's design approach; and its relationship to other tools. + +ProofFrog operates in the computational model. It is positioned as a deliberate trade-off against tools like [EasyCrypt](https://www.easycrypt.info/) and [CryptoVerif](https://bblanche.gitlabpages.inria.fr/CryptoVerif/): narrower scope in exchange for a shallower learning curve, targeting cryptographers entering computer-aided proofs for the first time and educational contexts where the overhead of a full-scale proof assistant would impede rather than support learning. + +- TOC +{:toc} + +--- + +## The three tasks of a game-hopping proof + +Verifying a game-hopping proof involves three distinct tasks: + +1. State and justify a sequence of games, with each hop justified as either an *interchangeability-based hop* (distinguishing advantage zero, demonstrated by code equivalence) or a *reduction-based hop* (bounded distinguishing advantage, justified by an explicit reduction to an assumed security property). +2. Accumulate the bounded advantages from each reduction-based hop into a single security bound and track the resource usage of each reduction. +3. Assess whether that bound is acceptable given the intended cost model, parameter choices, and any idealized models (random oracle, ideal cipher, etc.). + +**ProofFrog addresses Task 1 only.** Tasks 2 and 3 remain the responsibility of the proof author and reader. ProofFrog checks the structural validity of a game sequence, not the tightness or appropriateness of the resulting security bound. + +--- + +## Design choices + +ProofFrog's design reflects a set of deliberate choices about where to trade expressivity for simplicity. + +### Syntactic rather than logical + +Existing tools such as EasyCrypt work at the level of logical formulae over probabilistic relational Hoare logic. ProofFrog instead treats games as abstract syntax trees (ASTs) and verifies game equivalence using techniques drawn from compiler design and static analysis. The engine takes pairs of game ASTs, applies a fixed-point pipeline of transformations, and compares the resulting canonical forms. + +This is a deliberate trade-off. Probabilistic relational Hoare logic is strictly more expressive: it can reason about a wider class of programs and a wider class of equivalences. The AST-level approach gives up some of that expressivity in exchange for some automation and a representation that (hopefully) more closely matches how a cryptographer thinks about game code on paper. + +### User-supplied reductions, not reduction search + +ProofFrog verifies user-written reductions; it does not search for them. The proof author specifies the game sequence, writes the reductions that justify reduction-based hops, and ProofFrog checks that the required interchangeability conditions hold. Reduction search is not attempted (though you can [ask an LLM-based coding agent to try]({% link researchers/gen-ai.md %})). + +### Canonicalization, not explicit rewriting + +Within each interchangeability-based hop, the user does not direct step-by-step rewriting. The engine instead runs a canonicalization process and compares the resulting canonical forms. If the canonical forms are structurally identical (up to variable renaming), the games are considered interchangeable. For cases where canonical forms differ only in branch conditions, the engine invokes an SMT solver (Z3) to check logical equivalence of those conditions. + +This matches the intuition behind a typical pen-and-paper "obviously the same" argument: the proof author does not spell out every algebraic step; they rely on the reader to see that two pieces of code are equivalent after routine simplification. ProofFrog tries to automate that routine simplification. More details are available on the [Canonicalization]({% link manual/canonicalization.md %}) and [Engine Internals]({% link researchers/engine-internals.md %}) pages. + +### C/Java-like imperative syntax + +The FrogLang domain-specific language is designed to look like pen-and-paper cryptographic pseudocode, using the conventions of Rosulek's [*The Joy of Cryptography*](https://joyofcryptography.com/). Games are written with explicit `Initialize` methods, oracle bodies, and field declarations. A proof from that textbook can typically be transcribed into ProofFrog syntax with minimal structural changes. + +The proof file lists the variable and scheme declarations, the security assumptions, the statement to be proved, and the game sequence, including any reductions needed. The proof syntax also supports bounded induction for hybrid arguments: ProofFrog verifies the validity of each hop within the induction and checks both boundary conditions. + +--- + +## Comparison with other tools + +**EasyCrypt** uses an imperative language for specifying games together with a formula language for expressing probabilistic relational Hoare logic judgments. Proofs are written using a tactic language that navigates the program and applies logical rules to the formula being proved. EasyCrypt is very expressive and can handle a wide class of general statements; the cost is complexity. Tactic-level reasoning operates at a fine granularity, and proofs can be difficult to read without stepping through them interactively, because the formula each tactic is being applied to is not always apparent from the static proof text. + +**CryptoVerif** takes the opposite approach: rather than maximizing expressivity, it prioritizes automation. Games are specified in a process calculus syntax. Proofs can often be discovered automatically, but CryptoVerif also supports an interactive mode where a user can explicitly direct which game transformations to apply when the automatic search fails. Security properties are expressed as internal logical formulae, verified by an internal equational prover rather than by the user directly. + +Beyond these two, the computer-aided cryptography space includes a number of other tools. [EasyUC](https://github.com/easyuc/EasyUC) formalizes universal composability within EasyCrypt. [FCF](https://github.com/adampetcher/fcf) (Foundational Cryptography Framework) and [SSProve](https://github.com/SSProve/ssprove) both use the [Rocq](https://rocq-prover.org/) proof assistant: FCF encodes a probabilistic programming language directly, while SSProve formalizes state-separating proofs for more modular game-based arguments. [Squirrel](https://squirrel-prover.github.io/) develops a higher-order logic based on the computationally complete symbolic attacker framework. [CryptHOL](https://www.isa-afp.org/entries/CryptHOL.html) formalizes cryptographic arguments within [Isabelle/HOL](https://isabelle.in.tum.de/). [Owl](https://github.com/secure-foundations/owl) uses refinement types and an information-flow control type system for integrity properties. [F\*](https://www.fstar-lang.org/) has been used to formalize protocols such as TLS, functioning both as a proof assistant and a general-purpose programming language. + +ProofFrog's scope is narrower than any of these: it addresses only Task 1 of the game-hopping proof process and relies on AST canonicalization rather than a formally verified program logic. Its intended advantage is a shallower learning curve and a syntax close enough to pen-and-paper conventions that the gap between a textbook proof and a machine-checked one is small. ProofFrog is targeted at educational use and at cryptographers who want to experiment with machine-checked arguments before committing to the overhead of a full-scale proof assistant. For research-grade proofs with complex reductions or non-standard security definitions, EasyCrypt or CryptoVerif will be more appropriate. + +ProofFrog's soundness (or lack thereof) is discussed further on the [Soundness]({% link researchers/soundness.md %}) page. The [Limitations]({% link manual/limitations.md %}) page discusses limits on ProofFrog's functionality, completeness, and expressivity. + +--- + +## The bottom line + +**ProofFrog is** a tool for verifying the structural validity of a game-hopping sequence in the computational model. It checks that the sequence of games in a proof correctly starts and ends at the appropriate games, and verifies each hop as either an interchangeability-based or reduction-based hop. Interchangeability is checked by AST canonicalization: the engine applies a fixed-point pipeline of compiler-style transformations and compares canonical forms. + +**ProofFrog is not** a replacement for EasyCrypt or CryptoVerif. It does not accumulate probability bounds or track resource usage (Tasks 2 and 3 remain manual). It does not reason about computational complexity, concurrency, or side channels. Its engine does not have a formal soundness proof: the correctness of ProofFrog's transformations has not been verified (either manually or mechanically) against a formalized semantics. The set of transformations and heuristics it can apply is limited, as is its expressivity for mathematical and probabilistic reasoning. diff --git a/researchers/soundness.md b/researchers/soundness.md new file mode 100644 index 0000000..bb8bc7e --- /dev/null +++ b/researchers/soundness.md @@ -0,0 +1,219 @@ +--- +title: Soundness +layout: default +parent: For Researchers +nav_order: 3 +--- + +# Soundness +{: .no_toc } + +ProofFrog has no formal soundness proof. Each transformation in the canonicalization +pipeline is written by hand (and possibly with the assistance of coding agents) and +intended to be semantics-preserving, but correctness and soundness have not been +formally verified. + +When ProofFrog validates a game hop, that is evidence the hop is correct -- not a +machine-checked certificate. Serious use requires additional external checking: manual +review of the proof structure, cross-comparison with pen-and-paper arguments, and a +preference for small hops that can be individually inspected. A user treating ProofFrog +as a rubber stamp is misusing the tool. The validation means the engine did not find a +counterexample using its current pipeline; it does not mean no counterexample exists. + +It is a future goal to link ProofFrog with existing formal verification tools, such as +EasyCrypt, to improve assurance. + +- TOC +{:toc} + +--- + +## ProofFrog's checking methodology + +ProofFrog's engine attempts to verify three kinds of proof steps. + +**Interchangeability hops.** Two games are interchangeable if and only if for all +adversaries, the probability distribution over adversary outputs is identical. Formally: + +``` +Pr[A interacting with Game1 outputs 1] = Pr[A interacting with Game2 outputs 1] +``` + +Adversary interaction with games is described in more detail on the [Execution Model]({% link +manual/language-reference/execution-model.md %}) page. ProofFrog attempts to verify +interchangeability by canonicalizing both games and comparing their canonical forms. The +canonicalization pipeline is a deterministic rewrite sequence: inlining, +algebraic simplification, dead code elimination, sampling normalization, and others. If +the canonical forms are structurally identical (up to variable renaming), the engine +reports the hop valid. When the canonical forms differ only in the conditions of `if` +statements, the engine calls Z3 to check logical equivalence of those conditions. + +**Assumption hops.** An assumption hop substitutes one side of a declared `assume:` +security property for the other. ProofFrog verifies that the game step pattern matches +the assumed property (`Security.Side1 compose R` to `Security.Side2 compose R`) and +trusts the assumption itself. The assumption is not verified -- it is what the proof +depends on. + +**Lemma hops.** ProofFrog recursively verifies the lemma's proof file (unless +`--skip-lemmas` is used), checks that the lemma's assumptions are a subset of the +current proof's `assume:` block, and adds the lemma's theorem to the available +assumptions. The soundness of a lemma hop reduces to the soundness of the lemma's own +proof. + +--- + +## The trust base + +Every component listed below can harbor a bug that causes the engine to validate an +invalid hop. + +**The parser** (`proof_frog/frog_parser.py` and the ANTLR grammars under +`proof_frog/parsing/`). A parser bug could miscategorize a construct and feed the wrong +AST into the engine. The grammars are not formally specified beyond the ANTLR grammar +files; their correctness relative to the intended language semantics is not proved. + +**The type checker and semantic analysis** (`proof_frog/semantic_analysis.py`). A +semantic-analysis bug could allow a malformed program through, or could annotate a +well-formed program with incorrect type information that downstream transforms rely on. + +**The transformation pipeline** (all of `proof_frog/transforms/`). Each transform is a +Python function, written by hand or with the help of coding agents, that is intended to +be semantics-preserving. This is the largest +component of the trust base and the most likely source of soundness failures. A transform +that is almost correct -- one that is semantics-preserving in 999 out of 1000 inputs and +wrong on the 1000th -- could validate an incorrect hop without any diagnostic signal. The +current practice is to introduce both positive and negative unit tests when adding or +modifying transforms, and to add near-miss instrumentation at precondition-failure points. +This reduces the risk of wrong transforms going undetected, but does not eliminate it. + +**SMT integration** (Z3 calls, primarily in the symbolic transforms). Z3 is used to +check logical equivalence of conditions when canonical forms are structurally close but +not identical. Z3's own correctness is outside ProofFrog's control. A Z3 soundness bug +could cause ProofFrog to accept logically non-equivalent conditions as equivalent. + +**SymPy** (for symbolic arithmetic simplification). SymPy is used to simplify symbolic +expressions involving modular arithmetic and group operations. Like Z3, its correctness +is an external dependency. + +**The Python runtime.** Interpreter bugs, floating-point behavior, dictionary ordering +semantics, and similar runtime properties are all implicitly trusted. + +--- + +## What is NOT claimed + +The following are things that a user seeking machine-checked proofs of cryptographic +arguments might hope for, but ProofFrog does not claim to provide. + +**Soundness of individual transforms.** No individual transform in +`proof_frog/transforms/` is proved correct in isolation or in composition. The transforms +are tested, not verified. + +**Completeness of the engine.** Some interchangeable games will fail to canonicalize to +the same form. The engine is incomplete: it cannot find all valid interchangeability +relationships. Such failures are capability limitations, not soundness issues -- the engine +does not accept invalid hops just because it cannot verify valid ones. See the +[Limitations]({% link manual/limitations.md %}) page for a catalogue of known gaps. + +**Tight security bounds.** ProofFrog reports whether a hop is valid, not how much +advantage an adversary could gain. Concrete security bounds -- loss factors, collision +probabilities, hybrid counts -- are the proof author's responsibility and are stated +outside what ProofFrog verifies. + +**Side-channel resistance, timing attacks, fault attacks.** None of these are modeled. +All games are defined solely by the sequence of return values their oracles produce. + +**Abort semantics.** FrogLang has no abort primitive. Proofs that rely on game aborts +with explicit probability accounting must model those probabilities as indistinguishable +events within the reduction, not as built-in abort steps. + +**Concurrency.** All oracle calls are sequential. Concurrent adversaries, parallel oracle +queries, and reactive systems are outside the model. + +**Correctness of the language semantics.** FrogLang does not have a formal semantics +document. The intended semantics is described in the manual and in the published paper, +but the correspondence between that prose description and the engine's actual behavior is +not formally established. + +--- + +## Writing and interpreting ProofFrog proofs in light of lack of formal soundness guarantees + +Because ProofFrog lacks a soundness proof and has a large trust base, validation using +ProofFrog is evidence but not a guarantee, and responsibility for +believing a proof sits with the people writing and reading it. The practices below don't +eliminate that responsibility, but they give a proof author and a proof reviewer concrete +ways to form a judgement. + +**When writing a proof, keep hops small.** The smaller a hop, the fewer transforms fire +and the easier it is to manually inspect what changed. When a hop applies a large number +of transforms, it may be helpful to split it into a few smaller hops -- both for your +own assurance while constructing the proof and to give later readers a sequence of claims +they can each individually check. + +**When writing a proof, explicit intermediate games help understanding.** Writing +out each intermediate game explicitly, rather than relying on ProofFrog to accept a large +implicit hop, forces you to state precisely what you think the intermediate game is. +That statement then becomes a piece of the proof a reader -- or future you -- can check +independently, without having to trust that the engine's implicit reasoning matched your +own. + +**When reviewing a proof, inspect canonical forms directly.** When you are unsure +whether the engine is validating a hop for the reason you think it is, the `step-detail` +and `canonicalization-trace` CLI commands (or the hop inspector in the web editor) +expose the canonical form of a specific game +step and the sequence of intermediate rewrites the pipeline applied. `step-after-transform` +shows the game AST after all transforms up to a named pass. Reviewing the canonical form +before and after a hop gives you direct evidence of what the engine is claiming, which +you can then reconcile with what the proof author intended. (`proof_frog prove -v` adds +game-level output to a proof run; `-vv` adds per-transform tracing.) + +**Cross-check against pen-and-paper arguments.** If a textbook or a previously +published pen-and-paper proof agrees with what ProofFrog accepts, the two checks +reinforce each other: an automated check and a manual one, each covering errors the +other might miss. If they disagree, one of them is wrong and the divergence itself is +valuable information. When reviewing someone else's ProofFrog proof, try to reconstruct +the argument on paper for at least the hops you find most load-bearing. + +**Consider validating tricky ProofFrog hops in another tool.** If a ProofFrog proof +introduces a bespoke assumption to bridge a tricky hop, or leaves the hop unverified, +it may be possible to formalize that specific hop in another tool, such as EasyCrypt. + +--- + +## Soundness issues + +**Report suspicious validations.** If while writing or reviewing a proof you suspect +the engine has accepted an incorrect hop -- for example, if ProofFrog validates a hop +that you believe is mathematically invalid -- file an issue on the +[ProofFrog issue tracker](https://github.com/ProofFrog/ProofFrog/issues) and apply the +`soundness` label. +Include the proof file, the specific hop, and your analysis of why you think the hop is +wrong. A validated hop that is actually invalid is a soundness bug and should be treated +as high priority. + +Issues suspected of being soundness concerns are tagged with the +[`soundness`](https://github.com/ProofFrog/ProofFrog/issues?q=label%3Asoundness) label +on the issue tracker. These are distinct from +[capability limitations]({% link manual/limitations.md %}), where the engine +correctly rejects a valid hop because its current pipeline cannot show the hop is in +fact valid. + +--- + +## Comparison with other tools + +Other more established formal verification tools for cryptography like EasyCrypt and +CryptoVerif have stronger trust bases: their program logics and tactic +languages have pen-and-paper formalizations with meta-theoretic soundness arguments for +those logics. ProofFrog lacks such soundness arguments. For high-assurance cryptographic work -- standards, +deployed protocols, production code -- the more established tools remain the appropriate +choice. ProofFrog's may be suitable for more preliminary work: exploration, education, and +iterative proof development where the ease of writing and checking a game-hopping proof +is worth the weaker soundness guarantee. + +One concrete direction that could narrow this gap is an export functionality that encodes +ProofFrog's automated transformations into the syntax of a more established engine such +as EasyCrypt, so that individual hops could be discharged by a tool with a stronger +logical foundation. While this is not yet implemented, we hope to explore this direction +in the future.