+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).
+
+
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` + +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 +ModIntu <- ModInt; +return u + m; +// simplifies to: +ModIntu <- 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; +GroupElemu <- 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(Setbookkeeping) { + // <-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