Levin's Universal One-Way Function

# Background A natural question in cryptography: if one-way functions exist, which ones? Could there be a **single** function that is hard to invert whenever *any* OWF exists? **Levin's theorem** (1987) answers yes. Given an effective enumeration $\varphi_0, \varphi_1, \ldots$ of all poly-time computable functions, define a single function $f_L$ that encodes an index into its input and runs the corresponding $\varphi_i$ on the remainder. If any component $\varphi_{i^*}$ is a one-way function, then $f_L$ is also a one-way function. The key insight is a **coupling argument**: any adversary inverting $f_L$ on a random $n$-bit input is, averaged over the $2^k$ possible $k$-bit prefixes (indices), also inverting *some* $\varphi_i$ on the suffix. Since the set of indices is finite, an infinite pigeonhole argument produces a *fixed* index $i^*$ whose component is non-negligibly invertible. # Definition 1: Effective enumeration An **effective enumeration** at prefix length $k$ assigns to each bit string $i$ (interpreted as a $k$-bit index) a poly-time function $\varphi_i : \{0,1\}^* \to \{0,1\}^*$. We use raw `BitString` for indices so that `z.take k` serves directly as a lookup key — the same idiom used by [`fPrime`](QuadraticOWF.html#definition-1 "Quadratic-time one-way functions, Definition 1") in the quadratic-OWF construction. The **completeness** axiom says every poly-time function appears at the image of some `FixedBitString k` element under `.toList`.
structure EffectiveEnum (k : ℕ) : Type where
  /-- The enumerated family, indexed by a raw bit string (typically `z.take k`). -/
  fn       : BitString → BitString → BitString
  /-- Every fixed-length index gives a poly-time function. -/
  polytime : ∀ i : FixedBitString k, PolyTimeComputable (fn i.toList)
  /-- The universal computation `z ↦ fn (z.take k) (z.drop k)` is itself poly-time.
      This is the "universal machine" axiom: running the enumeration on an encoded
      (index, input) pair is no harder than running any single component. -/
  universal : PolyTimeComputable (fun z => fn (z.take k) (z.drop k))
  /-- Completeness: every poly-time function appears at some $k$-bit index. -/
  complete : ∀ f : BitString → BitString, PolyTimeComputable f →
               ∃ i : FixedBitString k, ∀ x, fn i.toList x = f x
# Definition 2: Levin's universal OWF Split any input $z$ into a $k$-bit **index** prefix $i = z_{[0:k]}$ and a **data** suffix $x = z_{[k:]}$. Output: $$f_L(z) \;=\; z_{[0:k]} \;\|\; \varphi_{z_{[0:k]}}(x)$$ The output preserves the first $k$ bits verbatim. Crucially, this means that inverting $f_L(z)$ on a target $y = i \| y'$ forces the inverter to produce a preimage of $y'$ under $\varphi_i$ — the inversion condition $f_L(z') = f_L(z)$ implies $z'_{[0:k]} = z_{[0:k]}$ (since both sides share the same $k$-bit prefix) and then $\varphi_i(z'_{[k:]}) = \varphi_i(x)$.
noncomputable def levinOWF (ee : EffectiveEnum k) : BitString → BitString :=
take the first k bits as the index; apply the indexed function to the rest
  fun z => z.take k ++ ee.fn (z.take k) (z.drop k)
levinOWF unfolding lemma ▸
On a `FixedBitString n` input with `k ≤ n`, [`levinOWF`](LevinOWF.html#definition-2 "Levin's Universal One-Way Function, Definition 2") reduces cleanly.
private lemma levinOWF_apply (ee : EffectiveEnum k) (n : ℕ) (_hkn : k ≤ n)
    (z : FixedBitString n) :
    levinOWF ee z.toList = z.toList.take k ++ ee.fn (z.toList.take k) (z.toList.drop k) :=
  rfl
Indicator dominance: if `levinOWF ee z'` inverts at `(i, x)` and `z'.length = n`, then the suffix `z'.drop k` inverts `ee.fn i.toList` at `x.toList`. Proof: concat-injectivity forces `z'.take k = i.toList`, giving the suffix condition.
private lemma levinOWF_success_implies_suffix (ee : EffectiveEnum k) (n : ℕ)
    (hkn : k ≤ n) (i : FixedBitString k) (x : FixedBitString (n - k)) (z' : BitString) :
    (if levinOWF ee z' = i.toList ++ ee.fn i.toList x.toList ∧ z'.length = n
     then (1 : ENNReal) else 0) ≤
    (if ee.fn i.toList (z'.drop k) = ee.fn i.toList x.toList ∧
        (z'.drop k).length = n - k
     then (1 : ENNReal) else 0) := by
case split on whether the levinOWF inversion condition holds
  by_cases h : levinOWF ee z' = i.toList ++ ee.fn i.toList x.toList ∧ z'.length = n
  · simp only [if_pos h]
unfold levinOWF so h.1 reads: z'.take k ++ ee.fn (z'.take k) (z'.drop k) = i.toList ++ ee.fn i.toList x.toList
    simp only [levinOWF] at h
z'.take k has exactly k elements since z'.length = n ≥ k
    have htake_len : (z'.take k).length = k :=
      by simp [List.length_take, Nat.min_eq_left (h.2 ▸ hkn)]
concat-injectivity: z'.take k = i.toList AND ee.fn (z'.take k) ... = ee.fn i.toList ...
    have hinj := List.append_inj h.1 (htake_len.trans i.toList_length.symm)
substitute z'.take k = i.toList into hinj.2 to get the suffix condition
    rw [if_pos ⟨hinj.1 ▸ hinj.2, by simp [List.length_drop, h.2]⟩]
  · simp [if_neg h]
Bijection splitting a length-n bit string into a k-bit prefix and (n-k)-bit suffix.
private def splitN (k n : ℕ) (hkn : k ≤ n) :
    FixedBitString n ≃ FixedBitString k × FixedBitString (n - k) where
  toFun v :=
    ⟨⟨v.toList.take k, by have h := v.toList_length; simp [List.length_take]; omega⟩,
     ⟨v.toList.drop k, by have h := v.toList_length; simp [List.length_drop, h]⟩⟩
  invFun p :=
    ⟨p.1.toList ++ p.2.toList, by
      have h1 := p.1.toList_length; have h2 := p.2.toList_length
      simp only [List.length_append, h1, h2, Nat.add_sub_cancel' hkn]⟩
  left_inv v := List.Vector.ext (by simp [List.take_append_drop])
  right_inv p := by
    rcases p with ⟨a, b⟩; apply Prod.ext <;> apply List.Vector.ext
    all_goals simp [a.toList_length]
The uniform measure on FixedBitString n splits over splitN: uniform(n)(splitN.symm(i,x)) = uniform(k)(i) * uniform(n-k)(x)
private lemma uniform_split (k n : ℕ) (hkn : k ≤ n)
    (i : FixedBitString k) (x : FixedBitString (n - k)) :
    PMF.uniformOfFintype (FixedBitString n) ((splitN k n hkn).symm (i, x)) =
    PMF.uniformOfFintype (FixedBitString k) i *
    PMF.uniformOfFintype (FixedBitString (n - k)) x := by
  simp only [PMF.uniformOfFintype_apply]
  rw [show Fintype.card (FixedBitString n) =
        Fintype.card (FixedBitString k) * Fintype.card (FixedBitString (n - k)) from by
    simp [← pow_add, Nat.add_sub_cancel' hkn]]
  push_cast
  rw [ENNReal.mul_inv (Or.inl (by positivity)) (Or.inr (by positivity))]
Scaling: 2^k * uniform(n)(splitN.symm(i,x)) = uniform(n-k)(x) Because 2^k * (1/2^k * 1/2^(n-k)) = 1/2^(n-k).
private lemma two_pow_mul_uniform (k n : ℕ) (hkn : k ≤ n)
    (i : FixedBitString k) (x : FixedBitString (n - k)) :
    (2 : ENNReal) ^ k *
    PMF.uniformOfFintype (FixedBitString n) ((splitN k n hkn).symm (i, x)) =
    PMF.uniformOfFintype (FixedBitString (n - k)) x := by
  rw [uniform_split k n hkn i x, ← mul_assoc]
2^k * uniform(k)(i) = 1 since uniform(k)(i) = 1/2^k
  have h2k : (2 : ENNReal) ^ k * PMF.uniformOfFintype (FixedBitString k) i = 1 := by
    simp [PMF.uniformOfFintype_apply]
    rw [ENNReal.mul_inv_cancel (by positivity) (by simp)]
  rw [h2k, one_mul]
# Definition 3: The suffix adversary Given an adversary $A$ that tries to invert $f_L$, and a fixed index $i^*$, the **suffix adversary** $B_{i^*}$ attacks $\varphi_{i^*}$ directly: $$B_{i^*}(y') \;=\; \bigl(A(i^* \| y')\bigr).\mathrm{drop}(k)$$ On input $y' = \varphi_{i^*}(x)$, $B_{i^*}$ presents the $f_L$-formatted challenge $i^* \| y'$ to $A$, then strips the $k$-bit prefix from $A$'s output to obtain a candidate preimage of $y'$ under $\varphi_{i^*}$. If $A$ succeeds — returning $z'$ with $f_L(z') = i^* \| y'$ and $|z'| = n$ — then the output prefix is forced to equal $i^*$ (since $f_L$ preserves the first $k$ bits), so the stripped suffix $z'.\mathrm{drop}(k)$ has length $n - k$ and satisfies $\varphi_{i^*}(z'.\mathrm{drop}(k)) = y'$.
noncomputable def suffixAdv (A : BitString → PMF BitString) (k : ℕ) (i : FixedBitString k)
    : BitString → PMF BitString :=
prepend the fixed index to the challenge; run A; strip the k-bit prefix
  fun y => (A (i.toList ++ y)).map (·.drop k)
Fubini for suffixAdv: ∑' x', suffixAdv A k i y x' * c(x') = ∑' z, A(i++y)(z) * c(z.drop k)
private lemma suffixAdv_indicator_eq (A : BitString → PMF BitString) (k : ℕ) (i : FixedBitString k)
    (g : BitString → BitString) (y fy : BitString) (r : ℕ) :
    (∑' x' : BitString, suffixAdv A k i y x' *
      (if g x' = fy ∧ x'.length = r then (1 : ENNReal) else 0)) =
    ∑' z : BitString, A (i.toList ++ y) z *
      (if g (z.drop k) = fy ∧ (z.drop k).length = r then 1 else 0) := by
  simp only [suffixAdv, PMF.map_apply]
  simp_rw [← ENNReal.tsum_mul_right]
  rw [ENNReal.tsum_comm]
  apply tsum_congr; intro z
the sum ∑' a, (if a = z.drop k then A(i++y)(z) else 0) * c(a) has one nonzero term
  rw [tsum_eq_single (z.drop k) (by intro a ha; simp [ha])]
  simp
ENNReal body of invertProb is ≤ 1 (hence ≠ ⊤)
private lemma invertProb_body_le_one (f : BitString → BitString)
    (F : BitString → PMF BitString) (n : ℕ) :
    (∑ x : FixedBitString n,
      PMF.uniformOfFintype (FixedBitString n) x *
      (∑' x' : BitString, F (f x.toList) x' *
        (if f x' = f x.toList ∧ x'.length = n then (1 : ENNReal) else 0))) ≤ 1 := by
  calc _ ≤ ∑ x : FixedBitString n, PMF.uniformOfFintype (FixedBitString n) x := by
        apply Finset.sum_le_sum; intro x _
bound: a * b ≤ a when b ≤ 1
        apply mul_le_of_le_one_right (zero_le _)
        calc ∑' x', F (f x.toList) x' * (if f x' = f x.toList ∧ x'.length = n then 1 else 0)
            ≤ ∑' x', F (f x.toList) x' := by
              apply ENNReal.tsum_le_tsum; intro x'
              exact mul_le_of_le_one_right (zero_le _) (by split_ifs <;> norm_num)
          _ = 1 := PMF.tsum_coe _
     _ = 1 := by
        have h := (PMF.uniformOfFintype (FixedBitString n)).tsum_coe
        rw [tsum_fintype (L := .unconditional _)] at h; exact h
# Lemma 1: Suffix adversary is poly-time If $A$ is a poly-time adversary, then $B_{i^*}$ is also poly-time: prepending a fixed string and stripping a fixed prefix are both $O(k)$.
lemma suffixAdv_polytime (A : BitString → PMF BitString) (hA : PolyTimeAdversary A)
    (k : ℕ) (i : FixedBitString k) : PolyTimeAdversary (suffixAdv A k i) := by
unfold [`suffixAdv`](LevinOWF.html#definition-3 "Levin's Universal One-Way Function, Definition 3") to expose the fixed-prefix map pattern
  unfold suffixAdv
`fixedPad` exactly captures: prepend a fixed prefix, run A, strip k bits
  exact PolyTimeAdversary.fixedPad i.toList k hA
# Lemma 2: The coupling inequality The sum of component inversion probabilities dominates the $f_L$ inversion probability, scaled by $2^k$: $$\sum_{i \in \{0,1\}^k} \mathrm{invertProb}(\varphi_i,\, B_i,\, n-k) \;\geq\; 2^k \cdot \mathrm{invertProb}(f_L,\, A,\, n)$$ **Proof sketch.** Reindex the sum defining $\mathrm{invertProb}(f_L, A, n)$ over $\{0,1\}^k \times \{0,1\}^{n-k}$ (prefix $i$, data $x$). The $f_L$- inversion indicator $[f_L(z') = i \| \varphi_i(x) \wedge |z'| = n]$ forces $z'_{[0:k]} = i$, so it collapses to $[\varphi_i(z'_{[k:]}) = \varphi_i(x) \wedge |z'_{[k:]}| = n-k]$. Summing the mass $A(i \| \varphi_i(x))(z')$ over all $z'$ with $z'_{[k:]} = x'$ gives $(A(\ldots).\mathrm{map\;drop}_k)(x')$, which is exactly $B_i(\varphi_i(x))(x')$. Summing over $i$ and normalizing yields the stated bound.
lemma levinOWF_coupling (ee : EffectiveEnum k) (A : BitString → PMF BitString)
    (n : ℕ) (hkn : k ≤ n) :
    (2 : ℝ) ^ k * invertProb (levinOWF ee) A n ≤
    ∑ i : FixedBitString k,
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) := by
  simp only [invertProb]
abbreviate the ENNReal body on each side
  set E_L : ENNReal := ∑ z : FixedBitString n,
    PMF.uniformOfFintype (FixedBitString n) z *
    (∑' z', A (levinOWF ee z.toList) z' *
      (if levinOWF ee z' = levinOWF ee z.toList ∧ z'.length = n then 1 else 0))
  set E_R : FixedBitString k → ENNReal := fun i =>
    ∑ x : FixedBitString (n - k),
      PMF.uniformOfFintype (FixedBitString (n - k)) x *
      (∑' x', suffixAdv A k i (ee.fn i.toList x.toList) x' *
        (if ee.fn i.toList x' = ee.fn i.toList x.toList ∧ x'.length = n - k then 1 else 0))
finiteness: both sides are ≤ 1, hence ≠ ⊤
  have hEL : E_L ≠ ⊤ := ne_top_of_le_ne_top ENNReal.one_ne_top (invertProb_body_le_one _ _ _)
  have hER : ∀ i, E_R i ≠ ⊤ := fun i =>
    ne_top_of_le_ne_top ENNReal.one_ne_top (invertProb_body_le_one _ _ _)
rewrite LHS: (2:ℝ)^k * E_L.toReal = ((2:ENNReal)^k * E_L).toReal
  rw [show (2 : ℝ) ^ k * E_L.toReal = ((2 : ENNReal) ^ k * E_L).toReal by
    rw [ENNReal.toReal_mul, ENNReal.toReal_pow]; norm_cast]
rewrite RHS: ∑ i, (E_R i).toReal = (∑ i, E_R i).toReal
  rw [show ∑ i : FixedBitString k, (E_R i).toReal = (∑ i, E_R i).toReal by
    rw [ENNReal.toReal_sum (fun i _ => hER i)]]
ne_top facts needed for toReal conversion
  have h2k : (2 : ENNReal) ^ k ≠ ⊤ := by norm_cast; exact ENNReal.natCast_ne_top _
  have hL : (2 : ENNReal) ^ k * E_L ≠ ⊤ := ENNReal.mul_ne_top h2k hEL
  have hR : ∑ i : FixedBitString k, E_R i ≠ ⊤ := ENNReal.sum_ne_top.mpr (fun i _ => hER i)
reduce to the ENNReal inequality via monotonicity of toReal
  rw [ENNReal.toReal_le_toReal hL hR]
*** Main ENNReal inequality: (2:ENNReal)^k * E_L ≤ ∑ i, E_R i *** distribute 2^k into the FixedBitString n sum
  rw [Finset.mul_sum]
reindex FixedBitString n as FixedBitString k × FixedBitString (n-k) via splitN; outer sum remains over FixedBitString k, so no sum_comm needed
  rw [← Equiv.sum_comp (splitN k n hkn).symm, Fintype.sum_prod_type]
bound each (i, x) term
  apply Finset.sum_le_sum; intro i _
  apply Finset.sum_le_sum; intro x _
simplify: splitN.symm (i, x) has toList = i.toList ++ x.toList
  simp_rw [show ((splitN k n hkn).symm (i, x)).toList = i.toList ++ x.toList from
    by simp [splitN]]
simplify: levinOWF on a concatenated input = i.toList ++ ee.fn i.toList x.toList
  simp_rw [show levinOWF ee (i.toList ++ x.toList) = i.toList ++ ee.fn i.toList x.toList from
    by simp [levinOWF, List.take_left' i.toList_length, List.drop_left' i.toList_length]]
apply 2^k * uniform(n)(splitN.symm(i,x)) = uniform(n-k)(x), absorbing the scaling ← mul_assoc: reassociate 2^k * (u * inner) to (2^k * u) * inner two_pow_mul_uniform: (2^k * u) = uniform(n-k)(x), leaving uniform(n-k)(x) * inner
  rw [← mul_assoc, two_pow_mul_uniform k n hkn i x]
rewrite the RHS suffixAdv tsum to the z'-indexed form first, so both sides share the same index and the same A(...)(z') factor; then a pointwise comparison closes the goal suffixAdv_indicator_eq: ∑' x', suffixAdv A k i y x' * ind(x') = ∑' z', A(i++y)(z') * ind(z'.drop k) (no ← : we want to convert the suffixAdv form in the RHS of ≤ to the z-indexed form)
  rw [suffixAdv_indicator_eq A k i (ee.fn i.toList) (ee.fn i.toList x.toList)
        (ee.fn i.toList x.toList) (n - k)]
now goal: uniform(n-k)(x) * (∑' z', A(...) z' * ind_L z') ≤ uniform(n-k)(x) * (∑' z', A(...) z' * ind_R z') strip the common uniform(n-k)(x) factor (it is the same on both sides)
  apply mul_le_mul_of_nonneg_left _ (zero_le _)
reduce to a termwise bound under the tsum
  apply ENNReal.tsum_le_tsum; intro z'
both sides share the factor A(...)(z'); bound the indicator
  gcongr
the levinOWF inversion indicator implies the suffix indicator (concat-injectivity)
  exact levinOWF_success_implies_suffix ee n hkn i x z'
# Corollary 1: Some prefix achieves the average From the coupling and the fact that $\{0,1\}^k$ has exactly $2^k$ elements, at least one prefix $i \in \{0,1\}^k$ satisfies $$\mathrm{invertProb}(\varphi_i,\, B_i,\, n-k) \;\geq\; \mathrm{invertProb}(f_L,\, A,\, n)$$ **Proof.** If every term were strictly less than the average, the sum would be less than $2^k \cdot \mathrm{invertProb}(f_L,\, A,\, n)$, contradicting the coupling inequality.
lemma levinOWF_exists_ge (ee : EffectiveEnum k) (A : BitString → PMF BitString)
    (n : ℕ) (hkn : k ≤ n) :
    ∃ i : FixedBitString k,
      invertProb (levinOWF ee) A n ≤
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) := by
derive the averaging bound from the coupling inequality
  have hsum := levinOWF_coupling ee A n hkn
if no single term meets the average, the finite sum would fall below the coupling lower bound — contradiction
  by_contra hall
  push_neg at hall
  have hlt : ∑ i : FixedBitString k,
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) <
      (2 : ℝ) ^ k * invertProb (levinOWF ee) A n := by
    calc ∑ i : FixedBitString k, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)
every term is strictly below the average invertProb (levinOWF ee) A n
        < ∑ _i : FixedBitString k, invertProb (levinOWF ee) A n :=
            Finset.sum_lt_sum_of_nonempty (by simp) (fun i _ => hall i)
sum of the constant is card * value
      _ = (Fintype.card (FixedBitString k) : ℝ) * invertProb (levinOWF ee) A n := by
            simp [Finset.sum_const, nsmul_eq_mul]
|{0,1}^k| = 2^k
      _ = (2 : ℝ) ^ k * invertProb (levinOWF ee) A n := by
            rw [bitStringCardinality]; push_cast; ring
  linarith
# Theorem 1: Levin's OWF is a universal one-way function If any component $\varphi_{i^*}$ of the enumeration is a one-way function, then $f_L$ is also a one-way function. **Efficiency.** $f_L$ runs one poly-time index lookup and one poly-time function application, so it is poly-time. **Hardness** — Levin's argument. Suppose $A$ inverts $f_L$ with non-negligible probability $\varepsilon(n) \geq n^{-c}$ for infinitely many $n$. By [`levinOWF_exists_ge`](LevinOWF.html#corollary-1 "Levin's Universal One-Way Function, Corollary 1"), for each such $n$ there is some $i^*(n) \in \{0,1\}^k$ with $$\mathrm{invertProb}(\varphi_{i^*(n)},\, B_{i^*(n)},\, n-k) \geq \varepsilon(n) \geq n^{-c}$$ Since $|\{0,1\}^k| = 2^k$ is **finite**, the infinite sequence $i^*(n)$ must take some fixed value $i^{**}$ infinitely often (infinite pigeonhole principle). For that $i^{**}$, the poly-time adversary $B_{i^{**}}$ inverts $\varphi_{i^{**}}$ with probability $\geq n^{-c}$ for infinitely many $n$, contradicting $\varphi_{i^{**}}$ being a OWF.
theorem levinOWF_is_owf (ee : EffectiveEnum k)
    (hf : ∀ i : FixedBitString k, IsOneWayFunction (ee.fn i.toList)) :
    IsOneWayFunction (levinOWF ee) := by
  constructor
  · -- Efficiency: levinOWF ee z = z.take k ++ ee.fn (z.take k) (z.drop k)
= (take k)(z) ++ (ee.universal)(z) `append` closes this from `take k` and `ee.universal`
    show PolyTimeComputable (fun z => z.take k ++ ee.fn (z.take k) (z.drop k))
    exact PolyTimeComputable.append (PolyTimeComputable.take k) ee.universal
  · -- Hardness: coupling + finite sum of negligibles argument.
If every component φ_i is a OWF, then for any poly-time adversary A: 2^k * invertProb(f_L, A, n) ≤ ∑_i invertProb(φ_i, B_i, n-k) Each term on the right is negligible (by hf i), so the sum is negligible (finite sum), hence so is invertProb(f_L, A, n).
    intro A hA c
each component adversary is poly-time and each component is a OWF
    have h_comp : ∀ i : FixedBitString k,
        Negligible (invertProb (ee.fn i.toList) (suffixAdv A k i)) :=
      fun i => (hf i).hard (suffixAdv A k i) (suffixAdv_polytime A hA k i)
each shifted component is negligible: ε_i(n) := invertProb(φ_i, B_i, n-k)
    have h_shift : ∀ i : FixedBitString k,
        Negligible (fun n => invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)) :=
      fun i => negligible_shift _ k (h_comp i)
for exponent c+1, get per-index threshold N_i
    have h_shift_c : ∀ i : FixedBitString k, ∃ N,
        ∀ n ≥ N, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      fun i => h_shift i (c + 1)
    choose Ni hNi using h_shift_c
threshold: large enough for all indices and for coupling to apply
    refine ⟨Finset.univ.sup Ni + k + 1, fun n hn => ?_⟩
n ≥ k (so levinOWF_coupling applies) and n ≥ Ni i for all i
    have hn_k : k ≤ n := by
      have : Finset.univ.sup Ni + k ≤ n := by omega
      omega
    have hn_Ni : ∀ i : FixedBitString k, Ni i ≤ n := fun i =>
      (Finset.le_sup (f := Ni) (Finset.mem_univ i)).trans (by omega)
    have hn_pos : (1 : ℝ) ≤ (n : ℝ) := by exact_mod_cast (show 1 ≤ n by omega)
coupling: 2^k * invertProb(f_L, A, n) ≤ ∑_i invertProb(φ_i, B_i, n-k)
    have hcoupling := levinOWF_coupling ee A n hn_k
each term in the sum is ≤ n^{-(c+1)}
    have h_each : ∀ i : FixedBitString k,
        invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      fun i => hNi i n (hn_Ni i)
the sum ≤ 2^k * n^{-(c+1)}
    have h_sum : ∑ i : FixedBitString k,
        invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      calc ∑ i : FixedBitString k, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)
          ≤ ∑ _i : FixedBitString k, (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
            Finset.sum_le_sum (fun i _ => h_each i)
        _ = (Fintype.card (FixedBitString k) : ℝ) * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := by
            simp [Finset.sum_const, nsmul_eq_mul]
        _ = (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := by
            rw [bitStringCardinality]; push_cast; ring
combine: 2^k * invertProb(f_L, A, n) ≤ 2^k * n^{-(c+1)}
    have h_scaled : (2 : ℝ) ^ k * invertProb (levinOWF ee) A n ≤
        (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      hcoupling.trans h_sum
divide both sides by 2^k > 0
    have h2k_pos : (0 : ℝ) < (2 : ℝ) ^ k := by positivity
    have h_inv : invertProb (levinOWF ee) A n ≤ (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      le_of_mul_le_mul_left h_scaled h2k_pos
n^{-(c+1)} ≤ n^{-c} since -(c+1) ≤ -c and n ≥ 1
    calc invertProb (levinOWF ee) A n
        ≤ (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := h_inv
      _ ≤ (n : ℝ) ^ (-(↑c : ℤ)) := by
-(↑(c+1) : ℤ) ≤ -(↑c : ℤ) and base n ≥ 1 → zpow is monotone in exponent
          apply zpow_le_zpow_right₀ hn_pos
          push_cast; omega
Raw source
import Mathlib.Probability.Distributions.Uniform
import Mathlib.Data.Fintype.BigOperators
import Onewayf.Adversary
import Onewayf.OneWayFunction
import Onewayf.Bitstrings

-- leandown
-- [meta]
-- title = "Levin's Universal One-Way Function"
-- group = "Cryptography"
-- [content]

-- # Background
--
-- A natural question in cryptography: if one-way functions exist, which ones?
-- Could there be a **single** function that is hard to invert whenever *any*
-- OWF exists?
--
-- **Levin's theorem** (1987) answers yes.  Given an effective enumeration
-- $\varphi_0, \varphi_1, \ldots$ of all poly-time computable functions,
-- define a single function $f_L$ that encodes an index into its input and
-- runs the corresponding $\varphi_i$ on the remainder.  If any component
-- $\varphi_{i^*}$ is a one-way function, then $f_L$ is also a one-way function.
--
-- The key insight is a **coupling argument**: any adversary inverting $f_L$
-- on a random $n$-bit input is, averaged over the $2^k$ possible $k$-bit
-- prefixes (indices), also inverting *some* $\varphi_i$ on the suffix.
-- Since the set of indices is finite, an infinite pigeonhole argument
-- produces a *fixed* index $i^*$ whose component is non-negligibly invertible.

-- # {{definition}}: Effective enumeration
--
-- An **effective enumeration** at prefix length $k$ assigns to each bit string
-- $i$ (interpreted as a $k$-bit index) a poly-time function
-- $\varphi_i : \{0,1\}^* \to \{0,1\}^*$.
--
-- We use raw `BitString` for indices so that `z.take k` serves directly as a
-- lookup key — the same idiom used by `fPrime` in the quadratic-OWF construction.
-- The **completeness** axiom says every poly-time function appears at the image
-- of some `FixedBitString k` element under `.toList`.
--
structure EffectiveEnum (k : ℕ) : Type where
  /-- The enumerated family, indexed by a raw bit string (typically `z.take k`). -/
  fn       : BitString → BitString → BitString
  /-- Every fixed-length index gives a poly-time function. -/
  polytime : ∀ i : FixedBitString k, PolyTimeComputable (fn i.toList)
  /-- The universal computation `z ↦ fn (z.take k) (z.drop k)` is itself poly-time.
      This is the "universal machine" axiom: running the enumeration on an encoded
      (index, input) pair is no harder than running any single component. -/
  universal : PolyTimeComputable (fun z => fn (z.take k) (z.drop k))
  /-- Completeness: every poly-time function appears at some $k$-bit index. -/
  complete : ∀ f : BitString → BitString, PolyTimeComputable f →
               ∃ i : FixedBitString k, ∀ x, fn i.toList x = f x

-- # {{definition}}: Levin's universal OWF
--
-- Split any input $z$ into a $k$-bit **index** prefix $i = z_{[0:k]}$ and
-- a **data** suffix $x = z_{[k:]}$.  Output:
--
-- $$f_L(z) \;=\; z_{[0:k]} \;\|\; \varphi_{z_{[0:k]}}(x)$$
--
-- The output preserves the first $k$ bits verbatim.  Crucially, this means
-- that inverting $f_L(z)$ on a target $y = i \| y'$ forces the inverter to
-- produce a preimage of $y'$ under $\varphi_i$ — the inversion condition
-- $f_L(z') = f_L(z)$ implies $z'_{[0:k]} = z_{[0:k]}$ (since both sides
-- share the same $k$-bit prefix) and then $\varphi_i(z'_{[k:]}) = \varphi_i(x)$.
--
noncomputable def levinOWF (ee : EffectiveEnum k) : BitString → BitString :=
  -- take the first k bits as the index; apply the indexed function to the rest
  fun z => z.take k ++ ee.fn (z.take k) (z.drop k)

-- [hidden: levinOWF unfolding lemma]
-- On a `FixedBitString n` input with `k ≤ n`, `levinOWF` reduces cleanly.
private lemma levinOWF_apply (ee : EffectiveEnum k) (n : ℕ) (_hkn : k ≤ n)
    (z : FixedBitString n) :
    levinOWF ee z.toList = z.toList.take k ++ ee.fn (z.toList.take k) (z.toList.drop k) :=
  rfl

-- Indicator dominance: if `levinOWF ee z'` inverts at `(i, x)` and `z'.length = n`,
-- then the suffix `z'.drop k` inverts `ee.fn i.toList` at `x.toList`.
-- Proof: concat-injectivity forces `z'.take k = i.toList`, giving the suffix condition.
private lemma levinOWF_success_implies_suffix (ee : EffectiveEnum k) (n : ℕ)
    (hkn : k ≤ n) (i : FixedBitString k) (x : FixedBitString (n - k)) (z' : BitString) :
    (if levinOWF ee z' = i.toList ++ ee.fn i.toList x.toList ∧ z'.length = n
     then (1 : ENNReal) else 0) ≤
    (if ee.fn i.toList (z'.drop k) = ee.fn i.toList x.toList ∧
        (z'.drop k).length = n - k
     then (1 : ENNReal) else 0) := by
  -- case split on whether the levinOWF inversion condition holds
  by_cases h : levinOWF ee z' = i.toList ++ ee.fn i.toList x.toList ∧ z'.length = n
  · simp only [if_pos h]
    -- unfold levinOWF so h.1 reads:
    -- z'.take k ++ ee.fn (z'.take k) (z'.drop k) = i.toList ++ ee.fn i.toList x.toList
    simp only [levinOWF] at h
    -- z'.take k has exactly k elements since z'.length = n ≥ k
    have htake_len : (z'.take k).length = k :=
      by simp [List.length_take, Nat.min_eq_left (h.2 ▸ hkn)]
    -- concat-injectivity: z'.take k = i.toList  AND  ee.fn (z'.take k) ... = ee.fn i.toList ...
    have hinj := List.append_inj h.1 (htake_len.trans i.toList_length.symm)
    -- substitute z'.take k = i.toList into hinj.2 to get the suffix condition
    rw [if_pos ⟨hinj.1 ▸ hinj.2, by simp [List.length_drop, h.2]⟩]
  · simp [if_neg h]

-- Bijection splitting a length-n bit string into a k-bit prefix and (n-k)-bit suffix.
private def splitN (k n : ℕ) (hkn : k ≤ n) :
    FixedBitString n ≃ FixedBitString k × FixedBitString (n - k) where
  toFun v :=
    ⟨⟨v.toList.take k, by have h := v.toList_length; simp [List.length_take]; omega⟩,
     ⟨v.toList.drop k, by have h := v.toList_length; simp [List.length_drop, h]⟩⟩
  invFun p :=
    ⟨p.1.toList ++ p.2.toList, by
      have h1 := p.1.toList_length; have h2 := p.2.toList_length
      simp only [List.length_append, h1, h2, Nat.add_sub_cancel' hkn]⟩
  left_inv v := List.Vector.ext (by simp [List.take_append_drop])
  right_inv p := by
    rcases p with ⟨a, b⟩; apply Prod.ext <;> apply List.Vector.ext
    all_goals simp [a.toList_length]

-- The uniform measure on FixedBitString n splits over splitN:
-- uniform(n)(splitN.symm(i,x)) = uniform(k)(i) * uniform(n-k)(x)
private lemma uniform_split (k n : ℕ) (hkn : k ≤ n)
    (i : FixedBitString k) (x : FixedBitString (n - k)) :
    PMF.uniformOfFintype (FixedBitString n) ((splitN k n hkn).symm (i, x)) =
    PMF.uniformOfFintype (FixedBitString k) i *
    PMF.uniformOfFintype (FixedBitString (n - k)) x := by
  simp only [PMF.uniformOfFintype_apply]
  rw [show Fintype.card (FixedBitString n) =
        Fintype.card (FixedBitString k) * Fintype.card (FixedBitString (n - k)) from by
    simp [← pow_add, Nat.add_sub_cancel' hkn]]
  push_cast
  rw [ENNReal.mul_inv (Or.inl (by positivity)) (Or.inr (by positivity))]

-- Scaling: 2^k * uniform(n)(splitN.symm(i,x)) = uniform(n-k)(x)
-- Because 2^k * (1/2^k * 1/2^(n-k)) = 1/2^(n-k).
private lemma two_pow_mul_uniform (k n : ℕ) (hkn : k ≤ n)
    (i : FixedBitString k) (x : FixedBitString (n - k)) :
    (2 : ENNReal) ^ k *
    PMF.uniformOfFintype (FixedBitString n) ((splitN k n hkn).symm (i, x)) =
    PMF.uniformOfFintype (FixedBitString (n - k)) x := by
  rw [uniform_split k n hkn i x, ← mul_assoc]
  -- 2^k * uniform(k)(i) = 1 since uniform(k)(i) = 1/2^k
  have h2k : (2 : ENNReal) ^ k * PMF.uniformOfFintype (FixedBitString k) i = 1 := by
    simp [PMF.uniformOfFintype_apply]
    rw [ENNReal.mul_inv_cancel (by positivity) (by simp)]
  rw [h2k, one_mul]
-- [/hidden]

-- # {{definition}}: The suffix adversary
--
-- Given an adversary $A$ that tries to invert $f_L$, and a fixed index $i^*$,
-- the **suffix adversary** $B_{i^*}$ attacks $\varphi_{i^*}$ directly:
--
-- $$B_{i^*}(y') \;=\; \bigl(A(i^* \| y')\bigr).\mathrm{drop}(k)$$
--
-- On input $y' = \varphi_{i^*}(x)$, $B_{i^*}$ presents the $f_L$-formatted
-- challenge $i^* \| y'$ to $A$, then strips the $k$-bit prefix from $A$'s
-- output to obtain a candidate preimage of $y'$ under $\varphi_{i^*}$.
--
-- If $A$ succeeds — returning $z'$ with $f_L(z') = i^* \| y'$ and $|z'| = n$
-- — then the output prefix is forced to equal $i^*$ (since $f_L$ preserves
-- the first $k$ bits), so the stripped suffix $z'.\mathrm{drop}(k)$ has
-- length $n - k$ and satisfies $\varphi_{i^*}(z'.\mathrm{drop}(k)) = y'$.
--
noncomputable def suffixAdv (A : BitString → PMF BitString) (k : ℕ) (i : FixedBitString k)
    : BitString → PMF BitString :=
  -- prepend the fixed index to the challenge; run A; strip the k-bit prefix
  fun y => (A (i.toList ++ y)).map (·.drop k)

-- Fubini for suffixAdv: ∑' x', suffixAdv A k i y x' * c(x') = ∑' z, A(i++y)(z) * c(z.drop k)
private lemma suffixAdv_indicator_eq (A : BitString → PMF BitString) (k : ℕ) (i : FixedBitString k)
    (g : BitString → BitString) (y fy : BitString) (r : ℕ) :
    (∑' x' : BitString, suffixAdv A k i y x' *
      (if g x' = fy ∧ x'.length = r then (1 : ENNReal) else 0)) =
    ∑' z : BitString, A (i.toList ++ y) z *
      (if g (z.drop k) = fy ∧ (z.drop k).length = r then 1 else 0) := by
  simp only [suffixAdv, PMF.map_apply]
  simp_rw [← ENNReal.tsum_mul_right]
  rw [ENNReal.tsum_comm]
  apply tsum_congr; intro z
  -- the sum ∑' a, (if a = z.drop k then A(i++y)(z) else 0) * c(a) has one nonzero term
  rw [tsum_eq_single (z.drop k) (by intro a ha; simp [ha])]
  simp

-- ENNReal body of invertProb is ≤ 1 (hence ≠ ⊤)
private lemma invertProb_body_le_one (f : BitString → BitString)
    (F : BitString → PMF BitString) (n : ℕ) :
    (∑ x : FixedBitString n,
      PMF.uniformOfFintype (FixedBitString n) x *
      (∑' x' : BitString, F (f x.toList) x' *
        (if f x' = f x.toList ∧ x'.length = n then (1 : ENNReal) else 0))) ≤ 1 := by
  calc _ ≤ ∑ x : FixedBitString n, PMF.uniformOfFintype (FixedBitString n) x := by
        apply Finset.sum_le_sum; intro x _
        -- bound: a * b ≤ a when b ≤ 1
        apply mul_le_of_le_one_right (zero_le _)
        calc ∑' x', F (f x.toList) x' * (if f x' = f x.toList ∧ x'.length = n then 1 else 0)
            ≤ ∑' x', F (f x.toList) x' := by
              apply ENNReal.tsum_le_tsum; intro x'
              exact mul_le_of_le_one_right (zero_le _) (by split_ifs <;> norm_num)
          _ = 1 := PMF.tsum_coe _
     _ = 1 := by
        have h := (PMF.uniformOfFintype (FixedBitString n)).tsum_coe
        rw [tsum_fintype (L := .unconditional _)] at h; exact h

-- # {{lemma}}: Suffix adversary is poly-time
--
-- If $A$ is a poly-time adversary, then $B_{i^*}$ is also poly-time:
-- prepending a fixed string and stripping a fixed prefix are both $O(k)$.
--
lemma suffixAdv_polytime (A : BitString → PMF BitString) (hA : PolyTimeAdversary A)
    (k : ℕ) (i : FixedBitString k) : PolyTimeAdversary (suffixAdv A k i) := by
  -- unfold `suffixAdv` to expose the fixed-prefix map pattern
  unfold suffixAdv
  -- `fixedPad` exactly captures: prepend a fixed prefix, run A, strip k bits
  exact PolyTimeAdversary.fixedPad i.toList k hA

-- # {{lemma}}: The coupling inequality
--
-- The sum of component inversion probabilities dominates the $f_L$ inversion
-- probability, scaled by $2^k$:
--
-- $$\sum_{i \in \{0,1\}^k} \mathrm{invertProb}(\varphi_i,\, B_i,\, n-k)
--   \;\geq\; 2^k \cdot \mathrm{invertProb}(f_L,\, A,\, n)$$
--
-- **Proof sketch.**  Reindex the sum defining $\mathrm{invertProb}(f_L, A, n)$
-- over $\{0,1\}^k \times \{0,1\}^{n-k}$ (prefix $i$, data $x$).  The $f_L$-
-- inversion indicator $[f_L(z') = i \| \varphi_i(x) \wedge |z'| = n]$
-- forces $z'_{[0:k]} = i$, so it collapses to
-- $[\varphi_i(z'_{[k:]}) = \varphi_i(x) \wedge |z'_{[k:]}| = n-k]$.
-- Summing the mass $A(i \| \varphi_i(x))(z')$ over all $z'$ with
-- $z'_{[k:]} = x'$ gives $(A(\ldots).\mathrm{map\;drop}_k)(x')$, which is
-- exactly $B_i(\varphi_i(x))(x')$.  Summing over $i$ and normalizing yields
-- the stated bound.
--
lemma levinOWF_coupling (ee : EffectiveEnum k) (A : BitString → PMF BitString)
    (n : ℕ) (hkn : k ≤ n) :
    (2 : ℝ) ^ k * invertProb (levinOWF ee) A n ≤
    ∑ i : FixedBitString k,
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) := by
  simp only [invertProb]
  -- abbreviate the ENNReal body on each side
  set E_L : ENNReal := ∑ z : FixedBitString n,
    PMF.uniformOfFintype (FixedBitString n) z *
    (∑' z', A (levinOWF ee z.toList) z' *
      (if levinOWF ee z' = levinOWF ee z.toList ∧ z'.length = n then 1 else 0))
  set E_R : FixedBitString k → ENNReal := fun i =>
    ∑ x : FixedBitString (n - k),
      PMF.uniformOfFintype (FixedBitString (n - k)) x *
      (∑' x', suffixAdv A k i (ee.fn i.toList x.toList) x' *
        (if ee.fn i.toList x' = ee.fn i.toList x.toList ∧ x'.length = n - k then 1 else 0))
  -- finiteness: both sides are ≤ 1, hence ≠ ⊤
  have hEL : E_L ≠ ⊤ := ne_top_of_le_ne_top ENNReal.one_ne_top (invertProb_body_le_one _ _ _)
  have hER : ∀ i, E_R i ≠ ⊤ := fun i =>
    ne_top_of_le_ne_top ENNReal.one_ne_top (invertProb_body_le_one _ _ _)
  -- rewrite LHS: (2:ℝ)^k * E_L.toReal = ((2:ENNReal)^k * E_L).toReal
  rw [show (2 : ℝ) ^ k * E_L.toReal = ((2 : ENNReal) ^ k * E_L).toReal by
    rw [ENNReal.toReal_mul, ENNReal.toReal_pow]; norm_cast]
  -- rewrite RHS: ∑ i, (E_R i).toReal = (∑ i, E_R i).toReal
  rw [show ∑ i : FixedBitString k, (E_R i).toReal = (∑ i, E_R i).toReal by
    rw [ENNReal.toReal_sum (fun i _ => hER i)]]
  -- ne_top facts needed for toReal conversion
  have h2k : (2 : ENNReal) ^ k ≠ ⊤ := by norm_cast; exact ENNReal.natCast_ne_top _
  have hL : (2 : ENNReal) ^ k * E_L ≠ ⊤ := ENNReal.mul_ne_top h2k hEL
  have hR : ∑ i : FixedBitString k, E_R i ≠ ⊤ := ENNReal.sum_ne_top.mpr (fun i _ => hER i)
  -- reduce to the ENNReal inequality via monotonicity of toReal
  rw [ENNReal.toReal_le_toReal hL hR]
  -- *** Main ENNReal inequality: (2:ENNReal)^k * E_L ≤ ∑ i, E_R i ***
  -- distribute 2^k into the FixedBitString n sum
  rw [Finset.mul_sum]
  -- reindex FixedBitString n as FixedBitString k × FixedBitString (n-k) via splitN;
  -- outer sum remains over FixedBitString k, so no sum_comm needed
  rw [← Equiv.sum_comp (splitN k n hkn).symm, Fintype.sum_prod_type]
  -- bound each (i, x) term
  apply Finset.sum_le_sum; intro i _
  apply Finset.sum_le_sum; intro x _
  -- simplify: splitN.symm (i, x) has toList = i.toList ++ x.toList
  simp_rw [show ((splitN k n hkn).symm (i, x)).toList = i.toList ++ x.toList from
    by simp [splitN]]
  -- simplify: levinOWF on a concatenated input = i.toList ++ ee.fn i.toList x.toList
  simp_rw [show levinOWF ee (i.toList ++ x.toList) = i.toList ++ ee.fn i.toList x.toList from
    by simp [levinOWF, List.take_left' i.toList_length, List.drop_left' i.toList_length]]
  -- apply 2^k * uniform(n)(splitN.symm(i,x)) = uniform(n-k)(x), absorbing the scaling
  -- ← mul_assoc: reassociate 2^k * (u * inner) to (2^k * u) * inner
  -- two_pow_mul_uniform: (2^k * u) = uniform(n-k)(x), leaving uniform(n-k)(x) * inner
  rw [← mul_assoc, two_pow_mul_uniform k n hkn i x]
  -- rewrite the RHS suffixAdv tsum to the z'-indexed form first, so both sides share the
  -- same index and the same A(...)(z') factor; then a pointwise comparison closes the goal
  -- suffixAdv_indicator_eq: ∑' x', suffixAdv A k i y x' * ind(x') = ∑' z', A(i++y)(z') * ind(z'.drop k)
  -- (no ← : we want to convert the suffixAdv form in the RHS of ≤ to the z-indexed form)
  rw [suffixAdv_indicator_eq A k i (ee.fn i.toList) (ee.fn i.toList x.toList)
        (ee.fn i.toList x.toList) (n - k)]
  -- now goal: uniform(n-k)(x) * (∑' z', A(...) z' * ind_L z') ≤ uniform(n-k)(x) * (∑' z', A(...) z' * ind_R z')
  -- strip the common uniform(n-k)(x) factor (it is the same on both sides)
  apply mul_le_mul_of_nonneg_left _ (zero_le _)
  -- reduce to a termwise bound under the tsum
  apply ENNReal.tsum_le_tsum; intro z'
  -- both sides share the factor A(...)(z'); bound the indicator
  gcongr
  -- the levinOWF inversion indicator implies the suffix indicator (concat-injectivity)
  exact levinOWF_success_implies_suffix ee n hkn i x z'

-- # {{corollary}}: Some prefix achieves the average
--
-- From the coupling and the fact that $\{0,1\}^k$ has exactly $2^k$ elements,
-- at least one prefix $i \in \{0,1\}^k$ satisfies
--
-- $$\mathrm{invertProb}(\varphi_i,\, B_i,\, n-k) \;\geq\;
--   \mathrm{invertProb}(f_L,\, A,\, n)$$
--
-- **Proof.** If every term were strictly less than the average, the sum would
-- be less than $2^k \cdot \mathrm{invertProb}(f_L,\, A,\, n)$, contradicting
-- the coupling inequality.
--
lemma levinOWF_exists_ge (ee : EffectiveEnum k) (A : BitString → PMF BitString)
    (n : ℕ) (hkn : k ≤ n) :
    ∃ i : FixedBitString k,
      invertProb (levinOWF ee) A n ≤
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) := by
  -- derive the averaging bound from the coupling inequality
  have hsum := levinOWF_coupling ee A n hkn
  -- if no single term meets the average, the finite sum would fall below the
  -- coupling lower bound — contradiction
  by_contra hall
  push_neg at hall
  have hlt : ∑ i : FixedBitString k,
      invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) <
      (2 : ℝ) ^ k * invertProb (levinOWF ee) A n := by
    calc ∑ i : FixedBitString k, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)
        -- every term is strictly below the average invertProb (levinOWF ee) A n
        < ∑ _i : FixedBitString k, invertProb (levinOWF ee) A n :=
            Finset.sum_lt_sum_of_nonempty (by simp) (fun i _ => hall i)
        -- sum of the constant is card * value
      _ = (Fintype.card (FixedBitString k) : ℝ) * invertProb (levinOWF ee) A n := by
            simp [Finset.sum_const, nsmul_eq_mul]
        -- |{0,1}^k| = 2^k
      _ = (2 : ℝ) ^ k * invertProb (levinOWF ee) A n := by
            rw [bitStringCardinality]; push_cast; ring
  linarith

-- # {{theorem}}: Levin's OWF is a universal one-way function
--
-- If any component $\varphi_{i^*}$ of the enumeration is a one-way function,
-- then $f_L$ is also a one-way function.
--
-- **Efficiency.** $f_L$ runs one poly-time index lookup and one poly-time
-- function application, so it is poly-time.
--
-- **Hardness** — Levin's argument.  Suppose $A$ inverts $f_L$ with
-- non-negligible probability $\varepsilon(n) \geq n^{-c}$ for infinitely many
-- $n$.  By `levinOWF_exists_ge`, for each such $n$ there is some
-- $i^*(n) \in \{0,1\}^k$ with
--
-- $$\mathrm{invertProb}(\varphi_{i^*(n)},\, B_{i^*(n)},\, n-k) \geq
--   \varepsilon(n) \geq n^{-c}$$
--
-- Since $|\{0,1\}^k| = 2^k$ is **finite**, the infinite sequence
-- $i^*(n)$ must take some fixed value $i^{**}$ infinitely often (infinite
-- pigeonhole principle).  For that $i^{**}$, the poly-time adversary
-- $B_{i^{**}}$ inverts $\varphi_{i^{**}}$ with probability $\geq n^{-c}$
-- for infinitely many $n$, contradicting $\varphi_{i^{**}}$ being a OWF.
--
theorem levinOWF_is_owf (ee : EffectiveEnum k)
    (hf : ∀ i : FixedBitString k, IsOneWayFunction (ee.fn i.toList)) :
    IsOneWayFunction (levinOWF ee) := by
  constructor
  · -- Efficiency: levinOWF ee z = z.take k ++ ee.fn (z.take k) (z.drop k)
    -- = (take k)(z) ++ (ee.universal)(z)
    -- `append` closes this from `take k` and `ee.universal`
    show PolyTimeComputable (fun z => z.take k ++ ee.fn (z.take k) (z.drop k))
    exact PolyTimeComputable.append (PolyTimeComputable.take k) ee.universal
  · -- Hardness: coupling + finite sum of negligibles argument.
    -- If every component φ_i is a OWF, then for any poly-time adversary A:
    -- 2^k * invertProb(f_L, A, n) ≤ ∑_i invertProb(φ_i, B_i, n-k)
    -- Each term on the right is negligible (by hf i), so the sum is negligible
    -- (finite sum), hence so is invertProb(f_L, A, n).
    intro A hA c
    -- each component adversary is poly-time and each component is a OWF
    have h_comp : ∀ i : FixedBitString k,
        Negligible (invertProb (ee.fn i.toList) (suffixAdv A k i)) :=
      fun i => (hf i).hard (suffixAdv A k i) (suffixAdv_polytime A hA k i)
    -- each shifted component is negligible: ε_i(n) := invertProb(φ_i, B_i, n-k)
    have h_shift : ∀ i : FixedBitString k,
        Negligible (fun n => invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)) :=
      fun i => negligible_shift _ k (h_comp i)
    -- for exponent c+1, get per-index threshold N_i
    have h_shift_c : ∀ i : FixedBitString k, ∃ N,
        ∀ n ≥ N, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      fun i => h_shift i (c + 1)
    choose Ni hNi using h_shift_c
    -- threshold: large enough for all indices and for coupling to apply
    refine ⟨Finset.univ.sup Ni + k + 1, fun n hn => ?_⟩
    -- n ≥ k (so levinOWF_coupling applies) and n ≥ Ni i for all i
    have hn_k : k ≤ n := by
      have : Finset.univ.sup Ni + k ≤ n := by omega
      omega
    have hn_Ni : ∀ i : FixedBitString k, Ni i ≤ n := fun i =>
      (Finset.le_sup (f := Ni) (Finset.mem_univ i)).trans (by omega)
    have hn_pos : (1 : ℝ) ≤ (n : ℝ) := by exact_mod_cast (show 1 ≤ n by omega)
    -- coupling: 2^k * invertProb(f_L, A, n) ≤ ∑_i invertProb(φ_i, B_i, n-k)
    have hcoupling := levinOWF_coupling ee A n hn_k
    -- each term in the sum is ≤ n^{-(c+1)}
    have h_each : ∀ i : FixedBitString k,
        invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      fun i => hNi i n (hn_Ni i)
    -- the sum ≤ 2^k * n^{-(c+1)}
    have h_sum : ∑ i : FixedBitString k,
        invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k) ≤
          (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      calc ∑ i : FixedBitString k, invertProb (ee.fn i.toList) (suffixAdv A k i) (n - k)
          ≤ ∑ _i : FixedBitString k, (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
            Finset.sum_le_sum (fun i _ => h_each i)
        _ = (Fintype.card (FixedBitString k) : ℝ) * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := by
            simp [Finset.sum_const, nsmul_eq_mul]
        _ = (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := by
            rw [bitStringCardinality]; push_cast; ring
    -- combine: 2^k * invertProb(f_L, A, n) ≤ 2^k * n^{-(c+1)}
    have h_scaled : (2 : ℝ) ^ k * invertProb (levinOWF ee) A n ≤
        (2 : ℝ) ^ k * (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      hcoupling.trans h_sum
    -- divide both sides by 2^k > 0
    have h2k_pos : (0 : ℝ) < (2 : ℝ) ^ k := by positivity
    have h_inv : invertProb (levinOWF ee) A n ≤ (n : ℝ) ^ (-(↑(c + 1) : ℤ)) :=
      le_of_mul_le_mul_left h_scaled h2k_pos
    -- n^{-(c+1)} ≤ n^{-c} since -(c+1) ≤ -c and n ≥ 1
    calc invertProb (levinOWF ee) A n
        ≤ (n : ℝ) ^ (-(↑(c + 1) : ℤ)) := h_inv
      _ ≤ (n : ℝ) ^ (-(↑c : ℤ)) := by
          -- -(↑(c+1) : ℤ) ≤ -(↑c : ℤ) and base n ≥ 1 → zpow is monotone in exponent
          apply zpow_le_zpow_right₀ hn_pos
          push_cast; omega