import Mathlib.Data.ZMod.Basic import Mathlib.Data.Int.ModEq import Mathlib.Tactic -- ============================================================================ -- Paillier: additively homomorphic encryption -- ============================================================================ -- -- Plaintexts live in ZMod n, ciphertexts in ZMod n². With the standard -- simplification g = n + 1 the scheme is: -- -- keygen n = p·q, λ = lcm(p-1, q-1), µ = λ⁻¹ (mod n) -- encrypt m c = gᵐ · rⁿ (mod n²), r random and coprime to n -- add c₁ c₂ c₁ · c₂ (mod n²) -- decrypt c L(c^λ) · µ (mod n), L(x) = (x-1)/n -- -- The scheme rests on two facts about arithmetic mod n²: -- -- binomial (1+n)^k ≡ 1 + k·n (mod n²), pure algebra -- carmichael rⁿᐧλ ≡ 1 (mod n²), number theory -- -- This file proves the algebra that turns those two facts into decryption -- correctness, in particular the L(x) = (x-1)/n integer division, whose -- exactness is the subtle part. The carmichael kernel is deferred (`sorry`); -- binomial is proved here since it needs no number theory. namespace Paillier -- ============================================================================ -- Section 1: The L-function and decryption correctness (the algebra) -- ============================================================================ -- -- Work over ℤ with `a ≡ b [ZMOD m]`. We can't stay inside `ZMod n²` because of -- L: dividing by n is not an operation there (n isn't a unit). So decryption -- steps out to ℤ, divides, and steps back mod n, and the division is only -- meaningful because it's exact. /-- The Paillier L-function `L(x) = (x-1)/n` (integer division). -/ def L (n x : ℤ) : ℤ := (x - 1) / n -- `cl` stands for the integer `c^λ`; the `kernel` hypothesis is exactly what -- binomial and carmichael combine to give (see Section 3). Given it, decryption -- is pure algebra: the division by n comes out exact, and the µ = λ⁻¹ step -- collapses λ·µ to 1. theorem decrypt_correct (n lam mu m cl : ℤ) (hn : n ≠ 0) (kernel : cl ≡ 1 + m * lam * n [ZMOD n * n]) (hinv : lam * mu ≡ 1 [ZMOD n]) : L n cl * mu ≡ m [ZMOD n] := by -- kernel gives `cl = 1 + m·λ·n - n²·t`, so (cl-1)/n is the exact quotient. obtain ⟨t, ht⟩ := Int.modEq_iff_dvd.mp kernel have hLc : L n cl = m * lam - n * t := by unfold L rw [show cl - 1 = n * (m * lam - n * t) from by linear_combination -ht] exact Int.mul_ediv_cancel_left _ hn rw [hLc] -- λ·µ ≡ 1 (mod n), so (m·λ - n·t)·µ ≡ m·(λ·µ) ≡ m. obtain ⟨s, hs⟩ := Int.modEq_iff_dvd.mp hinv rw [Int.modEq_iff_dvd] exact ⟨m * s + t * mu, by linear_combination m * hs⟩ -- ============================================================================ -- Section 2: Additive homomorphism (a thin corollary of the above) -- ============================================================================ -- -- The product of two ciphertexts is an encryption of the sum of the -- plaintexts, so once `decrypt_correct` holds for every plaintext, additive -- homomorphism is immediate; just instantiate it at m := m₁ + m₂. (That the -- product really encrypts the sum is itself a kernel-level fact, folded into -- `kernel` below.) theorem add_correct (n lam mu m₁ m₂ cl : ℤ) (hn : n ≠ 0) (kernel : cl ≡ 1 + (m₁ + m₂) * lam * n [ZMOD n * n]) (hinv : lam * mu ≡ 1 [ZMOD n]) : L n cl * mu ≡ m₁ + m₂ [ZMOD n] := decrypt_correct n lam mu (m₁ + m₂) cl hn kernel hinv -- ============================================================================ -- Section 3: The two kernels feeding `kernel` -- ============================================================================ -- Binomial: pure algebra, no number theory. This is half of what produces the -- `kernel` hypothesis: raising g = 1+n to a power only ever contributes a -- linear term mod n². theorem binomial_modSq (n : ℤ) (k : ℕ) : (1 + n) ^ k ≡ 1 + (k : ℤ) * n [ZMOD n * n] := by induction k with | zero => simp | succ k ih => calc (1 + n) ^ (k + 1) = (1 + n) ^ k * (1 + n) := by rw [pow_succ] _ ≡ (1 + (k : ℤ) * n) * (1 + n) [ZMOD n * n] := ih.mul_right _ _ ≡ 1 + ((k : ℤ) + 1) * n [ZMOD n * n] := Int.modEq_iff_dvd.mpr ⟨-(k : ℤ), by ring⟩ _ = 1 + ((k + 1 : ℕ) : ℤ) * n := by push_cast; ring -- Carmichael: the number-theoretic kernel. For r coprime to n, the random mask -- rⁿ vanishes after raising to λ. The proof needs the structure of (ZMod n²)ˣ -- (Carmichael's theorem via CRT on the prime-power factors), deferred for now. -- (λ = lcm(p-1,q-1) and the primality of p, q will become hypotheses when we -- discharge this; for now the statement records only what the algebra consumes.) theorem carmichael_modSq (n lam : ℤ) (r : ℤ) (hr : IsCoprime r n) (k : ℕ) (hk : (k : ℤ) = n * lam) : r ^ k ≡ 1 [ZMOD n * n] := by sorry end Paillier -- ============================================================================ -- Section 4: A concrete, executable instance (sanity check on real numbers) -- ============================================================================ -- -- The OCaml reference uses p = 61, q = 53. Everything below actually runs: -- `native_decide` evaluates encryption and decryption end-to-end in ZMod n². namespace Paillier.Example abbrev p : ℕ := 61 abbrev q : ℕ := 53 abbrev n : ℕ := p * q -- 3233 (public modulus) abbrev nn : ℕ := n * n -- 10452289 = n² abbrev lam : ℕ := Nat.lcm (p - 1) (q - 1) -- 780 (Carmichael λ, secret) abbrev g : ℕ := n + 1 -- 3234 = 1 + n abbrev mu : ℕ := 1173 -- λ⁻¹ (mod n) example : n = 3233 := by decide example : lam = 780 := by decide example : Nat.gcd lam n = 1 := by decide example : (lam * mu) % n = 1 := by decide -- µ really is λ⁻¹ mod n /-- Encrypt `m` with randomness `r` (coprime to n): c = gᵐ · rⁿ in ZMod n². -/ def encrypt (m r : ℕ) : ZMod nn := (g : ZMod nn) ^ m * (r : ZMod nn) ^ n /-- Decrypt: `L(c^λ) · µ (mod n)`, with L stepping out to ℕ to divide by n. -/ def decrypt (c : ZMod nn) : ℕ := (((c ^ lam).val - 1) / n * mu) % n /-- Homomorphic addition: multiply ciphertexts in ZMod n². -/ def homAdd (c₁ c₂ : ZMod nn) : ZMod nn := c₁ * c₂ -- Round-trip: decrypt ∘ encrypt = id (for plaintexts < n) example : decrypt (encrypt 111 2) = 111 := by native_decide example : decrypt (encrypt 42 5) = 42 := by native_decide -- Additive homomorphism: 111 + 222 = 333, computed under encryption example : decrypt (homAdd (encrypt 111 2) (encrypt 222 3)) = 333 := by native_decide end Paillier.Example