From 222c0282c602a955c1a817363276930309b7ca97 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Sat, 25 Jul 2026 14:24:41 -0500 Subject: [PATCH 1/5] feat(ToMathlib): TM-grounded polynomial-time toolkit on current Mathlib/cslib Port the Turing-machine complexity layer from the closed reference draft #481, with the machine-counting cruxes proven (from #487): - Encoding: bundled PackedEncoding (Mathlib unbundled Encoding's alphabet on 2026-05-07; the poly-time layer needs Sigma-packaged alphabets before boolify normalizes them to Bool), FinEnum/option/sigma/pair/sum/BitVec combinators with length lemmas. - CslibPolyTime: EncPolyTime over cslib's PolyTimeComputable, retargeted to the restructured Cslib.Turing.SingleTapeTM namespace. - PolyTimeTM, BitEncoding: description-size accounting and binary encodings. - MachineCounting: state-relabeling normalization and the counting bounds, including the exists_tmTable_of_card_le and realizable-covering proofs contributed in #487, adjusted for Mathlib's new ReflTransGen.lift signature. Co-authored-by: Elias Judin Co-authored-by: Aristotle (Harmonic) Co-Authored-By: Claude Fable 5 --- ToMathlib.lean | 6 + ToMathlib/Computability/BitEncoding.lean | 445 ++++++++++++++++ ToMathlib/Computability/CslibPolyTime.lean | 199 +++++++ ToMathlib/Computability/Encoding.lean | 337 ++++++++++++ ToMathlib/Computability/MachineCounting.lean | 518 ++++++++++++++++++ ToMathlib/Computability/PolyTimeTM.lean | 526 +++++++++++++++++++ ToMathlib/Data/BitVec.lean | 74 +++ 7 files changed, 2105 insertions(+) create mode 100644 ToMathlib/Computability/BitEncoding.lean create mode 100644 ToMathlib/Computability/CslibPolyTime.lean create mode 100644 ToMathlib/Computability/Encoding.lean create mode 100644 ToMathlib/Computability/MachineCounting.lean create mode 100644 ToMathlib/Computability/PolyTimeTM.lean create mode 100644 ToMathlib/Data/BitVec.lean diff --git a/ToMathlib.lean b/ToMathlib.lean index 0b98c0b07..243bfd10c 100644 --- a/ToMathlib.lean +++ b/ToMathlib.lean @@ -1,5 +1,10 @@ import ToMathlib.Analysis.MeanInequalities import ToMathlib.Combinatorics.FinPairs +import ToMathlib.Computability.BitEncoding +import ToMathlib.Computability.CslibPolyTime +import ToMathlib.Computability.Encoding +import ToMathlib.Computability.MachineCounting +import ToMathlib.Computability.PolyTimeTM import ToMathlib.Control.AlternativeMonad import ToMathlib.Control.Lawful.MonadControl import ToMathlib.Control.Lawful.MonadFunctor @@ -19,6 +24,7 @@ import ToMathlib.Control.Monad.Transformer import ToMathlib.Control.OptionT import ToMathlib.Control.StateT import ToMathlib.Control.WriterT +import ToMathlib.Data.BitVec import ToMathlib.Data.ENNReal.AbsDiff import ToMathlib.Data.ENNReal.Gauss import ToMathlib.Data.ENNReal.SumSquares diff --git a/ToMathlib/Computability/BitEncoding.lean b/ToMathlib/Computability/BitEncoding.lean new file mode 100644 index 000000000..00688d5d1 --- /dev/null +++ b/ToMathlib/Computability/BitEncoding.lean @@ -0,0 +1,445 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import ToMathlib.Computability.PolyTimeTM +public import Mathlib.Data.Nat.Log + +/-! +# Canonical Fixed-Width Bit Encodings and Uniform Machine Families + +The canonical boundary representation for the polynomial-time adversary model, and the +reusable unit of machine-computability it consumes. + +## Why fixed canonical encodings + +"Computable in polynomial time relative to *some* encoding" is vacuous: an encoding +`enc x := std x ++ block (f x)` caches any function `f` inside the representation, and +every machine witness degenerates to a projection. Polynomial time is only well-defined +relative to a *fixed canonical* representation (syntactic frameworks fix one implicitly +through the programming language's value representation; a machine-grounded framework +must fix it explicitly). This file provides that representation: + +* `Computability.StrEncFam` — a security-parameter-indexed family of injective raw + `List Bool` encodings with a polynomial length bound. This is the *variable-width* + notion, the representation freedom left to a machine's internal state. +* `Computability.BitEncFam` — the *fixed-width* refinement: at each parameter every + value encodes to exactly `wid n` bits, with `wid` polynomially bounded. This is the + canonical *boundary* representation for inputs, outputs, and oracle interfaces. The + polynomial width bound is the formal content of the Katz–Lindell `1^n` convention: + all game values at parameter `n` have `poly(n)`-length representations, so + "polynomial in `n`" and "polynomial in the input length" agree. +* Constructors: `BitEncFam.const` (fixed-width binary index encoding of a finite type), + `BitEncFam.bitVec`/`bitVecX` (raw bits), `BitEncFam.pair` (append — widths are fixed, + so no tags or alphabets are needed), `BitEncFam.option` (tag bit plus padded payload), + and `StrEncFam.pairVar` (variable-width left ++ fixed-width right, injective because + the split point is determined from the right — the shape of a machine's + state/answer update input). +* `Computability.EncPolyTimeFam` — a family of `EncPolyTime` witnesses with uniform + polynomial time and description-size bounds: the reusable unit "this function family + is computed by polynomial machines relative to these encodings". Base machines + produce these; the closure combinators (`comp`, `id`, `const`, `ofFintype`) compose + them; a polynomial-time adversary carries four of them. + +Everything here is raw `α → List Bool`: no `PackedEncoding` alphabets and no one-hot +`boolify` relabeling on the canonical side (the legacy `Computability.PackedEncoding` +layer remains available for machine-internal state representations). +-/ + +@[expose] public section + +universe u + +open Cslib.Turing.SingleTapeTM + +namespace Computability + +/-! ## Fixed-width binary strings for natural numbers -/ + +/-- The `w` low bits of `m`, least significant first. -/ +def natToBits (w m : ℕ) : List Bool := (List.range w).map m.testBit + +@[simp] theorem length_natToBits (w m : ℕ) : (natToBits w m).length = w := by + simp [natToBits] + +/-- Distinct numbers below `2 ^ w` have distinct `w`-bit strings. -/ +theorem natToBits_inj {w m₁ m₂ : ℕ} (h₁ : m₁ < 2 ^ w) (h₂ : m₂ < 2 ^ w) + (h : natToBits w m₁ = natToBits w m₂) : m₁ = m₂ := by + refine Nat.eq_of_testBit_eq fun i => ?_ + rcases lt_or_ge i w with hi | hi + · have := List.map_inj_left.mp (by simpa [natToBits] using h) i (List.mem_range.mpr hi) + exact this + · rw [Nat.testBit_eq_false_of_lt (lt_of_lt_of_le h₁ (Nat.pow_le_pow_right (by omega) hi)), + Nat.testBit_eq_false_of_lt (lt_of_lt_of_le h₂ (Nat.pow_le_pow_right (by omega) hi))] + +/-! ## Variable-width bounded string encodings -/ + +/-- A security-parameter-indexed family of injective raw bit-string encodings with a +polynomial length bound: the representation freedom left to a machine's internal +state. Injectivity is the only semantic demand; the length bound is what keeps +resource accounting polynomial. -/ +structure StrEncFam (α : ℕ → Type u) : Type u where + /-- The raw bit-string encoding at each parameter. -/ + enc : (n : ℕ) → α n → List Bool + /-- The encoding is injective at each parameter. -/ + enc_injective : ∀ n, Function.Injective (enc n) + /-- Polynomial bound on encoded lengths (over *all* values, not only reachable ones). -/ + bound : Polynomial ℕ + /-- All encodings respect the length bound. -/ + len_le : ∀ n x, (enc n x).length ≤ bound.eval n + +/-! ## Fixed-width canonical boundary encodings -/ + +/-- A security-parameter-indexed family of **fixed-width** raw bit-string encodings: +the canonical boundary representation. At parameter `n` every value encodes to exactly +`wid n` bits, and `wid` is polynomially bounded — the formal content of the +Katz–Lindell `1^n` convention. Fixed widths make pairing literal append and let the +split point of any concatenation be recovered positionally, with no alphabets, tags, +or self-delimiting machinery. -/ +structure BitEncFam (α : ℕ → Type u) : Type u where + /-- The exact encoded width at each parameter. -/ + wid : ℕ → ℕ + /-- Polynomial bound on the widths — the `1^n` convention. -/ + widBound : Polynomial ℕ + /-- The widths respect the bound. -/ + wid_le : ∀ n, wid n ≤ widBound.eval n + /-- The raw bit-string encoding at each parameter. -/ + enc : (n : ℕ) → α n → List Bool + /-- Every encoding has exactly the fixed width. -/ + len_eq : ∀ n x, (enc n x).length = wid n + /-- The encoding is injective at each parameter. -/ + enc_injective : ∀ n, Function.Injective (enc n) + +namespace BitEncFam + +variable {α β : ℕ → Type u} + +/-- Forget the fixed width, keeping the polynomial length bound. -/ +def toStrEncFam (e : BitEncFam α) : StrEncFam α where + enc := e.enc + enc_injective := e.enc_injective + bound := e.widBound + len_le n x := (e.len_eq n x).le.trans (e.wid_le n) + +@[simp] theorem toStrEncFam_enc (e : BitEncFam α) : e.toStrEncFam.enc = e.enc := rfl + +@[simp] theorem toStrEncFam_bound (e : BitEncFam α) : e.toStrEncFam.bound = e.widBound := rfl + +/-- The canonical encoding of a constant finite type: the fixed-width binary encoding +of the enumeration index, width `⌈log₂ card γ⌉`. `Unit` gets width `0`, `Bool` width +`1`, `Fin k` width `⌈log₂ k⌉`. -/ +noncomputable def const (γ : Type u) [Fintype γ] : BitEncFam (fun _ => γ) where + wid _ := Nat.clog 2 (Fintype.card γ) + widBound := .C (Nat.clog 2 (Fintype.card γ)) + wid_le _ := by simp + enc _ x := natToBits (Nat.clog 2 (Fintype.card γ)) (Fintype.equivFin γ x) + len_eq _ _ := length_natToBits _ _ + enc_injective n x y h := by + have hlt : ∀ z : γ, ((Fintype.equivFin γ) z : ℕ) < 2 ^ Nat.clog 2 (Fintype.card γ) := + fun z => lt_of_lt_of_le (Fintype.equivFin γ z).isLt (Nat.le_pow_clog one_lt_two _) + exact (Fintype.equivFin γ).injective (Fin.val_injective (natToBits_inj (hlt x) (hlt y) h)) + +/-- The canonical `Unit` boundary: width `0`. -/ +noncomputable abbrev unit : BitEncFam (fun _ => PUnit.{u + 1}) := const PUnit + +/-- The canonical `Bool` boundary: width `1`. -/ +noncomputable abbrev bool : BitEncFam (fun _ => Bool) := const Bool + +/-- The canonical encoding of a `Fin (k n + 1)` family (e.g. a round counter): the +binary index in exactly `k n` bits, using `i ≤ k n < 2 ^ (k n)`. -/ +noncomputable def fin (k : ℕ → ℕ) (p : Polynomial ℕ) (hk : ∀ n, k n ≤ p.eval n) : + BitEncFam (fun n => Fin (k n + 1)) where + wid := k + widBound := p + wid_le := hk + enc n i := natToBits (k n) i + len_eq n i := length_natToBits _ _ + enc_injective n i j h := by + have hlt : ∀ m : Fin (k n + 1), (m : ℕ) < 2 ^ k n := + fun m => lt_of_lt_of_le m.isLt (Nat.succ_le_of_lt Nat.lt_two_pow_self) + exact Fin.val_injective (natToBits_inj (hlt i) (hlt j) h) + +/-- The canonical encoding of a bitvector family: the raw bits, least significant +first, width exactly `w n` — linear, where a unary enumeration would be exponential. -/ +noncomputable def bitVec (w : ℕ → ℕ) (p : Polynomial ℕ) (hw : ∀ n, w n ≤ p.eval n) : + BitEncFam (fun n => BitVec (w n)) where + wid := w + widBound := p + wid_le := hw + enc n v := (List.range (w n)).map v.getLsbD + len_eq n v := by simp + enc_injective n v₁ v₂ h := by + refine BitVec.eq_of_getLsbD_eq_iff.mpr fun i hi => ?_ + exact List.map_inj_left.mp h i (List.mem_range.mpr hi) + +/-- The canonical `BitVec n` boundary. -/ +noncomputable def bitVecX : BitEncFam (fun n => BitVec n) := + bitVec id .X fun n => (Polynomial.eval_X (x := n)).ge + +/-- Pair two fixed-width boundaries by literal append: the widths are fixed, so the +split point is positional and no separator is needed. Widths add. -/ +noncomputable def pair (e₁ : BitEncFam α) (e₂ : BitEncFam β) : BitEncFam (fun n => α n × β n) where + wid n := e₁.wid n + e₂.wid n + widBound := e₁.widBound + e₂.widBound + wid_le n := by + have := e₁.wid_le n; have := e₂.wid_le n + simp only [Polynomial.eval_add]; omega + enc n p := e₁.enc n p.1 ++ e₂.enc n p.2 + len_eq n p := by rw [List.length_append, e₁.len_eq, e₂.len_eq] + enc_injective n p q h := by + obtain ⟨h₁, h₂⟩ := List.append_inj h (by rw [e₁.len_eq, e₁.len_eq]) + exact Prod.ext (e₁.enc_injective n h₁) (e₂.enc_injective n h₂) + +/-- The canonical optional boundary: one tag bit, then the payload (zero-padded for +`none`), width `1 + wid`. This is the shape of a machine's optional readout. -/ +noncomputable def option (e : BitEncFam α) : BitEncFam (fun n => Option (α n)) where + wid n := e.wid n + 1 + widBound := e.widBound + .C 1 + wid_le n := by have := e.wid_le n; simp only [Polynomial.eval_add, Polynomial.eval_C]; omega + enc n + | Option.none => false :: List.replicate (e.wid n) false + | Option.some x => true :: e.enc n x + len_eq n x := by cases x <;> simp [e.len_eq] + enc_injective n x y h := by + cases x <;> cases y <;> simp only [List.cons.injEq] at h + · rfl + · exact absurd h.1 (by simp) + · exact absurd h.1 (by simp) + · exact congrArg _ (e.enc_injective n h.2) + +end BitEncFam + +/-! ## Variable-left, fixed-right pairing -/ + +namespace StrEncFam + +variable {σ β : ℕ → Type u} + +/-- Pair a variable-width encoding with a fixed-width one by append: injective because +the fixed-width right component determines the split point from the right. This is the +input shape of a machine's state/answer update. -/ +noncomputable def pairVar (s : StrEncFam σ) (e : BitEncFam β) : StrEncFam (fun n => σ n × β n) where + enc n p := s.enc n p.1 ++ e.enc n p.2 + enc_injective n p q h := by + obtain ⟨h₁, h₂⟩ := List.append_inj' h (by rw [e.len_eq, e.len_eq]) + exact Prod.ext (s.enc_injective n h₁) (e.enc_injective n h₂) + bound := s.bound + e.widBound + len_le n p := by + rw [List.length_append, e.len_eq] + have := s.len_le n p.1; have := e.wid_le n + simp only [Polynomial.eval_add]; omega + +@[simp] theorem pairVar_enc (s : StrEncFam σ) (e : BitEncFam β) (n : ℕ) (p : σ n × β n) : + (s.pairVar e).enc n p = s.enc n p.1 ++ e.enc n p.2 := rfl + +/-- Tag-bit sum of raw string encodings: `false ::` the left payload, `true ::` the +right payload. The state shape of a two-phase (`⊕`-state) machine. -/ +noncomputable def sum {τ : ℕ → Type u} (s₁ : StrEncFam σ) (s₂ : StrEncFam τ) : + StrEncFam (fun n => σ n ⊕ τ n) where + enc n := Sum.elim (fun x => false :: s₁.enc n x) (fun y => true :: s₂.enc n y) + enc_injective n x y h := by + cases x <;> cases y <;> simp only [Sum.elim_inl, Sum.elim_inr, List.cons.injEq] at h + · exact congrArg Sum.inl (s₁.enc_injective n h.2) + · exact absurd h.1 (by simp) + · exact absurd h.1 (by simp) + · exact congrArg Sum.inr (s₂.enc_injective n h.2) + bound := s₁.bound + s₂.bound + .C 1 + len_le n x := by + cases x with + | inl x => + have := s₁.len_le n x + simp only [Sum.elim_inl, List.length_cons, Polynomial.eval_add, Polynomial.eval_C] + omega + | inr y => + have := s₂.len_le n y + simp only [Sum.elim_inr, List.length_cons, Polynomial.eval_add, Polynomial.eval_C] + omega + +@[simp] theorem sum_enc_inl {τ : ℕ → Type u} (s₁ : StrEncFam σ) (s₂ : StrEncFam τ) + (n : ℕ) (x : σ n) : (s₁.sum s₂).enc n (Sum.inl x) = false :: s₁.enc n x := rfl + +@[simp] theorem sum_enc_inr {τ : ℕ → Type u} (s₁ : StrEncFam σ) (s₂ : StrEncFam τ) + (n : ℕ) (y : τ n) : (s₁.sum s₂).enc n (Sum.inr y) = true :: s₂.enc n y := rfl + +end StrEncFam + +namespace BitEncFam + +variable {γ : ℕ → Type u} {σ : ℕ → Type u} + +/-- Pair a fixed-width encoding on the left with a variable-width one on the right by +append — the mirror of `StrEncFam.pairVar`. Injective because the fixed-width left +component determines the split point from the left. This is the state shape of a +machine carrying a fixed-width value alongside a running machine's state. -/ +noncomputable def pairFix (e : BitEncFam γ) (s : StrEncFam σ) : + StrEncFam (fun n => γ n × σ n) where + enc n p := e.enc n p.1 ++ s.enc n p.2 + enc_injective n p q h := by + obtain ⟨h₁, h₂⟩ := List.append_inj h (by rw [e.len_eq, e.len_eq]) + exact Prod.ext (e.enc_injective n h₁) (s.enc_injective n h₂) + bound := e.widBound + s.bound + len_le n p := by + rw [List.length_append, e.len_eq] + have := s.len_le n p.2; have := e.wid_le n + simp only [Polynomial.eval_add]; omega + +@[simp] theorem pairFix_enc (e : BitEncFam γ) (s : StrEncFam σ) (n : ℕ) (p : γ n × σ n) : + (e.pairFix s).enc n p = e.enc n p.1 ++ s.enc n p.2 := rfl + +/-- The all-zero padding block of a prescribed fixed width: a `PUnit` boundary whose +encoding is `wid n` zero bits — the `none`-payload shape of `BitEncFam.option`. -/ +noncomputable def pad (w : ℕ → ℕ) (p : Polynomial ℕ) (hw : ∀ n, w n ≤ p.eval n) : + BitEncFam (fun _ => PUnit.{u + 1}) where + wid := w + widBound := p + wid_le := hw + enc n _ := List.replicate (w n) false + len_eq n _ := List.length_replicate + enc_injective _ x y _ := by cases x; cases y; rfl + +@[simp] theorem pad_enc (w : ℕ → ℕ) (p : Polynomial ℕ) (hw : ∀ n, w n ≤ p.eval n) + (n : ℕ) (x : PUnit) : (pad w p hw).enc n x = List.replicate (w n) false := rfl + +end BitEncFam + +/-! ## Uniform polynomial-time machine families -/ + +/-- A family of encoded polynomial-time machine witnesses with **uniform** polynomial +bounds: one machine per security parameter computing `f n` relative to the given +string encodings, a single polynomial bounding all running times (in `n` plus the +input length), and a single polynomial bounding all description sizes (the advice +bound — without it, per-parameter table machines smuggle unbounded advice). This is +the reusable unit of the polynomial-time adversary model: base machines produce these, +combinators compose them, and an adversary's four step functions each carry one. -/ +structure EncPolyTimeFam {α β : ℕ → Type u} + (ea : (n : ℕ) → α n → List Bool) (eb : (n : ℕ) → β n → List Bool) + (f : (n : ℕ) → α n → β n) : Type (u + 1) where + /-- The machine witness at each parameter. -/ + wit : (n : ℕ) → EncPolyTime (ea n) (eb n) (f n) + /-- Uniform polynomial bound on running times, in `n` plus the input length. -/ + time : Polynomial ℕ + /-- Every witness runs within the uniform time bound. -/ + time_le : ∀ n k, ((wit n).time).eval k ≤ time.eval (n + k) + /-- Uniform polynomial bound on description sizes — the advice bound. -/ + size : Polynomial ℕ + /-- Every witness's machine description is within the advice bound. -/ + size_le : ∀ n, (wit n).size ≤ size.eval n + +namespace EncPolyTimeFam + +variable {α β γ : ℕ → Type u} + {ea : (n : ℕ) → α n → List Bool} {eb : (n : ℕ) → β n → List Bool} + {ec : (n : ℕ) → γ n → List Bool} + +/-- Transport a witness family along string-equal encodings on both sides: the machines, +time polynomial, and advice bound are untouched (`EncPolyTime.recode` per parameter). +The workhorse for pure re-bracketings of encoded data — `cons`/append associativity and +pair/sum reshuffles cost no machine content. -/ +def recode {α' β' : ℕ → Type u} {ea' : (n : ℕ) → α' n → List Bool} + {eb' : (n : ℕ) → β' n → List Bool} {f : (n : ℕ) → α n → β n} + (h : EncPolyTimeFam ea eb f) (φ : (n : ℕ) → α' n → α n) (g : (n : ℕ) → α' n → β' n) + (hin : ∀ n x, ea' n x = ea n (φ n x)) + (hout : ∀ n x, eb' n (g n x) = eb n (f n (φ n x))) : + EncPolyTimeFam ea' eb' g where + wit n := (h.wit n).recode (φ n) (g n) (hin n) (hout n) + time := h.time + time_le := h.time_le + size := h.size + size_le := h.size_le + +/-- The identity family: one state, unit time. -/ +noncomputable def id (ea : (n : ℕ) → α n → List Bool) : + EncPolyTimeFam ea ea (fun _ => _root_.id) where + wit n := .id (ea n) + time := .C 1 + time_le n k := by + simp only [EncPolyTime.time, EncPolyTime.id, PolyTimeComputable.id, Polynomial.eval_one, + Polynomial.eval_C] + exact le_rfl + size := .C 1 + size_le n := by simp + +/-- Transport a family along pointwise-equal functions. -/ +def copy {f : (n : ℕ) → α n → β n} (h : EncPolyTimeFam ea eb f) + (f' : (n : ℕ) → α n → β n) (hf : ∀ n x, f n x = f' n x) : + EncPolyTimeFam ea eb f' where + wit n := (h.wit n).copy (f' n) (hf n) + time := h.time + time_le n k := by simpa [EncPolyTime.copy, EncPolyTime.time] using h.time_le n k + size := h.size + size_le n := by simpa using h.size_le n + +/-- Composition of uniform families: witnesses compose by `EncPolyTime.comp`; the +uniform time bound composes through the output-length envelope, and description +sizes add. -/ +noncomputable def comp {f : (n : ℕ) → α n → β n} {g : (n : ℕ) → β n → γ n} + (h : EncPolyTimeFam ea eb f) (h' : EncPolyTimeFam eb ec g) : + EncPolyTimeFam ea ec (fun n => g n ∘ f n) where + wit n := (h.wit n).comp (h'.wit n) + time := h.time + h'.time.comp (.C 1 + .X + h.time) + time_le n k := by + rw [EncPolyTime.comp_time_eval] + have h1 : ((h.wit n).time).eval k ≤ h.time.eval (n + k) := h.time_le n k + have h2 : ((h'.wit n).time).eval (1 + k + ((h.wit n).time).eval k) ≤ + h'.time.eval (n + (1 + k + ((h.wit n).time).eval k)) := h'.time_le n _ + have h3 : h'.time.eval (n + (1 + k + ((h.wit n).time).eval k)) ≤ + h'.time.eval (1 + (n + k) + h.time.eval (n + k)) := + Polynomial.eval_le_eval (by omega) + simp only [Polynomial.eval_add, Polynomial.eval_comp, Polynomial.eval_X, + Polynomial.eval_C] + omega + size := h.size + h'.size + size_le n := by + rw [EncPolyTime.size_comp] + have := h.size_le n; have := h'.size_le n + simp only [Polynomial.eval_add]; omega + +/-- The constant family, from a length bound on the encoded constants: the machine +erases its input and writes the constant. -/ +noncomputable def const (ea : (n : ℕ) → α n → List Bool) {eb : (n : ℕ) → β n → List Bool} + (c : (n : ℕ) → β n) (B : Polynomial ℕ) (hB : ∀ n, (eb n (c n)).length ≤ B.eval n) : + EncPolyTimeFam ea eb (fun n _ => c n) where + wit n := .const (ea n) (eb n) (c n) + time := .X + B + .C 2 + time_le n k := by + have hlen := hB n + have hmono : B.eval n ≤ B.eval (n + k) := Polynomial.eval_le_eval (Nat.le_add_right n k) + change ((constPolyTimeComputable (eb n (c n))).poly).eval k ≤ _ + simp only [constPolyTimeComputable, Polynomial.eval_add, Polynomial.eval_X, + Polynomial.eval_C] + omega + size := B + .C 2 + size_le n := by + refine (EncPolyTime.size_const_le _ _ _).trans ?_ + have := hB n + simp only [Polynomial.eval_add, Polynomial.eval_C]; omega + +/-- The finite-table family, for input families of polynomially bounded cardinality +and encoding length: any function family is computable by lookup tables, within the +advice bound exactly when the domain stays polynomially small. -/ +noncomputable def ofFintype [∀ n, Fintype (α n)] {ea : (n : ℕ) → α n → List Bool} + (hea : ∀ n, Function.Injective (ea n)) {eb : (n : ℕ) → β n → List Bool} + (f : (n : ℕ) → α n → β n) + (cardIn : Polynomial ℕ) (hcard : ∀ n, Fintype.card (α n) ≤ cardIn.eval n) + (lenIn : Polynomial ℕ) (hlenIn : ∀ n x, (ea n x).length ≤ lenIn.eval n) + (lenOut : Polynomial ℕ) (hlenOut : ∀ n x, (eb n (f n x)).length ≤ lenOut.eval n) : + EncPolyTimeFam ea eb f where + wit n := .ofFintype (ea n) (hea n) (eb n) (f n) + time := .X + lenOut + .C 1 + time_le n k := by + refine (EncPolyTime.time_ofFintype_eval_le (hea n) (hlenOut n) k).trans ?_ + have : lenOut.eval n ≤ lenOut.eval (n + k) := Polynomial.eval_le_eval (Nat.le_add_right n k) + simp only [Polynomial.eval_add, Polynomial.eval_X, Polynomial.eval_C]; omega + size := cardIn * (lenIn + .C 1 + lenOut) + .C 1 + size_le n := by + refine (EncPolyTime.size_ofFintype_le_of_bounds (hea n) (hcard n) (hlenIn n) + (hlenOut n)).trans ?_ + simp only [Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_C] + exact le_rfl + +end EncPolyTimeFam + +end Computability diff --git a/ToMathlib/Computability/CslibPolyTime.lean b/ToMathlib/Computability/CslibPolyTime.lean new file mode 100644 index 000000000..6497421a0 --- /dev/null +++ b/ToMathlib/Computability/CslibPolyTime.lean @@ -0,0 +1,199 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import Cslib.Computability.Machines.Turing.SingleTape.Deterministic +public import Mathlib.Algebra.Polynomial.Eval.Degree +public import ToMathlib.Computability.Encoding + +/-! +# Encoded Polynomial-Time Computability + +Cslib's `Cslib.Turing.SingleTapeTM.PolyTimeComputable` certifies polynomial-time computability +of raw string functions `List Symbol → List Symbol`. This file adds the encoding layer: +`Computability.EncPolyTime ea eb f` witnesses that a function `f : α → β` between +arbitrary types is polynomial-time computable relative to `Bool`-string encodings +`ea : α → List Bool` and `eb : β → List Bool`, by bundling a machine-computed total +string function that intertwines the encodings. Encodings typically arise from a +`Computability.PackedEncoding` via `PackedEncoding.boolify`. + +Identity and composition (`EncPolyTime.id`, `EncPolyTime.comp`) lift directly from +Cslib's proven `PolyTimeComputable.id` and `PolyTimeComputable.comp`; the monotone +time-bound side condition of the latter is discharged by `PolyTimeComputable.normalize`, +which replaces a machine's time bound with its own polynomial. + +Besides the running time `EncPolyTime.time`, every witness has a **description size** +`EncPolyTime.size`: the state count of its machine. Cslib's `PolyTimeComputable` bounds +only the running time, which suffices for a *single* function but not for a *family* of +witnesses indexed by a security parameter: a finite-table machine looks up any function +in linear time using one state per valid input, so without a size bound a family of +witnesses smuggles unbounded advice and the induced "polynomial-time" class contains +every function on polynomially-encodable domains. Families must therefore bound +`size` polynomially as well (see `PolyTimeAdversary.descBound`), giving the standard +non-uniform P/poly model. +-/ + +@[expose] public section + +universe u v w u_1 u_2 + +/-- Evaluation of a natural-number polynomial is monotone in the argument. -/ +theorem Polynomial.eval_le_eval {p : Polynomial ℕ} {m n : ℕ} (h : m ≤ n) : + p.eval m ≤ p.eval n := by + rw [p.eval_eq_sum_range, p.eval_eq_sum_range] + exact Finset.sum_le_sum fun i _ => Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h i) + +namespace Cslib.Turing.SingleTapeTM + +variable {Symbol : Type} [Inhabited Symbol] [Fintype Symbol] + +/-- Replace the time bound of a polynomial-time machine by the evaluation of its own +polynomial. The resulting bound is monotone, as required by `PolyTimeComputable.comp` +for the second machine. -/ +def PolyTimeComputable.normalize {f : List Symbol → List Symbol} + (h : PolyTimeComputable f) : PolyTimeComputable f where + tm := h.tm + timeBound n := h.poly.eval n + outputsFunInTime a := (h.outputsFunInTime a).of_le (h.bounds _) + poly := h.poly + bounds _ := le_rfl + +theorem PolyTimeComputable.monotone_normalize_timeBound {f : List Symbol → List Symbol} + (h : PolyTimeComputable f) : Monotone h.normalize.timeBound := + fun _ _ hmn => Polynomial.eval_le_eval hmn + +/-- The description size of a machine witness: its number of states. Over a fixed tape +alphabet the transition table has one row per state, so this measures the machine's +description — the "advice" of a non-uniform family. Time bounds alone do not control +it: a table machine looks up any function on a finite domain in linear time using one +state per valid input. -/ +def PolyTimeComputable.size {f : List Symbol → List Symbol} + (h : PolyTimeComputable f) : ℕ := Fintype.card h.tm.State + +@[simp] theorem PolyTimeComputable.size_normalize {f : List Symbol → List Symbol} + (h : PolyTimeComputable f) : h.normalize.size = h.size := rfl + +end Cslib.Turing.SingleTapeTM + +namespace Computability + +open Cslib.Turing.SingleTapeTM + +variable {α : Type u} {β : Type v} {γ : Type w} + +/-- A witness that `f : α → β` is polynomial-time computable relative to `Bool`-string +encodings of its domain and codomain: a total string function, computed by a single-tape +machine in polynomial time, that maps the encoding of `a` to the encoding of `f a`. + +The string function is total: its behavior on strings outside the range of `ea` is +unconstrained. -/ +structure EncPolyTime (ea : α → List Bool) (eb : β → List Bool) (f : α → β) where + /-- The total string function the machine computes. -/ + toFun : List Bool → List Bool + /-- The machine computing `toFun`, with its polynomial time bound. -/ + polyTime : PolyTimeComputable toFun + /-- The string function intertwines the encodings. -/ + map_encode : ∀ a, toFun (ea a) = eb (f a) + +namespace EncPolyTime + +variable {ea : α → List Bool} {eb : β → List Bool} {ec : γ → List Bool} + +/-- The polynomial time bound of the underlying machine. -/ +def time {f : α → β} (h : EncPolyTime ea eb f) : Polynomial ℕ := h.polyTime.poly + +/-- The description size (machine state count) of the underlying machine. Families of +witnesses indexed by a security parameter must bound this polynomially — the advice +bound of the non-uniform P/poly model; see the module docstring. -/ +def size {f : α → β} (h : EncPolyTime ea eb f) : ℕ := h.polyTime.size + +/-- The identity function is polynomial-time computable relative to any encoding. -/ +noncomputable def id (ea : α → List Bool) : EncPolyTime ea ea _root_.id where + toFun := _root_.id + polyTime := PolyTimeComputable.id + map_encode _ := rfl + +/-- The identity witness has a single machine state. -/ +@[simp] theorem size_id (ea : α → List Bool) : (EncPolyTime.id ea).size = 1 := + Fintype.card_punit + +/-- Transport a witness along a pointwise-equal function. -/ +def copy {f : α → β} (h : EncPolyTime ea eb f) (f' : α → β) (hf : ∀ a, f a = f' a) : + EncPolyTime ea eb f' where + toFun := h.toFun + polyTime := h.polyTime + map_encode a := (h.map_encode a).trans (congrArg eb (hf a)) + +/-- Transporting along a pointwise-equal function preserves the machine, hence the size. -/ +@[simp] theorem size_copy {f : α → β} (h : EncPolyTime ea eb f) (f' : α → β) + (hf : ∀ a, f a = f' a) : (h.copy f' hf).size = h.size := rfl + +/-- Transport a witness along string-equal encodings on both sides: if `ea'` encodes +each `a'` exactly as `ea` encodes `φ a'`, and `eb'` encodes each `g a'` exactly as `eb` +encodes `f (φ a')`, the same machine witnesses `g` relative to `ea'`/`eb'`. The machine, +time, and size are untouched — this discharges pure re-bracketings and re-taggings of +encoded data (`cons`/append associativity, pair/sum reshuffles) with no machine content. -/ +def recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} {eb' : β' → List Bool} + {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') + (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : + EncPolyTime ea' eb' g where + toFun := h.toFun + polyTime := h.polyTime + map_encode a' := by rw [hin, h.map_encode, ← hout] + +/-- Recoding preserves the machine's time polynomial. -/ +@[simp] theorem time_recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} + {eb' : β' → List Bool} {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') + (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : + (h.recode φ g hin hout).time = h.time := rfl + +/-- Recoding preserves the machine, hence the description size. -/ +@[simp] theorem size_recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} + {eb' : β' → List Bool} {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') + (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : + (h.recode φ g hin hout).size = h.size := rfl + +/-- Composition of encoded polynomial-time witnesses, from Cslib's +`PolyTimeComputable.comp`. -/ +noncomputable def comp {f : α → β} {f' : β → γ} + (h : EncPolyTime ea eb f) (h' : EncPolyTime eb ec f') : + EncPolyTime ea ec (f' ∘ f) where + toFun := h'.toFun ∘ h.toFun + polyTime := h.polyTime.comp h'.polyTime.normalize h'.polyTime.monotone_normalize_timeBound + map_encode a := by + simp only [Function.comp_apply, h.map_encode, h'.map_encode] + +/-- The polynomial time bound of a composition, unfolded: the first machine's polynomial plus +the second's evaluated at the first's output-length envelope `1 + X + h.time`. -/ +theorem comp_time {f : α → β} {f' : β → γ} + (h : EncPolyTime ea eb f) (h' : EncPolyTime eb ec f') : + (h.comp h').time = h.time + h'.time.comp (1 + Polynomial.X + h.time) := rfl + +/-- Evaluation of the composed time bound: `h`'s cost at input length `k`, plus `h'`'s cost at the +length `h`'s output can reach (`1 + k + h.time.eval k`). -/ +theorem comp_time_eval {f : α → β} {f' : β → γ} + (h : EncPolyTime ea eb f) (h' : EncPolyTime eb ec f') (k : ℕ) : + (h.comp h').time.eval k = h.time.eval k + h'.time.eval (1 + k + h.time.eval k) := by + rw [comp_time]; simp [Polynomial.eval_comp] + +/-- The composed machine is Cslib's phase-sum `compComputer`, so description sizes add. -/ +theorem size_comp {f : α → β} {f' : β → γ} + (h : EncPolyTime ea eb f) (h' : EncPolyTime eb ec f') : + (h.comp h').size = h.size + h'.size := + Fintype.card_sum + +/-- The output encoding of a polynomial-time computable function is at most polynomially +longer than the input encoding, by `output_length_le_input_length_add_time`. -/ +theorem length_le {f : α → β} (h : EncPolyTime ea eb f) (a : α) : + (eb (f a)).length ≤ max 1 (ea a).length + h.time.eval (ea a).length := by + rw [← h.map_encode a] + refine le_trans (output_length_le_input_length_add_time h.polyTime.tm _ _ _ + (h.polyTime.outputsFunInTime (ea a))) ?_ + exact Nat.add_le_add_left (h.polyTime.bounds _) _ + +end EncPolyTime + +end Computability diff --git a/ToMathlib/Computability/Encoding.lean b/ToMathlib/Computability/Encoding.lean new file mode 100644 index 000000000..60688708c --- /dev/null +++ b/ToMathlib/Computability/Encoding.lean @@ -0,0 +1,337 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import Mathlib.Computability.Encoding +public import Mathlib.Data.FinEnum + +/-! +# Additional Encoding Combinators + +This file extends `Mathlib.Computability.Encoding` with combinators needed to feed +structured values to Turing machines: + +- `Computability.finEncodingOfFinEnum`: a `PackedEncoding` of any `FinEnum` type (unary + over a `Unit` alphabet), making the common finite cases — `Unit`, `Bool`, `Fin n`, + products, sums, sigmas — encodable for free. +- `Computability.finEncodingOption`: a `PackedEncoding` of `Option β` from one of `β`. +- `Computability.finEncodingSigma`: a `PackedEncoding` of a dependent pair `(t : ι) × F t` + from an encoding of the index and per-index encodings over a shared fiber alphabet. +- `Computability.PackedEncoding.boolify`: relabel any finite-alphabet encoding into + `List Bool` via fixed-width one-hot symbol codes, so that encodings over different + alphabets can serve as inputs and outputs of machines over a single alphabet. +- `Computability.finEncodingBitVec`: the fixed-width binary encoding of `BitVec w`, + linear in `w` where the unary `finEncodingOfFinEnum` would be exponential, together + with length lemmas for it and for pair and option encodings. + +These mirror the design of `Computability.encodingProd`. +-/ + +@[expose] public section + +universe u v + +namespace List + +/-- A `flatMap` by an injective fixed-width block code is injective. -/ +theorem flatMap_injective {α : Type u} {β : Type v} {f : α → List β} {w : ℕ} (hw : 0 < w) + (hlen : ∀ a, (f a).length = w) (hinj : Function.Injective f) : + Function.Injective fun l : List α => l.flatMap f := by + intro l₁ l₂ h + induction l₁ generalizing l₂ with + | nil => + cases l₂ with + | nil => rfl + | cons b t => + simp only [flatMap_nil, flatMap_cons] at h + have := congrArg length h + simp [hlen b] at this + omega + | cons a t ih => + cases l₂ with + | nil => + simp only [flatMap_cons, flatMap_nil] at h + have := congrArg length h + simp [hlen a] at this + omega + | cons b t' => + simp only [flatMap_cons] at h + obtain ⟨hfab, htail⟩ := append_inj h ((hlen a).trans (hlen b).symm) + rw [hinj hfab, ih htail] + +end List + +namespace Computability + +/-! ## Bundled Finite-Alphabet Encodings -/ + +/-- A finite-alphabet encoding with the alphabet **bundled** as a field. + +Mathlib's `Computability.Encoding` takes the alphabet as a type parameter; this +Σ-style packaging remains necessary here because the polynomial-time layer +manipulates encodings of *varying* alphabet — per-`FinEnum` unary alphabets, sum +alphabets for options and sigmas — before `PackedEncoding.boolify` normalizes +them all to `Bool`. Local to `ToMathlib`. -/ +structure PackedEncoding (α : Type u) where + /-- The finite tape alphabet of the encoding. -/ + Γ : Type + /-- The encoding function. -/ + encode : α → List Γ + /-- The decoding function. -/ + decode : List Γ → Option α + /-- Decoding is a retraction of encoding. -/ + decode_encode : ∀ x, decode (encode x) = some x + /-- The alphabet is finite. -/ + ΓFin : Fintype Γ + +attribute [instance] PackedEncoding.ΓFin +attribute [simp] PackedEncoding.decode_encode + +namespace PackedEncoding + +variable {α : Type u} (e : PackedEncoding α) + +/-- The underlying unbundled Mathlib encoding. -/ +def toEncoding : Encoding α e.Γ where + encode := e.encode + decode := e.decode + decode_encode := e.decode_encode + +@[simp] theorem toEncoding_encode (x : α) : e.toEncoding.encode x = e.encode x := rfl + +/-- Packed pair encoding over the sum alphabet: the bundled form of +`Computability.encodingProd`. -/ +def pair {β : Type v} (eb : PackedEncoding β) : PackedEncoding (α × β) where + Γ := e.Γ ⊕ eb.Γ + encode x := (e.encode x.1).map .inl ++ (eb.encode x.2).map .inr + decode x := Option.map₂ Prod.mk (e.decode (x.filterMap Sum.getLeft?)) + (eb.decode (x.filterMap Sum.getRight?)) + decode_encode x := by simp + ΓFin := inferInstance + +end PackedEncoding + +/-! ## Encodings of Enumerable Finite Types -/ + +/-- A `PackedEncoding` of any `FinEnum` type: unary encoding over the `Unit` alphabet, +with string length the enumeration index. Gives encodings of the common finite types +(`Unit`, `Bool`, `Fin n`, products, sums, sigmas of such) for free. -/ +def finEncodingOfFinEnum (α : Type u) [FinEnum α] : PackedEncoding α where + Γ := Unit + encode x := List.replicate (FinEnum.equiv x : ℕ) () + decode l := + if h : l.length < FinEnum.card α then some (FinEnum.equiv.symm ⟨l.length, h⟩) else none + decode_encode x := by simp + ΓFin := inferInstance + +/-! ## Option and Sigma Encodings -/ + +/-- A `PackedEncoding` of `Option β`: `none` is the empty string, `some b` is a marker +symbol followed by the relabeled encoding of `b`. -/ +def finEncodingOption {β : Type u} (eb : PackedEncoding β) : PackedEncoding (Option β) where + Γ := Unit ⊕ eb.Γ + encode + | none => [] + | some b => .inl () :: (eb.encode b).map .inr + decode + | [] => some none + | .inl _ :: l => (eb.decode (l.filterMap Sum.getRight?)).map some + | .inr _ :: _ => none + decode_encode x := by cases x <;> simp + ΓFin := inferInstance + +/-- Sigma analogue of `PackedEncoding.pair`: encode the index with `.inl` symbols and the +fiber value with `.inr` symbols. The fiber encodings share a single alphabet `Γ` +(per-index alphabets could not form one machine alphabet); they are given as raw +encode/decode functions with a round-trip proof rather than per-index +`PackedEncoding`s for the same reason. -/ +def finEncodingSigma {ι : Type u} (ei : PackedEncoding ι) {F : ι → Type v} {Γ : Type} + [Fintype Γ] (enc : (t : ι) → F t → List Γ) (dec : (t : ι) → List Γ → Option (F t)) + (henc : ∀ t x, dec t (enc t x) = some x) : PackedEncoding ((t : ι) × F t) where + Γ := ei.Γ ⊕ Γ + encode x := (ei.encode x.1).map .inl ++ (enc x.1 x.2).map .inr + decode l := (ei.decode (l.filterMap Sum.getLeft?)).bind + fun t => (dec t (l.filterMap Sum.getRight?)).map (⟨t, ·⟩) + decode_encode x := by + obtain ⟨t, y⟩ := x + simp [henc] + ΓFin := inferInstance + +/-! ## Relabeling into a Boolean Alphabet -/ + +namespace PackedEncoding + +variable {α : Type u} (e : PackedEncoding α) + +/-- The fixed-width one-hot code of a single alphabet symbol: a `Bool` string of length +`Fintype.card e.Γ + 1` that is `true` exactly at the symbol's index. The `+ 1` keeps the +width positive even for an empty alphabet. -/ +noncomputable def symbolCode (g : e.Γ) : List Bool := + (List.range (Fintype.card e.Γ + 1)).map fun i => decide (i = (Fintype.equivFin e.Γ g : ℕ)) + +@[simp] theorem length_symbolCode (g : e.Γ) : + (e.symbolCode g).length = Fintype.card e.Γ + 1 := by + simp [symbolCode] + +theorem symbolCode_injective : Function.Injective e.symbolCode := by + intro g₁ g₂ h + have hlt : ((Fintype.equivFin e.Γ) g₁ : ℕ) ∈ List.range (Fintype.card e.Γ + 1) := + List.mem_range.mpr (Nat.lt_succ_of_lt (Fintype.equivFin e.Γ g₁).isLt) + have := (List.map_inj_left.mp h) _ hlt + simp only [decide_eq_decide, true_iff] at this + exact (Fintype.equivFin e.Γ).injective (Fin.val_injective (this ▸ rfl)) + +/-- Relabel a finite-alphabet encoding into `List Bool` by replacing each symbol with +its fixed-width one-hot code. Machines over the single alphabet `Bool` can then +consume and produce values of any `PackedEncoding`-encodable type. -/ +noncomputable def boolify : α → List Bool := + fun x => (e.encode x).flatMap e.symbolCode + +theorem boolify_injective : Function.Injective e.boolify := + (List.flatMap_injective (Nat.succ_pos _) e.length_symbolCode e.symbolCode_injective).comp + e.toEncoding.encode_injective + +@[simp] theorem length_boolify (x : α) : + (e.boolify x).length = (Fintype.card e.Γ + 1) * (e.encode x).length := by + simp only [boolify, List.length_flatMap] + simp [Nat.mul_comm] + +end PackedEncoding + +/-- The boolified unary `FinEnum` encoding has length at most `2 * card`: the unary +string has length the enumeration index (below `card`), and the one-hot symbol width +over the `Unit` alphabet is `2`. This is the pointwise bound feeding +`Computability.EncPolyTime.time_ofFintype_eval_le` at `FinEnum` encodings. -/ +theorem length_boolify_finEncodingOfFinEnum {γ : Type u} [FinEnum γ] [Fintype γ] (x : γ) : + ((finEncodingOfFinEnum γ).boolify x).length ≤ 2 * Fintype.card γ := by + have hx : (FinEnum.equiv x : ℕ) < Fintype.card γ := + FinEnum.card_eq_fintypeCard (α := γ) ▸ (FinEnum.equiv x).isLt + rw [PackedEncoding.length_boolify] + simp only [finEncodingOfFinEnum, List.length_replicate, Fintype.card_unique] + omega + +/-! ## Binary Bitvector Encoding -/ + +/-- Fixed-width binary encoding of `BitVec w` over the `Bool` alphabet: the string of the +`w` bits, least significant first. The encoded length is `w`, linear where the unary +`finEncodingOfFinEnum` encoding would have length up to `2 ^ w`; machine states containing +bitvectors need this encoding for polynomial size bounds. -/ +def finEncodingBitVec (w : ℕ) : PackedEncoding (BitVec w) where + Γ := Bool + encode m := (List.range w).map m.getLsbD + decode l := if h : l.length = w then some (BitVec.cast h (BitVec.ofBoolListLE l)) else none + decode_encode m := by + have hlen : ((List.range w).map m.getLsbD).length = w := by simp + rw [dif_pos hlen] + refine congrArg some (BitVec.eq_of_getLsbD_eq_iff.mpr fun i hi => ?_) + rw [BitVec.getLsbD_cast, BitVec.getLsbD_ofBoolListLE] + simp [List.getD_eq_getElem?_getD, hi] + ΓFin := inferInstance + +@[simp] theorem length_encode_finEncodingBitVec {w : ℕ} (m : BitVec w) : + ((finEncodingBitVec w).encode m).length = w := by + simp [finEncodingBitVec] + +/-- The boolified binary bitvector encoding has length exactly `3 * w`: `w` symbols of +one-hot width `card Bool + 1`. The pointwise bound feeding +`Computability.EncPolyTime.time_ofFintype_eval_le` at bitvector-shaped machine states. -/ +theorem length_boolify_finEncodingBitVec {w : ℕ} (m : BitVec w) : + ((finEncodingBitVec w).boolify m).length = 3 * w := by + rw [PackedEncoding.length_boolify, length_encode_finEncodingBitVec] + change (Fintype.card Bool + 1) * w = 3 * w + rw [Fintype.card_bool] + +/-! ## Encoding Lengths of Pairs and Options -/ + +theorem length_encode_pair {α : Type u} {β : Type v} (ea : PackedEncoding α) + (eb : PackedEncoding β) (x : α × β) : + ((ea.pair eb).encode x).length = + (ea.encode x.1).length + (eb.encode x.2).length := by + simp [PackedEncoding.pair] + +@[simp] theorem length_encode_finEncodingOption_none {β : Type u} (eb : PackedEncoding β) : + ((finEncodingOption eb).encode (none : Option β)).length = 0 := rfl + +theorem length_encode_finEncodingOption_some {β : Type u} (eb : PackedEncoding β) (b : β) : + ((finEncodingOption eb).encode (some b)).length = (eb.encode b).length + 1 := by + simp [finEncodingOption] + +/-- The boolified pair encoding has length `(card Γ₁ + card Γ₂ + 1)` times the sum of the two +component encode-lengths: the one-hot symbol width over the combined alphabet `Γ₁ ⊕ Γ₂` times +the concatenated encoding length. The pointwise bound for paired machine states (e.g. a counter +paired with an accumulator). -/ +theorem length_boolify_pair {α : Type u} {β : Type v} (ea : PackedEncoding α) + (eb : PackedEncoding β) (x : α × β) : + ((ea.pair eb).boolify x).length + = (Fintype.card ea.Γ + Fintype.card eb.Γ + 1) + * ((ea.encode x.1).length + (eb.encode x.2).length) := by + have hc : Fintype.card (ea.pair eb).Γ = Fintype.card ea.Γ + Fintype.card eb.Γ := + Fintype.card_sum + rw [PackedEncoding.length_boolify, length_encode_pair, hc] + +/-- The boolified option encoding has length `(card Γ + 2)` times the option encode-length: the +`Unit ⊕ Γ` alphabet has one more symbol than `Γ`, so the one-hot width is `card Γ + 2`. The +pointwise bound for optional machine outputs. -/ +theorem length_boolify_finEncodingOption {β : Type v} (eb : PackedEncoding β) (x : Option β) : + ((finEncodingOption eb).boolify x).length + = (Fintype.card eb.Γ + 2) * ((finEncodingOption eb).encode x).length := by + have hc : Fintype.card (finEncodingOption eb).Γ = Fintype.card eb.Γ + 1 := + (Fintype.card_sum (α := Unit) (β := eb.Γ)).trans (by rw [Fintype.card_unit, Nat.add_comm]) + rw [PackedEncoding.length_boolify, hc] + +/-! ## Sum Encoding -/ + +/-- Encode a disjoint union `α ⊕ β` over the combined alphabet `Bool ⊕ ea.Γ ⊕ eb.Γ`: each value is +prefixed with a `Bool` tag symbol (`.inl false` for a left value, `.inl true` for a right value) so +the branch is always readable from the head, and the payload symbols land in `.inr (.inl _)` (left) +or `.inr (.inr _)` (right). This is the two-sided generalization of `finEncodingOption` and the +state encoding for the two-phase machine `OracleMachine.seqComp`, whose state is +`M₁.State ⊕ M₂.State`. -/ +def finEncodingSum {α : Type u} {β : Type v} (ea : PackedEncoding α) (eb : PackedEncoding β) : + PackedEncoding (α ⊕ β) where + Γ := Bool ⊕ ea.Γ ⊕ eb.Γ + encode + | .inl a => .inl false :: (ea.encode a).map (Sum.inr ∘ Sum.inl) + | .inr b => .inl true :: (eb.encode b).map (Sum.inr ∘ Sum.inr) + decode + | .inl false :: l => + (ea.decode (l.filterMap fun s => (Sum.getRight? s).bind Sum.getLeft?)).map Sum.inl + | .inl true :: l => + (eb.decode (l.filterMap fun s => (Sum.getRight? s).bind Sum.getRight?)).map Sum.inr + | _ => none + decode_encode x := by + cases x with + | inl a => simp [List.filterMap_map, Function.comp_def, ea.decode_encode] + | inr b => simp [List.filterMap_map, Function.comp_def, eb.decode_encode] + ΓFin := inferInstance + +@[simp] theorem length_encode_finEncodingSum_inl {α : Type u} {β : Type v} (ea : PackedEncoding α) + (eb : PackedEncoding β) (a : α) : + ((finEncodingSum ea eb).encode (Sum.inl a : α ⊕ β)).length = (ea.encode a).length + 1 := by + simp [finEncodingSum] + +@[simp] theorem length_encode_finEncodingSum_inr {α : Type u} {β : Type v} (ea : PackedEncoding α) + (eb : PackedEncoding β) (b : β) : + ((finEncodingSum ea eb).encode (Sum.inr b : α ⊕ β)).length = (eb.encode b).length + 1 := by + simp [finEncodingSum] + +/-- The boolified sum encoding has length `(card Γ₁ + card Γ₂ + 3)` times the sum encode-length: the +`Bool ⊕ Γ₁ ⊕ Γ₂` alphabet has two more symbols than `Γ₁ ⊕ Γ₂`, so the one-hot width is +`card Γ₁ + card Γ₂ + 3`. The pointwise bound feeding `encState_length_le` for the two-phase +`seqComp` machine state. -/ +theorem length_boolify_finEncodingSum {α : Type u} {β : Type v} (ea : PackedEncoding α) + (eb : PackedEncoding β) (x : α ⊕ β) : + ((finEncodingSum ea eb).boolify x).length + = (Fintype.card ea.Γ + Fintype.card eb.Γ + 3) * ((finEncodingSum ea eb).encode x).length := by + have hc : Fintype.card (finEncodingSum ea eb).Γ = Fintype.card ea.Γ + Fintype.card eb.Γ + 2 := by + change Fintype.card (Bool ⊕ ea.Γ ⊕ eb.Γ) = Fintype.card ea.Γ + Fintype.card eb.Γ + 2 + rw [Fintype.card_sum, Fintype.card_sum, Fintype.card_bool] + omega + rw [PackedEncoding.length_boolify, hc, + show Fintype.card ea.Γ + Fintype.card eb.Γ + 2 + 1 + = Fintype.card ea.Γ + Fintype.card eb.Γ + 3 from by omega] + +end Computability diff --git a/ToMathlib/Computability/MachineCounting.lean b/ToMathlib/Computability/MachineCounting.lean new file mode 100644 index 000000000..7c018e431 --- /dev/null +++ b/ToMathlib/Computability/MachineCounting.lean @@ -0,0 +1,518 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import ToMathlib.Computability.BitEncoding +public import Mathlib.Analysis.SpecificLimits.Normed +public import Mathlib.Data.FinEnum + +/-! +# Counting Polynomial-Size Turing Machines + +The combinatorial core of the non-triviality certificate for the machine-grounded +polynomial-time model (`VCVio.OracleComp.Coinductive.PolyTimeNontrivial`): only +sub-doubly-exponentially many predicates `BitVec n → Bool` are realizable by +`d`-state single-tape machines, while there are `2 ^ (2 ^ n)` such predicates. + +The pieces, each isolated so the diagonalization argument reads as pure counting: + +* **Canonical `d`-state machines** (`Cslib.Turing.SingleTapeTM.TMTable`): a transition table + `Fin d → Option Bool → Stmt Bool × Option (Fin d)` together with an initial state. + These are a `Fintype` with an explicit, provable cardinality + (`card_tmTable`, bounded by `Cslib.Turing.SingleTapeTM.B`), and `reify` packages one back + into a `SingleTapeTM Bool`. The `Fintype`/`DecidableEq` instances for the underlying + `Turing.Dir` and `SingleTapeTM.Stmt Bool` are supplied here. +* **State normalization** (`exists_tmTable_of_card_le`): any + `SingleTapeTM Bool` with at most `d` states computes the same string function as + `reify` of some `TMTable d`. +* **Realizable predicates** (`Computability.RealizableLE`): the predicates realizable by + an input/output `EncPolyTime` pair of description size at most `d`. This set is covered + by a `Finset` of cardinality at most `B d ^ 2` (`exists_realizableLE_covering`, the + counting core: the cover of `RealizableLE n d` by the image of + `TMTable d × TMTable d` under `tablePairPred`, built from state normalization), and it is + monotone in `d` (`realizableLE_mono`). +* **Growth bounds**: every polynomial is eventually dominated by `2 ^ (n / 4)` + (`Computability.eventually_poly_le`), while the machine count stays below the function + count (`Computability.eventually_count_lt`). +* **The function space** has cardinality `2 ^ (2 ^ n)` (`Computability.card_bitVec_fun`), + and a `Finset` family of subexponential cardinality misses a diagonal predicate + eventually (`Computability.exists_diagonal`). +-/ + +@[expose] public section + +open Filter Asymptotics + +namespace Cslib.Turing.SingleTapeTM + +/-! ## Finiteness of statements and directions -/ + +/-- A `SingleTapeTM.Stmt` is a pair of an optional write symbol and an optional move. -/ +def stmtProdEquiv : SingleTapeTM.Stmt Bool ≃ (Option Bool × Option Turing.Dir) where + toFun s := (s.symbol, s.movement) + invFun p := ⟨p.1, p.2⟩ + left_inv _ := rfl + right_inv _ := rfl + +instance : Fintype Turing.Dir := + ⟨{Turing.Dir.left, Turing.Dir.right}, fun d => by cases d <;> decide⟩ + +instance : DecidableEq (SingleTapeTM.Stmt Bool) := + fun _ _ => decidable_of_iff _ stmtProdEquiv.injective.eq_iff + +noncomputable instance : Fintype (SingleTapeTM.Stmt Bool) := + Fintype.ofEquiv _ stmtProdEquiv.symm + +theorem card_dir : Fintype.card Turing.Dir = 2 := by decide + +theorem card_stmt : Fintype.card (SingleTapeTM.Stmt Bool) = 9 := by + rw [Fintype.card_congr stmtProdEquiv]; decide + +/-! ## Canonical `d`-state machines -/ + +/-- A canonical `d`-state single-tape machine over `Bool`: a transition table on states +`Fin d` together with an initial state. Every machine with at most `d` states computes, +after relabeling, the same function as `reify` of one of these (`exists_tmTable_of_card_le`), +so `TMTable d` is the finite index of `d`-state machines used for counting. -/ +abbrev TMTable (d : ℕ) : Type := + (Fin d → Option Bool → SingleTapeTM.Stmt Bool × Option (Fin d)) × Fin d + +noncomputable instance (d : ℕ) : Fintype (TMTable d) := inferInstance + +instance (d : ℕ) : DecidableEq (TMTable d) := inferInstance + +/-- A crude closed-form upper bound on the number of `d`-state machines: the exact +cardinality `card_tmTable`. -/ +def B (d : ℕ) : ℕ := (9 * (d + 1)) ^ (3 * d) * d + +/-- The exact number of canonical `d`-state machines: each of the `d` states maps each of +the `3` head symbols (`Option Bool`) to one of the `9 * (d + 1)` statement/next-state +pairs, and there are `d` choices of initial state. -/ +theorem card_tmTable (d : ℕ) : Fintype.card (TMTable d) = B d := by + simp only [B, TMTable, Fintype.card_prod, Fintype.card_fun, card_stmt, + Fintype.card_option, Fintype.card_fin, Fintype.card_bool, ← pow_mul] + +theorem card_tmTable_le (d : ℕ) : Fintype.card (TMTable d) ≤ B d := + (card_tmTable d).le + +/-- Package a canonical table back into a `SingleTapeTM Bool` on state space `Fin d`. -/ +def reify {d : ℕ} (t : TMTable d) : SingleTapeTM Bool where + State := Fin d + q₀ := t.2 + tr := t.1 + +/-! ## State-relabeling normalization construction + +The machinery discharging `exists_tmTable_of_card_le`: relabel the finite state space of a +machine `tm` through `Fintype.equivFin`, embed `Fin (card tm.State)` into `Fin d` along the +cardinality inequality (`embFin`), and transport the transition function on the image +(`normTr`), sending every spare state (those outside the image, detected by `decFin`) to a +fixed halting transition. Configurations transport along `normCfg`, single steps correspond +(`step_normCfg`), and this lifts through `ReflTransGen` in both directions +(`normCfg_reflTransGen`, `reflTransGen_normCfg_reverse`), giving the `Outputs` equivalence. -/ + +section Normalize + +/-- Embed a finite type into `Fin d` (with `card ≤ d`) via its `Fintype.equivFin` labeling. -/ +noncomputable def embFin {α : Type*} [Fintype α] {d : ℕ} (hd : Fintype.card α ≤ d) (s : α) : + Fin d := + (Fintype.equivFin α s).castLE hd + +/-- The partial inverse of `embFin`: recover the state whose label is `i`, or `none` for +spare indices `i` with no preimage. -/ +noncomputable def decFin {α : Type*} [Fintype α] {d : ℕ} (i : Fin d) : Option α := + if hi : (i : ℕ) < Fintype.card α then some ((Fintype.equivFin α).symm ⟨i, hi⟩) else none + +/-- `decFin` inverts `embFin` on the image. -/ +lemma decFin_embFin {α : Type*} [Fintype α] {d : ℕ} (hd : Fintype.card α ≤ d) (s : α) : + (decFin (embFin hd s) : Option α) = some s := by + have hlt : ((embFin hd s : Fin d) : ℕ) < Fintype.card α := by + simp only [embFin, Fin.val_castLE]; exact (Fintype.equivFin α s).isLt + simp only [decFin, dif_pos hlt] + congr 1 + apply (Fintype.equivFin α).symm_apply_eq.mpr + apply Fin.ext + simp [embFin, Fin.val_castLE] + +/-- `embFin` is injective. -/ +lemma embFin_injective {α : Type*} [Fintype α] {d : ℕ} (hd : Fintype.card α ≤ d) : + Function.Injective (embFin hd) := fun _ _ hab => + (Fintype.equivFin α).injective (Fin.castLE_injective hd hab) + +variable {d : ℕ} (tm : SingleTapeTM Bool) (emb : tm.State → Fin d) + (dec : Fin d → Option tm.State) + +/-- Transition table transporting `tm`'s transitions along `emb`; spare states (those with +`dec i = none`) are given a fixed halting transition. -/ +noncomputable def normTr : Fin d → Option Bool → SingleTapeTM.Stmt Bool × Option (Fin d) := + fun i b => + match dec i with + | some s => ((tm.tr s b).1, (tm.tr s b).2.map emb) + | none => (default, none) + +/-- The canonical `d`-state table normalizing `tm` onto `Fin d` along `emb`/`dec`. -/ +noncomputable def normTable : TMTable d := (normTr tm emb dec, emb tm.q₀) + +/-- Transport a configuration of `tm` to the reified normalized machine. -/ +noncomputable def normCfg (c : tm.Cfg) : (reify (normTable tm emb dec)).Cfg := + ⟨c.state.map emb, c.BiTape⟩ + +variable {tm emb dec} + +/-- The reified normalized machine's step transports `tm`'s step along `normCfg`, provided +`dec` inverts `emb` on the image. -/ +lemma step_normCfg (hdec : ∀ s, dec (emb s) = some s) (c : tm.Cfg) : + (reify (normTable tm emb dec)).step (normCfg tm emb dec c) + = (tm.step c).map (normCfg tm emb dec) := by + obtain ⟨st, tp⟩ := c + cases st with + | none => rfl + | some q => + have hdq : dec (emb q) = some q := hdec q + rcases htr : tm.tr q tp.head with ⟨⟨wr, dir⟩, q''⟩ + simp only [step, normCfg, reify, normTable, normTr, Option.map_some, hdq, htr] + +/-- `normCfg` is injective when `emb` is. -/ +lemma normCfg_injective (hemb : Function.Injective emb) : + Function.Injective (normCfg tm emb dec) := by + rintro ⟨s1, t1⟩ ⟨s2, t2⟩ h + simp only [normCfg, Cfg.mk.injEq] at h + obtain ⟨hs, ht⟩ := h + have hss : s1 = s2 := Option.map_injective hemb hs + subst hss; subst ht; rfl + +/-- A run of `tm` maps forward to a run of the normalized machine. -/ +lemma normCfg_reflTransGen (hdec : ∀ s, dec (emb s) = some s) {c c' : tm.Cfg} + (h : Relation.ReflTransGen tm.TransitionRelation c c') : + Relation.ReflTransGen (reify (normTable tm emb dec)).TransitionRelation + (normCfg tm emb dec c) (normCfg tm emb dec c') := by + have hstep : ∀ a b, tm.TransitionRelation a b → + (reify (normTable tm emb dec)).TransitionRelation + (normCfg tm emb dec a) (normCfg tm emb dec b) := by + intro a b hab + have hs := step_normCfg hdec a + rw [show tm.step a = some b from hab, Option.map_some] at hs + exact hs + exact Relation.ReflTransGen.lift (normCfg tm emb dec) hstep c c' h + +/-- A run of the normalized machine from an image configuration stays in the image and maps +back to a run of `tm`. -/ +lemma reflTransGen_normCfg_reverse (hdec : ∀ s, dec (emb s) = some s) {c : tm.Cfg} + {c' : (reify (normTable tm emb dec)).Cfg} + (h : Relation.ReflTransGen (reify (normTable tm emb dec)).TransitionRelation + (normCfg tm emb dec c) c') : + ∃ c₂, c' = normCfg tm emb dec c₂ ∧ Relation.ReflTransGen tm.TransitionRelation c c₂ := by + induction h with + | refl => exact ⟨c, rfl, Relation.ReflTransGen.refl⟩ + | @tail b e hab hbc ih => + obtain ⟨c₂, rfl, hrun⟩ := ih + have hs := step_normCfg hdec c₂ + rw [show (reify (normTable tm emb dec)).step (normCfg tm emb dec c₂) = some e from hbc] at hs + obtain ⟨c₃, hstep, hc3⟩ := Option.map_eq_some_iff.mp hs.symm + exact ⟨c₃, hc3.symm, hrun.tail hstep⟩ + +end Normalize + +/-! ## Determinism of machine runs + +Supporting facts for `Computability.exists_realizableLE_covering`: a single-tape machine +is deterministic (its `step` is a function), so the output list of a halting run is +unique. This lets the covering predicate attached to a table pair be read off by an +unbounded-search-free choice construction and still agree with any witness predicate. -/ + +section Determinism + +/-- In a relation that is a partial function (deterministic), two irreducible points +reachable from a common source coincide. -/ +theorem _root_.Relation.ReflTransGen.unique_of_deterministic + {α : Type*} {R : α → α → Prop} + (hdet : ∀ {a b c : α}, R a b → R a c → b = c) + {a b c : α} (hb : Relation.ReflTransGen R a b) (hc : Relation.ReflTransGen R a c) + (hbf : ∀ y, ¬ R b y) (hcf : ∀ y, ¬ R c y) : b = c := by + induction hb using Relation.ReflTransGen.head_induction_on with + | refl => + rcases hc.cases_head with h | ⟨y, hy, _⟩ + · exact h + · exact absurd hy (hbf y) + | head h' _ ih => + rename_i a' _ + rcases hc.cases_head with h | ⟨y, hy, hyc⟩ + · exact absurd (h ▸ h') (hcf a') + · rw [hdet h' hy] at ih; exact ih hyc + +/-- Distinct input lists give distinct initial/halting tapes: `BiTape.mk₁` is injective. -/ +theorem _root_.Cslib.Turing.BiTape.mk₁_injective {Symbol : Type} : + Function.Injective (Cslib.Turing.BiTape.mk₁ : List Symbol → Cslib.Turing.BiTape Symbol) := by + intro l₁ l₂ h + cases l₁ with + | nil => + cases l₂ with + | nil => rfl + | cons b t => simp [Cslib.Turing.BiTape.mk₁, Cslib.Turing.BiTape.nil] at h + | cons a s => + cases l₂ with + | nil => simp [Cslib.Turing.BiTape.mk₁, Cslib.Turing.BiTape.nil] at h + | cons b t => + simp only [Cslib.Turing.BiTape.mk₁, Cslib.Turing.BiTape.mk.injEq, Option.some.injEq] at h + obtain ⟨hab, -, hst⟩ := h + have : (s.map some) = (t.map some) := by + have := congrArg Cslib.Turing.StackTape.toList hst + simpa [Cslib.Turing.StackTape.mapSome] using this + have hst' : s = t := List.map_injective_iff.mpr (Option.some_injective _) this + rw [hab, hst'] + +variable {Symbol : Type} [Inhabited Symbol] [Fintype Symbol] + +/-- A halting configuration is irreducible: no transition leaves the halting state. -/ +theorem not_transitionRelation_haltCfg (tm : SingleTapeTM Symbol) (l : List Symbol) + (y : tm.Cfg) : ¬ tm.TransitionRelation (tm.haltCfg l) y := by + intro hy + simp only [TransitionRelation, haltCfg, step] at hy + exact absurd hy (by simp) + +/-- The output list of a halting machine run is unique: the machine is deterministic. -/ +theorem Outputs_unique (tm : SingleTapeTM Symbol) {l l₁ l₂ : List Symbol} + (h1 : tm.Outputs l l₁) (h2 : tm.Outputs l l₂) : l₁ = l₂ := by + have hcfg : tm.haltCfg l₁ = tm.haltCfg l₂ := by + refine Relation.ReflTransGen.unique_of_deterministic (R := tm.TransitionRelation) + (fun {a b c} hab hac => ?_) h1 h2 (not_transitionRelation_haltCfg tm l₁) + (not_transitionRelation_haltCfg tm l₂) + rw [TransitionRelation] at hab hac + rw [hab] at hac + exact Option.some.inj hac + have := congrArg Cfg.BiTape hcfg + simp only [haltCfg] at this + exact Cslib.Turing.BiTape.mk₁_injective this + +/-- A polynomial-time machine halts with the correct output on every input. -/ +theorem PolyTimeComputable.outputs {f : List Symbol → List Symbol} + (h : PolyTimeComputable f) (a : List Symbol) : h.tm.Outputs a (f a) := by + obtain ⟨m, _, hm⟩ := h.outputsFunInTime a + exact hm.reflTransGen + +end Determinism + +/-! ## State normalization -/ + +/-- Every `SingleTapeTM Bool` computing a string function with at most `d` states computes +the same function as `reify` of some `TMTable d` — the state space is relabeled to `Fin d` +along `Fintype.equivFin`, preserving the `Outputs` relation. It is the machine-theoretic +input to `exists_realizableLE_covering`. -/ +theorem exists_tmTable_of_card_le {f : List Bool → List Bool} (h : PolyTimeComputable f) + {d : ℕ} (hd : Fintype.card h.tm.State ≤ d) : + ∃ t : TMTable d, ∀ l l', (reify t).Outputs l l' ↔ h.tm.Outputs l l' := by + set tm := h.tm with htm + refine ⟨normTable tm (embFin hd) (decFin (α := tm.State)), fun l l' => ?_⟩ + have hdec : ∀ s, decFin (α := tm.State) (embFin hd s) = some s := decFin_embFin hd + have hemb : Function.Injective (embFin (α := tm.State) hd) := embFin_injective hd + have hinit : normCfg tm (embFin hd) (decFin (α := tm.State)) (tm.initCfg l) + = (reify (normTable tm (embFin hd) (decFin (α := tm.State)))).initCfg l := rfl + have hhalt : normCfg tm (embFin hd) (decFin (α := tm.State)) (tm.haltCfg l') + = (reify (normTable tm (embFin hd) (decFin (α := tm.State)))).haltCfg l' := rfl + constructor + · intro hout + have hout' : Relation.ReflTransGen + (reify (normTable tm (embFin hd) (decFin (α := tm.State)))).TransitionRelation + (normCfg tm (embFin hd) (decFin (α := tm.State)) (tm.initCfg l)) + ((reify (normTable tm (embFin hd) (decFin (α := tm.State)))).haltCfg l') := by + rw [hinit]; exact hout + obtain ⟨c₂, hc₂, hrun⟩ := reflTransGen_normCfg_reverse hdec hout' + rw [← hhalt] at hc₂ + have hcfg : tm.haltCfg l' = c₂ := normCfg_injective hemb hc₂ + rw [← hcfg] at hrun + exact hrun + · intro hout + have hmap := normCfg_reflTransGen hdec hout + rw [hinit, hhalt] at hmap + exact hmap + +end Cslib.Turing.SingleTapeTM + +namespace Computability + +open Cslib.Turing.SingleTapeTM + +/-! ## Realizable predicates -/ + +/-- The predicates `BitVec n → Bool` realizable at description size at most `d`: those +computed by an initialization witness into some state encoding followed by an output +witness, both `EncPolyTime` machines of description size at most `d`, against the canonical +`BitVec`/`Option Bool` boundary encodings. An implementing machine adversary at these +boundaries lands its computed predicate here (its `initF`/`outputF` witnesses), and the +count of such predicates is controlled by counting the underlying machines +(`exists_realizableLE_covering`). -/ +def RealizableLE (n d : ℕ) : Set (BitVec n → Bool) := + {g | ∃ (σ : Type) (es : σ → List Bool) (init : BitVec n → σ) (output : σ → Option Bool) + (_i : EncPolyTime (BitEncFam.bitVecX.enc n) es init) + (_o : EncPolyTime es (BitEncFam.bool.option.enc n) output), + _i.size ≤ d ∧ _o.size ≤ d ∧ ∀ x, output (init x) = some (g x)} + +/-- Realizability at a larger description size is a weaker requirement. -/ +theorem realizableLE_mono {n : ℕ} {d d' : ℕ} (h : d ≤ d') : + RealizableLE n d ⊆ RealizableLE n d' := by + rintro g ⟨σ, es, init, output, i, o, hi, ho, hg⟩ + exact ⟨σ, es, init, output, i, o, hi.trans h, ho.trans h, hg⟩ + +open Classical in +/-- The total predicate `BitVec n → Bool` attached to a pair of canonical `d`-state tables: +run `reify p.1` on the canonical input encoding of `x`, feed its (deterministic) output to +`reify p.2`, and decode the resulting canonical `Option Bool` encoding. Totality is ensured +by a deterministic choice over the (at most one, by `Outputs_unique`) successful run, with +an arbitrary `false` fallback where no such run exists — no claim that either raw table +pair halts or is polynomial-time. -/ +noncomputable def tablePairPred (n d : ℕ) (p : TMTable d × TMTable d) : BitVec n → Bool := + fun x => + if h : ∃ b : Bool, ∃ l₁ : List Bool, + (reify p.1).Outputs (BitEncFam.bitVecX.enc n x) l₁ ∧ + (reify p.2).Outputs l₁ (BitEncFam.bool.option.enc n (some b)) + then h.choose else false + +/-- The realizable predicates at description size at most `d` are covered by a `Finset` of +cardinality at most `B d ^ 2`. State normalization (`exists_tmTable_of_card_le`) reduces each +realizing pair of witness machines to a pair `TMTable d × TMTable d` of canonical `d`-state +tables; the realized predicate is recovered from the two tables by `tablePairPred` (running +both reified machines and decoding the canonical output encoding, using determinism of the +runs via `Outputs_unique`). Thus `RealizableLE n d` lands in the image of +`TMTable d × TMTable d` under `tablePairPred`, whence +`card ≤ Fintype.card (TMTable d × TMTable d) = B d ^ 2` (`card_tmTable`). The map need not be +injective — it only needs to cover the realizable set. -/ +theorem exists_realizableLE_covering (n d : ℕ) : + ∃ s : Finset (BitVec n → Bool), RealizableLE n d ⊆ ↑s ∧ s.card ≤ B d ^ 2 := by + classical + refine ⟨Finset.image (tablePairPred n d) + (Finset.univ : Finset (TMTable d × TMTable d)), ?_, ?_⟩ + · rintro g ⟨σ, es, init, output, i, o, hi, ho, hg⟩ + obtain ⟨t₁, ht₁⟩ := exists_tmTable_of_card_le i.polyTime (d := d) hi + obtain ⟨t₂, ht₂⟩ := exists_tmTable_of_card_le o.polyTime (d := d) ho + refine Finset.mem_coe.mpr (Finset.mem_image.mpr ⟨(t₁, t₂), Finset.mem_univ _, ?_⟩) + funext x + have hrun1 : (reify t₁).Outputs (BitEncFam.bitVecX.enc n x) (es (init x)) := by + rw [ht₁] + have h := i.polyTime.outputs (BitEncFam.bitVecX.enc n x) + rwa [i.map_encode x] at h + have hrun2 : (reify t₂).Outputs (es (init x)) + (BitEncFam.bool.option.enc n (some (g x))) := by + rw [ht₂] + have h := o.polyTime.outputs (es (init x)) + rwa [o.map_encode (init x), hg x] at h + have hex : ∃ b : Bool, ∃ l₁ : List Bool, + (reify t₁).Outputs (BitEncFam.bitVecX.enc n x) l₁ ∧ + (reify t₂).Outputs l₁ (BitEncFam.bool.option.enc n (some b)) := + ⟨g x, es (init x), hrun1, hrun2⟩ + have hpick : tablePairPred n d (t₁, t₂) x = hex.choose := dif_pos hex + rw [hpick] + obtain ⟨l₁', hl1', hl2'⟩ := hex.choose_spec + have hl1eq : l₁' = es (init x) := Outputs_unique _ hl1' hrun1 + rw [hl1eq] at hl2' + have henc := Outputs_unique _ hl2' hrun2 + exact Option.some.inj ((BitEncFam.bool.option).enc_injective n henc) + · refine Finset.card_image_le.trans ?_ + rw [Finset.card_univ, Fintype.card_prod, card_tmTable, sq] + +/-! ## Cardinality of the predicate space -/ + +/-- There are exactly `2 ^ (2 ^ n)` predicates `BitVec n → Bool`. -/ +theorem card_bitVec_fun (n : ℕ) : Fintype.card (BitVec n → Bool) = 2 ^ (2 ^ n) := by + rw [Fintype.card_fun, Fintype.card_bool, ← FinEnum.card_eq_fintypeCard, FinEnum.card_bitVec] + +/-! ## Polynomial versus exponential growth -/ + +/-- Any fixed power is eventually dominated by `2 ^ n`. -/ +theorem nat_pow_le_two_pow (k : ℕ) : ∀ᶠ n in atTop, n ^ k ≤ 2 ^ n := by + have h : (fun n : ℕ => (n : ℝ) ^ k) =o[atTop] fun n : ℕ => (2 : ℝ) ^ n := + isLittleO_pow_const_const_pow_of_one_lt k (by norm_num) + refine h.eventuallyLE.mono fun n hn => ?_ + simp only [Real.norm_eq_abs] at hn + rw [abs_of_nonneg (by positivity), abs_of_nonneg (by positivity)] at hn + exact_mod_cast (by push_cast; exact hn : ((n ^ k : ℕ) : ℝ) ≤ ((2 ^ n : ℕ) : ℝ)) + +/-- A constant multiple of any fixed power of `n + 1` is eventually dominated by `2 ^ n`. -/ +theorem const_mul_pow_le_two_pow (C k : ℕ) : ∀ᶠ m in atTop, C * (m + 1) ^ k ≤ 2 ^ m := by + filter_upwards [eventually_ge_atTop (C * 2 ^ k), eventually_ge_atTop 1, + nat_pow_le_two_pow (k + 1)] with m hm hm1 hm3 + calc C * (m + 1) ^ k ≤ C * (2 * m) ^ k := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) k) + _ = C * 2 ^ k * m ^ k := by rw [mul_pow]; ring + _ ≤ m * m ^ k := Nat.mul_le_mul_right _ hm + _ = m ^ (k + 1) := by rw [pow_succ]; ring + _ ≤ 2 ^ m := hm3 + +/-- **Polynomials are eventually dominated by `2 ^ (n / 4)`.** The exponent `n / 4` is the +threshold fed to the machine count: fast enough to eventually exceed every polynomial +description bound (this lemma), yet slow enough that the resulting machine count stays below +`2 ^ (2 ^ n)` (`eventually_count_lt`). -/ +theorem eventually_poly_le (p : Polynomial ℕ) : + ∀ᶠ n in atTop, p.eval n ≤ 2 ^ (n / 4) := by + obtain ⟨C, k, hCk⟩ : ∃ C k : ℕ, ∀ n : ℕ, p.eval n ≤ C * (n + 1) ^ k := by + refine ⟨∑ i ∈ Finset.range (p.natDegree + 1), p.coeff i, p.natDegree, fun n => ?_⟩ + rw [Polynomial.eval_eq_sum_range, Finset.sum_mul] + refine Finset.sum_le_sum fun i hi => ?_ + rw [Finset.mem_range] at hi + exact Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) i).trans + (Nat.pow_le_pow_right (by omega) (by omega))) + have htend : Tendsto (fun n : ℕ => n / 4) atTop atTop := + Nat.tendsto_div_const_atTop (by norm_num) + filter_upwards [htend.eventually (const_mul_pow_le_two_pow (C * 4 ^ k) k)] with n hn + calc p.eval n ≤ C * (n + 1) ^ k := hCk n + _ ≤ C * 4 ^ k * (n / 4 + 1) ^ k := by + rw [mul_assoc, ← mul_pow] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) k) + _ ≤ 2 ^ (n / 4) := hn + +/-- A crude closed-form bound on the squared machine count: for `9 * (d + 1) ≤ 2 ^ d` and +`d ≥ 1`, `B d ^ 2 ≤ 2 ^ (8 * d ^ 2)`. Uses `9 * (d + 1) ≤ 2 ^ d` on the statement/next-state +base and `d ^ 2 ≤ 2 ^ (2 * d)` on the initial-state factor. -/ +theorem B_sq_le (d : ℕ) (hd : 9 * (d + 1) ≤ 2 ^ d) (hd1 : 1 ≤ d) : + B d ^ 2 ≤ 2 ^ (8 * d ^ 2) := by + have hd2 : d ^ 2 ≤ 2 ^ (2 * d) := by + calc d ^ 2 ≤ (2 ^ d) ^ 2 := Nat.pow_le_pow_left (Nat.le_of_lt d.lt_two_pow_self) 2 + _ = 2 ^ (2 * d) := by rw [← pow_mul, Nat.mul_comm] + calc B d ^ 2 = ((9 * (d + 1)) ^ (3 * d)) ^ 2 * d ^ 2 := by rw [B, mul_pow] + _ = (9 * (d + 1)) ^ (6 * d) * d ^ 2 := by rw [← pow_mul]; ring_nf + _ ≤ (2 ^ d) ^ (6 * d) * 2 ^ (2 * d) := Nat.mul_le_mul (Nat.pow_le_pow_left hd _) hd2 + _ = 2 ^ (6 * d ^ 2) * 2 ^ (2 * d) := by rw [← pow_mul]; ring_nf + _ = 2 ^ (6 * d ^ 2 + 2 * d) := by rw [← pow_add] + _ ≤ 2 ^ (8 * d ^ 2) := Nat.pow_le_pow_right (by norm_num) (by nlinarith [hd1]) + +/-- **The squared machine count at the threshold size `2 ^ (n / 4)` stays below the +predicate count `2 ^ (2 ^ n)` eventually.** The count at size `d = 2 ^ (n / 4)` is at most +`2 ^ (8 * d ^ 2)` (`B_sq_le`, whose hypothesis `9 * (d + 1) ≤ 2 ^ d` holds cofinitely as +`d → ∞`), and its exponent `8 * d ^ 2 = 2 ^ (3 + n / 4 * 2)` is eventually below `2 ^ n`. -/ +theorem eventually_count_lt : + ∀ᶠ n in atTop, B (2 ^ (n / 4)) ^ 2 < 2 ^ (2 ^ n) := by + have htwo : Tendsto (fun m : ℕ => 2 ^ m) atTop atTop := + tendsto_atTop_mono (fun m => (Nat.lt_two_pow_self).le) tendsto_id + have htend : Tendsto (fun n : ℕ => 2 ^ (n / 4)) atTop atTop := + htwo.comp (Nat.tendsto_div_const_atTop (by norm_num)) + filter_upwards [htend.eventually (const_mul_pow_le_two_pow 9 1), + eventually_ge_atTop 8] with n ha hn + rw [pow_one] at ha + refine lt_of_le_of_lt (B_sq_le _ ha Nat.one_le_two_pow) ?_ + apply Nat.pow_lt_pow_right (by norm_num) + calc 8 * (2 ^ (n / 4)) ^ 2 + = 2 ^ (3 + n / 4 * 2) := by rw [show (8 : ℕ) = 2 ^ 3 from rfl, ← pow_mul, ← pow_add] + _ < 2 ^ n := Nat.pow_lt_pow_right (by norm_num) (by omega) + +/-! ## The diagonal predicate -/ + +/-- A `Finset` family of subexponential cardinality misses a predicate eventually: if +`(S n).card < 2 ^ (2 ^ n)` cofinitely, some family `f` has `f n ∉ S n` cofinitely. -/ +theorem exists_diagonal (S : (n : ℕ) → Finset (BitVec n → Bool)) + (hS : ∀ᶠ n in atTop, (S n).card < 2 ^ (2 ^ n)) : + ∃ f : (n : ℕ) → BitVec n → Bool, ∀ᶠ n in atTop, f n ∉ S n := by + classical + have key : ∀ n, (S n).card < 2 ^ (2 ^ n) → ∃ g : BitVec n → Bool, g ∉ S n := by + intro n hn + have hlt : (S n).card < (Finset.univ : Finset (BitVec n → Bool)).card := by + rw [Finset.card_univ, card_bitVec_fun]; exact hn + obtain ⟨e, -, he⟩ := Finset.exists_mem_notMem_of_card_lt_card hlt + exact ⟨e, he⟩ + refine ⟨fun n => if h : (S n).card < 2 ^ (2 ^ n) then (key n h).choose else default, ?_⟩ + refine hS.mono fun n hn => ?_ + simp only [dif_pos hn] + exact (key n hn).choose_spec + +end Computability diff --git a/ToMathlib/Computability/PolyTimeTM.lean b/ToMathlib/Computability/PolyTimeTM.lean new file mode 100644 index 000000000..f920bac33 --- /dev/null +++ b/ToMathlib/Computability/PolyTimeTM.lean @@ -0,0 +1,526 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import ToMathlib.Computability.CslibPolyTime + +/-! +# Base Polynomial-Time Machines + +Concrete single-tape machines witnessing polynomial-time computability of basic +functions, in Cslib's `Cslib.Turing.SingleTapeTM` model: + +- `Cslib.Turing.SingleTapeTM.clearComputer` / `Cslib.Turing.SingleTapeTM.constComputer`: erase the + input and produce a fixed output string, giving `constPolyTimeComputable` and the + encoding-level witness `Computability.EncPolyTime.const` for constant functions. +- `Cslib.Turing.SingleTapeTM.tableComputer`: read the input into machine state through the + prefix tree of a finite set of valid inputs, then write the corresponding table + output, giving `tablePolyTimeComputable` and the encoding-level witness + `Computability.EncPolyTime.ofFintype`: **any function with a finite domain is + polynomial-time computable** relative to an injective encoding — but with a + description size (`EncPolyTime.size_ofFintype_le`) that grows with the domain's + total encoded length, so a *family* of tables stays within a polynomial advice + bound (`PolyTimeAdversary.descBound`) only on domains of polynomially bounded + cardinality. Within that regime it subsumes constants, relabelings, and projections, + and discharges all four per-step machine witnesses of `PolyTimeAdversary` for + small-state oracle machines. + +The machines follow one design: **clear the input moving right, then write the output +backwards moving left**. Clearing onto an empty left stack keeps the tape in canonical +form (`StackTape` normalizes blanks), and writing the output back-to-front while moving +left lands the head on its first symbol, which is exactly the halting configuration +`BiTape.mk₁` — no rewind phases are needed. + +Base machines for *unbounded* domains (symbol relabeling, projections with respect to +paired encodings of infinite types) would follow the same skeleton and remain future +work; together with `EncPolyTime.comp` (from Cslib's proven machine composition) they +would extend the generic witnesses beyond finite domains. The declared frontier of +those combinators — stated in the raw-encoding family form +(`Computability.EncPolyTimeFam`) that the adversary layer consumes — is +`ToMathlib.Computability.MachineCombinators`. +-/ + +@[expose] public section + +universe u v + +namespace Cslib.Turing.SingleTapeTM + +open Relation + +variable {Symbol : Type} [Inhabited Symbol] [Fintype Symbol] + +/-! ## The machines -/ + +/-- Erase the input moving right, then halt: computes the constant `[]`. -/ +def clearComputer : SingleTapeTM Symbol where + State := Unit + q₀ := () + tr _ h := match h with + | some _ => ⟨⟨none, some .right⟩, some ()⟩ + | none => ⟨⟨none, none⟩, none⟩ + +/-- Erase the input moving right, then write `o :: os` backwards moving left: computes +the constant `o :: os`. State `.inl ()` is the clearing phase; state `.inr i` writes +symbol `i` of the output next. -/ +def constComputer (o : Symbol) (os : List Symbol) : SingleTapeTM Symbol where + State := Unit ⊕ Fin (o :: os).length + q₀ := .inl () + tr q h := match q with + | .inl () => match h with + | some _ => ⟨⟨none, some .right⟩, some (.inl ())⟩ + | none => ⟨⟨none, none⟩, some (.inr ⟨os.length, by simp⟩)⟩ + | .inr i => ⟨⟨some (o :: os)[i], if i.val = 0 then none else some .left⟩, + if i.val = 0 then none else some (.inr ⟨i.val - 1, by omega⟩)⟩ + +/-! ## Clearing-phase verification -/ + +omit [Inhabited Symbol] [Fintype Symbol] in +private lemma bitape_head_tail_eq_mk₁ (t : List Symbol) : + (⟨(StackTape.mapSome t).head, ∅, (StackTape.mapSome t).tail⟩ : BiTape Symbol) = + .mk₁ t := by + cases t with + | nil => rfl + | cons a as => rfl + +private lemma clearComputer_clear_steps (l : List Symbol) : + RelatesInSteps (clearComputer (Symbol := Symbol)).TransitionRelation + ⟨some (), .mk₁ l⟩ ⟨some (), .nil⟩ l.length := by + induction l with + | nil => exact .refl _ + | cons c t ih => + refine .head _ ⟨some (), .mk₁ t⟩ _ _ ?_ ih + change (clearComputer (Symbol := Symbol)).step ⟨some (), .mk₁ (c :: t)⟩ = _ + rw [← bitape_head_tail_eq_mk₁ t] + rfl + +private lemma constComputer_clear_steps (o : Symbol) (os l : List Symbol) : + RelatesInSteps (constComputer o os).TransitionRelation + ⟨some (.inl ()), .mk₁ l⟩ ⟨some (.inl ()), .nil⟩ l.length := by + induction l with + | nil => exact .refl _ + | cons c t ih => + refine .head _ ⟨some (.inl ()), .mk₁ t⟩ _ _ ?_ ih + change (constComputer o os).step ⟨some (.inl ()), .mk₁ (c :: t)⟩ = _ + rw [← bitape_head_tail_eq_mk₁ t] + rfl + +/-! ## Writing-phase verification -/ + +private lemma constComputer_enter_step (o : Symbol) (os : List Symbol) : + (constComputer o os).TransitionRelation ⟨some (.inl ()), .nil⟩ + ⟨some (.inr ⟨os.length, by simp⟩), + ⟨none, ∅, .mapSome ((o :: os).drop (os.length + 1))⟩⟩ := by + change (constComputer o os).step _ = _ + rw [show (o :: os).drop (os.length + 1) = [] by simp] + rfl + +private lemma constComputer_write_steps (o : Symbol) (os : List Symbol) (i : ℕ) : + ∀ (hi : i < (o :: os).length), + RelatesInSteps (constComputer o os).TransitionRelation + ⟨some (.inr ⟨i, hi⟩), ⟨none, ∅, .mapSome ((o :: os).drop (i + 1))⟩⟩ + ⟨none, .mk₁ (o :: os)⟩ (i + 1) := by + induction i with + | zero => + intro hi + refine .single ?_ + change (constComputer o os).step _ = _ + rfl + | succ i ih => + intro hi + refine .head _ ⟨some (.inr ⟨i, by omega⟩), + ⟨none, ∅, .mapSome ((o :: os).drop (i + 1))⟩⟩ _ _ ?_ (ih (by omega)) + change (constComputer o os).step _ = _ + rw [List.drop_eq_getElem_cons hi] + rfl + +/-! ## Assembly -/ + +/-- Constant functions are machine-computable in linear time. -/ +def constTimeComputable : (out : List Symbol) → + TimeComputable (Symbol := Symbol) (fun _ => out) + | [] => + { tm := clearComputer + timeBound := fun n => n + 1 + outputsFunInTime := fun l => by + refine ⟨l.length + 1, le_rfl, ?_⟩ + exact RelatesInSteps.tail + (r := (clearComputer (Symbol := Symbol)).TransitionRelation) + ⟨some (), .mk₁ l⟩ ⟨some (), .nil⟩ ⟨none, .mk₁ []⟩ _ + (clearComputer_clear_steps l) rfl } + | o :: os => + { tm := constComputer o os + timeBound := fun n => n + (os.length + 2) + outputsFunInTime := fun l => by + refine ⟨l.length + (1 + (os.length + 1)), by omega, ?_⟩ + exact (constComputer_clear_steps o os l).trans + ((RelatesInSteps.single (constComputer_enter_step o os)).trans + (constComputer_write_steps o os os.length (by simp))) } + +/-- Constant functions are machine-computable in polynomial time. -/ +noncomputable def constPolyTimeComputable (out : List Symbol) : + PolyTimeComputable (Symbol := Symbol) (fun _ => out) where + toTimeComputable := constTimeComputable out + poly := .X + .C (out.length + 2) + bounds n := by + cases out with + | nil => + simp only [constTimeComputable, Polynomial.eval_add, Polynomial.eval_X, + Polynomial.eval_C, List.length_nil] + omega + | cons o os => + simp only [constTimeComputable, Polynomial.eval_add, Polynomial.eval_X, + Polynomial.eval_C, List.length_cons] + omega + +/-- The constant-function machine has at most `out.length + 2` states: one clearing +state plus one writing state per output symbol. -/ +theorem size_constPolyTimeComputable_le (out : List Symbol) : + (constPolyTimeComputable (Symbol := Symbol) out).size ≤ out.length + 2 := by + cases out with + | nil => + change Fintype.card Unit ≤ 2 + simp + | cons o os => + change Fintype.card (Unit ⊕ Fin (o :: os).length) ≤ (o :: os).length + 2 + simp only [Fintype.card_sum, Fintype.card_unit, Fintype.card_fin, List.length_cons] + omega + +/-! ## The finite-table machine + +A machine computing `fun l => if l ∈ S then T l else []` for a *finite* set `S` of +valid inputs: read the input into machine state through the prefix tree of `S` +(clearing as it goes), then on the blank look up the table and write the output +backwards moving left exactly as `constComputer` does. Inputs that stop matching any +prefix of `S` fall into an absorbing junk state and clear to a blank tape. -/ + +section Table + +variable [DecidableEq Symbol] + +omit [Inhabited Symbol] [Fintype Symbol] [DecidableEq Symbol] in +private lemma bitape_mk₁_eq_of_length_pos {t : List Symbol} (h : 0 < t.length) : + (BiTape.mk₁ t : BiTape Symbol) = ⟨some t[0], ∅, .mapSome (t.drop 1)⟩ := by + cases t with + | nil => simp at h + | cons a as => rfl + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- All prefixes of members of `S`: the reading states of `tableComputer`. -/ +def prefixClosure (S : Finset (List Symbol)) : Finset (List Symbol) := + S.biUnion fun s => s.inits.toFinset + +omit [Inhabited Symbol] [Fintype Symbol] in +theorem mem_prefixClosure {S : Finset (List Symbol)} {l : List Symbol} : + l ∈ prefixClosure S ↔ ∃ s ∈ S, l <+: s := by + simp [prefixClosure, List.mem_inits] + +omit [Inhabited Symbol] [Fintype Symbol] in +theorem subset_prefixClosure (S : Finset (List Symbol)) : S ⊆ prefixClosure S := + fun s hs => mem_prefixClosure.mpr ⟨s, hs, List.prefix_refl s⟩ + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- Path independence of falling out of the prefix tree: once the consumed prefix +matches no member of `S`, no extension does either. -/ +theorem append_not_mem_prefixClosure {S : Finset (List Symbol)} {l : List Symbol} + (h : l ∉ prefixClosure S) (b : Symbol) : l ++ [b] ∉ prefixClosure S := by + intro hmem + obtain ⟨s, hs, hpre⟩ := mem_prefixClosure.mp hmem + exact h (mem_prefixClosure.mpr ⟨s, hs, (List.prefix_append l [b]).trans hpre⟩) + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- States of the finite-table machine: reading (tracking the consumed prefix through +the prefix tree of `S`), junk (clearing an unmatched input), or writing symbol `i` of +the table output of `s`. -/ +abbrev TableState (S : Finset (List Symbol)) (T : List Symbol → List Symbol) : Type := + ({l // l ∈ prefixClosure S} ⊕ Unit) ⊕ (Σ s : {s // s ∈ S}, Fin (T s.1).length) + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- The state after consuming the prefix `l`: still reading if `l` can extend to a +member of `S`, junk otherwise. Totality in `l` makes the reading-step lemma uniform +(via `append_not_mem_prefixClosure`). -/ +def readState (S : Finset (List Symbol)) (T : List Symbol → List Symbol) + (l : List Symbol) : TableState S T := + if h : l ∈ prefixClosure S then .inl (.inl ⟨l, h⟩) else .inl (.inr ()) + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- The state entered on reaching the blank at the end of the input with consumed +prefix `l`: write the table output backwards if `l ∈ S` and the output is nonempty, +otherwise halt (the tape is already the blank output). -/ +def enterState (S : Finset (List Symbol)) (T : List Symbol → List Symbol) + (l : List Symbol) : Option (TableState S T) := + if hS : l ∈ S then + if hT : (T l).length = 0 then none + else some (.inr ⟨⟨l, hS⟩, + ⟨(T l).length - 1, by change (T l).length - 1 < (T l).length; omega⟩⟩) + else none + +/-- The finite-table machine: clear the input moving right while tracking the consumed +prefix through the prefix tree of `S`; on the blank, look up the table and write the +output backwards moving left. Computes `fun l => if l ∈ S then T l else []`. -/ +def tableComputer (S : Finset (List Symbol)) (T : List Symbol → List Symbol) : + SingleTapeTM Symbol where + State := TableState S T + q₀ := readState S T [] + tr q h := match q with + | .inl (.inl ⟨l, _⟩) => match h with + | some b => ⟨⟨none, some .right⟩, some (readState S T (l ++ [b]))⟩ + | none => ⟨⟨none, none⟩, enterState S T l⟩ + | .inl (.inr ()) => match h with + | some _ => ⟨⟨none, some .right⟩, some (.inl (.inr ()))⟩ + | none => ⟨⟨none, none⟩, none⟩ + | .inr ⟨s, i⟩ => ⟨⟨some (T s.1)[i], if i.val = 0 then none else some .left⟩, + if i.val = 0 then none else some (.inr ⟨s, ⟨i.val - 1, by omega⟩⟩)⟩ + +/-! ### Reading-phase verification -/ + +private lemma tableComputer_read_step (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) (l : List Symbol) (b : Symbol) (t : List Symbol) : + (tableComputer S T).TransitionRelation + ⟨some (readState S T l), .mk₁ (b :: t)⟩ + ⟨some (readState S T (l ++ [b])), .mk₁ t⟩ := by + change (tableComputer S T).step _ = _ + rw [← bitape_head_tail_eq_mk₁ t] + by_cases h : l ∈ prefixClosure S + · simp only [readState] + rw [dif_pos h] + rfl + · simp only [readState] + rw [dif_neg h, dif_neg (append_not_mem_prefixClosure h b)] + rfl + +private lemma tableComputer_read_steps (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) (t l : List Symbol) : + RelatesInSteps (tableComputer S T).TransitionRelation + ⟨some (readState S T l), .mk₁ t⟩ ⟨some (readState S T (l ++ t)), .nil⟩ + t.length := by + induction t generalizing l with + | nil => rw [List.append_nil]; exact .refl _ + | cons b t ih => + refine .head _ ⟨some (readState S T (l ++ [b])), .mk₁ t⟩ _ _ + (tableComputer_read_step S T l b t) ?_ + have h := ih (l ++ [b]) + rwa [← List.append_cons] at h + +/-! ### Lookup and writing-phase verification -/ + +private lemma tableComputer_blank_step (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) (l : List Symbol) : + (tableComputer S T).TransitionRelation + ⟨some (readState S T l), .nil⟩ ⟨enterState S T l, .nil⟩ := by + change (tableComputer S T).step _ = _ + by_cases h : l ∈ prefixClosure S + · simp only [readState] + rw [dif_pos h] + rfl + · simp only [readState, enterState] + rw [dif_neg h, dif_neg fun hS => h (subset_prefixClosure S hS)] + rfl + +private lemma tableComputer_write_steps (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) (s : {s // s ∈ S}) (i : ℕ) : + ∀ (hi : i < (T s.1).length), + RelatesInSteps (tableComputer S T).TransitionRelation + ⟨some (.inr ⟨s, ⟨i, hi⟩⟩), ⟨none, ∅, .mapSome ((T s.1).drop (i + 1))⟩⟩ + ⟨none, .mk₁ (T s.1)⟩ (i + 1) := by + induction i with + | zero => + intro hi + refine .single ?_ + change (tableComputer S T).step _ = _ + rw [bitape_mk₁_eq_of_length_pos hi] + rfl + | succ i ih => + intro hi + refine .head _ ⟨some (.inr ⟨s, ⟨i, by omega⟩⟩), + ⟨none, ∅, .mapSome ((T s.1).drop (i + 1))⟩⟩ _ _ ?_ (ih (by omega)) + change (tableComputer S T).step _ = _ + rw [List.drop_eq_getElem_cons hi] + rfl + +private lemma tableComputer_finish_within (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) (l : List Symbol) : + RelatesWithinSteps (tableComputer S T).TransitionRelation + ⟨some (readState S T l), .nil⟩ + ⟨none, .mk₁ (if l ∈ S then T l else [])⟩ + ((S.sup fun s => (T s).length) + 1) := by + have h1 := tableComputer_blank_step S T l + simp only [enterState] at h1 + by_cases hS : l ∈ S + · by_cases hT : (T l).length = 0 + · rw [dif_pos hS, dif_pos hT] at h1 + rw [if_pos hS, List.length_eq_zero_iff.mp hT] + exact RelatesWithinSteps.of_le (.single h1) (by omega) + · rw [dif_pos hS, dif_neg hT] at h1 + rw [if_pos hS] + have h2 := tableComputer_write_steps S T ⟨l, hS⟩ ((T l).length - 1) + (by change (T l).length - 1 < (T l).length; omega) + rw [show (T l).length - 1 + 1 = (T l).length from by omega, + List.drop_length] at h2 + refine RelatesWithinSteps.of_le + ((RelatesWithinSteps.single h1).trans + (RelatesWithinSteps.of_relatesInSteps h2)) ?_ + have hle : (T l).length ≤ S.sup fun s => (T s).length := + Finset.le_sup (f := fun s => (T s).length) hS + omega + · rw [dif_neg hS] at h1 + rw [if_neg hS] + exact RelatesWithinSteps.of_le (.single h1) (by omega) + +/-! ### Assembly -/ + +/-- Finite tables are machine-computable in linear time: `n` steps to read the input +into state, then at most one plus the longest table output to write the result. -/ +def tableTimeComputable (S : Finset (List Symbol)) (T : List Symbol → List Symbol) : + TimeComputable (Symbol := Symbol) (fun l => if l ∈ S then T l else []) where + tm := tableComputer S T + timeBound n := n + ((S.sup fun s => (T s).length) + 1) + outputsFunInTime l := by + exact (RelatesWithinSteps.of_relatesInSteps + (tableComputer_read_steps S T l [])).trans (tableComputer_finish_within S T l) + +/-- Finite tables are machine-computable in polynomial time. -/ +noncomputable def tablePolyTimeComputable (S : Finset (List Symbol)) + (T : List Symbol → List Symbol) : + PolyTimeComputable (Symbol := Symbol) (fun l => if l ∈ S then T l else []) where + toTimeComputable := tableTimeComputable S T + poly := .X + .C ((S.sup fun s => (T s).length) + 1) + bounds n := by + simp only [tableTimeComputable, Polynomial.eval_add, Polynomial.eval_X, + Polynomial.eval_C] + omega + +/-! ### Description size of the table machine + +The table machine's *time* is linear, but its *state count* — the reading prefix tree +plus the writing states — grows with the total length of the valid inputs and their +table outputs. This is the advice a table smuggles: a family of tables over +exponentially large domains has exponential description size, which is why witness +families must carry an explicit size bound (`Computability.EncPolyTime.size`). -/ + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- The prefix tree of a finite set of strings has at most `∑ (length + 1)` nodes. -/ +theorem card_prefixClosure_le (S : Finset (List Symbol)) : + (prefixClosure S).card ≤ ∑ s ∈ S, (s.length + 1) := + Finset.card_biUnion_le.trans (Finset.sum_le_sum fun s _ => + (List.toFinset_card_le _).trans (by simp)) + +omit [Inhabited Symbol] [Fintype Symbol] in +/-- The state count of the finite-table machine: prefix-tree nodes, the junk state, and +one writing state per output symbol. -/ +theorem card_tableState (S : Finset (List Symbol)) (T : List Symbol → List Symbol) : + Fintype.card (TableState S T) = + ((prefixClosure S).card + 1) + ∑ s ∈ S, (T s).length := by + simp [TableState, Fintype.card_sigma, Finset.sum_attach S fun s => (T s).length] + +end Table + +end Cslib.Turing.SingleTapeTM + +namespace Computability.EncPolyTime + +/-- Constant functions are polynomial-time computable relative to any encodings. -/ +noncomputable def const {α : Type u} {β : Type v} (ea : α → List Bool) + (eb : β → List Bool) (c : β) : EncPolyTime ea eb (fun _ => c) where + toFun _ := eb c + polyTime := Cslib.Turing.SingleTapeTM.constPolyTimeComputable (eb c) + map_encode _ := rfl + +/-- **Any function with a finite domain is polynomial-time computable** relative to an +injective input encoding: the machine reads the input into state through the prefix +tree of the finitely many valid encodings, then writes the encoded output. The trade is +time for description: the machine has one reading state per prefix of a valid input +(`size_ofFintype_le`), so families of these witnesses respect a polynomial advice bound +only on domains of polynomially bounded cardinality. Instantiated at the `boolify` of a +`PackedEncoding` (injective by `PackedEncoding.boolify_injective`), this discharges the +per-step machine witnesses of small-state oracle machines. -/ +noncomputable def ofFintype {α : Type u} {β : Type v} [Fintype α] + (ea : α → List Bool) (hea : Function.Injective ea) (eb : β → List Bool) + (f : α → β) : EncPolyTime ea eb f where + toFun l := if l ∈ Finset.univ.image ea + then ((Function.partialInv ea l).map fun a => eb (f a)).getD [] else [] + polyTime := Cslib.Turing.SingleTapeTM.tablePolyTimeComputable (Finset.univ.image ea) + fun l => ((Function.partialInv ea l).map fun a => eb (f a)).getD [] + map_encode a := by + rw [if_pos (Finset.mem_image_of_mem ea (Finset.mem_univ a)), + Function.partialInv_left hea] + rfl + +/-- The finite-table witness runs in time linear in the input plus the longest encoded +output: with a pointwise bound `B` on the output encodings, evaluation at `k` is at +most `k + (B + 1)`. This is the shape that discharges the uniform per-step time bounds +of `PolyTimeAdversary`. -/ +theorem time_ofFintype_eval_le {α : Type u} {β : Type v} [Fintype α] + {ea : α → List Bool} (hea : Function.Injective ea) {eb : β → List Bool} + {f : α → β} {B : ℕ} (hB : ∀ a, (eb (f a)).length ≤ B) (k : ℕ) : + (ofFintype ea hea eb f).time.eval k ≤ k + (B + 1) := by + have htime : (ofFintype ea hea eb f).time = + .X + .C (((Finset.univ.image ea).sup fun l => + (((Function.partialInv ea l).map fun a => eb (f a)).getD []).length) + 1) := rfl + have hsup : ((Finset.univ.image ea).sup fun l => + (((Function.partialInv ea l).map fun a => eb (f a)).getD []).length) ≤ B := by + refine Finset.sup_le fun l hl => ?_ + obtain ⟨a, -, rfl⟩ := Finset.mem_image.mp hl + rw [Function.partialInv_left hea] + exact hB a + rw [htime, Polynomial.eval_add, Polynomial.eval_X, Polynomial.eval_C] + omega + +/-- The constant-function witness has at most `(eb c).length + 2` machine states. -/ +theorem size_const_le {α : Type u} {β : Type v} (ea : α → List Bool) + (eb : β → List Bool) (c : β) : + (const ea eb c).size ≤ (eb c).length + 2 := + Cslib.Turing.SingleTapeTM.size_constPolyTimeComputable_le (eb c) + +/-- The description size of the finite-table witness: the machine hard-codes the whole +input/output table, so its state count grows with the **total encoded length of the +domain** — for a domain of exponential cardinality this is exponential advice, however +fast the machine runs. Families of `ofFintype` witnesses are therefore only usable +where the domain cardinality is polynomially bounded in the security parameter. -/ +theorem size_ofFintype_le {α : Type u} {β : Type v} [Fintype α] + {ea : α → List Bool} (hea : Function.Injective ea) (eb : β → List Bool) + (f : α → β) : + (ofFintype ea hea eb f).size ≤ + (∑ a : α, ((ea a).length + 1)) + 1 + ∑ a : α, (eb (f a)).length := by + change Fintype.card (Cslib.Turing.SingleTapeTM.TableState (Finset.univ.image ea) + fun l => ((Function.partialInv ea l).map fun a => eb (f a)).getD []) ≤ _ + rw [Cslib.Turing.SingleTapeTM.card_tableState] + have hinj : ∀ a ∈ Finset.univ, ∀ b ∈ Finset.univ, ea a = ea b → a = b := + fun a _ b _ h => hea h + have h1 : (Cslib.Turing.SingleTapeTM.prefixClosure (Finset.univ.image ea)).card ≤ + ∑ a : α, ((ea a).length + 1) := by + refine (Cslib.Turing.SingleTapeTM.card_prefixClosure_le _).trans (le_of_eq ?_) + rw [Finset.sum_image hinj] + have h2 : (∑ s ∈ Finset.univ.image ea, + (((Function.partialInv ea s).map fun a => eb (f a)).getD []).length) = + ∑ a : α, (eb (f a)).length := by + rw [Finset.sum_image hinj] + exact Finset.sum_congr rfl fun a _ => by rw [Function.partialInv_left hea]; rfl + omega + +/-- Discharge form of `size_ofFintype_le`: a cardinality bound on the domain and +pointwise bounds on both encodings give the table size bound consumed by +`PolyTimeAdversary.descBound` fields. -/ +theorem size_ofFintype_le_of_bounds {α : Type u} {β : Type v} [Fintype α] + {ea : α → List Bool} (hea : Function.Injective ea) {eb : β → List Bool} + {f : α → β} {A La B : ℕ} (hcard : Fintype.card α ≤ A) + (hla : ∀ a, (ea a).length ≤ La) (hB : ∀ a, (eb (f a)).length ≤ B) : + (ofFintype ea hea eb f).size ≤ A * (La + 1 + B) + 1 := by + refine (size_ofFintype_le hea eb f).trans ?_ + have h1 : (∑ a : α, ((ea a).length + 1)) ≤ Fintype.card α * (La + 1) := by + refine (Finset.sum_le_card_nsmul _ _ (La + 1) fun a _ => ?_).trans_eq (by + simp [smul_eq_mul]) + have := hla a; omega + have h2 : (∑ a : α, (eb (f a)).length) ≤ Fintype.card α * B := by + refine (Finset.sum_le_card_nsmul _ _ B fun a _ => hB a).trans_eq (by + simp [smul_eq_mul]) + have h3 : Fintype.card α * (La + 1) ≤ A * (La + 1) := Nat.mul_le_mul_right _ hcard + have h4 : Fintype.card α * B ≤ A * B := Nat.mul_le_mul_right _ hcard + calc (∑ a : α, ((ea a).length + 1)) + 1 + ∑ a : α, (eb (f a)).length + ≤ A * (La + 1) + 1 + A * B := by omega + _ = A * (La + 1 + B) + 1 := by ring + +end Computability.EncPolyTime diff --git a/ToMathlib/Data/BitVec.lean b/ToMathlib/Data/BitVec.lean new file mode 100644 index 000000000..91b3b5fb2 --- /dev/null +++ b/ToMathlib/Data/BitVec.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +module + +public import Mathlib.Data.BitVec + +/-! +# Overwriting a Single Bit of a Bitvector + +This file defines `BitVec.overwriteBit i b m`, the bitvector `m` with its `i`-th least +significant bit replaced by `b`, together with its `getLsbD` description and the involution +`(m, b) ↦ (m with bit i overwritten by b, original bit i of m)` on `BitVec n × Bool`. +The involution is the change of variables underlying uniform-distribution splitting +arguments: sampling a uniform bitvector and reading its `i`-th bit is equivalent to +sampling a uniform bit and a uniform bitvector and overwriting the `i`-th bit. +-/ + +@[expose] public section + +namespace BitVec + +variable {n : ℕ} + +/-- Overwrite the `i`-th least significant bit of `m` with `b`, leaving all other bits +unchanged. Out-of-range positions (`n ≤ i`) leave `m` unmodified. -/ +def overwriteBit (i : ℕ) (b : Bool) (m : BitVec n) : BitVec n := + if b then m ||| twoPow n i else m &&& ~~~ twoPow n i + +@[simp] theorem getLsbD_overwriteBit_self {i : ℕ} (hi : i < n) (b : Bool) (m : BitVec n) : + (overwriteBit i b m).getLsbD i = b := by + cases b <;> simp [overwriteBit, hi] + +@[simp] theorem getLsbD_overwriteBit_of_ne {i j : ℕ} (h : j ≠ i) (b : Bool) (m : BitVec n) : + (overwriteBit i b m).getLsbD j = m.getLsbD j := by + rcases lt_or_ge j n with hj | hj + · cases b <;> simp [overwriteBit, h, hj] + · cases b <;> simp [overwriteBit, getLsbD_of_ge _ _ hj] + +theorem getLsbD_overwriteBit (i j : ℕ) (b : Bool) (m : BitVec n) : + (overwriteBit i b m).getLsbD j = if j = i ∧ i < n then b else m.getLsbD j := by + rcases eq_or_ne j i with rfl | h + · rcases lt_or_ge j n with hj | hj + · rw [if_pos ⟨rfl, hj⟩] + exact getLsbD_overwriteBit_self hj b m + · cases b <;> simp [overwriteBit, getLsbD_of_ge _ _ hj, Nat.not_lt.mpr hj] + · simp [h] + +/-- Overwriting the same position twice keeps only the outer write. -/ +@[simp] theorem overwriteBit_overwriteBit (i : ℕ) (b c : Bool) (m : BitVec n) : + overwriteBit i c (overwriteBit i b m) = overwriteBit i c m := + eq_of_getLsbD_eq_iff.mpr fun j _ => by + simp only [getLsbD_overwriteBit] + split <;> simp_all + +/-- Overwriting a bit with its current value is the identity. -/ +@[simp] theorem overwriteBit_getLsbD_self (i : ℕ) (m : BitVec n) : + overwriteBit i (m.getLsbD i) m = m := + eq_of_getLsbD_eq_iff.mpr fun j _ => by + rw [getLsbD_overwriteBit] + split <;> simp_all + +/-- Pairing a bitvector with its `i`-th bit while overwriting that bit with the paired +value is an involution on `BitVec n × Bool`. This is the change of variables that splits +a uniform bitvector by the value of its `i`-th bit. -/ +theorem involutive_overwriteBit_pair {i : ℕ} (hi : i < n) : + Function.Involutive fun p : BitVec n × Bool => + (overwriteBit i p.2 p.1, p.1.getLsbD i) := by + rintro ⟨m, b⟩ + simp [getLsbD_overwriteBit_self hi] + +end BitVec From 39f9eabce0aab44ed545d9ffa7cb4a99aebf3b91 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Sat, 25 Jul 2026 15:49:34 -0500 Subject: [PATCH 2/5] feat(coinductive): IsPolyTime and MachineAdversary over the DynComputation carrier Rebuild the TM-grounded polynomial-time adversary core on the merged machine layer: - The TM-facing step maps (expose, updateFlat, output, stepD) are total functions derived from the machine's one-step view, spelled with Sum combinators so they transport definitionally to machines sharing the same dynamics; the readout-stability field is gone (returns are absorbing by construction), and resolution within the round budget is a theorem (PolyTimeWitness.resolvesIn), handler-free via ResolvesIn. - MachineAdversary/PolyTimeWitness/IsPolyTime retarget runK to runWith and the fuelled Implements to DynComputation.ImplementsWithin; the master transfer equation and the no-mass-on-fuel-exhaustion lemma are inherited readings of the upstream run theory. - Closure combinators simplify: replacing init leaves runs unchanged via unroll_setInit (upstream candidate), output post-composition is upstream mapResult with unroll_mapResult, and the precomposition implements-proofs reduce to input reindexing. Co-Authored-By: Claude Fable 5 --- VCVio.lean | 6 + VCVio/OracleComp/Coinductive/PolyTime.lean | 562 ++++++++++++++++++ .../Coinductive/PolyTimeClosure.lean | 405 +++++++++++++ 3 files changed, 973 insertions(+) create mode 100644 VCVio/OracleComp/Coinductive/PolyTime.lean create mode 100644 VCVio/OracleComp/Coinductive/PolyTimeClosure.lean diff --git a/VCVio.lean b/VCVio.lean index 8467a3733..d3c1fcb54 100644 --- a/VCVio.lean +++ b/VCVio.lean @@ -5,6 +5,7 @@ import VCVio.CryptoFoundations.AsymmEncAlg.INDCPA.GenericLift import VCVio.CryptoFoundations.AsymmEncAlg.INDCPA.OneTime import VCVio.CryptoFoundations.AsymmEncAlg.INDCPA.Oracle import VCVio.CryptoFoundations.Asymptotics.Negligible +import VCVio.CryptoFoundations.Asymptotics.PolyTime import VCVio.CryptoFoundations.Asymptotics.ReductionCost import VCVio.CryptoFoundations.Asymptotics.Security import VCVio.CryptoFoundations.CommitmentScheme @@ -102,8 +103,13 @@ import VCVio.Interaction.UC.StdDoBridge import VCVio.OracleComp.Coercions.Add import VCVio.OracleComp.Coercions.SubSpec import VCVio.OracleComp.Coinductive.Bridge +import VCVio.OracleComp.Coinductive.CoinFold import VCVio.OracleComp.Coinductive.DynSystem import VCVio.OracleComp.Coinductive.Machine +import VCVio.OracleComp.Coinductive.PolyTime +import VCVio.OracleComp.Coinductive.PolyTimeClosure +import VCVio.OracleComp.Coinductive.PolyTimeConstructions +import VCVio.OracleComp.Coinductive.PolyTimeNontrivial import VCVio.OracleComp.Coinductive.Responder import VCVio.OracleComp.Coinductive.WiredRun import VCVio.OracleComp.Constructions.BitVec diff --git a/VCVio/OracleComp/Coinductive/PolyTime.lean b/VCVio/OracleComp/Coinductive/PolyTime.lean new file mode 100644 index 000000000..82435c0f9 --- /dev/null +++ b/VCVio/OracleComp/Coinductive/PolyTime.lean @@ -0,0 +1,562 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.OracleComp.Coinductive.Machine +import ToMathlib.Computability.BitEncoding + +/-! +# Turing-Machine-Grounded Polynomial-Time Adversaries + +A `MachineAdversary bd` is a family of oracle machines (`OracleMachine`), indexed by a +security parameter, whose step functions are each Turing-machine computable in +polynomial time (via `Computability.EncPolyTimeFam`, grounded in Cslib's single-tape +machines) relative to **pinned canonical boundary encodings** `bd : BoundaryData`. +Defining polynomial time on machines rather than on `OracleComp` programs directly is +what makes a Turing-machine grounding possible: a machine is a state type with an +initialization and a one-step `view`, from which the TM-facing total maps witnessed +here (`expose`, `updateFlat`, `output`) are derived — each computable by a concrete +machine on encoded states, whereas the continuations of a program tree have no bounded +syntactic presentation. + +`OracleComp.IsPolyTime bd oa` holds when some adversary carries a `PolyTimeWitness` +for the program family `oa`: it implements `oa` (`OracleMachine.Implements`) within its +round budget, and `oa` is syntactically query-bounded by that budget. It is the +intended instantiation of the `isPPT` predicate of `SecurityGame.secureAgainst`. + +## Model + +The adversary model is **non-uniform P/poly relative to a fixed canonical +representation**. The pieces, and why each exists: + +* **Canonical boundaries** (`BoundaryData`: input, output, and oracle-interface + encodings, all fixed-width `Computability.BitEncFam`). Without pinning these, + "polynomial time relative to *some* encoding" is vacuous: the encoding + `enc x := std x ++ block (f x)` caches any `f` inside the representation, and every + machine witness degenerates to a small projection — the class would contain every + function, making hardness assumptions such as `PRGScheme.PRGSecure` unsatisfiable. + The oracle-answer encoding is an equally real caching channel and is pinned for the + same reason. Statement-site discipline: `bd` is always an explicit pinned parameter + of a security definition, never existentially quantified and never adversary-chosen + (the sole documented exemption is a multi-phase adversary's *own* cross-phase state, + which its phases share — every bit cached there was produced by a witnessed machine + from canonical inputs and answers). The registry of canonical constructors + (`BitEncFam.const/bitVec/pair/option`) is deliberately tiny and structural; theorems + mean "secure against P/poly relative to the standard representation". +* **The `1^n` convention, formalized.** Katz–Lindell define PPT as polynomial in the + *input length* and reconcile it with "polynomial in `n`" by handing algorithms the + security parameter in unary. Here the boundary widths are polynomially bounded by + definition (`BitEncFam.widBound`), so the two readings agree — the width bound *is* + the `1^n` convention. +* **Machine-internal freedom.** The state representation (`StrEncFam`, variable-width, + polynomially length-bounded, existential in the bundle) is the machine's choice of + data structure. This is harmless by construction: every bit entering a state + encoding is written by a witnessed step machine from a canonical input or a + canonical oracle answer, so a crafted state encoding can only cache what the + machines already computed within their budgets. +* **Resources.** Rounds (`steps`), state length (`StrEncFam.bound`), per-step time + (`EncPolyTimeFam.time`), and description size (`EncPolyTimeFam.size` — the advice + bound; time bounds alone admit lookup tables with one state per input, i.e. + unbounded advice). All four are single polynomials uniform across the family: + non-uniform machines, P/poly-style, with uniformly polynomial bounds. A uniform + variant (one machine reading `n`) is deliberately out of scope rather than stubbed. +* Bounds are functions of `n` alone, matching `PolyQueries`; boundary widths being + `poly(n)` is what makes this equivalent to input-length-based bounds on game inputs. + +## Universes + +The coalgebra layer (`VCVio.OracleComp.Coinductive.DynSystem`, +`VCVio.OracleComp.Coinductive.Machine`) is universe-polymorphic at `OracleSpec.{u, u}` +(single-universe, forced by `SPMF : Type u → Type u`). This file and everything above +it (closure properties, concrete constructions, asymptotic security) is pinned to +`OracleSpec.{0, 0}` and `Type`: Cslib's single-tape machines, the bit-string +encodings, `Polynomial ℕ`, and the program logic's `wp` all live at `Type 0`. +-/ + +open OracleSpec OracleComp Computability + +variable {ι : ℕ → Type} + +/-! ## Canonical interface encodings -/ + +/-- Turing-machine-facing canonical encodability of an oracle interface: fixed-width +encodings of the query indices and of the typed query/answer pairs. The answer +component encodes the dependent pair `⟨t, r⟩` so that a machine can consume answers of +varying type through one representation. Pinned per spec family: adversary-chosen +answer encodings would be a caching channel (see the module docstring). -/ +structure OracleSpec.InterfaceBitEnc (spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)) where + /-- Canonical fixed-width encoding of the query index type. -/ + encQuery : BitEncFam ι + /-- Canonical fixed-width encoding of typed query/answer pairs. -/ + encAns : BitEncFam (fun n => (t : ι n) × (spec n).Range t) + +/-- The canonical coin-oracle interface: queries have width `0` (the index type is +`Unit`), answers width `1` (the coin bit). -/ +noncomputable def OracleSpec.InterfaceBitEnc.coin : + InterfaceBitEnc (fun _ => coinSpec) where + encQuery := .const Unit + encAns := + { wid := fun _ => 1 + widBound := .C 1 + wid_le := fun _ => by simp + enc := fun _ a => [a.2] + len_eq := fun _ _ => rfl + enc_injective := fun n a₁ a₂ h => by + rcases a₁ with ⟨⟨⟩, b₁⟩ + rcases a₂ with ⟨⟨⟩, b₂⟩ + simpa using h } + +/-- The pinned boundary data of a polynomial-time program family: canonical fixed-width +encodings of its inputs and outputs, and the canonical interface encoding of its oracle. +Always an explicit parameter of security statements — never existential (see the module +docstring). -/ +structure BoundaryData (spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)) (α β : ℕ → Type) where + /-- Canonical input encoding. -/ + eIn : BitEncFam α + /-- Canonical output encoding. -/ + eOut : BitEncFam β + /-- Canonical oracle-interface encoding. -/ + eIface : OracleSpec.InterfaceBitEnc spec + +namespace BoundaryData + +variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β γ : ℕ → Type} + +/-- Replace the input boundary. -/ +def withIn (bd : BoundaryData spec α β) (eIn' : BitEncFam γ) : BoundaryData spec γ β := + ⟨eIn', bd.eOut, bd.eIface⟩ + +/-- Replace the output boundary. -/ +def withOut (bd : BoundaryData spec α β) (eOut' : BitEncFam γ) : BoundaryData spec α γ := + ⟨bd.eIn, eOut', bd.eIface⟩ + +/-- Replace the interface boundary. -/ +def withIface (bd : BoundaryData spec α β) (eIface' : OracleSpec.InterfaceBitEnc spec) : + BoundaryData spec α β := + ⟨bd.eIn, bd.eOut, eIface'⟩ + +@[simp] theorem withIn_eIn (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withIn e).eIn = e := rfl + +@[simp] theorem withIn_eOut (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withIn e).eOut = bd.eOut := rfl + +@[simp] theorem withOut_eOut (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withOut e).eOut = e := rfl + +@[simp] theorem withOut_eIn (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withOut e).eIn = bd.eIn := rfl + +@[simp] theorem withIn_eIface (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withIn e).eIface = bd.eIface := rfl + +@[simp] theorem withOut_eIface (bd : BoundaryData spec α β) (e : BitEncFam γ) : + (bd.withOut e).eIface = bd.eIface := rfl + +@[simp] theorem withIface_eIface (bd : BoundaryData spec α β) + (e : OracleSpec.InterfaceBitEnc spec) : (bd.withIface e).eIface = e := rfl + +@[simp] theorem withIface_eIn (bd : BoundaryData spec α β) + (e : OracleSpec.InterfaceBitEnc spec) : (bd.withIface e).eIn = bd.eIn := rfl + +@[simp] theorem withIface_eOut (bd : BoundaryData spec α β) + (e : OracleSpec.InterfaceBitEnc spec) : (bd.withIface e).eOut = bd.eOut := rfl + +end BoundaryData + +/-- Boundary data over the coin oracle: the common case for textbook adversaries (the +coin oracle is the random bit tape). -/ +noncomputable def BoundaryData.coin {α β : ℕ → Type} (eIn : BitEncFam α) + (eOut : BitEncFam β) : BoundaryData (fun _ => coinSpec) α β := + ⟨eIn, eOut, .coin⟩ + +/-! ## TM-facing total step maps + +A `DynComputation`'s one-step `view` returns either a value or a query position with a +*continuation function* — which has no bounded syntactic presentation. The maps a +Turing machine witnesses are total flattenings derived from `view`: the readout +(`output`), the exposed query (`expose`, with a `default` on returned states), and the +tagged-answer update (`updateFlat`, identity on mismatched tags and returned states). +The identity completions are canonical, not modeling commitments: the run semantics +only ever consults them in the matching branch, and any other completion would certify +the same machines. -/ + +section StepMaps + +variable {ι : Type} + +namespace OracleComp.OracleMachine + +variable {spec : OracleSpec.{0, 0} ι} {α β : Type} + +/- The accessors below are spelled with `Sum` combinators rather than `match` so that +they transport definitionally to machines sharing the same dynamics: an auto-generated +matcher abstracts the machine (blocking unification across distinct input types), +whereas plain combinator applications reduce by congruence over the shared `view`. -/ + +/-- The machine's readout: the returned value, if the state has resolved. -/ +def output (M : OracleMachine spec α β) (s : M.State) : Option β := + (M.view s).getLeft? + +/-- The query exposed by an unresolved state (`default` on returned states, which the +run semantics never consults). -/ +def expose [Inhabited ι] (M : OracleMachine spec α β) (s : M.State) : ι := + (M.view s).elim (fun _ => (default : ι)) Sigma.fst + +/-- The machine's dependent continuation flattened to a total function on tagged +query/answer pairs, as a Turing machine must consume it: answers tagged with the +exposed query advance the state, mismatched tags and returned states act as the +identity. -/ +def updateFlat [DecidableEq ι] (M : OracleMachine spec α β) : + M.State × ((t : ι) × spec.Range t) → M.State := fun p => + (M.view p.1).elim (fun _ => p.1) + (fun q => if h : p.2.1 = q.1 then q.2 (h ▸ p.2.2) else p.1) + +@[simp] theorem output_of_view_return (M : OracleMachine spec α β) {s : M.State} {b : β} + (hview : M.view s = Sum.inl b) : M.output s = some b := by + simp [output, hview] + +@[simp] theorem expose_of_view_query [Inhabited ι] (M : OracleMachine spec α β) + {s : M.State} {q : spec.toPFunctor.Obj M.State} (hview : M.view s = Sum.inr q) : + M.expose s = q.1 := by + simp only [expose, hview] + rfl + +/-- On an unresolved state, the flattened update at the exposed query applies the +continuation: the coherence between `updateFlat` and the run semantics. -/ +theorem updateFlat_of_view_query [DecidableEq ι] (M : OracleMachine spec α β) {s : M.State} + {t : ι} {next : spec.Range t → M.State} (hview : M.view s = Sum.inr ⟨t, next⟩) + (r : spec.Range t) : M.updateFlat (s, ⟨t, r⟩) = next r := by + simp [updateFlat, hview] + +end OracleComp.OracleMachine + +end StepMaps + +/-! ## Machine adversaries -/ + +variable [∀ n, DecidableEq (ι n)] [∀ n, Inhabited (ι n)] + +/-- A Turing-machine-grounded polynomial-time adversary at pinned boundaries `bd`: +a family of oracle machines indexed by the security parameter, together with + +* a polynomial round budget `steps`; +* an injective, polynomially length-bounded string representation of the machine + states (`state : Computability.StrEncFam` — machine-internal, hence variable-width + and freely chosen); +* uniform polynomial-time machine families (`Computability.EncPolyTimeFam`, each + bundling per-parameter Cslib machine witnesses with one time and one description + polynomial) for the four step functions, against the canonical boundary encodings. + +There is no readout-stability field: returns are absorbing by construction in +`DynComputation` (a returned position has no directions), resolution within the round +budget is *derivable* for any adversary that implements a program family +(`PolyTimeWitness.resolvesIn`), and probabilistic resolution likewise +(`DynComputation.ImplementsWithin.probOutput_none_runWithInput`). The witnesses are +data (they carry concrete machines); the Prop-level predicate on program families is +`OracleComp.IsPolyTime`. -/ +structure MachineAdversary {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + (bd : BoundaryData spec α β) where + /-- The machine at each security parameter. -/ + M : (n : ℕ) → OracleMachine (spec n) (α n) (β n) + /-- Polynomial bound on the number of oracle rounds. -/ + steps : Polynomial ℕ + /-- The machine-internal state representation: injective raw bit strings with a + polynomial length bound (over all states). -/ + state : StrEncFam (fun n => (M n).State) + /-- The initialization map is uniformly polynomial-time computable. -/ + initF : EncPolyTimeFam bd.eIn.enc state.enc (fun n => (M n).init) + /-- The query-selection map is uniformly polynomial-time computable. -/ + exposeF : EncPolyTimeFam state.enc bd.eIface.encQuery.enc (fun n => (M n).expose) + /-- The (flattened) state-update map is uniformly polynomial-time computable, on the + append encoding of state/answer pairs. -/ + updateF : EncPolyTimeFam (state.pairVar bd.eIface.encAns).enc state.enc + (fun n => (M n).updateFlat) + /-- The readout map is uniformly polynomial-time computable, into the canonical + optional output encoding. -/ + outputF : EncPolyTimeFam state.enc (bd.eOut.option).enc (fun n => (M n).output) + +namespace MachineAdversary + +variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + {bd : BoundaryData spec α β} + +/-- A single polynomial dominating every per-step running time. -/ +noncomputable def stepTime (D : MachineAdversary bd) : Polynomial ℕ := + D.initF.time + D.exposeF.time + D.updateF.time + D.outputF.time + +/-- A single polynomial dominating every witness description size — the total advice. -/ +noncomputable def descBound (D : MachineAdversary bd) : Polynomial ℕ := + D.initF.size + D.exposeF.size + D.updateF.size + D.outputF.size + +/-! ## Run semantics and the implements relation -/ + +/-- The adversary's run at security parameter `n`: the early-stopping run of the +machine at its round budget, against a query implementation in any monad. The +probabilistic run is the `m := SPMF` instance (`H : ProbHandler (spec n)`); a run +against a stateful challenger oracle is the `m := StateT σ SPMF` instance. -/ +noncomputable def exec (D : MachineAdversary bd) (n : ℕ) {m : Type → Type} [Monad m] + (H : QueryImpl (spec n) m) (x : α n) : m (Option (β n)) := + (D.M n).runWith H (D.steps.eval n) ((D.M n).init x) + +/-- The adversary implements a program family when each machine implements the +program at the round budget (`DynComputation.ImplementsWithin`). -/ +def Implements (D : MachineAdversary bd) + (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) : Prop := + ∀ n, (D.M n).ImplementsWithin (oa n) (D.steps.eval n) + +@[inherit_doc Implements] +scoped notation:50 D " ⊨ " oa => MachineAdversary.Implements D oa + +/-- **Master transfer equation**: the run of an implementing adversary computes the +program's `simulateQ` semantics, in every lawful monad. Game-level advantage transfers +— including against stateful challenger oracles at `m := StateT σ SPMF` — are +instances. -/ +theorem exec_eq_of_implements {D : MachineAdversary bd} + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : D ⊨ oa) + (n : ℕ) {m : Type → Type} [Monad m] [LawfulMonad m] + (H : QueryImpl (spec n) m) (x : α n) : + D.exec n H x = some <$> simulateQ H (oa n x) := + (h n).runWithInput_eq H x + +/-- An implementing adversary's run resolves along every randomized handler: no mass on +an unresolved readout. Probabilistic steadiness is a consequence of the implements +equation (`DynComputation.ImplementsWithin.probOutput_none_runWithInput`), not an +extra field. -/ +theorem probOutput_none_exec {D : MachineAdversary bd} + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : D ⊨ oa) + (n : ℕ) (H : ProbHandler (spec n)) (x : α n) : + Pr[= none | D.exec n H x] = 0 := + (h n).probOutput_none_runWithInput H x + +end MachineAdversary + +open scoped MachineAdversary + +/-! ## The polynomial-time certificate and predicate -/ + +/-- A certificate that the program family `oa` is polynomial time at boundaries `bd`: +an adversary together with proofs that it implements `oa` within its round budget and +that `oa` itself respects that budget syntactically. Proof-relevant data, mirroring +`OracleComp.PolyQueries`; the Prop-level predicate is `OracleComp.IsPolyTime`. + +The `queryBound` field is definitional, not a wart: "makes polynomially many queries" +is part of what polynomial time means, it feeds the `PolyQueries` bridge directly, and +every route to `implements` produces it as an input or byproduct. It is *conjectured* +to follow from `implements` alone, but the extraction is genuinely hard: + +* A counting handler cannot do it. `Implements` quantifies over + `ProbHandler spec = QueryImpl spec SPMF`, and `SPMF` has no writer component, so no + handler admissible in the quantification observes query counts. +* The plausible route drives the program along *scaled* handlers `H_ε` (each answer + distribution scaled to total mass `ε ∈ (0, 1]`): the output mass of the fuelled run + is a polynomial of degree at most the budget in `ε`, while a program family + violating the bound contributes a positive higher-degree monomial to the mass of + `some <$> simulateQ H_ε`, and agreement on `(0, 1]` forces equal coefficients. The + coefficient-extraction step over `ℝ≥0∞` is the hard part; it is recorded here as a + conjecture rather than smuggled as an axiom. -/ +structure PolyTimeWitness {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + (bd : BoundaryData spec α β) + (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) where + /-- The machine adversary. -/ + A : MachineAdversary bd + /-- The adversary implements the program family within its round budget. -/ + implements : A ⊨ oa + /-- The program family syntactically respects the round budget. -/ + queryBound : ∀ n x, OracleComp.IsTotalQueryBound (oa n x) (A.steps.eval n) + +/-- A program family is polynomial time at pinned boundaries `bd` when it carries a +`PolyTimeWitness`. This is the intended `isPPT` instantiation for +`SecurityGame.secureAgainst`; `bd` must be a fixed parameter of the enclosing security +statement (see the module docstring's statement-site discipline). -/ +def OracleComp.IsPolyTime {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + (bd : BoundaryData spec α β) + (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) : Prop := + Nonempty (PolyTimeWitness bd oa) + +namespace PolyTimeWitness + +variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + {bd : BoundaryData spec α β} {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} + +/-- Resolution within the round budget is derivable for any certified adversary: the +old `steady` field, now a theorem (via `DynComputation.ImplementsWithin.resolvesIn`) — +and handler-free, since `ResolvesIn` quantifies over every typed answer path. -/ +theorem resolvesIn (w : PolyTimeWitness bd oa) (n : ℕ) (x : α n) : + (w.A.M n).ResolvesIn (w.A.steps.eval n) ((w.A.M n).init x) := + (w.implements n).resolvesIn x + +end PolyTimeWitness + +/- The `PolyQueries` bridge (`PolyTimeWitness.toPolyQueries` / +`OracleComp.IsPolyTime.polyQueries`) is deferred: it needs the per-`n` index-family +generalization of `OracleComp.PolyQueries`, whereas `QueryBound.lean` currently fixes a +single index type `ι : Type` with a per-oracle-index bound `qb : ι → Polynomial ℕ`. The +total query bound the bridge would export is already recorded by the `queryBound` field of +`PolyTimeWitness`. -/ + +/-! ## Total running time + +The total Turing-machine time of a run is polynomial in the security parameter, +hypothesis-free: the round count is bounded by `steps`, each per-step time by its +family's polynomial at inputs whose lengths the state bound (for states) and the +canonical fixed widths (for inputs and answers) control. This is pure polynomial +arithmetic over the per-step witnesses — no machine is constructed. An end-to-end +single-machine witness for the whole run is a separate, genuinely harder goal (it +needs machine iteration on top of Cslib's composition). -/ + +namespace MachineAdversary + +variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + {bd : BoundaryData spec α β} + +/-- One deterministic step of a machine against a handler: answer the exposed query +and advance; returned states are fixed points. The accounting-side sibling of the +fuelled run `runWith` at a deterministic handler. -/ +def _root_.OracleComp.OracleMachine.stepD {ι : Type} {spec : OracleSpec.{0, 0} ι} + {α' β' : Type} (M : OracleMachine spec α' β') (h : OracleHandler spec) + (s : M.State) : M.State := + (M.view s).elim (fun _ => s) (fun q => q.2 (h q.1)) + +/-- The state at round `j` of the deterministic run against handler `h` on input `x`. -/ +def stateAt (D : MachineAdversary bd) (n : ℕ) (h : OracleHandler (spec n)) + (x : α n) (j : ℕ) : (D.M n).State := + ((D.M n).stepD h)^[j] ((D.M n).init x) + +/-- The tagged query/answer pair received at round `j` of the deterministic run. -/ +def answerAt (D : MachineAdversary bd) (n : ℕ) (h : OracleHandler (spec n)) + (x : α n) (j : ℕ) : (t : ι n) × (spec n).Range t := + ⟨(D.M n).expose (D.stateAt n h x j), h ((D.M n).expose (D.stateAt n h x j))⟩ + +/-- The total Turing-machine time of the deterministic run against handler `h` on +input `x`: the initialization cost plus, per round, the expose, update, and readout +costs, each evaluated at the encoded lengths actually occurring along the run. -/ +noncomputable def detTotalTime (D : MachineAdversary bd) (n : ℕ) + (h : OracleHandler (spec n)) (x : α n) : ℕ := + ((D.initF.wit n).time).eval (bd.eIn.enc n x).length + + ∑ j ∈ Finset.range (D.steps.eval n), + (((D.exposeF.wit n).time).eval (D.state.enc n (D.stateAt n h x j)).length + + ((D.updateF.wit n).time).eval + ((D.state.pairVar bd.eIface.encAns).enc n + (D.stateAt n h x j, D.answerAt n h x j)).length + + ((D.outputF.wit n).time).eval (D.state.enc n (D.stateAt n h x j)).length) + +/-- **Total-time bound, hypothesis-free**: the total machine time of any run is bounded +by an explicit polynomial expression in `n` — the canonical fixed input width bounds +the initialization input, the state bound covers every occurring state, and the +canonical answer width caps the update inputs. -/ +theorem detTotalTime_le (D : MachineAdversary bd) + (n : ℕ) (h : OracleHandler (spec n)) (x : α n) : + D.detTotalTime n h x ≤ + D.initF.time.eval (n + bd.eIn.widBound.eval n) + + D.steps.eval n * + (D.exposeF.time.eval (n + D.state.bound.eval n) + + D.updateF.time.eval + (n + (D.state.bound.eval n + bd.eIface.encAns.widBound.eval n)) + + D.outputF.time.eval (n + D.state.bound.eval n)) := by + refine Nat.add_le_add ?_ ?_ + · refine (D.initF.time_le n _).trans (Polynomial.eval_le_eval ?_) + have h1 : (bd.eIn.enc n x).length ≤ bd.eIn.widBound.eval n := + (bd.eIn.len_eq n x).le.trans (bd.eIn.wid_le n) + omega + · refine le_trans (Finset.sum_le_card_nsmul _ _ + (D.exposeF.time.eval (n + D.state.bound.eval n) + + D.updateF.time.eval + (n + (D.state.bound.eval n + bd.eIface.encAns.widBound.eval n)) + + D.outputF.time.eval (n + D.state.bound.eval n)) + fun j _ => ?_) (by rw [Finset.card_range, smul_eq_mul]) + have hstate : (D.state.enc n (D.stateAt n h x j)).length ≤ D.state.bound.eval n := + D.state.len_le n _ + have hpair : ((D.state.pairVar bd.eIface.encAns).enc n + (D.stateAt n h x j, D.answerAt n h x j)).length ≤ + D.state.bound.eval n + bd.eIface.encAns.widBound.eval n := by + rw [StrEncFam.pairVar_enc, List.length_append, bd.eIface.encAns.len_eq] + have h1 := D.state.len_le n (D.stateAt n h x j, D.answerAt n h x j).1 + have h2 := bd.eIface.encAns.wid_le n + omega + have hexpose := (D.exposeF.time_le n _).trans + (Polynomial.eval_le_eval (Nat.add_le_add_left hstate n)) + have hupdate := (D.updateF.time_le n _).trans + (Polynomial.eval_le_eval (Nat.add_le_add_left hpair n)) + have houtput := (D.outputF.time_le n _).trans + (Polynomial.eval_le_eval (Nat.add_le_add_left hstate n)) + omega + +/-- Packaged form of `detTotalTime_le`: the total run time of any adversary is bounded +by a single polynomial in the security parameter, with no side conditions. -/ +theorem exists_polynomial_detTotalTime_le (D : MachineAdversary bd) : + ∃ p : Polynomial ℕ, ∀ (n : ℕ) (h : OracleHandler (spec n)) (x : α n), + D.detTotalTime n h x ≤ p.eval n := by + refine ⟨D.initF.time.comp (.X + bd.eIn.widBound) + + D.steps * (D.exposeF.time.comp (.X + D.state.bound) + + D.updateF.time.comp (.X + (D.state.bound + bd.eIface.encAns.widBound)) + + D.outputF.time.comp (.X + D.state.bound)), + fun n h x => (D.detTotalTime_le n h x).trans_eq ?_⟩ + simp [Polynomial.eval_comp] + +end MachineAdversary + +/-! ## Query-free adversaries are polynomial time -/ + +section PureFn + +section SingleSpec + +variable {ι : Type} + +/-- The trivial machine of a query-free function: the state is the input and every +state returns immediately. The input-state sibling of `DynComputation.ofFn`. -/ +def OracleComp.OracleMachine.ofPureFn {spec : OracleSpec.{0, 0} ι} {α β : Type} (f : α → β) : + OracleMachine spec α β where + State := α + toDynSystem := (fun s => Sum.inl (f s)) ⇆ fun _ => PEmpty.elim + init := id + +@[simp] theorem OracleComp.OracleMachine.view_ofPureFn {spec : OracleSpec.{0, 0} ι} {α β : Type} + (f : α → β) (s : α) : + (OracleMachine.ofPureFn (spec := spec) f).view s = Sum.inl (f s) := rfl + +/-- The flattened update of the trivial machine is the first projection. -/ +theorem OracleComp.OracleMachine.updateFlat_ofPureFn [DecidableEq ι] + {spec : OracleSpec.{0, 0} ι} {α β : Type} (f : α → β) : + (OracleMachine.ofPureFn (spec := spec) f).updateFlat = Prod.fst := by + funext p + simp [OracleMachine.updateFlat] + +end SingleSpec + +/-- **Query-free adversaries are polynomial time**, given uniform machine families for +their (trivial) step functions: the identity family serves initialization and the +state representation is the canonical input boundary itself, so the caller supplies +families for the constant query selection, the first-projection update, and the output +map. Once base machines for constants and projections exist, all three discharge +generically; until then this is the honest hypothesis form — and the output family is a +genuine assumption ("`f` is P/poly-computable"), not a formality. -/ +theorem OracleComp.isPolyTime_pure_of_witnesses + {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} (bd : BoundaryData spec α β) + (f : (n : ℕ) → α n → β n) + (exposeF : EncPolyTimeFam bd.eIn.enc bd.eIface.encQuery.enc + (fun n _ => (default : ι n))) + (updateF : EncPolyTimeFam (bd.eIn.toStrEncFam.pairVar bd.eIface.encAns).enc + bd.eIn.enc (fun _ => Prod.fst)) + (outputF : EncPolyTimeFam bd.eIn.enc (bd.eOut.option).enc + (fun n x => some (f n x))) : + OracleComp.IsPolyTime bd (fun n x => (pure (f n x) : OracleComp (spec n) (β n))) := by + refine ⟨{ + A := { + M := fun n => .ofPureFn (f n) + steps := 0 + state := bd.eIn.toStrEncFam + initF := .id bd.eIn.enc + exposeF := exposeF + updateF := updateF.copy _ + (fun n p => congrFun (OracleMachine.updateFlat_ofPureFn (f n)).symm p) + outputF := outputF } + implements := fun n => ?_ + queryBound := fun n x => trivial }⟩ + intro x + simp only [Polynomial.eval_zero] + rfl + +end PureFn diff --git a/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean b/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean new file mode 100644 index 000000000..bd75c3fdf --- /dev/null +++ b/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean @@ -0,0 +1,405 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.OracleComp.Coinductive.PolyTime + +/-! +# Closure Properties of Polynomial-Time Adversaries + +Closure of `MachineAdversary` — and hence of `OracleComp.IsPolyTime` — under input +precomposition and output maps, at pinned canonical boundaries: + +* `MachineAdversary.precomp` / `IsPolyTime.precomp`: precompose with a pure input map + on per-parameter input types of polynomially bounded cardinality. The machine family + is reused with only its initialization changed to `init ∘ f`, witnessed by a finite + table; the cardinality bound is what keeps the table within the advice budget. +* `MachineAdversary.precompComp` / `IsPolyTime.precompComp`: precompose with a pure + input map carrying its **own machine witness** between the canonical input encodings + — the unbounded-input sibling, for maps on superpolynomially large types (bitstring + glue, projections) that a finite table can never certify. +* `MachineAdversary.mapComp`: post-compose the readout with a supplied uniform machine + family for `Option.map g` — the general engine; all time and size accounting is + `Computability.EncPolyTimeFam.comp`. +* `EncPolyTimeFam.optionMap` / `IsPolyTime.map`: the output-map closure on an + **abstract** `IsPolyTime` hypothesis, for finite output types of polynomially + bounded cardinality. This is derivable *because* the output boundary is canonical: + the post-map table reads the pinned fixed-width output encoding, whose lengths are + known — with existential output encodings (the pre-canonicalization model) no such + table could be bounded, which was both a compositionality wall and a soundness leak. + +The remaining missing closure is sequential composition (`bind`), which needs the +two-phase machine construction; with canonical boundaries its statement is finally +well-formed (the mid boundary is shared by construction). +-/ + +open OracleSpec OracleComp Computability + +variable {ι : ℕ → Type} [∀ n, DecidableEq (ι n)] [∀ n, Inhabited (ι n)] + +/-- `IsPolyTime` transports along pointwise program equality: a family equal to a +polynomial-time family is polynomial time. Lets a call site name its program directly +and bridge to a combinator's canonical form. -/ +theorem OracleComp.IsPolyTime.congr {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} + {α β : ℕ → Type} {bd : BoundaryData spec α β} + {oa oa' : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : ∀ n x, oa n x = oa' n x) + (hp : OracleComp.IsPolyTime bd oa) : OracleComp.IsPolyTime bd oa' := + (funext fun n => funext fun x => h n x : oa = oa') ▸ hp + +section SingleSpec + +variable {ι : Type} + +namespace OracleComp.OracleMachine + +variable {spec : OracleSpec.{0, 0} ι} {m : Type → Type} [Monad m] + +/-- Fuelled unrolling ignores the initialization field: replacing `init` (possibly +changing the input type) leaves `unroll` unchanged from any state. Upstream candidate +for `DynComputation/Bounded`. -/ +theorem unroll_setInit {α α' β : Type} + (M : OracleMachine spec α β) (g : α' → M.State) (k : ℕ) (s : M.State) : + PFunctor.DynSystem.DynComputation.unroll ⟨M.toMachine, g⟩ k s = M.unroll k s := by + induction k generalizing s with + | zero => + rw [PFunctor.DynSystem.DynComputation.unroll_zero, + PFunctor.DynSystem.DynComputation.unroll_zero] + cases hview : M.view s with + | inl b => + rw [show PFunctor.DynSystem.DynComputation.view + (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = Sum.inl b from hview] + | inr q => + rw [show PFunctor.DynSystem.DynComputation.view + (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = Sum.inr q from hview] + | succ k ih => + rw [PFunctor.DynSystem.DynComputation.unroll_succ, + PFunctor.DynSystem.DynComputation.unroll_succ] + cases hview : M.view s with + | inl b => + rw [show PFunctor.DynSystem.DynComputation.view + (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = Sum.inl b from hview] + | inr q => + rw [show PFunctor.DynSystem.DynComputation.view + (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = Sum.inr q from hview] + exact congrArg (PFunctor.FreeM.liftBind q.1) (funext fun d => ih (q.2 d)) + +/-- The TM-facing accessors ignore the initialization field, definitionally: they +scrutinize `toMachine`, which `⟨M.toMachine, g⟩` shares with `M`. -/ +theorem output_setInit {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) + (s : M.State) : + output (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = M.output s := rfl + +@[simp] theorem expose_setInit [Inhabited ι] {α α' β : Type} (M : OracleMachine spec α β) + (g : α' → M.State) (s : M.State) : + expose (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = M.expose s := rfl + +theorem updateFlat_setInit [DecidableEq ι] {α α' β : Type} (M : OracleMachine spec α β) + (g : α' → M.State) (p : M.State × ((t : ι) × spec.Range t)) : + updateFlat (⟨M.toMachine, g⟩ : OracleMachine spec α' β) p = M.updateFlat p := rfl + +/-- The run of a machine ignores the initialization field, in any monad. -/ +theorem runWith_setInit {α α' β : Type} + (M : OracleMachine spec α β) (g : α' → M.State) (H : QueryImpl spec m) (k : ℕ) + (s : M.State) : + PFunctor.DynSystem.DynComputation.runWith ⟨M.toMachine, g⟩ H k s = + M.runWith H k s := by + change PFunctor.FreeM.liftM H + (PFunctor.DynSystem.DynComputation.unroll ⟨M.toMachine, g⟩ k s) = _ + rw [unroll_setInit] + rfl + +variable {α β γ : Type} + +/-- Post-composing the result map commutes with fuelled unrolling, at the syntactic +(`FreeM`) level. Upstream candidate for `DynComputation/Bounded`. -/ +theorem unroll_mapResult (M : OracleMachine spec α β) (g : β → γ) (k : ℕ) + (s : M.State) : + (M.mapResult g).unroll k s = Option.map g <$> M.unroll k s := by + induction k generalizing s with + | zero => + cases hview : M.view s with + | inl b => + rw [M.unroll_return 0 s b hview, + (M.mapResult g).unroll_return 0 s (g b) (by simp [hview])] + rfl + | inr q => + obtain ⟨t, next⟩ := q + rw [M.unroll_query_zero s t next hview, + (M.mapResult g).unroll_query_zero s t next (by simp [hview])] + rfl + | succ k ih => + cases hview : M.view s with + | inl b => + rw [M.unroll_return (k + 1) s b hview, + (M.mapResult g).unroll_return (k + 1) s (g b) (by simp [hview])] + rfl + | inr q => + obtain ⟨t, next⟩ := q + rw [M.unroll_query_succ k s t next hview, + (M.mapResult g).unroll_query_succ k s t next (by simp [hview])] + exact congrArg (PFunctor.FreeM.liftBind t) (funext fun d => ih (next d)) + +/-- Post-composing the result map maps the fuelled run's result, through any lawful +handler. -/ +theorem runWith_mapResult [LawfulMonad m] (M : OracleMachine spec α β) (g : β → γ) + (H : QueryImpl spec m) (k : ℕ) (s : M.State) : + (M.mapResult g).runWith H k s = Option.map g <$> M.runWith H k s := by + change PFunctor.FreeM.liftM H ((M.mapResult g).unroll k s) = _ + rw [unroll_mapResult, PFunctor.FreeM.liftM_map] + rfl + +@[simp] theorem output_mapResult (M : OracleMachine spec α β) (g : β → γ) + (s : M.State) : output (M.mapResult g) s = (M.output s).map g := by + cases hview : M.view s <;> simp only [output, hview, + PFunctor.DynSystem.DynComputation.mapResult_view] <;> rfl + +@[simp] theorem expose_mapResult [Inhabited ι] (M : OracleMachine spec α β) (g : β → γ) + (s : M.State) : expose (M.mapResult g) s = M.expose s := by + cases hview : M.view s <;> simp only [expose, hview, + PFunctor.DynSystem.DynComputation.mapResult_view] <;> rfl + +@[simp] theorem updateFlat_mapResult [DecidableEq ι] (M : OracleMachine spec α β) + (g : β → γ) : updateFlat (M.mapResult g) = M.updateFlat := by + funext p + cases hview : M.view p.1 <;> simp only [OracleMachine.updateFlat, hview, + PFunctor.DynSystem.DynComputation.mapResult_view] <;> rfl + +end OracleComp.OracleMachine + +end SingleSpec + +namespace MachineAdversary + +variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β γ : ℕ → Type} + {bd : BoundaryData spec α β} + +/-- Precompose an adversary with a pure input map on per-parameter finite input types +of polynomially bounded cardinality. The machine family is reused with only its +initialization changed to `init ∘ f`, witnessed by a finite table; the table stays +within the advice budget because the new inputs are canonically fixed-width and the +state encoding is polynomially bounded over *all* states. -/ +noncomputable def precomp (D : MachineAdversary bd) + (f : (n : ℕ) → γ n → α n) [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) + (cardIn : Polynomial ℕ) (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) : + MachineAdversary (bd.withIn eIn') where + M n := ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ + steps := D.steps + state := D.state + initF := .ofFintype eIn'.enc_injective (fun n x => (D.M n).init (f n x)) + cardIn hcard eIn'.widBound + (fun n x => (eIn'.len_eq n x).le.trans (eIn'.wid_le n)) + D.state.bound (fun n _ => D.state.len_le n _) + exposeF := D.exposeF.copy _ fun n s => + (OracleMachine.expose_setInit (D.M n) _ s).symm + updateF := D.updateF.copy _ fun n p => + (OracleMachine.updateFlat_setInit (D.M n) _ p).symm + outputF := D.outputF.copy _ fun n s => + (OracleMachine.output_setInit (D.M n) _ s).symm + +@[simp] theorem precomp_M (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) + [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) + (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) (n : ℕ) : + (D.precomp f eIn' cardIn hcard).M n = + ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ := rfl + +@[simp] theorem precomp_steps (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) + [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) + (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) : + (D.precomp f eIn' cardIn hcard).steps = D.steps := rfl + +/-- The precomposed adversary implements the precomposed program family: the machine +run is unchanged except that it starts from `init (f n x)`. -/ +theorem precomp_implements {D : MachineAdversary bd} (f : (n : ℕ) → γ n → α n) + [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) + (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : D ⊨ oa) : + D.precomp f eIn' cardIn hcard ⊨ fun n x => oa n (f n x) := by + intro n x + change PFunctor.DynSystem.DynComputation.unroll + (⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ : + OracleMachine (spec n) (γ n) (β n)) (D.steps.eval n) ((D.M n).init (f n x)) = _ + rw [OracleMachine.unroll_setInit] + exact h n (f n x) + +/-- Precompose an adversary with a pure input map from a **supplied machine witness** +between the canonical input encodings — the sibling of `precomp` for input types too +large for a table (`precomp` needs polynomially many inputs; here the map carries its +own machine). The machine family is reused with only its initialization changed to +`init ∘ f`; the new initialization family is the composition. -/ +noncomputable def precompComp (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) + (eIn' : Computability.BitEncFam γ) + (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) : + MachineAdversary (bd.withIn eIn') where + M n := ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ + steps := D.steps + state := D.state + initF := wit.comp D.initF + exposeF := D.exposeF.copy _ fun n s => + (OracleMachine.expose_setInit (D.M n) _ s).symm + updateF := D.updateF.copy _ fun n p => + (OracleMachine.updateFlat_setInit (D.M n) _ p).symm + outputF := D.outputF.copy _ fun n s => + (OracleMachine.output_setInit (D.M n) _ s).symm + +@[simp] theorem precompComp_M (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) + (eIn' : Computability.BitEncFam γ) + (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) (n : ℕ) : + (D.precompComp f eIn' wit).M n = + ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ := rfl + +@[simp] theorem precompComp_steps (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) + (eIn' : Computability.BitEncFam γ) + (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) : + (D.precompComp f eIn' wit).steps = D.steps := rfl + +/-- The witness-precomposed adversary implements the precomposed program family: the +machine run is unchanged except that it starts from `init (f n x)`. -/ +theorem precompComp_implements {D : MachineAdversary bd} (f : (n : ℕ) → γ n → α n) + (eIn' : Computability.BitEncFam γ) + (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : D ⊨ oa) : + D.precompComp f eIn' wit ⊨ fun n x => oa n (f n x) := by + intro n x + change PFunctor.DynSystem.DynComputation.unroll + (⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ : + OracleMachine (spec n) (γ n) (β n)) (D.steps.eval n) ((D.M n).init (f n x)) = _ + rw [OracleMachine.unroll_setInit] + exact h n (f n x) + +/-- Post-compose an adversary with a pure output map, from a supplied uniform machine +family for `Option.map g` between the canonical optional output encodings. The machine +is reused with its read-out post-composed; the new output family is the composition, +with all time and size accounting inside `EncPolyTimeFam.comp`. -/ +noncomputable def mapComp (D : MachineAdversary bd) (g : (n : ℕ) → β n → γ n) + (eOut' : BitEncFam γ) + (wit : EncPolyTimeFam (bd.eOut.option).enc (eOut'.option).enc + (fun n => Option.map (g n))) : + MachineAdversary (bd.withOut eOut') where + M n := (D.M n).mapResult (g n) + steps := D.steps + state := D.state + initF := D.initF + exposeF := D.exposeF.copy _ fun n s => + (OracleMachine.expose_mapResult (D.M n) (g n) s).symm + updateF := D.updateF.copy _ fun n p => + congrFun (OracleMachine.updateFlat_mapResult (D.M n) (g n)).symm p + outputF := (D.outputF.comp wit).copy _ fun n s => + (OracleMachine.output_mapResult (D.M n) (g n) s).symm + +@[simp] theorem mapComp_M (D : MachineAdversary bd) (g : (n : ℕ) → β n → γ n) + (eOut' : BitEncFam γ) + (wit : EncPolyTimeFam (bd.eOut.option).enc (eOut'.option).enc + (fun n => Option.map (g n))) (n : ℕ) : + (D.mapComp g eOut' wit).M n = (D.M n).mapResult (g n) := rfl + +@[simp] theorem mapComp_steps (D : MachineAdversary bd) (g : (n : ℕ) → β n → γ n) + (eOut' : BitEncFam γ) + (wit : EncPolyTimeFam (bd.eOut.option).enc (eOut'.option).enc + (fun n => Option.map (g n))) : + (D.mapComp g eOut' wit).steps = D.steps := rfl + +/-- The output-mapped adversary implements the output-mapped program family: the +machine run is unchanged except that its result is post-composed with `g`. -/ +theorem mapComp_implements {D : MachineAdversary bd} (g : (n : ℕ) → β n → γ n) + (eOut' : BitEncFam γ) + (wit : EncPolyTimeFam (bd.eOut.option).enc (eOut'.option).enc + (fun n => Option.map (g n))) + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} (h : D ⊨ oa) : + D.mapComp g eOut' wit ⊨ fun n x => g n <$> oa n x := by + intro n x + change ((D.M n).mapResult (g n)).unroll (D.steps.eval n) ((D.M n).init x) = _ + rw [OracleMachine.unroll_mapResult] + change Option.map (g n) <$> + PFunctor.DynSystem.DynComputation.run (D.M n) (D.steps.eval n) x = _ + rw [h n x] + simp only [← PFunctor.FreeM.map_eq_map, ← PFunctor.FreeM.comp_map, + Function.comp_def, Option.map_some] + +end MachineAdversary + +/-! ## Closure of the abstract predicate -/ + +namespace Computability.EncPolyTimeFam + +/-- The finite-table family for `Option.map g` between canonical optional boundaries: +domain cardinality and both encoded lengths are pinned, so the table's time and +description bounds are automatic. -/ +noncomputable def optionMap {β γ : ℕ → Type} [∀ n, Fintype (β n)] + (eβ : BitEncFam β) (eγ : BitEncFam γ) (g : (n : ℕ) → β n → γ n) + (cardβ : Polynomial ℕ) (hcard : ∀ n, Fintype.card (β n) ≤ cardβ.eval n) : + EncPolyTimeFam (eβ.option).enc (eγ.option).enc (fun n => Option.map (g n)) := + .ofFintype (eβ.option).enc_injective (fun n => Option.map (g n)) + (cardβ + .C 1) + (fun n => by + have := hcard n + simp only [Fintype.card_option, Polynomial.eval_add, Polynomial.eval_C] + omega) + (eβ.option).widBound + (fun n x => ((eβ.option).len_eq n x).le.trans ((eβ.option).wid_le n)) + (eγ.option).widBound + (fun n x => ((eγ.option).len_eq n _).le.trans ((eγ.option).wid_le n)) + +end Computability.EncPolyTimeFam + +/-- `OracleComp.IsPolyTime` is closed under precomposition with a pure map on +per-parameter finite input types of polynomially bounded cardinality: the standard +"the reduction is polynomial time since the adversary is" step for input-reshaping +reductions. -/ +theorem OracleComp.IsPolyTime.precomp {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} + {α β γ : ℕ → Type} {bd : BoundaryData spec α β} + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} + (hoa : OracleComp.IsPolyTime bd oa) (f : (n : ℕ) → γ n → α n) [∀ n, Finite (γ n)] + (eIn' : BitEncFam γ) + (cardIn : Polynomial ℕ) (hcard : ∀ n, Nat.card (γ n) ≤ cardIn.eval n) : + OracleComp.IsPolyTime (bd.withIn eIn') fun n x => oa n (f n x) := by + letI : ∀ n, Fintype (γ n) := fun n => Fintype.ofFinite (γ n) + have hcard' : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n := fun n => by + simpa [Nat.card_eq_fintype_card] using hcard n + obtain ⟨w⟩ := hoa + exact ⟨{ + A := w.A.precomp f eIn' cardIn hcard' + implements := MachineAdversary.precomp_implements f eIn' cardIn hcard' w.implements + queryBound := fun n x => w.queryBound n (f n x) }⟩ + +/-- `OracleComp.IsPolyTime` is closed under pure input precomposition from a supplied +machine witness between the canonical input encodings — the unbounded-input sibling of +`IsPolyTime.precomp`, for input maps on superpolynomially large types (bitstring glue, +projections) that a finite table can never certify. -/ +theorem OracleComp.IsPolyTime.precompComp {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} + {α β γ : ℕ → Type} {bd : BoundaryData spec α β} + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} + (hoa : OracleComp.IsPolyTime bd oa) (f : (n : ℕ) → γ n → α n) + (eIn' : Computability.BitEncFam γ) + (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) : + OracleComp.IsPolyTime (bd.withIn eIn') fun n x => oa n (f n x) := by + obtain ⟨w⟩ := hoa + exact ⟨{ + A := w.A.precompComp f eIn' wit + implements := MachineAdversary.precompComp_implements f eIn' wit w.implements + queryBound := fun n x => w.queryBound n (f n x) }⟩ + +/-- `OracleComp.IsPolyTime` is closed under a pure **output** map on per-parameter +finite output types of polynomially bounded cardinality, on an abstract hypothesis: +the post-map is a finite table between the *canonical* optional output encodings, whose +widths are pinned — exactly what an existential output encoding could never provide. +This is the "post-process the Boolean result" primitive reductions need (output +negation, challenge comparison). -/ +theorem OracleComp.IsPolyTime.map {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} + {α β γ : ℕ → Type} {bd : BoundaryData spec α β} + {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} + (hoa : OracleComp.IsPolyTime bd oa) (g : (n : ℕ) → β n → γ n) [∀ n, Finite (β n)] + (eOut' : BitEncFam γ) + (cardβ : Polynomial ℕ) (hcard : ∀ n, Nat.card (β n) ≤ cardβ.eval n) : + OracleComp.IsPolyTime (bd.withOut eOut') fun n x => g n <$> oa n x := by + letI : ∀ n, Fintype (β n) := fun n => Fintype.ofFinite (β n) + have hcard' : ∀ n, Fintype.card (β n) ≤ cardβ.eval n := fun n => by + simpa [Nat.card_eq_fintype_card] using hcard n + obtain ⟨w⟩ := hoa + exact ⟨{ + A := w.A.mapComp g eOut' (.optionMap bd.eOut eOut' g cardβ hcard') + implements := MachineAdversary.mapComp_implements g eOut' _ w.implements + queryBound := fun n x => by + simp only [MachineAdversary.mapComp_steps] + rw [map_eq_bind_pure_comp] + exact isTotalQueryBound_bind (n₂ := 0) (w.queryBound n x) fun _ => trivial }⟩ From 1d84ab6fbbf0d8b1440e235b8a74f92c09049ccb Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Sun, 26 Jul 2026 11:31:40 -0500 Subject: [PATCH 3/5] feat(coinductive): coin-fold combinator and the non-vacuity certificate, sorry-free Complete the PPT re-extraction with the remaining machine-facing files: - CoinFold: the bounded coin-fold machine via a new generic OracleMachine.ofStep; the old simulation-relation and steadiness developments collapse to one direct unroll induction, since returns are absorbing by construction. - PolyTimeConstructions: unchanged content over the new carrier. - PolyTimeNontrivial: the isPolyTime_coin certificate and both headline non-triviality theorems, with the three formerly-open cruxes closed by the proofs contributed in #487, ported to the new machine API through runWith_eq_output_iterate_stepD (the unconditional deterministic- trajectory readout that replaces the stability-conditioned lemma). - Asymptotics/PolyTime: secureAgainstPolyTime and the per-query-loss former. No literal sorry remains in the polynomial-time layer. Co-authored-by: Elias Judin Co-authored-by: Aristotle (Harmonic) Co-Authored-By: Claude Fable 5 --- .../Asymptotics/PolyTime.lean | 65 +++ VCVio/OracleComp/Coinductive/CoinFold.lean | 320 +++++++++++++ VCVio/OracleComp/Coinductive/PolyTime.lean | 35 ++ .../Coinductive/PolyTimeConstructions.lean | 144 ++++++ .../Coinductive/PolyTimeNontrivial.lean | 444 ++++++++++++++++++ 5 files changed, 1008 insertions(+) create mode 100644 VCVio/CryptoFoundations/Asymptotics/PolyTime.lean create mode 100644 VCVio/OracleComp/Coinductive/CoinFold.lean create mode 100644 VCVio/OracleComp/Coinductive/PolyTimeConstructions.lean create mode 100644 VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean diff --git a/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean b/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean new file mode 100644 index 000000000..677d35b8c --- /dev/null +++ b/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean @@ -0,0 +1,65 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.CryptoFoundations.Asymptotics.Security +import VCVio.OracleComp.Coinductive.PolyTime + +/-! +# Security Against Polynomial-Time Adversaries + +This file connects the Turing-machine-grounded polynomial-time layer +(`PolyTimeAdversary`, `OracleComp.IsPolyTime`) to the asymptotic security games of +`VCVio.CryptoFoundations.Asymptotics.Security`: `SecurityGame.secureAgainstPolyTime` +instantiates the abstract `isPPT` slot of `SecurityGame.secureAgainst` with +`OracleComp.IsPolyTime`, and the per-query-loss former +`secureAgainstPolyTime_of_advantage_le_mul_totalQueries` is where the query-bound +conjunct of the certificate does quantitative work. Concrete game formers over both +adversary presentations (programs and machines) live in +`VCVio.CryptoFoundations.Asymptotics.Game.Challenger` and `….Game.TwoPhase`. +-/ + +open OracleComp OracleSpec Computability ENNReal + +variable {ι : ℕ → Type} [∀ n, DecidableEq (ι n)] [∀ n, Inhabited (ι n)] + +namespace SecurityGame + +/-- Security against Turing-machine-grounded polynomial-time adversaries at the pinned +canonical boundaries `bd`: `SecurityGame.secureAgainst` at the `isPPT` predicate +`OracleComp.IsPolyTime bd`. The boundary data is an explicit parameter of the security +notion, per the statement-site discipline of the model. -/ +abbrev secureAgainstPolyTime {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + (bd : BoundaryData spec α β) + (g : SecurityGame ((n : ℕ) → α n → OracleComp (spec n) (β n))) : Prop := + g.secureAgainst (OracleComp.IsPolyTime bd) + +/-- Security of a game over bundled machine adversaries: every `MachineAdversary bd` +has negligible advantage. A machine adversary carries its own polynomial-time +witnesses — the four step-machine families and the round budget are fields of the +bundle — so the `isPPT` slot of `SecurityGame.secureAgainst` is trivially `True`; +quantifying over the adversary type is already quantifying over the polynomial-time +class. -/ +abbrev secureAgainstMachines {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} + {bd : BoundaryData spec α β} (g : SecurityGame (MachineAdversary bd)) : Prop := + g.secureAgainst fun _ => True + +/-- **Per-query loss composes with the polynomial round budget**: a game whose advantage +against every `k`-total-query-bounded family is at most `k * ε n` for negligible `ε` is +secure against all polynomial-time families. This is where the query-bound conjunct of +`OracleComp.IsPolyTime` and the `steps` polynomial do quantitative work: the adversary's +polynomially many queries turn per-query loss into `poly * negligible = negligible`. -/ +theorem secureAgainstPolyTime_of_advantage_le_mul_totalQueries + {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} (bd : BoundaryData spec α β) + (g : SecurityGame ((n : ℕ) → α n → OracleComp (spec n) (β n))) + {ε : ℕ → ℝ≥0∞} (hε : negligible ε) + (hadv : ∀ (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) (k : ℕ → ℕ), + (∀ n x, OracleComp.IsTotalQueryBound (oa n x) (k n)) → + ∀ n, g.advantage oa n ≤ (k n : ℝ≥0∞) * ε n) : + g.secureAgainstPolyTime bd := by + rintro oa ⟨w⟩ + exact negligible_of_le (hadv oa (fun n => w.A.steps.eval n) w.queryBound) + (negligible_polynomial_mul hε w.A.steps) + +end SecurityGame diff --git a/VCVio/OracleComp/Coinductive/CoinFold.lean b/VCVio/OracleComp/Coinductive/CoinFold.lean new file mode 100644 index 000000000..7571196c5 --- /dev/null +++ b/VCVio/OracleComp/Coinductive/CoinFold.lean @@ -0,0 +1,320 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.OracleComp.Coinductive.PolyTimeClosure + +/-! +# Bounded Coin Fold: a Reusable Polynomial-Time Construction + +Many polynomial-time oracle computations have the same shape: flip the coin a polynomial +number of times, folding each answer into a finite-state accumulator, then read out a +value. `coinFoldProg step readout` is that program family, and `isPolyTime_coinFold` +bundles the whole Turing-machine polynomial-time witness for it once and for all, so a +concrete instance collapses to supplying `step`/`readout`, the encodings, and two +encoding-length bounds — no hand-built `OracleMachine`/`PolyTimeAdversary` needed. + +* `coinFoldProg` — the fold program; `coinFoldMachine` — the machine realizing it, with the + read-out map folded into `output` and the round counter as `Fin (rounds + 1)`. +* `coinFoldMachine_implements` (via the simulation relation `CoinFoldRel`) and + `coinFoldMachine_steadyBy` (via the round invariant) give the coalgebraic side. +* `coinFoldAdversary` / `isPolyTime_coinFold` — the fully assembled `PolyTimeAdversary` and + `OracleComp.IsPolyTime`. + +Worked instances: `Examples.DynamicalSystems.XorFlips` (single-`Bool` accumulator, `readout` +the identity) and `KatzLindell.Chapter03.SamplerMachine` (`BitVec` accumulator, nontrivial +`readout`), which collapse onto this combinator. + +Specialized to `coinSpec` (the coin oracle, answers `Bool`): both current consumers use it, +and it keeps the simulation free of the dependent answer-type transport that an +arbitrary-spec version would incur. Generalizing the fold to an arbitrary oracle is future +work. +-/ + +open OracleSpec OracleComp Computability + +namespace OracleComp + +/-! ## The fold program and its machine -/ + +section Machine + +variable {σ β : Type} + +/-- Flip the coin `r` times, folding each answer into `acc` with `step`, then read out +`readout` of the final accumulator. The round index (remaining count minus one) is passed to +`step`, so bit-position-dependent folds are expressible. -/ +def coinFoldProg (step : σ → ℕ → Bool → σ) (readout : σ → β) : + ℕ → σ → OracleComp coinSpec β + | 0, acc => pure (readout acc) + | r + 1, acc => OracleComp.queryBind () fun b => coinFoldProg step readout r (step acc r b) + +theorem coinFoldProg_succ (step : σ → ℕ → Bool → σ) (readout : σ → β) (r : ℕ) (acc : σ) : + coinFoldProg step readout (r + 1) acc = + OracleComp.queryBind () fun b => coinFoldProg step readout r (step acc r b) := rfl + +theorem isTotalQueryBound_coinFoldProg (step : σ → ℕ → Bool → σ) (readout : σ → β) + (r : ℕ) (acc : σ) : IsTotalQueryBound (coinFoldProg step readout r acc) r := by + induction r generalizing acc with + | zero => trivial + | succ r ih => exact ⟨Nat.succ_pos r, fun b => ih (step acc r b)⟩ + +variable (step : σ → ℕ → Bool → σ) (readout : σ → β) + +/-- Build a machine from a one-step transition returning either a value or a query +with an explicit continuation, together with an initialization. Generic over the +spec; upstream candidate for `DynComputation`. Reducible so that the state type of a +machine built from it is transparently the supplied carrier. -/ +@[reducible] def OracleMachine.ofStep {ι : Type} {spec : OracleSpec.{0, 0} ι} {α β S : Type} + (stepFn : S → β ⊕ spec.toPFunctor.Obj S) (init : α → S) : + OracleMachine spec α β where + State := S + toDynSystem := + (fun s => (PFunctor.Resumption.pack (stepFn s)).1) ⇆ + (fun s => (PFunctor.Resumption.pack (stepFn s)).2) + init := init + +@[simp] theorem OracleMachine.view_ofStep {ι : Type} {spec : OracleSpec.{0, 0} ι} + {α β S : Type} (stepFn : S → β ⊕ spec.toPFunctor.Obj S) (init : α → S) (s : S) : + (OracleMachine.ofStep (α := α) stepFn init).view s = stepFn s := by + change PFunctor.Resumption.unpack (PFunctor.Resumption.pack (stepFn s)) = stepFn s + cases stepFn s <;> rfl + +/-- The fold machine: state `(remaining rounds, accumulator)`; each step decrements the +counter and folds the answer at the next index; at zero rounds it returns `readout` of +the accumulator (returns are absorbing by construction). Reducible so that the state +type stays transparently `Fin (rounds + 1) × σ` for the encoding witnesses. -/ +@[reducible] def coinFoldMachine (rounds : ℕ) (init₀ : σ) : OracleMachine coinSpec Unit β := + OracleMachine.ofStep (S := Fin (rounds + 1) × σ) + (fun s => + if _h : (s.1 : ℕ) = 0 then Sum.inl (readout s.2) + else Sum.inr ⟨(), fun b => + (⟨(s.1 : ℕ) - 1, lt_of_le_of_lt (Nat.sub_le _ _) s.1.isLt⟩, + step s.2 ((s.1 : ℕ) - 1) b)⟩) + (fun _ => (Fin.last rounds, init₀)) + +theorem coinFoldMachine_view_zero (rounds : ℕ) (init₀ : σ) + {s : Fin (rounds + 1) × σ} (hs : (s.1 : ℕ) = 0) : + (coinFoldMachine step readout rounds init₀).view s = Sum.inl (readout s.2) := by + rw [OracleMachine.view_ofStep, dif_pos hs] + +theorem coinFoldMachine_view_succ (rounds : ℕ) (init₀ : σ) + {s : Fin (rounds + 1) × σ} (hs : ¬(s.1 : ℕ) = 0) : + (coinFoldMachine step readout rounds init₀).view s = + Sum.inr ⟨(), fun b => + (⟨(s.1 : ℕ) - 1, lt_of_le_of_lt (Nat.sub_le _ _) s.1.isLt⟩, + step s.2 ((s.1 : ℕ) - 1) b)⟩ := by + rw [OracleMachine.view_ofStep, dif_neg hs] + +/-- The fuelled unrolling from a state with `m` rounds remaining is the fold program +at `m`, for any sufficient fuel. -/ +theorem coinFoldMachine_unroll (rounds : ℕ) (init₀ : σ) : + ∀ (m k : ℕ) (hm : m ≤ rounds) (acc : σ), m ≤ k → + (coinFoldMachine step readout rounds init₀).unroll k + (⟨m, Nat.lt_succ_of_le hm⟩, acc) = + PFunctor.FreeM.map some (coinFoldProg step readout m acc) + | 0, k, hm, acc, _ => by + rw [(coinFoldMachine step readout rounds init₀).unroll_return k _ (readout acc) + (coinFoldMachine_view_zero step readout rounds init₀ rfl)] + rfl + | m + 1, 0, hm, acc, hk => absurd hk (by omega) + | m + 1, k + 1, hm, acc, hk => by + rw [(coinFoldMachine step readout rounds init₀).unroll_query_succ k _ () _ + (coinFoldMachine_view_succ step readout rounds init₀ (s := ⟨_, acc⟩) (by simp))] + exact congrArg (PFunctor.FreeM.liftBind ()) + (funext fun b => coinFoldMachine_unroll rounds init₀ m k (by omega) + (step acc m b) (by omega)) + +/-- The fold machine implements the fold program family within `rounds` rounds. The +old simulation-relation and steadiness developments are unnecessary: returns are +absorbing by construction, and resolution is derivable from this via +`DynComputation.ImplementsWithin.resolvesIn`. -/ +theorem coinFoldMachine_implementsWithin (rounds : ℕ) (init₀ : σ) : + (coinFoldMachine step readout rounds init₀).ImplementsWithin + (fun _ : Unit => coinFoldProg step readout rounds init₀) rounds := fun _ => + coinFoldMachine_unroll step readout rounds init₀ rounds rounds le_rfl init₀ le_rfl + +end Machine + +/-! ## The polynomial-time adversary and `IsPolyTime` + +The actual per-parameter round count is a plain `rnd : ℕ → ℕ` (so a call site's `2 * n` +stays definitionally equal, keeping the state family `Fin (rnd n + 1) × σ n` unadorned); +`steps` is the `Polynomial ℕ` round *bound* with `hrnd : ∀ n, rnd n = steps.eval n`. +Boundaries are pinned to the canonical `BoundaryData.coin BitEncFam.unit eβ`; the fold +state representation `st` is machine-internal data supplied by the caller. -/ + +section Adversary + +variable {σ β : ℕ → Type} [∀ n, Fintype (σ n)] + (step : (n : ℕ) → σ n → ℕ → Bool → σ n) + (readout : (n : ℕ) → σ n → β n) (init₀ : (n : ℕ) → σ n) + (rnd : ℕ → ℕ) (steps : Polynomial ℕ) (hrnd : ∀ n, rnd n = steps.eval n) + (st : StrEncFam (fun n => Fin (rnd n + 1) × σ n)) (eβ : BitEncFam β) + (Sc : Polynomial ℕ) (hcard : ∀ n, Fintype.card (σ n) ≤ Sc.eval n) + +/-- The bounded coin fold as a fully concrete `MachineAdversary` at the canonical +coin boundaries: round bound `steps`, state representation `st`, and all four step +witnesses discharged by finite tables (`Computability.EncPolyTimeFam.ofFintype`) — +within the advice budget exactly because the accumulator cardinality is polynomially +bounded (`Sc`). Folds into superpolynomially large accumulators need +`coinFoldAdversaryOfWitnesses` instead. -/ +noncomputable def coinFoldAdversary : + MachineAdversary (BoundaryData.coin BitEncFam.unit eβ) where + M n := coinFoldMachine (step n) (readout n) (rnd n) (init₀ n) + steps := steps + state := st + initF := .ofFintype BitEncFam.unit.enc_injective + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).init) + (.C 1) (fun n => by simp) + BitEncFam.unit.widBound + (fun n x => (BitEncFam.unit.len_eq n x).le.trans (BitEncFam.unit.wid_le n)) + st.bound (fun n _ => st.len_le n _) + exposeF := + letI : ∀ n, Fintype (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).State := + fun n => inferInstanceAs (Fintype (Fin (rnd n + 1) × σ n)) + .ofFintype st.enc_injective + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).expose) + ((steps + .C 1) * Sc) + (fun n => by + show Fintype.card (Fin (rnd n + 1) × σ n) ≤ _ + rw [Fintype.card_prod, Fintype.card_fin, hrnd n] + simp only [Polynomial.eval_mul, Polynomial.eval_add, Polynomial.eval_C] + exact Nat.mul_le_mul_left _ (hcard n)) + st.bound (fun n _ => st.len_le n _) + InterfaceBitEnc.coin.encQuery.widBound + (fun n x => (InterfaceBitEnc.coin.encQuery.len_eq n _).le.trans + (InterfaceBitEnc.coin.encQuery.wid_le n)) + updateF := + letI : ∀ n, Fintype (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).State := + fun n => inferInstanceAs (Fintype (Fin (rnd n + 1) × σ n)) + .ofFintype (st.pairVar InterfaceBitEnc.coin.encAns).enc_injective + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).updateFlat) + ((steps + .C 1) * Sc * .C 2) + (fun n => by + have hAns : Fintype.card ((t : Unit) × coinSpec.Range t) = 2 := by + simp [Fintype.card_sigma] + change Fintype.card ((Fin (rnd n + 1) × σ n) × ((t : Unit) × coinSpec.Range t)) ≤ _ + rw [Fintype.card_prod, hAns, Fintype.card_prod, Fintype.card_fin, hrnd n] + simp only [Polynomial.eval_mul, Polynomial.eval_add, Polynomial.eval_C] + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ (hcard n))) + (st.bound + .C 1) + (fun n p => by + have h1 := st.len_le n p.1 + have h2 := InterfaceBitEnc.coin.encAns.len_eq n p.2 + have h3 : InterfaceBitEnc.coin.encAns.wid n = 1 := rfl + simp only [StrEncFam.pairVar_enc, List.length_append, h2, h3, Polynomial.eval_add, + Polynomial.eval_C] + omega) + st.bound (fun n _ => st.len_le n _) + outputF := + letI : ∀ n, Fintype (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).State := + fun n => inferInstanceAs (Fintype (Fin (rnd n + 1) × σ n)) + .ofFintype st.enc_injective + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).output) + ((steps + .C 1) * Sc) + (fun n => by + show Fintype.card (Fin (rnd n + 1) × σ n) ≤ _ + rw [Fintype.card_prod, Fintype.card_fin, hrnd n] + simp only [Polynomial.eval_mul, Polynomial.eval_add, Polynomial.eval_C] + exact Nat.mul_le_mul_left _ (hcard n)) + st.bound (fun n _ => st.len_le n _) + (eβ.option).widBound + (fun n x => ((eβ.option).len_eq n _).le.trans ((eβ.option).wid_le n)) + +/-- The bundled fold adversary certifies the fold program family. -/ +noncomputable def coinFoldWitness : + PolyTimeWitness (BoundaryData.coin BitEncFam.unit eβ) + (fun n (_ : Unit) => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) where + A := coinFoldAdversary step readout init₀ rnd steps hrnd st eβ Sc hcard + implements n := by + change PFunctor.DynSystem.DynComputation.ImplementsWithin + (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)) + (fun _ => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) (steps.eval n) + rw [← hrnd n] + exact coinFoldMachine_implementsWithin (step n) (readout n) (rnd n) (init₀ n) + queryBound n _ := (isTotalQueryBound_coinFoldProg (step n) (readout n) (rnd n) + (init₀ n)).mono (le_of_eq (hrnd n)) + +include steps hrnd st Sc hcard in +/-- **The bounded coin fold is polynomial time.** Filling `rnd n` coin answers into an +accumulator of *polynomially bounded cardinality* and reading out is polynomial time, +witnessed end to end by concrete Turing machines — no hand-built machine required at +the call site. The cardinality bound `Sc` keeps the table witnesses within the advice +budget; folds into superpolynomially large accumulators (e.g. `BitVec n`) need +`isPolyTime_coinFold_of_witnesses`. -/ +theorem isPolyTime_coinFold : + OracleComp.IsPolyTime (BoundaryData.coin BitEncFam.unit eβ) + (fun n (_ : Unit) => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) := + ⟨coinFoldWitness step readout init₀ rnd steps hrnd st eβ Sc hcard⟩ + +end Adversary + +/-! ## The witness-parameterized fold adversary + +For folds whose accumulator type is *not* polynomially small (e.g. a `BitVec n` +accumulator), the table witnesses of `coinFoldAdversary` would exceed any polynomial +advice bound, so the machine witnesses must be supplied: uniform machine families for +the fold's step functions. All coalgebraic content (implements, stability, the +state-size invariant) is still discharged here; only the four `EncPolyTimeFam` +witnesses are hypotheses, pending a base-machine library. -/ + +section AdversaryOfWitnesses + +variable {σ β : ℕ → Type} + (step : (n : ℕ) → σ n → ℕ → Bool → σ n) + (readout : (n : ℕ) → σ n → β n) (init₀ : (n : ℕ) → σ n) + (rnd : ℕ → ℕ) (steps : Polynomial ℕ) (hrnd : ∀ n, rnd n = steps.eval n) + (st : StrEncFam (fun n => Fin (rnd n + 1) × σ n)) (eβ : BitEncFam β) + (initF : EncPolyTimeFam BitEncFam.unit.enc st.enc + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).init)) + (exposeF : EncPolyTimeFam st.enc InterfaceBitEnc.coin.encQuery.enc + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).expose)) + (updateF : EncPolyTimeFam (st.pairVar InterfaceBitEnc.coin.encAns).enc st.enc + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).updateFlat)) + (outputF : EncPolyTimeFam st.enc (eβ.option).enc + (fun n => (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)).output)) + +/-- The bounded coin fold as a `MachineAdversary`, from supplied step-function machine +families: the coalgebraic fields are discharged by the `coinFoldMachine` lemmas, the +machine fields are the hypotheses. -/ +noncomputable def coinFoldAdversaryOfWitnesses : + MachineAdversary (BoundaryData.coin BitEncFam.unit eβ) where + M n := coinFoldMachine (step n) (readout n) (rnd n) (init₀ n) + steps := steps + state := st + initF := initF + exposeF := exposeF + updateF := updateF + outputF := outputF + +/-- The witness-parameterized fold adversary certifies the fold program family. -/ +noncomputable def coinFoldWitnessOfWitnesses : + PolyTimeWitness (BoundaryData.coin BitEncFam.unit eβ) + (fun n (_ : Unit) => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) where + A := coinFoldAdversaryOfWitnesses step readout init₀ rnd steps st eβ + initF exposeF updateF outputF + implements n := by + change PFunctor.DynSystem.DynComputation.ImplementsWithin + (coinFoldMachine (step n) (readout n) (rnd n) (init₀ n)) + (fun _ => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) (steps.eval n) + rw [← hrnd n] + exact coinFoldMachine_implementsWithin (step n) (readout n) (rnd n) (init₀ n) + queryBound n _ := (isTotalQueryBound_coinFoldProg (step n) (readout n) (rnd n) + (init₀ n)).mono (le_of_eq (hrnd n)) + +include steps hrnd st initF exposeF updateF outputF in +/-- **The bounded coin fold is polynomial time, given machine families for its step +functions** — the honest hypothesis form for folds into accumulators of superpolynomial +cardinality, where the table witnesses of `isPolyTime_coinFold` would smuggle +superpolynomial advice. -/ +theorem isPolyTime_coinFold_of_witnesses : + OracleComp.IsPolyTime (BoundaryData.coin BitEncFam.unit eβ) + (fun n (_ : Unit) => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) := + ⟨coinFoldWitnessOfWitnesses step readout init₀ rnd steps hrnd st eβ + initF exposeF updateF outputF⟩ + +end AdversaryOfWitnesses + +end OracleComp diff --git a/VCVio/OracleComp/Coinductive/PolyTime.lean b/VCVio/OracleComp/Coinductive/PolyTime.lean index 82435c0f9..2ed44f194 100644 --- a/VCVio/OracleComp/Coinductive/PolyTime.lean +++ b/VCVio/OracleComp/Coinductive/PolyTime.lean @@ -419,6 +419,41 @@ def _root_.OracleComp.OracleMachine.stepD {ι : Type} {spec : OracleSpec.{0, 0} (s : M.State) : M.State := (M.view s).elim (fun _ => s) (fun q => q.2 (h q.1)) +/-- The deterministic run through a handler reads out the `stepD` trajectory: fuelled +`runWith` at `m := Id` is the readout after `k` deterministic steps — unconditionally, +since returns are absorbing (`stepD` fixes returned states). Replaces the old +stability-conditioned readout lemma. -/ +theorem _root_.OracleComp.OracleMachine.runWith_eq_output_iterate_stepD + {ι : Type} {spec : OracleSpec.{0, 0} ι} {α' β' : Type} + (M : OracleMachine spec α' β') (h : OracleHandler spec) (k : ℕ) (s : M.State) : + M.runWith h.toQueryImpl k s = M.output ((M.stepD h)^[k] s) := by + induction k generalizing s with + | zero => + cases hview : M.view s with + | inl b => + rw [M.runWith_return h.toQueryImpl 0 s b hview, Function.iterate_zero, id_eq, + M.output_of_view_return hview] + rfl + | inr q => + rw [M.runWith_query_zero h.toQueryImpl s q.1 q.2 hview, Function.iterate_zero, + id_eq] + simp [OracleMachine.output, hview] + rfl + | succ k ih => + cases hview : M.view s with + | inl b => + have hfix : M.stepD h s = s := by + simp only [OracleMachine.stepD, hview, Sum.elim_inl] + rw [Function.iterate_succ_apply, hfix, ← ih s, + M.runWith_return h.toQueryImpl (k + 1) s b hview, + M.runWith_return h.toQueryImpl k s b hview] + | inr q => + have hstep : M.stepD h s = q.2 (h q.1) := by + simp only [OracleMachine.stepD, hview, Sum.elim_inr] + rw [M.runWith_query_succ h.toQueryImpl k s q.1 q.2 hview, + Function.iterate_succ_apply, hstep] + exact ih (q.2 (h q.1)) + /-- The state at round `j` of the deterministic run against handler `h` on input `x`. -/ def stateAt (D : MachineAdversary bd) (n : ℕ) (h : OracleHandler (spec n)) (x : α n) (j : ℕ) : (D.M n).State := diff --git a/VCVio/OracleComp/Coinductive/PolyTimeConstructions.lean b/VCVio/OracleComp/Coinductive/PolyTimeConstructions.lean new file mode 100644 index 000000000..222c14a25 --- /dev/null +++ b/VCVio/OracleComp/Coinductive/PolyTimeConstructions.lean @@ -0,0 +1,144 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.OracleComp.Coinductive.CoinFold +import ToMathlib.Data.BitVec + +/-! +# Practical Polynomial-Time Constructions + +Reusable polynomial-time facts built on the coin-fold combinator and the pure-function +witness constructor, at pinned canonical boundaries: + +* **Randomized generation** — `uniformBitVec n` samples a uniform `BitVec n` by flipping + `n` coins (a `coinFoldProg` that overwrites one bit per round). + `isPolyTime_uniformBitVec` certifies it in the honest hypothesis form: the fold's + accumulator is `BitVec n`, of cardinality `2 ^ n`, so the finite-table witnesses of + `isPolyTime_coinFold` would exceed every polynomial advice bound; the caller supplies + genuine uniform machine families for the step functions, pending a base-machine + library. +* **Deterministic computation** — `isPolyTime_pure_ofFintype` certifies any function on + a per-parameter finite input type of *polynomially bounded cardinality* as polynomial + time via finite tables. There is deliberately no hypothesis-free bitvector-function + version: "every function `BitVec (a n) → BitVec (b n)` is polynomial time" is exactly + the unbounded-advice collapse the advice bound exists to prevent — a genuine `f` + needs a genuine machine, supplied through `OracleComp.isPolyTime_pure_of_witnesses`. +-/ + +open OracleSpec Computability + +namespace OracleComp + +/-! ## The single coin flip -/ + +/-- The state representation of the one-round coin fold: a one-bit round counter and a +Boolean accumulator. -/ +noncomputable def coinFlipState : StrEncFam (fun _ => Fin 2 × Bool) := + ((BitEncFam.fin (fun _ => 1) (.C 1) (fun _ => by simp)).pair + (BitEncFam.const Bool)).toStrEncFam + +/-- A single coin flip is polynomial time at the canonical boundaries: the one-round +bounded coin fold with a Boolean accumulator, witnessed end to end by finite tables. -/ +theorem isPolyTime_coin : + OracleComp.IsPolyTime (BoundaryData.coin BitEncFam.unit BitEncFam.bool) + (fun _ (_ : Unit) => OracleComp.coin) := by + refine OracleComp.IsPolyTime.congr (oa := fun n (_ : Unit) => + coinFoldProg (fun (_ : Bool) _ b => b) id 1 false) (fun n _ => ?_) + (isPolyTime_coinFold (fun _ (_ : Bool) _ b => b) (fun _ => id) (fun _ => false) + (fun _ => 1) (.C 1) (fun _ => by simp) coinFlipState BitEncFam.bool + (.C 2) (fun _ => by simp)) + rfl + +/-! ## Uniform `BitVec` generation -/ + +/-- Sample a uniform `BitVec n` by flipping `n` coins, folding the `r`-th answer into bit +`r` of the accumulator. This is the bounded coin fold with `step = overwriteBit` and the +identity read-out. -/ +def uniformBitVec (n : ℕ) : OracleComp coinSpec (BitVec n) := + coinFoldProg (fun (acc : BitVec n) r b => acc.overwriteBit r b) id n 0 + +/-- The counter/accumulator state representation of the `uniformBitVec` fold: binary +counter appended to the raw accumulator bits, total width `2 * n`. -/ +noncomputable def uniformBitVecState : StrEncFam (fun n => Fin (n + 1) × BitVec n) := + ((BitEncFam.fin id .X fun n => (Polynomial.eval_X (x := n)).ge).pair + BitEncFam.bitVecX).toStrEncFam + +/-- **Generating a uniform `BitVec n` is polynomial time, given machine families for the +fold's step functions.** The accumulator is `BitVec n` — cardinality `2 ^ n` — so the +generic table witnesses of `isPolyTime_coinFold` are unavailable (they would exceed +every polynomial advice bound); uniform machine families for initialization, query +selection, the bit-overwrite update, and the readout must be supplied. Discharging them +generically is the base-machine library deferred in +`ToMathlib.Computability.PolyTimeTM`. -/ +theorem isPolyTime_uniformBitVec + (initF : EncPolyTimeFam BitEncFam.unit.enc uniformBitVecState.enc + (fun n => (coinFoldMachine (fun (acc : BitVec n) r b => acc.overwriteBit r b) + id n 0).init)) + (exposeF : EncPolyTimeFam uniformBitVecState.enc InterfaceBitEnc.coin.encQuery.enc + (fun n => (coinFoldMachine (fun (acc : BitVec n) r b => acc.overwriteBit r b) + id n 0).expose)) + (updateF : EncPolyTimeFam + (uniformBitVecState.pairVar InterfaceBitEnc.coin.encAns).enc uniformBitVecState.enc + (fun n => (coinFoldMachine (fun (acc : BitVec n) r b => acc.overwriteBit r b) + id n 0).updateFlat)) + (outputF : EncPolyTimeFam uniformBitVecState.enc (BitEncFam.bitVecX.option).enc + (fun n => (coinFoldMachine (fun (acc : BitVec n) r b => acc.overwriteBit r b) + id n 0).output)) : + OracleComp.IsPolyTime (BoundaryData.coin BitEncFam.unit BitEncFam.bitVecX) + (fun n (_ : Unit) => uniformBitVec n) := + isPolyTime_coinFold_of_witnesses + (fun n (acc : BitVec n) r b => acc.overwriteBit r b) (fun _ => id) (fun _ => 0) + id Polynomial.X (fun n => by simp) + uniformBitVecState BitEncFam.bitVecX initF exposeF updateF outputF + +/-! ## Deterministic functions on finite inputs -/ + +/-- **Any deterministic function on a per-parameter finite input type of polynomially +bounded cardinality is polynomial time.** An ergonomic wrapper over +`isPolyTime_pure_of_witnesses`: the machine is `ofPureFn (f n)` (state = the input, +output `= some ∘ f`), so the query-selection witness is a constant machine and the +update (projection) and output (`some ∘ f`) witnesses are finite tables — within the +advice budget exactly because the input cardinality (`cardIn`) and the tagged-answer +cardinality (`cardIface`) are polynomially bounded. -/ +theorem isPolyTime_pure_ofFintype {ι : ℕ → Type} [∀ n, DecidableEq (ι n)] + [∀ n, Inhabited (ι n)] [∀ n, Finite (ι n)] + {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} [∀ n, (spec n).Fintype] + {α β : ℕ → Type} [∀ n, Finite (α n)] (bd : BoundaryData spec α β) + (f : (n : ℕ) → α n → β n) + (cardIn : Polynomial ℕ) (hcardIn : ∀ n, Nat.card (α n) ≤ cardIn.eval n) + (cardIface : Polynomial ℕ) + (hcardIface : ∀ n, Nat.card ((t : ι n) × (spec n).Range t) ≤ cardIface.eval n) : + OracleComp.IsPolyTime bd fun n x => (pure (f n x) : OracleComp (spec n) (β n)) := by + letI : ∀ n, Fintype (ι n) := fun n => Fintype.ofFinite (ι n) + letI : ∀ n, Fintype (α n) := fun n => Fintype.ofFinite (α n) + have hcardIn' : ∀ n, Fintype.card (α n) ≤ cardIn.eval n := fun n => by + simpa [Nat.card_eq_fintype_card] using hcardIn n + refine OracleComp.isPolyTime_pure_of_witnesses bd f + (.const bd.eIn.enc (fun n => (default : ι n)) bd.eIface.encQuery.widBound + (fun n => (bd.eIface.encQuery.len_eq n _).le.trans (bd.eIface.encQuery.wid_le n))) + (.ofFintype (bd.eIn.toStrEncFam.pairVar bd.eIface.encAns).enc_injective + (fun _ => Prod.fst) + (cardIn * cardIface) + (fun n => by + rw [Fintype.card_prod] + simp only [Polynomial.eval_mul] + exact Nat.mul_le_mul (hcardIn' n) + (by simpa [Nat.card_eq_fintype_card] using hcardIface n)) + (bd.eIn.widBound + bd.eIface.encAns.widBound) + (fun n p => by + have h1 := (bd.eIn.len_eq n p.1).le.trans (bd.eIn.wid_le n) + have h2 := (bd.eIface.encAns.len_eq n p.2).le.trans (bd.eIface.encAns.wid_le n) + simp only [StrEncFam.pairVar_enc, List.length_append, BitEncFam.toStrEncFam_enc, + Polynomial.eval_add] + omega) + bd.eIn.widBound + (fun n p => (bd.eIn.len_eq n _).le.trans (bd.eIn.wid_le n))) + (.ofFintype bd.eIn.enc_injective (fun n x => some (f n x)) + cardIn hcardIn' + bd.eIn.widBound (fun n x => (bd.eIn.len_eq n x).le.trans (bd.eIn.wid_le n)) + (bd.eOut.option).widBound + (fun n x => ((bd.eOut.option).len_eq n _).le.trans ((bd.eOut.option).wid_le n))) + +end OracleComp diff --git a/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean b/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean new file mode 100644 index 000000000..bf4e5233e --- /dev/null +++ b/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean @@ -0,0 +1,444 @@ +/- +Copyright (c) 2026 Devon Tuma. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Devon Tuma +-/ +import VCVio.OracleComp.Coinductive.PolyTimeClosure +import ToMathlib.Computability.MachineCounting + +/-! +# Non-Triviality Certificates for the Polynomial-Time Adversary Model + +Acceptance targets for the canonical-boundary machine model: theorems asserting that +the polynomial-time class does **not** contain every function. Their provability is the +model's soundness certificate, and their history is the model's design rationale: + +* With the pre-canonicalization model (existential boundary encodings), both statements + below were **false**: the encoding `enc x := std x ++ block (f x)` caches any `f` + inside the input representation, whereupon a bundle with `steps = 0`, `initTM` the + identity machine, `exposeTM` a constant machine, and two small streaming machines + (a projection and a block extraction) certifies `f` — no machine ever computes `f`. +* With pinned canonical boundaries (`BoundaryData`), the caching channel is closed, and + the counting/diagonalization argument goes through: an implementing bundle's + input→output behavior at parameter `n` factors through its four witness string + functions (machines of at most `descBound`-many states over the fixed `Bool` + alphabet, composed at most `steps`-many times), of which there are at most + `2^{O(d log d)}` at size `d`, while there are `2^{2^n}` functions `BitVec n → Bool`; + a function chosen (classically) to differ from every `2^{n/4}`-sized composite + behavior defeats every polynomial bundle at large `n`. + +The proofs are staged (see `docs/agents/polytime-model.md`): the run-factorization +lemma (the "compiled run": the deterministic run against a fixed handler is an iterate +of the witness string functions), machine counting and normalization to `Fin d` state +spaces, elementary `p.eval n ≤ 2^(n/4)` growth bounds, and the diagonal construction. +The `sorry`s below are those staged proofs' end products, recorded as the model's +falsifiable acceptance criteria rather than smuggled as axioms — nothing downstream +may depend on them. +-/ + +open OracleSpec OracleComp Computability + +/-! ## Local support: appending a fixed bit on a single-tape machine + +The compiled deterministic run inserts the fixed canonical coin answer into the state +encoding before each `update` step. At the canonical coin boundary the query index has +width `0` and the answer `⟨t, r⟩` encodes as the single bit `[r]`, so "insert the fixed +`true` answer" is exactly appending `[true]` to the state encoding. The Cslib base library +provides `idComputer`, `constComputer`, and `tableComputer`, but no combinator appending a +fixed suffix to an *unbounded* string; we supply one here as local support, in the same +`SingleTapeTM` style. -/ + +namespace Cslib.Turing.SingleTapeTM + +open Turing Relation + +/-- Append a fixed symbol `c` at the right end of the input: walk right keeping every +symbol (`inl`), write `c` at the trailing blank and turn around (`inr`), then walk back +left keeping every symbol until falling off the left end, where one right step lands the +head on the first symbol of the output `l ++ [c]`. -/ +def snocComputer (c : Bool) : SingleTapeTM Bool where + State := Unit ⊕ Unit + q₀ := .inl () + tr q h := match q with + | .inl () => match h with + | some b => ⟨⟨some b, some .right⟩, some (.inl ())⟩ + | none => ⟨⟨some c, some .left⟩, some (.inr ())⟩ + | .inr () => match h with + | some b => ⟨⟨some b, some .left⟩, some (.inr ())⟩ + | none => ⟨⟨none, some .right⟩, none⟩ + +/-- The left stack after the rightward pass has consumed `l` (starting from stack `L`): +each symbol of `l` is pushed on top in turn, so the top is the last symbol of `l`. -/ +private def snocPush : StackTape Bool → List Bool → StackTape Bool + | L, [] => L + | L, b :: t => snocPush (StackTape.cons (some b) L) t + +@[simp] private lemma snocPush_nil (L : StackTape Bool) : snocPush L [] = L := rfl + +@[simp] private lemma snocPush_cons (L : StackTape Bool) (b : Bool) (t : List Bool) : + snocPush L (b :: t) = snocPush (StackTape.cons (some b) L) t := rfl + +private lemma stackTape_ext {a b : StackTape Bool} (h : a.toList = b.toList) : a = b := by + cases a; cases b; cases h; rfl + +private lemma snocPush_toList (L : StackTape Bool) (l : List Bool) : + (snocPush L l).toList = l.reverse.map some ++ L.toList := by + induction l generalizing L with + | nil => simp + | cons b t ih => + rw [snocPush_cons, ih (StackTape.cons (some b) L), StackTape.cons_some_toList] + simp + +private lemma mapSome_head (l : List Bool) : + (StackTape.mapSome l).head = l.head?.map some := by + cases l <;> rfl + +private lemma mapSome_tail (l : List Bool) : + (StackTape.mapSome l).tail = StackTape.mapSome l.tail := by + cases l <;> rfl + +private lemma mk₁_eq (s : List Bool) : + (BiTape.mk₁ s : BiTape Bool) = + ⟨(StackTape.mapSome s).head, ∅, (StackTape.mapSome s).tail⟩ := by + cases s <;> rfl + +/-- Rightward pass: from head over the first of `l` with left stack `L`, reach the trailing +blank with `l` pushed onto `L`, in `l.length` steps. -/ +private lemma snocComputer_phaseA (c : Bool) (L : StackTape Bool) : ∀ l : List Bool, + RelatesInSteps (snocComputer c).TransitionRelation + ⟨some (.inl ()), ⟨(StackTape.mapSome l).head, L, (StackTape.mapSome l).tail⟩⟩ + ⟨some (.inl ()), ⟨none, snocPush L l, ∅⟩⟩ l.length := by + intro l + induction l generalizing L with + | nil => exact .refl _ + | cons b t ih => + refine .head _ (t' := (⟨some (.inl ()), + ⟨(StackTape.mapSome t).head, StackTape.cons (some b) L, (StackTape.mapSome t).tail⟩⟩ : + (snocComputer c).Cfg)) _ _ ?_ ?_ + · rfl + · simpa using ih (StackTape.cons (some b) L) + +/-- Leftward pass then turn-around: from head over the first of `m` (left stack becomes the +right output), reach the halting configuration reading the reconstructed output, in +`m.length + 1` steps. -/ +private lemma snocComputer_phaseB (c : Bool) : ∀ (m : List Bool) (R : StackTape Bool), + RelatesInSteps (snocComputer c).TransitionRelation + ⟨some (.inr ()), ⟨(StackTape.mapSome m).head, (StackTape.mapSome m).tail, R⟩⟩ + ⟨none, ⟨(snocPush R m).head, ∅, (snocPush R m).tail⟩⟩ (m.length + 1) := by + intro m + induction m with + | nil => intro R; exact .single rfl + | cons b t ih => + intro R + refine .head _ (t' := (⟨some (.inr ()), + ⟨(StackTape.mapSome t).head, (StackTape.mapSome t).tail, StackTape.cons (some b) R⟩⟩ : + (snocComputer c).Cfg)) _ _ ?_ ?_ + · rfl + · simpa using ih (StackTape.cons (some b) R) + +private lemma snocPush_empty (l : List Bool) : + snocPush (∅ : StackTape Bool) l = StackTape.mapSome l.reverse := by + apply stackTape_ext + rw [snocPush_toList] + simp [StackTape.mapSome] + +private lemma snocPush_final (c : Bool) (l : List Bool) : + snocPush (StackTape.cons (some c) ∅) l.reverse = StackTape.mapSome (l ++ [c]) := by + apply stackTape_ext + rw [snocPush_toList, StackTape.cons_some_toList] + simp [StackTape.mapSome] + +/-- The append-a-bit machine outputs `l ++ [c]` within `2 * |l| + 2` steps. -/ +lemma snocComputer_outputsWithinTime (c : Bool) (l : List Bool) : + (snocComputer c).OutputsWithinTime l (l ++ [c]) (2 * l.length + 2) := by + have hA := snocComputer_phaseA c ∅ l + have hB := snocComputer_phaseB c l.reverse (StackTape.cons (some c) ∅) + rw [← snocPush_empty l, snocPush_final c l] at hB + have hchain : + RelatesInSteps (snocComputer c).TransitionRelation + ⟨some (.inl ()), (BiTape.mk₁ l : BiTape Bool)⟩ + ⟨none, (BiTape.mk₁ (l ++ [c]) : BiTape Bool)⟩ + (l.length + (1 + (l.reverse.length + 1))) := by + rw [mk₁_eq l, mk₁_eq (l ++ [c])] + exact hA.trans ((RelatesInSteps.single (by rfl)).trans hB) + refine RelatesWithinSteps.of_le + (RelatesWithinSteps.of_relatesInSteps hchain) ?_ + simp only [List.length_reverse] + omega + +/-- The append-a-bit machine is a `TimeComputable` witness for `fun l => l ++ [c]`. -/ +def snocTimeComputable (c : Bool) : TimeComputable (fun l => l ++ [c]) where + tm := snocComputer c + timeBound n := 2 * n + 2 + outputsFunInTime l := snocComputer_outputsWithinTime c l + +/-- The append-a-bit machine is a `PolyTimeComputable` witness for `fun l => l ++ [c]`. -/ +noncomputable def snocPolyTimeComputable (c : Bool) : + PolyTimeComputable (fun l => l ++ [c]) where + toTimeComputable := snocTimeComputable c + poly := 2 * Polynomial.X + Polynomial.C 2 + bounds n := by simp [snocTimeComputable, two_mul] + +/-- The append-a-bit machine has two states. -/ +theorem size_snocPolyTimeComputable (c : Bool) : + (snocPolyTimeComputable c).size ≤ 2 := by + change Fintype.card (Unit ⊕ Unit) ≤ 2 + simp + +end Cslib.Turing.SingleTapeTM + +/-! ## Local support: appending a fixed bit, and finite iteration, at the encoding level -/ + +namespace Computability.EncPolyTime + +open Cslib.Turing.SingleTapeTM + +/-- Append a fixed bit `c` to any encoding: the identity `σ → σ` viewed from encoding `es` +into the encoding `fun s => es s ++ [c]`, computed by the append-a-bit machine. -/ +noncomputable def appendBit {σ : Type} (es : σ → List Bool) (c : Bool) : + EncPolyTime es (fun s => es s ++ [c]) _root_.id where + toFun l := l ++ [c] + polyTime := snocPolyTimeComputable c + map_encode _ := rfl + +theorem size_appendBit {σ : Type} (es : σ → List Bool) (c : Bool) : + (appendBit es c).size ≤ 2 := size_snocPolyTimeComputable c + +/-- Finite iteration of a self-map witness: the `k`-fold composition is polynomial-time +computable, with description size at most `1 + k *` the single-step size. Composition adds +state counts (`size_comp`), so the bound is by induction on `k`. -/ +theorem exists_iterate {σ : Type} (es : σ → List Bool) {g : σ → σ} + (hstep : EncPolyTime es es g) : + ∀ k : ℕ, ∃ h : EncPolyTime es es (g^[k]), h.size ≤ 1 + k * hstep.size := by + intro k + induction k with + | zero => + refine ⟨(EncPolyTime.id es).copy (g^[0]) (fun s => ?_), ?_⟩ + · simp + · simp + | succ k ih => + obtain ⟨h, hh⟩ := ih + refine ⟨(h.comp hstep).copy (g^[k+1]) (fun s => ?_), ?_⟩ + · exact (Function.iterate_succ_apply' g k s).symm + · rw [size_copy, size_comp] + have hmul : (k + 1) * hstep.size = k * hstep.size + hstep.size := by ring + omega + +end Computability.EncPolyTime + +namespace OracleComp + +/-! ## The realizable-predicate bridge + +An implementing adversary's computed predicate `f n` at parameter `n` lands in +`Computability.RealizableLE n (d n)` for a polynomial description bound `d`, so a +predicate chosen to escape every polynomial realizable set (the diagonal of +`MachineCounting`) is implemented by no polynomial adversary. The `steps = 0` case is a +real proof (`realizable_of_implements_steps_eq_zero`): the initial-state readout factors +through only the initialization and output witnesses. The general case needs the compiled +run's factorization through the witness string functions, isolated as +`exists_poly_realizable_of_implements`. -/ + +/-- **[B-factor — the compiled deterministic run]** For an adversary implementing `pure ∘ f` +at the coin boundary, the input→output behavior `x ↦ (M n).output (run …)` at parameter `n` +is realized by an `EncPolyTime` initialization/output pair whose description sizes are +bounded by a single polynomial `q` in the adversary's `descBound` and `steps`. + +Proof. Specialize `(himpl n).runK_eq` to `m := Id` and the deterministic constant-`true` +coin handler; the pure simulation collapses to `runD hdl (steps.eval n) (init x) = f n x`, +and `runD_eq_output_stateAfter` (using the adversary's `stable` readout to absorb early +termination) turns this into a readout of the fixed-fuel iterate of the one-step state map +`g = advanceOnce hdl M.toDynSystem`. That one-step map is compiled at the encoding level as +`updateF` precomposed with the fixed canonical coin answer `[true]` (built by the local +`Computability.EncPolyTime.appendBit` / `Cslib.Turing.SingleTapeTM.snocComputer` support), and its +`steps.eval n`-fold composition is assembled by `Computability.EncPolyTime.exists_iterate`. +Composition adds machine-state counts, so the compiled initialization +`initF ▸ g^[steps.eval n]` has description size at most +`descBound + 1 + steps * (2 + descBound)` evaluated at `n` — one uniform polynomial `q`, +with the (parameter-specific) iteration time absorbed into `RealizableLE`'s time component. +This is the general-`steps` input to the diagonal contradiction; the `steps = 0` special +case is proved directly by `realizable_of_implements_steps_eq_zero`. -/ +theorem exists_poly_realizable_of_implements + (A : MachineAdversary (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool)) + (f : (n : ℕ) → BitVec n → Bool) + (himpl : A.Implements (fun n x => (pure (f n x) : OracleComp coinSpec Bool))) : + ∃ q : Polynomial ℕ, ∀ n, f n ∈ RealizableLE n (q.eval n) := by + classical + -- The deterministic constant-`true` coin handler. + set hdl : OracleHandler coinSpec := OracleHandler.ofFn (fun _ => true) with hhdl + refine ⟨A.descBound + Polynomial.C 1 + A.steps * (Polynomial.C 2 + A.descBound), fun n => ?_⟩ + set M := A.M n with hM + set es := A.state.enc n with hes + set k := A.steps.eval n with hk + -- The one-step state map: expose the coin query, answer `true`, update. + set g : M.State → M.State := M.stepD hdl with hg + -- `g` is the flattened update against the canonical `true` answer. + have hg_upd : ∀ s, g s = M.updateFlat (s, ⟨M.expose s, true⟩) := by + intro s + rw [hg] + cases hview : M.view s with + | inl b => + simp only [OracleMachine.stepD, OracleMachine.updateFlat, hview, Sum.elim_inl] + | inr q => + obtain ⟨t, cont⟩ := q + simp only [OracleMachine.stepD, OracleMachine.updateFlat, hview, Sum.elim_inr, + M.expose_of_view_query hview, hhdl, OracleHandler.ofFn_apply] + rfl + -- The canonical coin answer encodes to the single bit `[true]`. + have hin : ∀ s : M.State, es s ++ [true] + = (A.state.pairVar (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool).eIface.encAns).enc n + (s, ⟨M.expose s, true⟩) := by + intro s + rw [StrEncFam.pairVar_enc, hes] + rfl + have hout : ∀ s : M.State, + es (g s) = A.state.enc n (M.updateFlat (s, ⟨M.expose s, true⟩)) := by + intro s; rw [hes]; exact congrArg (A.state.enc n) (hg_upd s) + -- The one-step witness: append the fixed answer, then run the update witness. + set recoded : EncPolyTime (fun s => es s ++ [true]) es g := + (A.updateF.wit n).recode (fun s => (s, ⟨M.expose s, true⟩)) g hin hout with hrec + set hstep : EncPolyTime es es g := + ((EncPolyTime.appendBit es true).comp recoded).copy g (fun _ => rfl) with hstepdef + have hstep_size : hstep.size ≤ 2 + (A.updateF.wit n).size := by + rw [hstepdef, EncPolyTime.size_copy, EncPolyTime.size_comp, hrec, EncPolyTime.size_recode] + have := EncPolyTime.size_appendBit es true + omega + -- Finite iteration of the one-step map, `k = steps.eval n` times. + obtain ⟨hIter, hIter_size⟩ := EncPolyTime.exists_iterate es hstep k + -- Correctness: the compiled run reads out `f n x`. + have hcorrect : ∀ x, M.output ((g^[k] ∘ M.init) x) = some (f n x) := by + intro x + have key := (himpl n).runWithInput_eq (m := Id) hdl.toQueryImpl x + rw [Function.comp_apply, hg, + ← M.runWith_eq_output_iterate_stepD hdl k (M.init x)] + rw [show M.runWith hdl.toQueryImpl k (M.init x) + = PFunctor.DynSystem.DynComputation.runWithInput M hdl.toQueryImpl k x from rfl, + hk, key] + rfl + -- Description-size bookkeeping. + have hdesc : A.descBound.eval n = A.initF.size.eval n + A.exposeF.size.eval n + + A.updateF.size.eval n + A.outputF.size.eval n := by + simp [MachineAdversary.descBound] + have hinit_le : (A.initF.wit n).size ≤ A.descBound.eval n := + (A.initF.size_le n).trans (by omega) + have hupd_le : (A.updateF.wit n).size ≤ A.descBound.eval n := + (A.updateF.size_le n).trans (by omega) + have hout_le : (A.outputF.wit n).size ≤ A.descBound.eval n := + (A.outputF.size_le n).trans (by omega) + have hqeval : (A.descBound + Polynomial.C 1 + + A.steps * (Polynomial.C 2 + A.descBound)).eval n + = A.descBound.eval n + 1 + k * (2 + A.descBound.eval n) := by + rw [hk]; simp [Polynomial.eval_add, Polynomial.eval_mul] + have hmul : k * hstep.size ≤ k * (2 + A.descBound.eval n) := + Nat.mul_le_mul_left k (hstep_size.trans (by omega)) + have hcomp_size : ((A.initF.wit n).comp hIter).size + ≤ A.descBound.eval n + 1 + k * (2 + A.descBound.eval n) := by + rw [EncPolyTime.size_comp] + have := hIter_size + omega + refine ⟨M.State, es, g^[k] ∘ M.init, M.output, (A.initF.wit n).comp hIter, + A.outputF.wit n, ?_, ?_, hcorrect⟩ + · -- initialization size + rw [hqeval] + exact hcomp_size + · -- output size + rw [hqeval] + exact hout_le.trans (by omega) + +/-- **Milestone A, realizability step (real).** A round-free adversary implementing +`pure ∘ f` realizes `f n` within its description bound: at `steps = 0` the run is the plain +readout of the initial state (`runK_zero`), so `(M n).output ((M n).init x) = some (f n x)`, +and the initialization and output witness families supply the required `EncPolyTime` pair +with sizes bounded by `descBound`. -/ +private theorem realizable_of_implements_steps_eq_zero + (A : MachineAdversary (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool)) + (f : (n : ℕ) → BitVec n → Bool) + (himpl : A.Implements (fun n x => (pure (f n x) : OracleComp coinSpec Bool))) + (hsteps : A.steps = 0) (n : ℕ) : + f n ∈ RealizableLE n (A.descBound.eval n) := by + have hout : ∀ x, (A.M n).output ((A.M n).init x) = some (f n x) := by + intro x + have h := himpl n + rw [hsteps] at h + simp only [Polynomial.eval_zero] at h + have key := PFunctor.DynSystem.DynComputation.ImplementsWithin.runWithInput_eq h + (m := Id) (OracleHandler.ofFn (fun _ => true)).toQueryImpl x + exact ((A.M n).runWith_eq_output_iterate_stepD (OracleHandler.ofFn (fun _ => true)) 0 + ((A.M n).init x)).symm.trans key + refine ⟨(A.M n).State, A.state.enc n, (A.M n).init, (A.M n).output, + A.initF.wit n, A.outputF.wit n, ?_, ?_, hout⟩ + · exact (A.initF.size_le n).trans (by + simp only [MachineAdversary.descBound, Polynomial.eval_add]; omega) + · exact (A.outputF.size_le n).trans (by + simp only [MachineAdversary.descBound, Polynomial.eval_add]; omega) + +/-- **The diagonal predicate (real).** A predicate family `f` together with the covering +`Finset` family `S` of the threshold realizable sets, such that `f` escapes `S` cofinitely. +Assembled from the machine count (`exists_realizableLE_covering`), the count-versus-function +bound (`eventually_count_lt`), and the diagonal argument (`exists_diagonal`). -/ +private theorem exists_diagonal_realizable : + ∃ (f : (n : ℕ) → BitVec n → Bool) (S : (n : ℕ) → Finset (BitVec n → Bool)), + (∀ n, RealizableLE n (2 ^ (n / 4)) ⊆ ↑(S n)) ∧ + (∀ᶠ n in Filter.atTop, f n ∉ S n) := by + classical + set S : (n : ℕ) → Finset (BitVec n → Bool) := + fun n => (exists_realizableLE_covering n (2 ^ (n / 4))).choose with hS + have hcov : ∀ n, RealizableLE n (2 ^ (n / 4)) ⊆ ↑(S n) := fun n => + (exists_realizableLE_covering n (2 ^ (n / 4))).choose_spec.1 + have hcard : ∀ n, (S n).card ≤ Cslib.Turing.SingleTapeTM.B (2 ^ (n / 4)) ^ 2 := fun n => + (exists_realizableLE_covering n (2 ^ (n / 4))).choose_spec.2 + have hSlt : ∀ᶠ n in Filter.atTop, (S n).card < 2 ^ (2 ^ n) := + eventually_count_lt.mono fun n h => lt_of_le_of_lt (hcard n) h + obtain ⟨f, hf⟩ := exists_diagonal S hSlt + exact ⟨f, S, hcov, hf⟩ + +/-- **The counting contradiction (real).** A diagonal predicate `f` escaping the threshold +realizable sets cofinitely is realized at no polynomial description bound: if `f n` were +realizable within `q.eval n` for every `n`, then cofinitely `q.eval n ≤ 2 ^ (n / 4)` +(`eventually_poly_le`), so `f n` would lie in the covered threshold set — contradicting that +it escapes it. -/ +private theorem not_realizable_of_diagonal + {f : (n : ℕ) → BitVec n → Bool} {S : (n : ℕ) → Finset (BitVec n → Bool)} + (hcov : ∀ n, RealizableLE n (2 ^ (n / 4)) ⊆ ↑(S n)) + (hnot : ∀ᶠ n in Filter.atTop, f n ∉ S n) (q : Polynomial ℕ) + (hreal : ∀ n, f n ∈ RealizableLE n (q.eval n)) : False := by + have hmem : ∀ᶠ n in Filter.atTop, f n ∈ S n := + (eventually_poly_le q).mono fun n h => + Finset.mem_coe.mp (hcov n (realizableLE_mono h (hreal n))) + obtain ⟨n, hin, hnotn⟩ := (hmem.and hnot).exists + exact hnotn hin + +/-! ## The certificates -/ + +/-- **Milestone A (sentinel)**: no family of *round-free* machine adversaries computes +every bitvector predicate. The `steps = 0` case needs no run analysis — the readout of +the initial state factors through just two witness machines — so it isolates the +counting core of the full certificate. False before boundary canonicalization (the +encoding-caching bundle above had `steps = 0`); its provability certifies the pinned +model. -/ +theorem exists_not_implements_pure_of_steps_eq_zero : + ∃ f : (n : ℕ) → BitVec n → Bool, + ∀ A : MachineAdversary + (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool), + A.steps = 0 → + ¬ A.Implements (fun n x => (pure (f n x) : OracleComp coinSpec Bool)) := by + obtain ⟨f, S, hcov, hnot⟩ := exists_diagonal_realizable + exact ⟨f, fun A hsteps himpl => not_realizable_of_diagonal hcov hnot A.descBound + (realizable_of_implements_steps_eq_zero A f himpl hsteps)⟩ + +/-- **Milestone B (the acceptance target)**: the polynomial-time class at canonical +boundaries does not contain every bitvector predicate. This is the model's +non-triviality certificate — the statement whose provability separates a sound +definition of P/poly from the encoding-caching collapse, by counting: polynomially +many description bits per parameter cannot name doubly-exponentially many functions. -/ +theorem exists_not_isPolyTime_pure : + ∃ f : (n : ℕ) → BitVec n → Bool, + ¬ OracleComp.IsPolyTime (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool) + (fun n x => (pure (f n x) : OracleComp coinSpec Bool)) := by + obtain ⟨f, S, hcov, hnot⟩ := exists_diagonal_realizable + refine ⟨f, fun h => ?_⟩ + obtain ⟨w⟩ := h + obtain ⟨q, hreal⟩ := exists_poly_realizable_of_implements w.A f w.implements + exact not_realizable_of_diagonal hcov hnot q hreal + +end OracleComp From b744343d5a05d5056086292251823c066c46c046 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Fri, 31 Jul 2026 22:53:23 -0500 Subject: [PATCH 4/5] fix(polytime): semantic-audit fixes for the TM-grounded layer - Charge the final readout in `detTotalTime`: the run's answer is `output (stepD^[steps] s)`, so the readout at the budget state is a real evaluation; `detTotalTime_le` and `exists_polynomial_detTotalTime_le` gain the matching term (closes the remaining #460 accounting bullet). - Derive `PolyTimeWitness.queryBound` as a theorem instead of a field: `ImplementsWithin` is the fuel-k unroll equality, whose bounded half is the total query bound (`implementsWithin_iff_implements_and_bound`; `IsTotalQueryBound` is definitionally `IsTotalRollBound`). All witness constructions shed the redundant proof obligation. - Pin `PolyTimeComputable.size` to the `Bool` alphabet: a bare state count only measures description size over a fixed alphabet, and only `Bool` is counted by `B`. - Drop the unused `PackedEncoding`/`boolify` layer (`Encoding.lean`) and the docstrings presenting it as the model's encoding source; import `Mathlib.Data.Nat.Bitwise` directly where its transitive import was load-bearing. - Generalize `exists_tmTable_of_card_le` to any `SingleTapeTM Bool`; add a real `OracleMachine.setInit` behind the `*_setInit` lemmas. - Refresh stale docstrings: retired names (`PolyTimeAdversary`, dead lemma references), sorry-era prose in `PolyTimeNontrivial`, nonexistent module references; state non-uniformity (P/poly) on `IsPolyTime`; warn against polynomial-depth `comp` iteration; credit Elias Judin in the headers of the two re-extracted #487 files. Co-authored-by: Elias Judin Co-authored-by: Aristotle (Harmonic) Co-Authored-By: Claude Fable 5 --- ToMathlib.lean | 1 - ToMathlib/Computability/BitEncoding.lean | 20 +- ToMathlib/Computability/CslibPolyTime.lean | 49 ++- ToMathlib/Computability/Encoding.lean | 337 ------------------ ToMathlib/Computability/MachineCounting.lean | 21 +- ToMathlib/Computability/PolyTimeTM.lean | 25 +- .../Asymptotics/PolyTime.lean | 15 +- VCVio/OracleComp/Coinductive/CoinFold.lean | 43 +-- VCVio/OracleComp/Coinductive/PolyTime.lean | 89 +++-- .../Coinductive/PolyTimeClosure.lean | 49 +-- .../Coinductive/PolyTimeNontrivial.lean | 46 +-- 11 files changed, 193 insertions(+), 502 deletions(-) delete mode 100644 ToMathlib/Computability/Encoding.lean diff --git a/ToMathlib.lean b/ToMathlib.lean index 9f46c3c1b..677fa0666 100644 --- a/ToMathlib.lean +++ b/ToMathlib.lean @@ -3,7 +3,6 @@ import ToMathlib.Analysis.SumIntegralComparisons import ToMathlib.Combinatorics.FinPairs import ToMathlib.Computability.BitEncoding import ToMathlib.Computability.CslibPolyTime -import ToMathlib.Computability.Encoding import ToMathlib.Computability.MachineCounting import ToMathlib.Computability.PolyTimeTM import ToMathlib.Control.AlternativeMonad diff --git a/ToMathlib/Computability/BitEncoding.lean b/ToMathlib/Computability/BitEncoding.lean index 00688d5d1..7a0bc536a 100644 --- a/ToMathlib/Computability/BitEncoding.lean +++ b/ToMathlib/Computability/BitEncoding.lean @@ -6,6 +6,7 @@ Authors: Devon Tuma module public import ToMathlib.Computability.PolyTimeTM +public import Mathlib.Data.Nat.Bitwise public import Mathlib.Data.Nat.Log /-! @@ -44,9 +45,9 @@ must fix it explicitly). This file provides that representation: produce these; the closure combinators (`comp`, `id`, `const`, `ofFintype`) compose them; a polynomial-time adversary carries four of them. -Everything here is raw `α → List Bool`: no `PackedEncoding` alphabets and no one-hot -`boolify` relabeling on the canonical side (the legacy `Computability.PackedEncoding` -layer remains available for machine-internal state representations). +Everything here is raw `α → List Bool`: no intermediate alphabet types and no one-hot +symbol relabeling — encodings are binary from the start, so encoded lengths are the +bit-lengths the polynomial bounds speak about. -/ @[expose] public section @@ -313,7 +314,11 @@ string encodings, a single polynomial bounding all running times (in `n` plus th input length), and a single polynomial bounding all description sizes (the advice bound — without it, per-parameter table machines smuggle unbounded advice). This is the reusable unit of the polynomial-time adversary model: base machines produce these, -combinators compose them, and an adversary's four step functions each carry one. -/ +combinators compose them, and an adversary's four step functions each carry one. + +Like `EncPolyTime`, the structure imposes nothing on the encodings themselves; its +certifying power comes from the call site pinning the injective families +(`Computability.BitEncFam`, `Computability.StrEncFam`). -/ structure EncPolyTimeFam {α β : ℕ → Type u} (ea : (n : ℕ) → α n → List Bool) (eb : (n : ℕ) → β n → List Bool) (f : (n : ℕ) → α n → β n) : Type (u + 1) where @@ -374,7 +379,12 @@ def copy {f : (n : ℕ) → α n → β n} (h : EncPolyTimeFam ea eb f) /-- Composition of uniform families: witnesses compose by `EncPolyTime.comp`; the uniform time bound composes through the output-length envelope, and description -sizes add. -/ +sizes add. + +The composed time bound substitutes one polynomial into another, so degrees multiply: +a fixed number of `comp`s stays polynomial, but iterating to a depth that grows with +`n` does not. Polynomial-length runs account time additively per step instead +(`MachineAdversary.detTotalTime`); only description size composes additively. -/ noncomputable def comp {f : (n : ℕ) → α n → β n} {g : (n : ℕ) → β n → γ n} (h : EncPolyTimeFam ea eb f) (h' : EncPolyTimeFam eb ec g) : EncPolyTimeFam ea ec (fun n => g n ∘ f n) where diff --git a/ToMathlib/Computability/CslibPolyTime.lean b/ToMathlib/Computability/CslibPolyTime.lean index 6497421a0..2835140cc 100644 --- a/ToMathlib/Computability/CslibPolyTime.lean +++ b/ToMathlib/Computability/CslibPolyTime.lean @@ -7,7 +7,6 @@ module public import Cslib.Computability.Machines.Turing.SingleTape.Deterministic public import Mathlib.Algebra.Polynomial.Eval.Degree -public import ToMathlib.Computability.Encoding /-! # Encoded Polynomial-Time Computability @@ -17,8 +16,10 @@ of raw string functions `List Symbol → List Symbol`. This file adds the encodi `Computability.EncPolyTime ea eb f` witnesses that a function `f : α → β` between arbitrary types is polynomial-time computable relative to `Bool`-string encodings `ea : α → List Bool` and `eb : β → List Bool`, by bundling a machine-computed total -string function that intertwines the encodings. Encodings typically arise from a -`Computability.PackedEncoding` via `PackedEncoding.boolify`. +string function that intertwines the encodings. The encodings are supplied by call +sites; the adversary model pins the injective fixed-width and length-bounded families +of `ToMathlib.Computability.BitEncoding` (`Computability.BitEncFam`, +`Computability.StrEncFam`) at its boundaries. Identity and composition (`EncPolyTime.id`, `EncPolyTime.comp`) lift directly from Cslib's proven `PolyTimeComputable.id` and `PolyTimeComputable.comp`; the monotone @@ -32,13 +33,13 @@ witnesses indexed by a security parameter: a finite-table machine looks up any f in linear time using one state per valid input, so without a size bound a family of witnesses smuggles unbounded advice and the induced "polynomial-time" class contains every function on polynomially-encodable domains. Families must therefore bound -`size` polynomially as well (see `PolyTimeAdversary.descBound`), giving the standard +`size` polynomially as well (see `MachineAdversary.descBound`), giving the standard non-uniform P/poly model. -/ @[expose] public section -universe u v w u_1 u_2 +universe u v w u' v' /-- Evaluation of a natural-number polynomial is monotone in the argument. -/ theorem Polynomial.eval_le_eval {p : Polynomial ℕ} {m n : ℕ} (h : m ≤ n) : @@ -65,15 +66,21 @@ theorem PolyTimeComputable.monotone_normalize_timeBound {f : List Symbol → Lis (h : PolyTimeComputable f) : Monotone h.normalize.timeBound := fun _ _ hmn => Polynomial.eval_le_eval hmn -/-- The description size of a machine witness: its number of states. Over a fixed tape -alphabet the transition table has one row per state, so this measures the machine's -description — the "advice" of a non-uniform family. Time bounds alone do not control -it: a table machine looks up any function on a finite domain in linear time using one -state per valid input. -/ -def PolyTimeComputable.size {f : List Symbol → List Symbol} +/-- The description size of a machine witness over the two-symbol tape alphabet: its +number of states. Over the fixed `Bool` alphabet the transition table has exactly three +rows per state, so the state count measures the machine's description up to a constant +factor — the "advice" of a non-uniform family, and the quantity the machine-counting +bound `B` counts. Time bounds alone do not control it: a table machine looks up any +function on a finite domain in linear time using one state per valid input. + +Deliberately restricted to `Symbol := Bool`: over a family of growing alphabets the +transition table has `Fintype.card Symbol + 1` rows per state, so a bare state count +would undercount the description (a one-state machine over an alphabet of size `2 ^ n` +hides `2 ^ n` advice bits in its transition row). -/ +def PolyTimeComputable.size {f : List Bool → List Bool} (h : PolyTimeComputable f) : ℕ := Fintype.card h.tm.State -@[simp] theorem PolyTimeComputable.size_normalize {f : List Symbol → List Symbol} +@[simp] theorem PolyTimeComputable.size_normalize {f : List Bool → List Bool} (h : PolyTimeComputable f) : h.normalize.size = h.size := rfl end Cslib.Turing.SingleTapeTM @@ -89,7 +96,10 @@ encodings of its domain and codomain: a total string function, computed by a sin machine in polynomial time, that maps the encoding of `a` to the encoding of `f a`. The string function is total: its behavior on strings outside the range of `ea` is -unconstrained. -/ +unconstrained. The structure imposes nothing on `ea` and `eb` themselves — with a +non-injective codomain encoding it is trivially inhabited — so its certifying power +comes from the call site pinning injective encoding families +(`Computability.BitEncFam`, `Computability.StrEncFam`). -/ structure EncPolyTime (ea : α → List Bool) (eb : β → List Bool) (f : α → β) where /-- The total string function the machine computes. -/ toFun : List Bool → List Bool @@ -136,7 +146,7 @@ each `a'` exactly as `ea` encodes `φ a'`, and `eb'` encodes each `g a'` exactly encodes `f (φ a')`, the same machine witnesses `g` relative to `ea'`/`eb'`. The machine, time, and size are untouched — this discharges pure re-bracketings and re-taggings of encoded data (`cons`/append associativity, pair/sum reshuffles) with no machine content. -/ -def recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} {eb' : β' → List Bool} +def recode {α' : Type u'} {β' : Type v'} {ea' : α' → List Bool} {eb' : β' → List Bool} {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : EncPolyTime ea' eb' g where @@ -145,19 +155,24 @@ def recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} {eb' : β map_encode a' := by rw [hin, h.map_encode, ← hout] /-- Recoding preserves the machine's time polynomial. -/ -@[simp] theorem time_recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} +@[simp] theorem time_recode {α' : Type u'} {β' : Type v'} {ea' : α' → List Bool} {eb' : β' → List Bool} {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : (h.recode φ g hin hout).time = h.time := rfl /-- Recoding preserves the machine, hence the description size. -/ -@[simp] theorem size_recode {α' : Type u_1} {β' : Type u_2} {ea' : α' → List Bool} +@[simp] theorem size_recode {α' : Type u'} {β' : Type v'} {ea' : α' → List Bool} {eb' : β' → List Bool} {f : α → β} (h : EncPolyTime ea eb f) (φ : α' → α) (g : α' → β') (hin : ∀ a', ea' a' = ea (φ a')) (hout : ∀ a', eb' (g a') = eb (f (φ a'))) : (h.recode φ g hin hout).size = h.size := rfl /-- Composition of encoded polynomial-time witnesses, from Cslib's -`PolyTimeComputable.comp`. -/ +`PolyTimeComputable.comp`. + +Time bounds compose by substitution (`comp_time`), so degrees multiply: iterating +`comp` to polynomial depth does not stay polynomial-time, and polynomial-length runs +must instead account time additively per step (as `MachineAdversary.detTotalTime` +does). Only the description size composes additively (`size_comp`). -/ noncomputable def comp {f : α → β} {f' : β → γ} (h : EncPolyTime ea eb f) (h' : EncPolyTime eb ec f') : EncPolyTime ea ec (f' ∘ f) where diff --git a/ToMathlib/Computability/Encoding.lean b/ToMathlib/Computability/Encoding.lean deleted file mode 100644 index 60688708c..000000000 --- a/ToMathlib/Computability/Encoding.lean +++ /dev/null @@ -1,337 +0,0 @@ -/- -Copyright (c) 2026 Devon Tuma. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Authors: Devon Tuma --/ -module - -public import Mathlib.Computability.Encoding -public import Mathlib.Data.FinEnum - -/-! -# Additional Encoding Combinators - -This file extends `Mathlib.Computability.Encoding` with combinators needed to feed -structured values to Turing machines: - -- `Computability.finEncodingOfFinEnum`: a `PackedEncoding` of any `FinEnum` type (unary - over a `Unit` alphabet), making the common finite cases — `Unit`, `Bool`, `Fin n`, - products, sums, sigmas — encodable for free. -- `Computability.finEncodingOption`: a `PackedEncoding` of `Option β` from one of `β`. -- `Computability.finEncodingSigma`: a `PackedEncoding` of a dependent pair `(t : ι) × F t` - from an encoding of the index and per-index encodings over a shared fiber alphabet. -- `Computability.PackedEncoding.boolify`: relabel any finite-alphabet encoding into - `List Bool` via fixed-width one-hot symbol codes, so that encodings over different - alphabets can serve as inputs and outputs of machines over a single alphabet. -- `Computability.finEncodingBitVec`: the fixed-width binary encoding of `BitVec w`, - linear in `w` where the unary `finEncodingOfFinEnum` would be exponential, together - with length lemmas for it and for pair and option encodings. - -These mirror the design of `Computability.encodingProd`. --/ - -@[expose] public section - -universe u v - -namespace List - -/-- A `flatMap` by an injective fixed-width block code is injective. -/ -theorem flatMap_injective {α : Type u} {β : Type v} {f : α → List β} {w : ℕ} (hw : 0 < w) - (hlen : ∀ a, (f a).length = w) (hinj : Function.Injective f) : - Function.Injective fun l : List α => l.flatMap f := by - intro l₁ l₂ h - induction l₁ generalizing l₂ with - | nil => - cases l₂ with - | nil => rfl - | cons b t => - simp only [flatMap_nil, flatMap_cons] at h - have := congrArg length h - simp [hlen b] at this - omega - | cons a t ih => - cases l₂ with - | nil => - simp only [flatMap_cons, flatMap_nil] at h - have := congrArg length h - simp [hlen a] at this - omega - | cons b t' => - simp only [flatMap_cons] at h - obtain ⟨hfab, htail⟩ := append_inj h ((hlen a).trans (hlen b).symm) - rw [hinj hfab, ih htail] - -end List - -namespace Computability - -/-! ## Bundled Finite-Alphabet Encodings -/ - -/-- A finite-alphabet encoding with the alphabet **bundled** as a field. - -Mathlib's `Computability.Encoding` takes the alphabet as a type parameter; this -Σ-style packaging remains necessary here because the polynomial-time layer -manipulates encodings of *varying* alphabet — per-`FinEnum` unary alphabets, sum -alphabets for options and sigmas — before `PackedEncoding.boolify` normalizes -them all to `Bool`. Local to `ToMathlib`. -/ -structure PackedEncoding (α : Type u) where - /-- The finite tape alphabet of the encoding. -/ - Γ : Type - /-- The encoding function. -/ - encode : α → List Γ - /-- The decoding function. -/ - decode : List Γ → Option α - /-- Decoding is a retraction of encoding. -/ - decode_encode : ∀ x, decode (encode x) = some x - /-- The alphabet is finite. -/ - ΓFin : Fintype Γ - -attribute [instance] PackedEncoding.ΓFin -attribute [simp] PackedEncoding.decode_encode - -namespace PackedEncoding - -variable {α : Type u} (e : PackedEncoding α) - -/-- The underlying unbundled Mathlib encoding. -/ -def toEncoding : Encoding α e.Γ where - encode := e.encode - decode := e.decode - decode_encode := e.decode_encode - -@[simp] theorem toEncoding_encode (x : α) : e.toEncoding.encode x = e.encode x := rfl - -/-- Packed pair encoding over the sum alphabet: the bundled form of -`Computability.encodingProd`. -/ -def pair {β : Type v} (eb : PackedEncoding β) : PackedEncoding (α × β) where - Γ := e.Γ ⊕ eb.Γ - encode x := (e.encode x.1).map .inl ++ (eb.encode x.2).map .inr - decode x := Option.map₂ Prod.mk (e.decode (x.filterMap Sum.getLeft?)) - (eb.decode (x.filterMap Sum.getRight?)) - decode_encode x := by simp - ΓFin := inferInstance - -end PackedEncoding - -/-! ## Encodings of Enumerable Finite Types -/ - -/-- A `PackedEncoding` of any `FinEnum` type: unary encoding over the `Unit` alphabet, -with string length the enumeration index. Gives encodings of the common finite types -(`Unit`, `Bool`, `Fin n`, products, sums, sigmas of such) for free. -/ -def finEncodingOfFinEnum (α : Type u) [FinEnum α] : PackedEncoding α where - Γ := Unit - encode x := List.replicate (FinEnum.equiv x : ℕ) () - decode l := - if h : l.length < FinEnum.card α then some (FinEnum.equiv.symm ⟨l.length, h⟩) else none - decode_encode x := by simp - ΓFin := inferInstance - -/-! ## Option and Sigma Encodings -/ - -/-- A `PackedEncoding` of `Option β`: `none` is the empty string, `some b` is a marker -symbol followed by the relabeled encoding of `b`. -/ -def finEncodingOption {β : Type u} (eb : PackedEncoding β) : PackedEncoding (Option β) where - Γ := Unit ⊕ eb.Γ - encode - | none => [] - | some b => .inl () :: (eb.encode b).map .inr - decode - | [] => some none - | .inl _ :: l => (eb.decode (l.filterMap Sum.getRight?)).map some - | .inr _ :: _ => none - decode_encode x := by cases x <;> simp - ΓFin := inferInstance - -/-- Sigma analogue of `PackedEncoding.pair`: encode the index with `.inl` symbols and the -fiber value with `.inr` symbols. The fiber encodings share a single alphabet `Γ` -(per-index alphabets could not form one machine alphabet); they are given as raw -encode/decode functions with a round-trip proof rather than per-index -`PackedEncoding`s for the same reason. -/ -def finEncodingSigma {ι : Type u} (ei : PackedEncoding ι) {F : ι → Type v} {Γ : Type} - [Fintype Γ] (enc : (t : ι) → F t → List Γ) (dec : (t : ι) → List Γ → Option (F t)) - (henc : ∀ t x, dec t (enc t x) = some x) : PackedEncoding ((t : ι) × F t) where - Γ := ei.Γ ⊕ Γ - encode x := (ei.encode x.1).map .inl ++ (enc x.1 x.2).map .inr - decode l := (ei.decode (l.filterMap Sum.getLeft?)).bind - fun t => (dec t (l.filterMap Sum.getRight?)).map (⟨t, ·⟩) - decode_encode x := by - obtain ⟨t, y⟩ := x - simp [henc] - ΓFin := inferInstance - -/-! ## Relabeling into a Boolean Alphabet -/ - -namespace PackedEncoding - -variable {α : Type u} (e : PackedEncoding α) - -/-- The fixed-width one-hot code of a single alphabet symbol: a `Bool` string of length -`Fintype.card e.Γ + 1` that is `true` exactly at the symbol's index. The `+ 1` keeps the -width positive even for an empty alphabet. -/ -noncomputable def symbolCode (g : e.Γ) : List Bool := - (List.range (Fintype.card e.Γ + 1)).map fun i => decide (i = (Fintype.equivFin e.Γ g : ℕ)) - -@[simp] theorem length_symbolCode (g : e.Γ) : - (e.symbolCode g).length = Fintype.card e.Γ + 1 := by - simp [symbolCode] - -theorem symbolCode_injective : Function.Injective e.symbolCode := by - intro g₁ g₂ h - have hlt : ((Fintype.equivFin e.Γ) g₁ : ℕ) ∈ List.range (Fintype.card e.Γ + 1) := - List.mem_range.mpr (Nat.lt_succ_of_lt (Fintype.equivFin e.Γ g₁).isLt) - have := (List.map_inj_left.mp h) _ hlt - simp only [decide_eq_decide, true_iff] at this - exact (Fintype.equivFin e.Γ).injective (Fin.val_injective (this ▸ rfl)) - -/-- Relabel a finite-alphabet encoding into `List Bool` by replacing each symbol with -its fixed-width one-hot code. Machines over the single alphabet `Bool` can then -consume and produce values of any `PackedEncoding`-encodable type. -/ -noncomputable def boolify : α → List Bool := - fun x => (e.encode x).flatMap e.symbolCode - -theorem boolify_injective : Function.Injective e.boolify := - (List.flatMap_injective (Nat.succ_pos _) e.length_symbolCode e.symbolCode_injective).comp - e.toEncoding.encode_injective - -@[simp] theorem length_boolify (x : α) : - (e.boolify x).length = (Fintype.card e.Γ + 1) * (e.encode x).length := by - simp only [boolify, List.length_flatMap] - simp [Nat.mul_comm] - -end PackedEncoding - -/-- The boolified unary `FinEnum` encoding has length at most `2 * card`: the unary -string has length the enumeration index (below `card`), and the one-hot symbol width -over the `Unit` alphabet is `2`. This is the pointwise bound feeding -`Computability.EncPolyTime.time_ofFintype_eval_le` at `FinEnum` encodings. -/ -theorem length_boolify_finEncodingOfFinEnum {γ : Type u} [FinEnum γ] [Fintype γ] (x : γ) : - ((finEncodingOfFinEnum γ).boolify x).length ≤ 2 * Fintype.card γ := by - have hx : (FinEnum.equiv x : ℕ) < Fintype.card γ := - FinEnum.card_eq_fintypeCard (α := γ) ▸ (FinEnum.equiv x).isLt - rw [PackedEncoding.length_boolify] - simp only [finEncodingOfFinEnum, List.length_replicate, Fintype.card_unique] - omega - -/-! ## Binary Bitvector Encoding -/ - -/-- Fixed-width binary encoding of `BitVec w` over the `Bool` alphabet: the string of the -`w` bits, least significant first. The encoded length is `w`, linear where the unary -`finEncodingOfFinEnum` encoding would have length up to `2 ^ w`; machine states containing -bitvectors need this encoding for polynomial size bounds. -/ -def finEncodingBitVec (w : ℕ) : PackedEncoding (BitVec w) where - Γ := Bool - encode m := (List.range w).map m.getLsbD - decode l := if h : l.length = w then some (BitVec.cast h (BitVec.ofBoolListLE l)) else none - decode_encode m := by - have hlen : ((List.range w).map m.getLsbD).length = w := by simp - rw [dif_pos hlen] - refine congrArg some (BitVec.eq_of_getLsbD_eq_iff.mpr fun i hi => ?_) - rw [BitVec.getLsbD_cast, BitVec.getLsbD_ofBoolListLE] - simp [List.getD_eq_getElem?_getD, hi] - ΓFin := inferInstance - -@[simp] theorem length_encode_finEncodingBitVec {w : ℕ} (m : BitVec w) : - ((finEncodingBitVec w).encode m).length = w := by - simp [finEncodingBitVec] - -/-- The boolified binary bitvector encoding has length exactly `3 * w`: `w` symbols of -one-hot width `card Bool + 1`. The pointwise bound feeding -`Computability.EncPolyTime.time_ofFintype_eval_le` at bitvector-shaped machine states. -/ -theorem length_boolify_finEncodingBitVec {w : ℕ} (m : BitVec w) : - ((finEncodingBitVec w).boolify m).length = 3 * w := by - rw [PackedEncoding.length_boolify, length_encode_finEncodingBitVec] - change (Fintype.card Bool + 1) * w = 3 * w - rw [Fintype.card_bool] - -/-! ## Encoding Lengths of Pairs and Options -/ - -theorem length_encode_pair {α : Type u} {β : Type v} (ea : PackedEncoding α) - (eb : PackedEncoding β) (x : α × β) : - ((ea.pair eb).encode x).length = - (ea.encode x.1).length + (eb.encode x.2).length := by - simp [PackedEncoding.pair] - -@[simp] theorem length_encode_finEncodingOption_none {β : Type u} (eb : PackedEncoding β) : - ((finEncodingOption eb).encode (none : Option β)).length = 0 := rfl - -theorem length_encode_finEncodingOption_some {β : Type u} (eb : PackedEncoding β) (b : β) : - ((finEncodingOption eb).encode (some b)).length = (eb.encode b).length + 1 := by - simp [finEncodingOption] - -/-- The boolified pair encoding has length `(card Γ₁ + card Γ₂ + 1)` times the sum of the two -component encode-lengths: the one-hot symbol width over the combined alphabet `Γ₁ ⊕ Γ₂` times -the concatenated encoding length. The pointwise bound for paired machine states (e.g. a counter -paired with an accumulator). -/ -theorem length_boolify_pair {α : Type u} {β : Type v} (ea : PackedEncoding α) - (eb : PackedEncoding β) (x : α × β) : - ((ea.pair eb).boolify x).length - = (Fintype.card ea.Γ + Fintype.card eb.Γ + 1) - * ((ea.encode x.1).length + (eb.encode x.2).length) := by - have hc : Fintype.card (ea.pair eb).Γ = Fintype.card ea.Γ + Fintype.card eb.Γ := - Fintype.card_sum - rw [PackedEncoding.length_boolify, length_encode_pair, hc] - -/-- The boolified option encoding has length `(card Γ + 2)` times the option encode-length: the -`Unit ⊕ Γ` alphabet has one more symbol than `Γ`, so the one-hot width is `card Γ + 2`. The -pointwise bound for optional machine outputs. -/ -theorem length_boolify_finEncodingOption {β : Type v} (eb : PackedEncoding β) (x : Option β) : - ((finEncodingOption eb).boolify x).length - = (Fintype.card eb.Γ + 2) * ((finEncodingOption eb).encode x).length := by - have hc : Fintype.card (finEncodingOption eb).Γ = Fintype.card eb.Γ + 1 := - (Fintype.card_sum (α := Unit) (β := eb.Γ)).trans (by rw [Fintype.card_unit, Nat.add_comm]) - rw [PackedEncoding.length_boolify, hc] - -/-! ## Sum Encoding -/ - -/-- Encode a disjoint union `α ⊕ β` over the combined alphabet `Bool ⊕ ea.Γ ⊕ eb.Γ`: each value is -prefixed with a `Bool` tag symbol (`.inl false` for a left value, `.inl true` for a right value) so -the branch is always readable from the head, and the payload symbols land in `.inr (.inl _)` (left) -or `.inr (.inr _)` (right). This is the two-sided generalization of `finEncodingOption` and the -state encoding for the two-phase machine `OracleMachine.seqComp`, whose state is -`M₁.State ⊕ M₂.State`. -/ -def finEncodingSum {α : Type u} {β : Type v} (ea : PackedEncoding α) (eb : PackedEncoding β) : - PackedEncoding (α ⊕ β) where - Γ := Bool ⊕ ea.Γ ⊕ eb.Γ - encode - | .inl a => .inl false :: (ea.encode a).map (Sum.inr ∘ Sum.inl) - | .inr b => .inl true :: (eb.encode b).map (Sum.inr ∘ Sum.inr) - decode - | .inl false :: l => - (ea.decode (l.filterMap fun s => (Sum.getRight? s).bind Sum.getLeft?)).map Sum.inl - | .inl true :: l => - (eb.decode (l.filterMap fun s => (Sum.getRight? s).bind Sum.getRight?)).map Sum.inr - | _ => none - decode_encode x := by - cases x with - | inl a => simp [List.filterMap_map, Function.comp_def, ea.decode_encode] - | inr b => simp [List.filterMap_map, Function.comp_def, eb.decode_encode] - ΓFin := inferInstance - -@[simp] theorem length_encode_finEncodingSum_inl {α : Type u} {β : Type v} (ea : PackedEncoding α) - (eb : PackedEncoding β) (a : α) : - ((finEncodingSum ea eb).encode (Sum.inl a : α ⊕ β)).length = (ea.encode a).length + 1 := by - simp [finEncodingSum] - -@[simp] theorem length_encode_finEncodingSum_inr {α : Type u} {β : Type v} (ea : PackedEncoding α) - (eb : PackedEncoding β) (b : β) : - ((finEncodingSum ea eb).encode (Sum.inr b : α ⊕ β)).length = (eb.encode b).length + 1 := by - simp [finEncodingSum] - -/-- The boolified sum encoding has length `(card Γ₁ + card Γ₂ + 3)` times the sum encode-length: the -`Bool ⊕ Γ₁ ⊕ Γ₂` alphabet has two more symbols than `Γ₁ ⊕ Γ₂`, so the one-hot width is -`card Γ₁ + card Γ₂ + 3`. The pointwise bound feeding `encState_length_le` for the two-phase -`seqComp` machine state. -/ -theorem length_boolify_finEncodingSum {α : Type u} {β : Type v} (ea : PackedEncoding α) - (eb : PackedEncoding β) (x : α ⊕ β) : - ((finEncodingSum ea eb).boolify x).length - = (Fintype.card ea.Γ + Fintype.card eb.Γ + 3) * ((finEncodingSum ea eb).encode x).length := by - have hc : Fintype.card (finEncodingSum ea eb).Γ = Fintype.card ea.Γ + Fintype.card eb.Γ + 2 := by - change Fintype.card (Bool ⊕ ea.Γ ⊕ eb.Γ) = Fintype.card ea.Γ + Fintype.card eb.Γ + 2 - rw [Fintype.card_sum, Fintype.card_sum, Fintype.card_bool] - omega - rw [PackedEncoding.length_boolify, hc, - show Fintype.card ea.Γ + Fintype.card eb.Γ + 2 + 1 - = Fintype.card ea.Γ + Fintype.card eb.Γ + 3 from by omega] - -end Computability diff --git a/ToMathlib/Computability/MachineCounting.lean b/ToMathlib/Computability/MachineCounting.lean index 7c018e431..b55e5dc82 100644 --- a/ToMathlib/Computability/MachineCounting.lean +++ b/ToMathlib/Computability/MachineCounting.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Devon Tuma. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Devon Tuma +Authors: Devon Tuma, Elias Judin -/ module @@ -297,14 +297,13 @@ end Determinism /-! ## State normalization -/ -/-- Every `SingleTapeTM Bool` computing a string function with at most `d` states computes -the same function as `reify` of some `TMTable d` — the state space is relabeled to `Fin d` -along `Fintype.equivFin`, preserving the `Outputs` relation. It is the machine-theoretic -input to `exists_realizableLE_covering`. -/ -theorem exists_tmTable_of_card_le {f : List Bool → List Bool} (h : PolyTimeComputable f) - {d : ℕ} (hd : Fintype.card h.tm.State ≤ d) : - ∃ t : TMTable d, ∀ l l', (reify t).Outputs l l' ↔ h.tm.Outputs l l' := by - set tm := h.tm with htm +/-- Every `SingleTapeTM Bool` with at most `d` states has the same `Outputs` relation as +`reify` of some `TMTable d` — the state space is relabeled to `Fin d` along +`Fintype.equivFin`. It is the machine-theoretic input to +`exists_realizableLE_covering`. -/ +theorem exists_tmTable_of_card_le (tm : SingleTapeTM Bool) + {d : ℕ} (hd : Fintype.card tm.State ≤ d) : + ∃ t : TMTable d, ∀ l l', (reify t).Outputs l l' ↔ tm.Outputs l l' := by refine ⟨normTable tm (embFin hd) (decFin (α := tm.State)), fun l l' => ?_⟩ have hdec : ∀ s, decFin (α := tm.State) (embFin hd s) = some s := decFin_embFin hd have hemb : Function.Injective (embFin (α := tm.State) hd) := embFin_injective hd @@ -385,8 +384,8 @@ theorem exists_realizableLE_covering (n d : ℕ) : refine ⟨Finset.image (tablePairPred n d) (Finset.univ : Finset (TMTable d × TMTable d)), ?_, ?_⟩ · rintro g ⟨σ, es, init, output, i, o, hi, ho, hg⟩ - obtain ⟨t₁, ht₁⟩ := exists_tmTable_of_card_le i.polyTime (d := d) hi - obtain ⟨t₂, ht₂⟩ := exists_tmTable_of_card_le o.polyTime (d := d) ho + obtain ⟨t₁, ht₁⟩ := exists_tmTable_of_card_le i.polyTime.tm (d := d) hi + obtain ⟨t₂, ht₂⟩ := exists_tmTable_of_card_le o.polyTime.tm (d := d) ho refine Finset.mem_coe.mpr (Finset.mem_image.mpr ⟨(t₁, t₂), Finset.mem_univ _, ?_⟩) funext x have hrun1 : (reify t₁).Outputs (BitEncFam.bitVecX.enc n x) (es (init x)) := by diff --git a/ToMathlib/Computability/PolyTimeTM.lean b/ToMathlib/Computability/PolyTimeTM.lean index f920bac33..ff43f1b55 100644 --- a/ToMathlib/Computability/PolyTimeTM.lean +++ b/ToMathlib/Computability/PolyTimeTM.lean @@ -23,9 +23,9 @@ functions, in Cslib's `Cslib.Turing.SingleTapeTM` model: polynomial-time computable** relative to an injective encoding — but with a description size (`EncPolyTime.size_ofFintype_le`) that grows with the domain's total encoded length, so a *family* of tables stays within a polynomial advice - bound (`PolyTimeAdversary.descBound`) only on domains of polynomially bounded + bound (`MachineAdversary.descBound`) only on domains of polynomially bounded cardinality. Within that regime it subsumes constants, relabelings, and projections, - and discharges all four per-step machine witnesses of `PolyTimeAdversary` for + and discharges all four per-step machine witnesses of `MachineAdversary` for small-state oracle machines. The machines follow one design: **clear the input moving right, then write the output @@ -37,10 +37,8 @@ left lands the head on its first symbol, which is exactly the halting configurat Base machines for *unbounded* domains (symbol relabeling, projections with respect to paired encodings of infinite types) would follow the same skeleton and remain future work; together with `EncPolyTime.comp` (from Cslib's proven machine composition) they -would extend the generic witnesses beyond finite domains. The declared frontier of -those combinators — stated in the raw-encoding family form -(`Computability.EncPolyTimeFam`) that the adversary layer consumes — is -`ToMathlib.Computability.MachineCombinators`. +would extend the generic witnesses beyond finite domains, stated in the raw-encoding +family form (`Computability.EncPolyTimeFam`) that the adversary layer consumes. -/ @[expose] public section @@ -177,9 +175,10 @@ noncomputable def constPolyTimeComputable (out : List Symbol) : omega /-- The constant-function machine has at most `out.length + 2` states: one clearing -state plus one writing state per output symbol. -/ -theorem size_constPolyTimeComputable_le (out : List Symbol) : - (constPolyTimeComputable (Symbol := Symbol) out).size ≤ out.length + 2 := by +state plus one writing state per output symbol. Stated over `Bool`, the alphabet +`PolyTimeComputable.size` is defined at. -/ +theorem size_constPolyTimeComputable_le (out : List Bool) : + (constPolyTimeComputable out).size ≤ out.length + 2 := by cases out with | nil => change Fintype.card Unit ≤ 2 @@ -434,8 +433,8 @@ injective input encoding: the machine reads the input into state through the pre tree of the finitely many valid encodings, then writes the encoded output. The trade is time for description: the machine has one reading state per prefix of a valid input (`size_ofFintype_le`), so families of these witnesses respect a polynomial advice bound -only on domains of polynomially bounded cardinality. Instantiated at the `boolify` of a -`PackedEncoding` (injective by `PackedEncoding.boolify_injective`), this discharges the +only on domains of polynomially bounded cardinality. Instantiated at the injective +encodings of `Computability.BitEncFam` / `Computability.StrEncFam`, this discharges the per-step machine witnesses of small-state oracle machines. -/ noncomputable def ofFintype {α : Type u} {β : Type v} [Fintype α] (ea : α → List Bool) (hea : Function.Injective ea) (eb : β → List Bool) @@ -452,7 +451,7 @@ noncomputable def ofFintype {α : Type u} {β : Type v} [Fintype α] /-- The finite-table witness runs in time linear in the input plus the longest encoded output: with a pointwise bound `B` on the output encodings, evaluation at `k` is at most `k + (B + 1)`. This is the shape that discharges the uniform per-step time bounds -of `PolyTimeAdversary`. -/ +of `MachineAdversary`. -/ theorem time_ofFintype_eval_le {α : Type u} {β : Type v} [Fintype α] {ea : α → List Bool} (hea : Function.Injective ea) {eb : β → List Bool} {f : α → β} {B : ℕ} (hB : ∀ a, (eb (f a)).length ≤ B) (k : ℕ) : @@ -503,7 +502,7 @@ theorem size_ofFintype_le {α : Type u} {β : Type v} [Fintype α] /-- Discharge form of `size_ofFintype_le`: a cardinality bound on the domain and pointwise bounds on both encodings give the table size bound consumed by -`PolyTimeAdversary.descBound` fields. -/ +`MachineAdversary.descBound` fields. -/ theorem size_ofFintype_le_of_bounds {α : Type u} {β : Type v} [Fintype α] {ea : α → List Bool} (hea : Function.Injective ea) {eb : β → List Bool} {f : α → β} {A La B : ℕ} (hcard : Fintype.card α ≤ A) diff --git a/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean b/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean index 677d35b8c..31a291ab7 100644 --- a/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean +++ b/VCVio/CryptoFoundations/Asymptotics/PolyTime.lean @@ -10,14 +10,12 @@ import VCVio.OracleComp.Coinductive.PolyTime # Security Against Polynomial-Time Adversaries This file connects the Turing-machine-grounded polynomial-time layer -(`PolyTimeAdversary`, `OracleComp.IsPolyTime`) to the asymptotic security games of +(`MachineAdversary`, `OracleComp.IsPolyTime`) to the asymptotic security games of `VCVio.CryptoFoundations.Asymptotics.Security`: `SecurityGame.secureAgainstPolyTime` instantiates the abstract `isPPT` slot of `SecurityGame.secureAgainst` with `OracleComp.IsPolyTime`, and the per-query-loss former -`secureAgainstPolyTime_of_advantage_le_mul_totalQueries` is where the query-bound -conjunct of the certificate does quantitative work. Concrete game formers over both -adversary presentations (programs and machines) live in -`VCVio.CryptoFoundations.Asymptotics.Game.Challenger` and `….Game.TwoPhase`. +`secureAgainstPolyTime_of_advantage_le_mul_totalQueries` is where the certificate's +derived query bound (`PolyTimeWitness.queryBound`) does quantitative work. -/ open OracleComp OracleSpec Computability ENNReal @@ -47,9 +45,10 @@ abbrev secureAgainstMachines {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α /-- **Per-query loss composes with the polynomial round budget**: a game whose advantage against every `k`-total-query-bounded family is at most `k * ε n` for negligible `ε` is -secure against all polynomial-time families. This is where the query-bound conjunct of -`OracleComp.IsPolyTime` and the `steps` polynomial do quantitative work: the adversary's -polynomially many queries turn per-query loss into `poly * negligible = negligible`. -/ +secure against all polynomial-time families. This is where the certificate's derived +query bound (`PolyTimeWitness.queryBound`) and the `steps` polynomial do quantitative +work: the adversary's polynomially many queries turn per-query loss into +`poly * negligible = negligible`. -/ theorem secureAgainstPolyTime_of_advantage_le_mul_totalQueries {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} (bd : BoundaryData spec α β) (g : SecurityGame ((n : ℕ) → α n → OracleComp (spec n) (β n))) diff --git a/VCVio/OracleComp/Coinductive/CoinFold.lean b/VCVio/OracleComp/Coinductive/CoinFold.lean index 7571196c5..7b1037fb1 100644 --- a/VCVio/OracleComp/Coinductive/CoinFold.lean +++ b/VCVio/OracleComp/Coinductive/CoinFold.lean @@ -13,23 +13,22 @@ number of times, folding each answer into a finite-state accumulator, then read value. `coinFoldProg step readout` is that program family, and `isPolyTime_coinFold` bundles the whole Turing-machine polynomial-time witness for it once and for all, so a concrete instance collapses to supplying `step`/`readout`, the encodings, and two -encoding-length bounds — no hand-built `OracleMachine`/`PolyTimeAdversary` needed. - -* `coinFoldProg` — the fold program; `coinFoldMachine` — the machine realizing it, with the - read-out map folded into `output` and the round counter as `Fin (rounds + 1)`. -* `coinFoldMachine_implements` (via the simulation relation `CoinFoldRel`) and - `coinFoldMachine_steadyBy` (via the round invariant) give the coalgebraic side. -* `coinFoldAdversary` / `isPolyTime_coinFold` — the fully assembled `PolyTimeAdversary` and - `OracleComp.IsPolyTime`. - -Worked instances: `Examples.DynamicalSystems.XorFlips` (single-`Bool` accumulator, `readout` -the identity) and `KatzLindell.Chapter03.SamplerMachine` (`BitVec` accumulator, nontrivial -`readout`), which collapse onto this combinator. - -Specialized to `coinSpec` (the coin oracle, answers `Bool`): both current consumers use it, -and it keeps the simulation free of the dependent answer-type transport that an -arbitrary-spec version would incur. Generalizing the fold to an arbitrary oracle is future -work. +encoding-length bounds — no hand-built `OracleMachine`/`MachineAdversary` needed. + +* `coinFoldProg` — the fold program; `coinFoldMachine` — the machine realizing it, with + the read-out map folded into `output` and the round counter as `Fin (rounds + 1)`. +* `coinFoldMachine_implementsWithin` — the machine implements the program family within + `rounds` rounds, by a direct induction on the fuelled unrolling (returns are absorbing + by construction, so no separate stability argument is needed). +* `coinFoldAdversary` / `coinFoldWitness` / `isPolyTime_coinFold` — the fully assembled + `MachineAdversary`, its certificate, and `OracleComp.IsPolyTime`, with all four step + witnesses discharged by finite tables for accumulators of polynomially bounded + cardinality; the `…OfWitnesses` variants accept explicit machine families for + superpolynomially large accumulators. + +Specialized to `coinSpec` (the coin oracle, answers `Bool`): this keeps the simulation +free of the dependent answer-type transport that an arbitrary-spec version would incur. +Generalizing the fold to an arbitrary oracle is future work. -/ open OracleSpec OracleComp Computability @@ -126,9 +125,9 @@ theorem coinFoldMachine_unroll (rounds : ℕ) (init₀ : σ) : (funext fun b => coinFoldMachine_unroll rounds init₀ m k (by omega) (step acc m b) (by omega)) -/-- The fold machine implements the fold program family within `rounds` rounds. The -old simulation-relation and steadiness developments are unnecessary: returns are -absorbing by construction, and resolution is derivable from this via +/-- The fold machine implements the fold program family within `rounds` rounds, by a +direct induction on the fuelled unrolling: returns are absorbing by construction, and +resolution within the budget is derivable via `DynComputation.ImplementsWithin.resolvesIn`. -/ theorem coinFoldMachine_implementsWithin (rounds : ℕ) (init₀ : σ) : (coinFoldMachine step readout rounds init₀).ImplementsWithin @@ -234,8 +233,6 @@ noncomputable def coinFoldWitness : (fun _ => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) (steps.eval n) rw [← hrnd n] exact coinFoldMachine_implementsWithin (step n) (readout n) (rnd n) (init₀ n) - queryBound n _ := (isTotalQueryBound_coinFoldProg (step n) (readout n) (rnd n) - (init₀ n)).mono (le_of_eq (hrnd n)) include steps hrnd st Sc hcard in /-- **The bounded coin fold is polynomial time.** Filling `rnd n` coin answers into an @@ -301,8 +298,6 @@ noncomputable def coinFoldWitnessOfWitnesses : (fun _ => coinFoldProg (step n) (readout n) (rnd n) (init₀ n)) (steps.eval n) rw [← hrnd n] exact coinFoldMachine_implementsWithin (step n) (readout n) (rnd n) (init₀ n) - queryBound n _ := (isTotalQueryBound_coinFoldProg (step n) (readout n) (rnd n) - (init₀ n)).mono (le_of_eq (hrnd n)) include steps hrnd st initF exposeF updateF outputF in /-- **The bounded coin fold is polynomial time, given machine families for its step diff --git a/VCVio/OracleComp/Coinductive/PolyTime.lean b/VCVio/OracleComp/Coinductive/PolyTime.lean index 2ed44f194..5f1f75bda 100644 --- a/VCVio/OracleComp/Coinductive/PolyTime.lean +++ b/VCVio/OracleComp/Coinductive/PolyTime.lean @@ -21,9 +21,10 @@ machine on encoded states, whereas the continuations of a program tree have no b syntactic presentation. `OracleComp.IsPolyTime bd oa` holds when some adversary carries a `PolyTimeWitness` -for the program family `oa`: it implements `oa` (`OracleMachine.Implements`) within its -round budget, and `oa` is syntactically query-bounded by that budget. It is the -intended instantiation of the `isPPT` predicate of `SecurityGame.secureAgainst`. +for the program family `oa`: it implements `oa` (`MachineAdversary.Implements`) within +its round budget — which also yields the syntactic query bound on `oa` +(`PolyTimeWitness.queryBound`). It is the intended instantiation of the `isPPT` +predicate of `SecurityGame.secureAgainst`. ## Model @@ -337,25 +338,17 @@ open scoped MachineAdversary /-! ## The polynomial-time certificate and predicate -/ /-- A certificate that the program family `oa` is polynomial time at boundaries `bd`: -an adversary together with proofs that it implements `oa` within its round budget and -that `oa` itself respects that budget syntactically. Proof-relevant data, mirroring -`OracleComp.PolyQueries`; the Prop-level predicate is `OracleComp.IsPolyTime`. - -The `queryBound` field is definitional, not a wart: "makes polynomially many queries" -is part of what polynomial time means, it feeds the `PolyQueries` bridge directly, and -every route to `implements` produces it as an input or byproduct. It is *conjectured* -to follow from `implements` alone, but the extraction is genuinely hard: - -* A counting handler cannot do it. `Implements` quantifies over - `ProbHandler spec = QueryImpl spec SPMF`, and `SPMF` has no writer component, so no - handler admissible in the quantification observes query counts. -* The plausible route drives the program along *scaled* handlers `H_ε` (each answer - distribution scaled to total mass `ε ∈ (0, 1]`): the output mass of the fuelled run - is a polynomial of degree at most the budget in `ε`, while a program family - violating the bound contributes a positive higher-degree monomial to the mass of - `some <$> simulateQ H_ε`, and agreement on `(0, 1]` forces equal coefficients. The - coefficient-extraction step over `ℝ≥0∞` is the hard part; it is recorded here as a - conjecture rather than smuggled as an axiom. -/ +an adversary together with a proof that it implements `oa` within its round budget. +Proof-relevant data, mirroring `OracleComp.PolyQueries`; the Prop-level predicate is +`OracleComp.IsPolyTime`. + +The syntactic query bound on `oa` — "makes polynomially many queries" — is part of +what polynomial time means, but it is not a field: `DynComputation.ImplementsWithin` +is the fuel-`k` unroll equality `M.run k x = FreeM.map some (oa x)`, whose bounded +half already carries the bound +(`DynComputation.implementsWithin_iff_implements_and_bound`), and +`OracleComp.IsTotalQueryBound` is definitionally `PFunctor.FreeM.IsTotalRollBound`. +`PolyTimeWitness.queryBound` exports it as a theorem. -/ structure PolyTimeWitness {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} (bd : BoundaryData spec α β) (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) where @@ -363,13 +356,14 @@ structure PolyTimeWitness {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β A : MachineAdversary bd /-- The adversary implements the program family within its round budget. -/ implements : A ⊨ oa - /-- The program family syntactically respects the round budget. -/ - queryBound : ∀ n x, OracleComp.IsTotalQueryBound (oa n x) (A.steps.eval n) /-- A program family is polynomial time at pinned boundaries `bd` when it carries a -`PolyTimeWitness`. This is the intended `isPPT` instantiation for -`SecurityGame.secureAgainst`; `bd` must be a fixed parameter of the enclosing security -statement (see the module docstring's statement-site discipline). -/ +`PolyTimeWitness`. The class is **non-uniform (P/poly)**: the witness supplies a +machine per security parameter, under single polynomials bounding time, rounds, state +length, and description size across the family — nothing computes the `n`-th machine +from `n` (see the module docstring's *Model* section). This is the intended `isPPT` +instantiation for `SecurityGame.secureAgainst`; `bd` must be a fixed parameter of the +enclosing security statement (see the statement-site discipline). -/ def OracleComp.IsPolyTime {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} (bd : BoundaryData spec α β) (oa : (n : ℕ) → α n → OracleComp (spec n) (β n)) : Prop := @@ -380,13 +374,24 @@ namespace PolyTimeWitness variable {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n)} {α β : ℕ → Type} {bd : BoundaryData spec α β} {oa : (n : ℕ) → α n → OracleComp (spec n) (β n)} -/-- Resolution within the round budget is derivable for any certified adversary: the -old `steady` field, now a theorem (via `DynComputation.ImplementsWithin.resolvesIn`) — +/-- Resolution within the round budget is derivable for any certified adversary, as a +theorem rather than a bundle field (via `DynComputation.ImplementsWithin.resolvesIn`) — and handler-free, since `ResolvesIn` quantifies over every typed answer path. -/ theorem resolvesIn (w : PolyTimeWitness bd oa) (n : ℕ) (x : α n) : (w.A.M n).ResolvesIn (w.A.steps.eval n) ((w.A.M n).init x) := (w.implements n).resolvesIn x +/-- The program family syntactically respects the adversary's round budget: the +bounded half of the implements relation +(`DynComputation.implementsWithin_iff_implements_and_bound`), read through the +definitional equality of `OracleComp.IsTotalQueryBound` with +`PFunctor.FreeM.IsTotalRollBound`. This is the query-bound conjunct that +`SecurityGame.secureAgainstPolyTime_of_advantage_le_mul_totalQueries` consumes. -/ +theorem queryBound (w : PolyTimeWitness bd oa) (n : ℕ) (x : α n) : + OracleComp.IsTotalQueryBound (oa n x) (w.A.steps.eval n) := + ((PFunctor.DynSystem.DynComputation.implementsWithin_iff_implements_and_bound + (w.A.M n) (oa n) (w.A.steps.eval n)).mp (w.implements n)).2 x + end PolyTimeWitness /- The `PolyQueries` bridge (`PolyTimeWitness.toPolyQueries` / @@ -421,8 +426,7 @@ def _root_.OracleComp.OracleMachine.stepD {ι : Type} {spec : OracleSpec.{0, 0} /-- The deterministic run through a handler reads out the `stepD` trajectory: fuelled `runWith` at `m := Id` is the readout after `k` deterministic steps — unconditionally, -since returns are absorbing (`stepD` fixes returned states). Replaces the old -stability-conditioned readout lemma. -/ +since returns are absorbing (`stepD` fixes returned states). -/ theorem _root_.OracleComp.OracleMachine.runWith_eq_output_iterate_stepD {ι : Type} {spec : OracleSpec.{0, 0} ι} {α' β' : Type} (M : OracleMachine spec α' β') (h : OracleHandler spec) (k : ℕ) (s : M.State) : @@ -465,8 +469,10 @@ def answerAt (D : MachineAdversary bd) (n : ℕ) (h : OracleHandler (spec n)) ⟨(D.M n).expose (D.stateAt n h x j), h ((D.M n).expose (D.stateAt n h x j))⟩ /-- The total Turing-machine time of the deterministic run against handler `h` on -input `x`: the initialization cost plus, per round, the expose, update, and readout -costs, each evaluated at the encoded lengths actually occurring along the run. -/ +input `x`: the initialization cost, plus, per round, the expose, update, and readout +costs, plus the final readout at the budget state (the run's answer is +`output (stepD^[steps] s)`, so the readout at round `steps` is a real evaluation and +is charged), each evaluated at the encoded lengths actually occurring along the run. -/ noncomputable def detTotalTime (D : MachineAdversary bd) (n : ℕ) (h : OracleHandler (spec n)) (x : α n) : ℕ := ((D.initF.wit n).time).eval (bd.eIn.enc n x).length + @@ -475,7 +481,9 @@ noncomputable def detTotalTime (D : MachineAdversary bd) (n : ℕ) ((D.updateF.wit n).time).eval ((D.state.pairVar bd.eIface.encAns).enc n (D.stateAt n h x j, D.answerAt n h x j)).length + - ((D.outputF.wit n).time).eval (D.state.enc n (D.stateAt n h x j)).length) + ((D.outputF.wit n).time).eval (D.state.enc n (D.stateAt n h x j)).length) + + ((D.outputF.wit n).time).eval + (D.state.enc n (D.stateAt n h x (D.steps.eval n))).length /-- **Total-time bound, hypothesis-free**: the total machine time of any run is bounded by an explicit polynomial expression in `n` — the canonical fixed input width bounds @@ -489,8 +497,11 @@ theorem detTotalTime_le (D : MachineAdversary bd) (D.exposeF.time.eval (n + D.state.bound.eval n) + D.updateF.time.eval (n + (D.state.bound.eval n + bd.eIface.encAns.widBound.eval n)) + - D.outputF.time.eval (n + D.state.bound.eval n)) := by - refine Nat.add_le_add ?_ ?_ + D.outputF.time.eval (n + D.state.bound.eval n)) + + D.outputF.time.eval (n + D.state.bound.eval n) := by + refine Nat.add_le_add (Nat.add_le_add ?_ ?_) + ((D.outputF.time_le n _).trans (Polynomial.eval_le_eval + (Nat.add_le_add_left (D.state.len_le n _) n))) · refine (D.initF.time_le n _).trans (Polynomial.eval_le_eval ?_) have h1 : (bd.eIn.enc n x).length ≤ bd.eIn.widBound.eval n := (bd.eIn.len_eq n x).le.trans (bd.eIn.wid_le n) @@ -526,7 +537,8 @@ theorem exists_polynomial_detTotalTime_le (D : MachineAdversary bd) : refine ⟨D.initF.time.comp (.X + bd.eIn.widBound) + D.steps * (D.exposeF.time.comp (.X + D.state.bound) + D.updateF.time.comp (.X + (D.state.bound + bd.eIface.encAns.widBound)) + - D.outputF.time.comp (.X + D.state.bound)), + D.outputF.time.comp (.X + D.state.bound)) + + D.outputF.time.comp (.X + D.state.bound), fun n h x => (D.detTotalTime_le n h x).trans_eq ?_⟩ simp [Polynomial.eval_comp] @@ -588,8 +600,7 @@ theorem OracleComp.isPolyTime_pure_of_witnesses updateF := updateF.copy _ (fun n p => congrFun (OracleMachine.updateFlat_ofPureFn (f n)).symm p) outputF := outputF } - implements := fun n => ?_ - queryBound := fun n x => trivial }⟩ + implements := fun n => ?_ }⟩ intro x simp only [Polynomial.eval_zero] rfl diff --git a/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean b/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean index bd75c3fdf..15e2b04f5 100644 --- a/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean +++ b/VCVio/OracleComp/Coinductive/PolyTimeClosure.lean @@ -55,12 +55,19 @@ namespace OracleComp.OracleMachine variable {spec : OracleSpec.{0, 0} ι} {m : Type → Type} [Monad m] +/-- Replace a machine's initialization map (possibly changing the input type), keeping +its dynamics. Reducible so that runs and views of `M.setInit g` reduce to those of `M`. +Upstream candidate for `DynComputation`. -/ +@[reducible] def setInit {α α' β : Type} (M : OracleMachine spec α β) + (g : α' → M.State) : OracleMachine spec α' β := + ⟨M.toMachine, g⟩ + /-- Fuelled unrolling ignores the initialization field: replacing `init` (possibly changing the input type) leaves `unroll` unchanged from any state. Upstream candidate for `DynComputation/Bounded`. -/ theorem unroll_setInit {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) (k : ℕ) (s : M.State) : - PFunctor.DynSystem.DynComputation.unroll ⟨M.toMachine, g⟩ k s = M.unroll k s := by + PFunctor.DynSystem.DynComputation.unroll (M.setInit g) k s = M.unroll k s := by induction k generalizing s with | zero => rw [PFunctor.DynSystem.DynComputation.unroll_zero, @@ -85,27 +92,27 @@ theorem unroll_setInit {α α' β : Type} exact congrArg (PFunctor.FreeM.liftBind q.1) (funext fun d => ih (q.2 d)) /-- The TM-facing accessors ignore the initialization field, definitionally: they -scrutinize `toMachine`, which `⟨M.toMachine, g⟩` shares with `M`. -/ +scrutinize `toMachine`, which `M.setInit g` shares with `M`. -/ theorem output_setInit {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) (s : M.State) : - output (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = M.output s := rfl + output (M.setInit g) s = M.output s := rfl @[simp] theorem expose_setInit [Inhabited ι] {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) (s : M.State) : - expose (⟨M.toMachine, g⟩ : OracleMachine spec α' β) s = M.expose s := rfl + expose (M.setInit g) s = M.expose s := rfl theorem updateFlat_setInit [DecidableEq ι] {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) (p : M.State × ((t : ι) × spec.Range t)) : - updateFlat (⟨M.toMachine, g⟩ : OracleMachine spec α' β) p = M.updateFlat p := rfl + updateFlat (M.setInit g) p = M.updateFlat p := rfl /-- The run of a machine ignores the initialization field, in any monad. -/ theorem runWith_setInit {α α' β : Type} (M : OracleMachine spec α β) (g : α' → M.State) (H : QueryImpl spec m) (k : ℕ) (s : M.State) : - PFunctor.DynSystem.DynComputation.runWith ⟨M.toMachine, g⟩ H k s = + PFunctor.DynSystem.DynComputation.runWith (M.setInit g) H k s = M.runWith H k s := by change PFunctor.FreeM.liftM H - (PFunctor.DynSystem.DynComputation.unroll ⟨M.toMachine, g⟩ k s) = _ + (PFunctor.DynSystem.DynComputation.unroll (M.setInit g) k s) = _ rw [unroll_setInit] rfl @@ -183,7 +190,7 @@ noncomputable def precomp (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) : MachineAdversary (bd.withIn eIn') where - M n := ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ + M n := (D.M n).setInit fun x => (D.M n).init (f n x) steps := D.steps state := D.state initF := .ofFintype eIn'.enc_injective (fun n x => (D.M n).init (f n x)) @@ -201,7 +208,7 @@ noncomputable def precomp (D : MachineAdversary bd) [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) (hcard : ∀ n, Fintype.card (γ n) ≤ cardIn.eval n) (n : ℕ) : (D.precomp f eIn' cardIn hcard).M n = - ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ := rfl + (D.M n).setInit fun x => (D.M n).init (f n x) := rfl @[simp] theorem precomp_steps (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) [∀ n, Fintype (γ n)] (eIn' : BitEncFam γ) (cardIn : Polynomial ℕ) @@ -217,8 +224,8 @@ theorem precomp_implements {D : MachineAdversary bd} (f : (n : ℕ) → γ n → D.precomp f eIn' cardIn hcard ⊨ fun n x => oa n (f n x) := by intro n x change PFunctor.DynSystem.DynComputation.unroll - (⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ : - OracleMachine (spec n) (γ n) (β n)) (D.steps.eval n) ((D.M n).init (f n x)) = _ + ((D.M n).setInit fun x => (D.M n).init (f n x)) (D.steps.eval n) + ((D.M n).init (f n x)) = _ rw [OracleMachine.unroll_setInit] exact h n (f n x) @@ -231,7 +238,7 @@ noncomputable def precompComp (D : MachineAdversary bd) (f : (n : ℕ) → γ n (eIn' : Computability.BitEncFam γ) (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) : MachineAdversary (bd.withIn eIn') where - M n := ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ + M n := (D.M n).setInit fun x => (D.M n).init (f n x) steps := D.steps state := D.state initF := wit.comp D.initF @@ -246,7 +253,7 @@ noncomputable def precompComp (D : MachineAdversary bd) (f : (n : ℕ) → γ n (eIn' : Computability.BitEncFam γ) (wit : Computability.EncPolyTimeFam eIn'.enc bd.eIn.enc f) (n : ℕ) : (D.precompComp f eIn' wit).M n = - ⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ := rfl + (D.M n).setInit fun x => (D.M n).init (f n x) := rfl @[simp] theorem precompComp_steps (D : MachineAdversary bd) (f : (n : ℕ) → γ n → α n) (eIn' : Computability.BitEncFam γ) @@ -262,8 +269,8 @@ theorem precompComp_implements {D : MachineAdversary bd} (f : (n : ℕ) → γ n D.precompComp f eIn' wit ⊨ fun n x => oa n (f n x) := by intro n x change PFunctor.DynSystem.DynComputation.unroll - (⟨(D.M n).toMachine, fun x => (D.M n).init (f n x)⟩ : - OracleMachine (spec n) (γ n) (β n)) (D.steps.eval n) ((D.M n).init (f n x)) = _ + ((D.M n).setInit fun x => (D.M n).init (f n x)) (D.steps.eval n) + ((D.M n).init (f n x)) = _ rw [OracleMachine.unroll_setInit] exact h n (f n x) @@ -359,8 +366,7 @@ theorem OracleComp.IsPolyTime.precomp {spec : (n : ℕ) → OracleSpec.{0, 0} ( obtain ⟨w⟩ := hoa exact ⟨{ A := w.A.precomp f eIn' cardIn hcard' - implements := MachineAdversary.precomp_implements f eIn' cardIn hcard' w.implements - queryBound := fun n x => w.queryBound n (f n x) }⟩ + implements := MachineAdversary.precomp_implements f eIn' cardIn hcard' w.implements }⟩ /-- `OracleComp.IsPolyTime` is closed under pure input precomposition from a supplied machine witness between the canonical input encodings — the unbounded-input sibling of @@ -376,8 +382,7 @@ theorem OracleComp.IsPolyTime.precompComp {spec : (n : ℕ) → OracleSpec.{0, 0 obtain ⟨w⟩ := hoa exact ⟨{ A := w.A.precompComp f eIn' wit - implements := MachineAdversary.precompComp_implements f eIn' wit w.implements - queryBound := fun n x => w.queryBound n (f n x) }⟩ + implements := MachineAdversary.precompComp_implements f eIn' wit w.implements }⟩ /-- `OracleComp.IsPolyTime` is closed under a pure **output** map on per-parameter finite output types of polynomially bounded cardinality, on an abstract hypothesis: @@ -398,8 +403,4 @@ theorem OracleComp.IsPolyTime.map {spec : (n : ℕ) → OracleSpec.{0, 0} (ι n) obtain ⟨w⟩ := hoa exact ⟨{ A := w.A.mapComp g eOut' (.optionMap bd.eOut eOut' g cardβ hcard') - implements := MachineAdversary.mapComp_implements g eOut' _ w.implements - queryBound := fun n x => by - simp only [MachineAdversary.mapComp_steps] - rw [map_eq_bind_pure_comp] - exact isTotalQueryBound_bind (n₂ := 0) (w.queryBound n x) fun _ => trivial }⟩ + implements := MachineAdversary.mapComp_implements g eOut' _ w.implements }⟩ diff --git a/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean b/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean index bf4e5233e..509f658f3 100644 --- a/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean +++ b/VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean @@ -1,7 +1,7 @@ /- Copyright (c) 2026 Devon Tuma. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Devon Tuma +Authors: Devon Tuma, Elias Judin -/ import VCVio.OracleComp.Coinductive.PolyTimeClosure import ToMathlib.Computability.MachineCounting @@ -27,13 +27,12 @@ model's soundness certificate, and their history is the model's design rationale a function chosen (classically) to differ from every `2^{n/4}`-sized composite behavior defeats every polynomial bundle at large `n`. -The proofs are staged (see `docs/agents/polytime-model.md`): the run-factorization +The proof is staged (see `docs/agents/polytime-model.md`): the run-factorization lemma (the "compiled run": the deterministic run against a fixed handler is an iterate of the witness string functions), machine counting and normalization to `Fin d` state spaces, elementary `p.eval n ≤ 2^(n/4)` growth bounds, and the diagonal construction. -The `sorry`s below are those staged proofs' end products, recorded as the model's -falsifiable acceptance criteria rather than smuggled as axioms — nothing downstream -may depend on them. +All stages are fully proved; the two headline theorems at the end of the file are the +model's falsifiable acceptance criteria. -/ open OracleSpec OracleComp Computability @@ -244,20 +243,20 @@ at the coin boundary, the input→output behavior `x ↦ (M n).output (run …)` is realized by an `EncPolyTime` initialization/output pair whose description sizes are bounded by a single polynomial `q` in the adversary's `descBound` and `steps`. -Proof. Specialize `(himpl n).runK_eq` to `m := Id` and the deterministic constant-`true` -coin handler; the pure simulation collapses to `runD hdl (steps.eval n) (init x) = f n x`, -and `runD_eq_output_stateAfter` (using the adversary's `stable` readout to absorb early -termination) turns this into a readout of the fixed-fuel iterate of the one-step state map -`g = advanceOnce hdl M.toDynSystem`. That one-step map is compiled at the encoding level as -`updateF` precomposed with the fixed canonical coin answer `[true]` (built by the local -`Computability.EncPolyTime.appendBit` / `Cslib.Turing.SingleTapeTM.snocComputer` support), and its -`steps.eval n`-fold composition is assembled by `Computability.EncPolyTime.exists_iterate`. -Composition adds machine-state counts, so the compiled initialization -`initF ▸ g^[steps.eval n]` has description size at most -`descBound + 1 + steps * (2 + descBound)` evaluated at `n` — one uniform polynomial `q`, -with the (parameter-specific) iteration time absorbed into `RealizableLE`'s time component. -This is the general-`steps` input to the diagonal contradiction; the `steps = 0` special -case is proved directly by `realizable_of_implements_steps_eq_zero`. -/ +Proof. Specialize `(himpl n).runWithInput_eq` to `m := Id` and the deterministic +constant-`true` coin handler `hdl`; `runWith_eq_output_iterate_stepD` (unconditional, +since returns are absorbing) turns the resulting run equation into a readout of the +fixed-fuel iterate of the one-step state map `g = stepD hdl`. That one-step map is +compiled at the encoding level as `updateF` precomposed with the fixed canonical coin +answer `[true]` (built by the local `Computability.EncPolyTime.appendBit` / +`Cslib.Turing.SingleTapeTM.snocComputer` support), and its `steps.eval n`-fold +composition is assembled by `Computability.EncPolyTime.exists_iterate`. Composition +adds machine-state counts, so the composition of the `initF` witness with the iterate +has description size at most `descBound + 1 + steps * (2 + descBound)` evaluated at +`n` — one uniform polynomial `q`; the iterate's parameter-specific running time needs +no accounting, since `RealizableLE` constrains only description sizes. This is the +general-`steps` input to the diagonal contradiction; the `steps = 0` special case is +proved directly by `realizable_of_implements_steps_eq_zero`. -/ theorem exists_poly_realizable_of_implements (A : MachineAdversary (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool)) (f : (n : ℕ) → BitVec n → Bool) @@ -346,10 +345,11 @@ theorem exists_poly_realizable_of_implements exact hout_le.trans (by omega) /-- **Milestone A, realizability step (real).** A round-free adversary implementing -`pure ∘ f` realizes `f n` within its description bound: at `steps = 0` the run is the plain -readout of the initial state (`runK_zero`), so `(M n).output ((M n).init x) = some (f n x)`, -and the initialization and output witness families supply the required `EncPolyTime` pair -with sizes bounded by `descBound`. -/ +`pure ∘ f` realizes `f n` within its description bound: at `steps = 0` the run is the +plain readout of the initial state (the zero-fuel case of +`runWith_eq_output_iterate_stepD`), so `(M n).output ((M n).init x) = some (f n x)`, +and the initialization and output witness families supply the required `EncPolyTime` +pair with sizes bounded by `descBound`. -/ private theorem realizable_of_implements_steps_eq_zero (A : MachineAdversary (BoundaryData.coin BitEncFam.bitVecX BitEncFam.bool)) (f : (n : ℕ) → BitVec n → Bool) From 95d8a9dfe2ba8b7a273d77f2ec51e95c81257f82 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Fri, 31 Jul 2026 22:53:23 -0500 Subject: [PATCH 5/5] docs(polytime): agent guide for the machine-adversary model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds docs/agents/polytime-model.md — the model in one paragraph, file ownership map, canonicity-as-discipline rules, proven-vs-deferred status, semantics notes, cslib positioning with the upstream collision watch, and the statement-site checklist — and indexes the layer from AGENTS.md. Co-Authored-By: Claude Fable 5 --- AGENTS.md | 6 ++ docs/agents/polytime-model.md | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 docs/agents/polytime-model.md diff --git a/AGENTS.md b/AGENTS.md index b4962e33b..c3bb81b4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -121,6 +121,11 @@ Structures use UpperCamelCase: `SecExp`, `SymmEncAlg`, `RelTriple`. - DLog / CDH / DDH via HHS: `VCVio/CryptoFoundations/HardnessAssumptions/DiffieHellman.lean` - Cost model / polynomial time: `VCVio/OracleComp/QueryTracking/CostModel.lean` - Query cost / weighted expected cost: `VCVio/OracleComp/QueryTracking/QueryCost.lean`, `VCVio/OracleComp/QueryTracking/WriterCost.lean` +- TM-grounded polynomial-time adversaries (`MachineAdversary`, `OracleComp.IsPolyTime`): `VCVio/OracleComp/Coinductive/PolyTime.lean` +- Bounded coin-fold poly-time combinator: `VCVio/OracleComp/Coinductive/CoinFold.lean` +- Poly-time non-triviality certificate (counting + diagonalization): `VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean` +- Encoded poly-time witnesses and bit-encoding families: `ToMathlib/Computability/CslibPolyTime.lean`, `ToMathlib/Computability/BitEncoding.lean` +- Security against poly-time adversaries: `VCVio/CryptoFoundations/Asymptotics/PolyTime.lean` - Asymptotic security games: `VCVio/CryptoFoundations/Asymptotics/Security.lean` - Negligible function algebra: `VCVio/CryptoFoundations/Asymptotics/Negligible.lean` - Query enforcement: `VCVio/OracleComp/QueryTracking/Enforcement.lean` @@ -198,6 +203,7 @@ Before working in a specific area, read the relevant guide in `docs/agents/`: - **LatticeCrypto layout and workflows**: [`docs/agents/lattice.md`](docs/agents/lattice.md) - **OracleComp / SubSpec / SimSemantics**: [`docs/agents/oracle-comp.md`](docs/agents/oracle-comp.md) - **Query tracking / weighted cost / expected runtime**: [`docs/agents/query-tracking.md`](docs/agents/query-tracking.md) +- **TM-grounded polynomial-time adversary model**: [`docs/agents/polytime-model.md`](docs/agents/polytime-model.md) - **Probability reasoning (EvalDist, ProbComp)**: [`docs/agents/probability.md`](docs/agents/probability.md) - **Crypto primitives and reductions**: [`docs/agents/crypto.md`](docs/agents/crypto.md) - **End-to-end crypto examples**: [`docs/agents/end-to-end-examples.md`](docs/agents/end-to-end-examples.md) diff --git a/docs/agents/polytime-model.md b/docs/agents/polytime-model.md new file mode 100644 index 000000000..2ac012340 --- /dev/null +++ b/docs/agents/polytime-model.md @@ -0,0 +1,187 @@ +# The Turing-Machine-Grounded Polynomial-Time Adversary Model + +This guide explains the machine-adversary polynomial-time layer: what the model is, +why each piece exists, which file owns what, and how the layer is positioned relative +to upstream cslib. Read it before touching anything under +`VCVio/OracleComp/Coinductive/PolyTime*.lean` or `ToMathlib/Computability/`. + +## The Model In One Paragraph + +`OracleComp.IsPolyTime bd oa` says: the program family +`oa : (n : ℕ) → α n → OracleComp (spec n) (β n)` is implemented, at pinned boundary +encodings `bd : BoundaryData spec α β`, by a family of oracle machines +(`MachineAdversary`) whose four step functions (initialization, query selection, +flattened update, readout) are each computed by concrete Cslib single-tape Turing +machines under **single polynomials, uniform across the family**, bounding running +time, round count, state-encoding length, and machine description size. This is +**non-uniform P/poly relative to a fixed canonical representation**: nothing computes +the `n`-th machine from `n`, and the description-size bound (`MachineAdversary.descBound`) +is the advice bound. A uniform variant (one machine reading `n`) is deliberately out of +scope rather than stubbed. + +## Main Files + +| File | Role | +|------|------| +| `ToMathlib/Computability/CslibPolyTime.lean` | `EncPolyTime`: encoded poly-time witness over cslib machines; adds the description-size measure (`PolyTimeComputable.size`, pinned to the `Bool` alphabet) that cslib lacks | +| `ToMathlib/Computability/PolyTimeTM.lean` | Base machines: `constComputer`, `tableComputer`; `EncPolyTime.const` / `.ofFintype` (finite tables) with size bounds | +| `ToMathlib/Computability/BitEncoding.lean` | `StrEncFam` (variable-width, injective, length-bounded), `BitEncFam` (fixed-width refinement), `EncPolyTimeFam` (uniform time + advice bounds across a family) | +| `ToMathlib/Computability/MachineCounting.lean` | `TMTable d`, the machine count `B d`, `RealizableLE`, the covering and diagonal lemmas — the counting core of non-triviality | +| `VCVio/OracleComp/Coinductive/PolyTime.lean` | `BoundaryData`, `MachineAdversary`, `PolyTimeWitness`, `OracleComp.IsPolyTime`, total-run-time accounting (`detTotalTime`) | +| `VCVio/OracleComp/Coinductive/PolyTimeClosure.lean` | Closure under input precomposition and output maps; `OracleMachine.setInit` | +| `VCVio/OracleComp/Coinductive/CoinFold.lean` | The bounded coin-fold combinator and its assembled witnesses | +| `VCVio/OracleComp/Coinductive/PolyTimeConstructions.lean` | `isPolyTime_coin`, `uniformBitVec`, `isPolyTime_pure_ofFintype` | +| `VCVio/OracleComp/Coinductive/PolyTimeNontrivial.lean` | The non-triviality certificates (see below) | +| `VCVio/CryptoFoundations/Asymptotics/PolyTime.lean` | `SecurityGame.secureAgainstPolyTime` / `secureAgainstMachines` | + +The machine carrier itself (`DynComputation`, `unroll`, `ImplementsWithin`, +`ResolvesIn`, `runWith`) lives upstream in the PolyFun package +(`PolyFun/PFunctor/Dynamical/DynComputation/`), read through +`VCVio/OracleComp/Coinductive/Machine.lean`. + +## Why Each Piece Exists + +- **Pinned canonical boundaries (`BoundaryData`).** "Poly-time relative to *some* + encoding" is vacuous: `enc x := std x ++ block (f x)` caches any `f` inside the + representation and every machine degenerates to a projection. The input, output, and + oracle-interface encodings are therefore explicit parameters of every security + statement. +- **The description-size (advice) bound.** Time bounds alone admit lookup tables with + one state per input — unbounded advice, and the class would contain every function + on encodable domains. `EncPolyTimeFam.size` bounds each witness machine's state + count by one polynomial across the family; over the fixed `Bool` alphabet the state + count measures the transition table up to a constant factor, which is exactly what + `MachineCounting.B` counts. +- **The `1^n` convention.** Boundary widths are polynomially bounded by definition + (`BitEncFam.widBound`), so "polynomial in the input length" and "polynomial in `n`" + agree — the width bound *is* the Katz–Lindell `1^n` convention. +- **Machine-internal freedom.** The state representation (`StrEncFam`) is existential + in the bundle: every bit entering it was produced by a witnessed machine from + canonical inputs and answers, so a crafted state encoding can only cache what was + already computed within budget. + +## Canonicity Is Discipline, Not Structure + +`BitEncFam` is structurally only *injective + fixed polynomial width*. The caching +attack above is still expressible as a `BitEncFam`; nothing in the type requires the +encoding to be computable, let alone efficiently decodable. What closes the channel is +the **statement-site discipline**: + +1. `bd : BoundaryData …` is always an explicit, pinned parameter of a security + definition — **never existentially quantified and never adversary-chosen**. A + theorem of the form `∃ bd, IsPolyTime bd oa` is meaningless; a hypothesis + `∀ bd, …` is fine. +2. Boundaries are built from the small structural constructor registry + (`BitEncFam.const/bool/fin/bitVec/bitVecX/pair/option/pad`, `StrEncFam.pairVar/sum`), + so "secure against poly-time" reads "…relative to the standard representation". +3. The sole exemption is a multi-phase adversary's own cross-phase state, which its + phases share (machine-internal data, covered by the freedom argument above). + +A structural refinement (bundling a poly-time decoder into `BitEncFam`, making +canonicity a property rather than a convention) is recorded future work; it was not +needed for the non-triviality certificates because those pin concrete boundaries. + +## What Is Proven, What Is Deferred + +Proven (all sorry-free, axioms `propext`, `Classical.choice`, `Quot.sound` only): + +- **Non-triviality**: `exists_not_isPolyTime_pure` — the class at the canonical + coin/bitvector boundaries does not contain every predicate family, by counting + (`B d`-many `d`-state machines vs `2^(2^n)` predicates) plus diagonalization. The + round-free sentinel `exists_not_implements_pure_of_steps_eq_zero` isolates the + counting core. Both were **false** before boundary canonicalization. +- **Hypothesis-free total time**: `MachineAdversary.exists_polynomial_detTotalTime_le` + — every adversary's deterministic-run machine time (including the final readout at + the budget state) is bounded by one polynomial in `n`, with no side conditions. +- **Query bound as a theorem**: `PolyTimeWitness.queryBound` derives + `IsTotalQueryBound (oa n x) (steps.eval n)` from `implements` via + `DynComputation.implementsWithin_iff_implements_and_bound` (`IsTotalQueryBound` is + definitionally `PFunctor.FreeM.IsTotalRollBound`). Resolution and readout stability + are likewise theorems, not bundle fields. +- **Closure** under input precomposition (finite-table and machine-witnessed) and + output maps; the coin-fold combinator with fully-discharged table witnesses for + polynomially-small accumulators. + +Deferred, deliberately: + +- **`bind` closure** (needs the two-phase machine construction; the statement is + well-formed now that the mid boundary is shared by construction). +- **An end-to-end compiled single machine** for a whole run: `detTotalTime` is + component-cost accounting over the four witnesses, not a constructed oracle TM + (needs machine iteration on top of cslib's composition). +- **A uniform (single-machine) variant** of the class. +- **The `PolyQueries` bridge** (needs the per-`n` index-family generalization of + `OracleComp.PolyQueries`). +- Hypothesis-free machine witnesses for superpolynomially-large-accumulator folds + (e.g. `uniformBitVec`) — pending a base-machine combinator library + (relabelings/projections on unbounded domains in `PolyTimeTM.lean`'s skeleton). + +Nothing elsewhere in the repo currently depends on this layer: `secureAgainstPolyTime` +has no call sites by design until the deferred items land (the #460 review's staging). + +## Semantics Notes (Read Before Changing Definitions) + +- `ImplementsWithin` is **syntactic**: fuel-`k` unroll equality + `M.run k x = FreeM.map some (oa x)` in the free monad — the machine makes literally + the same queries in the same order along every typed answer path. Distributional + agreement under every lawful handler (including stateful challengers at + `StateT σ SPMF`) is a corollary (`MachineAdversary.exec_eq_of_implements`), not the + definition. Complexity is intensional; this is the right strength. +- `ResolvesIn` and `IsTotalQueryBound` are worst-case, all-typed-answer-paths, + handler-free. +- Machines are deterministic; all randomness enters through the oracle (the coin + oracle is the random tape). There is no machine-internal sampling. +- `EncPolyTime`/`EncPolyTimeFam` impose nothing on their encoding arguments — their + certifying power comes from call sites pinning `BitEncFam`/`StrEncFam`. Never accept + an existentially-quantified encoding. +- `EncPolyTime(-Fam).comp` composes time bounds by substitution (degrees multiply): + fixed-depth composition only. Polynomial-length runs are accounted additively per + step (`detTotalTime`); only description size composes additively, which is what the + non-triviality iterate (`EncPolyTime.exists_iterate`) uses. + +## cslib Positioning And Upstream Watch + +The layer builds on `Cslib.Turing.SingleTapeTM` (single tape, `Bool` alphabet, +`TimeComputable`/`PolyTimeComputable`), which arrives transitively through the PolyFun +package's cslib pin. Assessment as of 2026-07: + +- cslib has **no complexity layer** and its whitepaper defers complexity theory to + 2027, naming RAM/query models and Boole cost semantics as the heavyweight targets. + The de-facto RFC (cslib issue #611) treats single-tape TMs as the bottom rung of a + simulation ladder; a multi-tape machine with time+space measures merged 2026-07 + (cslib PR #384) in a different namespace with different conventions. cslib is + converging on model-independence via simulation relations, not one canonical + machine. +- Everything upstream is **Prop-valued uniform complexity** (existentially quantified + machines); none of it can express description-size/advice bounds. The non-uniform + P/poly layer here has no upstream home and stays local — per the repo decision, + nothing in `ToMathlib/Computability/` is aimed at upstream Mathlib/cslib PRs. +- **Stable under any upstream outcome**: the `List Bool` boundary encodings and the + `timeBound : ℕ → ℕ` + `Polynomial ℕ` pair — every upstream proposal keeps both. +- **Model-welded, quarantined**: `PolyTimeComputable.size = Fintype.card State` + (meaningful only at a fixed alphabet — hence pinned to `Bool` at the definition) and + `MachineCounting.lean`'s hand-computed table count `B d`. A machine-model swap means + re-deriving `MachineCounting.lean` and re-instantiating the `EncPolyTime.polyTime` + field; `EncPolyTimeFam` is the interface the adversary layer consumes and is the + natural swap point. Keep it that way: nothing outside `CslibPolyTime.lean` / + `PolyTimeTM.lean` / `MachineCounting.lean` should reach into `tm.State`. +- **Name-collision watch**: cslib draft PR #192 (single-tape complexity classes) + independently defines `PolyTimeComputable.normalize` and `readState` in the same + namespace this repo extends. If a future cslib bump lands #192, drop the local + `normalize` in favor of upstream and rename/reconcile `readState` + (`PolyTimeTM.lean`). + +## Statement-Site Checklist For New Security Definitions + +1. Take `bd : BoundaryData spec α β` as an explicit parameter; build it from the + canonical constructor registry at the final instantiation. +2. Use `SecurityGame.secureAgainstPolyTime bd g` (programs + `IsPolyTime`) or + `SecurityGame.secureAgainstMachines g` (bundled `MachineAdversary`, `isPPT` slot + trivially `True`). +3. Per-query-loss bounds compose via + `secureAgainstPolyTime_of_advantage_le_mul_totalQueries`, consuming the derived + `PolyTimeWitness.queryBound`. +4. To certify a concrete sampler poly-time, reach for the combinators first: + `isPolyTime_coinFold` (poly-small accumulator), `isPolyTime_pure_ofFintype` + (poly-small domain), the `IsPolyTime.precomp/.precompComp/.map` closures, and + `IsPolyTime.congr` to bridge to the combinator's canonical program form.