Skip to content
The Lumenary

Formal mathematics laboratory

Lumenary.SecretCodes

Secret codes

Formal library

  • shiftdefDefinitionNo axioms
    shiftLean 4Formally verified
    /-- A Caesar shift by `k` on letter numbers (A = 0, …, Z = 25): move `k` places on, wrapping
    round from 25 back to 0. -/
    def shift (k n : Nat) : Nat := (n + k) % 26
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • wrap_aroundtheoremProved here from basic factsNo axioms
    wrap_aroundLean 4Proved here from basic factsFormally verified
    /-- Shifting by 3 wraps round the end of the alphabet: X (23) → A (0), Y (24) → B (1),
    Z (25) → C (2). And M (12) → P (15) needs no wrapping. -/
    theorem wrap_around : shift 3 23 = 0 ∧ shift 3 24 = 1 ∧ shift 3 25 = 2 ∧ shift 3 12 = 15 := by
      decide
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_lttheoremApplies a library theoremNo axioms
    shift_ltLean 4Applies a library theoremFormally verified
    /-- A shift always lands on one of the 26 letters: a remainder on division by 26 is less
    than 26. This applies core Lean's `Nat.mod_lt`. -/
    theorem shift_lt (k n : Nat) : shift k n < 26 :=
      Nat.mod_lt _ (by decide)
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_undotheoremProved here from basic factspropext, Quot.sound
    shift_undoLean 4Proved here from basic factsFormally verified
    /-- **A shift forward by `k` is undone by a shift forward by `26 − k`**: together they move
    26 places, once round the whole wheel. -/
    theorem shift_undo (k n : Nat) (hk : k ≤ 26) (hn : n < 26) : shift (26 - k) (shift k n) = n := by
      unfold shift
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_shifttheoremProved here from basic factspropext, Quot.sound
    shift_shiftLean 4Proved here from basic factsFormally verified
    /-- Two shifts, one after the other, are one shift: by `a`, then by `b`, is the same as by
    `a + b`. (So the order of two shifts does not matter.) -/
    theorem shift_shift (a b n : Nat) : shift b (shift a n) = shift (a + b) n := by
      unfold shift
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_twenty_sixtheoremProved here from basic factspropext, Quot.sound
    shift_twenty_sixLean 4Proved here from basic factsFormally verified
    /-- Shifting by 26 changes nothing: it goes once round the wheel. -/
    theorem shift_twenty_six (n : Nat) (hn : n < 26) : shift 26 n = n := by
      unfold shift
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_add_twenty_sixtheoremProved here from basic factspropext, Quot.sound
    shift_add_twenty_sixLean 4Proved here from basic factsFormally verified
    /-- Shifting by `k + 26` is the same as shifting by `k`. So every shift is one of the shifts
    0, 1, …, 25: there are **at most** 26 different shifts. -/
    theorem shift_add_twenty_six (k n : Nat) : shift (k + 26) n = shift k n := by
      unfold shift
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shifts_differtheoremProved here from basic factspropext, Quot.sound
    shifts_differLean 4Proved here from basic factsFormally verified
    /-- The shifts 0, 1, …, 25 are all different: shift `k` sends A (0) to letter number `k`. So
    there are **exactly** 26 shifts — and only 25 that change the message. -/
    theorem shifts_differ (k : Nat) (hk : k < 26) : shift k 0 = k := by
      unfold shift
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shift_injectivetheoremProved here from basic factspropext, Quot.sound
    shift_injectiveLean 4Proved here from basic factsFormally verified
    /-- A shift never sends two different letters to the same letter. That is why a coded message
    can always be decoded. -/
    theorem shift_injective (k n m : Nat) (hn : n < 26) (hm : m < 26)
        (h : shift k n = shift k m) : n = m := by
      unfold shift at h
      omega
    Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shiftChardefDefinitionNo axioms
    shiftCharLean 4Formally verified
    /-- Shift one character. Computers store `A` as the number 65, …, `Z` as 90. A capital letter
    is turned into its letter number (subtract 65), shifted, and turned back (add 65); spaces and
    other characters are left as they are. -/
    def shiftChar (k : Nat) (c : Char) : Char :=
      if 65 ≤ c.toNat ∧ c.toNat ≤ 90 then Char.ofNat ((c.toNat - 65 + k) % 26 + 65) else c
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • caesardefDefinitionpropext, Classical.choice, Quot.sound
    caesarLean 4Formally verified
    /-- The Caesar cipher with key `k`: shift every letter of the message by `k`. -/
    def caesar (k : Nat) (message : String) : String :=
      String.ofList (message.toList.map (shiftChar k))
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • meet_at_noontheoremProved here from basic factspropext, Classical.choice, Quot.sound
    meet_at_noonLean 4Proved here from basic factsFormally verified
    /-- Julius Caesar's shift of 3: MEET AT NOON becomes PHHW DW QRRQ. -/
    theorem meet_at_noon : caesar 3 "MEET AT NOON" = "PHHW DW QRRQ" := by
      decide
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • zebratheoremProved here from basic factspropext, Classical.choice, Quot.sound
    zebraLean 4Proved here from basic factsFormally verified
    /-- Wrapping round: with a shift of 3, ZEBRA becomes CHEUD (Z → C). -/
    theorem zebra : caesar 3 "ZEBRA" = "CHEUD" := by
      decide
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • decode_by_23theoremProved here from basic factspropext, Classical.choice, Quot.sound
    decode_by_23Lean 4Proved here from basic factsFormally verified
    /-- To decode a shift of 3, shift forward by 26 − 3 = 23. -/
    theorem decode_by_23 : caesar 23 "PHHW DW QRRQ" = "MEET AT NOON" := by
      decide
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • try_every_shifttheoremProved here from basic factspropext, Classical.choice, Quot.sound
    try_every_shiftLean 4Proved here from basic factsFormally verified
    /-- Breaking a Caesar cipher by trying every key: among the 26 shifts of WKLV LV IXQ, one
    reads THIS IS FUN. -/
    theorem try_every_shift : "THIS IS FUN" ∈ (List.range 26).map (fun k => caesar k "WKLV LV IXQ") := by
      decide
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • toNat_ofNat_smalltheoremProved here from basic factspropext
    toNat_ofNat_smallLean 4Proved here from basic factsFormally verified
    /-- Turning a small number into a character and back gives the same number. -/
    theorem toNat_ofNat_small (m : Nat) (h : m < 55296) : (Char.ofNat m).toNat = m := by
      have hv : m.isValidChar := Or.inl h
      simp [Char.ofNat, hv, Char.ofNatAux, Char.toNat]
    Axioms used: propextformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • shiftChar_undotheoremProved here from basic factspropext, Classical.choice, Quot.sound
    shiftChar_undoLean 4Proved here from basic factsFormally verified
    /-- Shifting a character by `k` and then by `26 − k` gives it back: for a capital letter by
    `shift_undo`, and any other character was never changed. -/
    theorem shiftChar_undo (k : Nat) (hk : k ≤ 26) (c : Char) :
        shiftChar (26 - k) (shiftChar k c) = c := by
      unfold shiftChar
      by_cases h : 65 ≤ c.toNat ∧ c.toNat ≤ 90
      · have e := toNat_ofNat_small ((c.toNat - 65 + k) % 26 + 65) (by omega)
        rw [ite_eq_left h, ite_eq_left (by rw [e]; omega)]
        apply Char.toNat_inj.mp
        rw [toNat_ofNat_small _ (by omega), e]
        omega
      · rw [ite_eq_right h, ite_eq_right h]
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • caesar_undotheoremProved here from basic factspropext, Classical.choice, Quot.sound
    caesar_undoLean 4Proved here from basic factsFormally verified
    /-- **Decoding undoes encoding, for every message and every key**: the Caesar cipher with key
    `26 − k` decodes the Caesar cipher with key `k`. -/
    theorem caesar_undo (k : Nat) (hk : k ≤ 26) (message : String) :
        caesar (26 - k) (caesar k message) = message := by
      unfold caesar
      rw [String.toList_ofList, List.map_map]
      have : (shiftChar (26 - k) ∘ shiftChar k) = id := funext (shiftChar_undo k hk)
      rw [this, List.map_id, String.ofList_toList]
    Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • factorialdefDefinitionNo axioms
    factorialLean 4Formally verified
    /-- `factorial n = 1 × 2 × ⋯ × n`. (Mathlib calls it `Nat.factorial`.) -/
    def factorial : Nat → Nat
      | 0 => 1
      | n + 1 => (n + 1) * factorial n
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
  • substitution_keystheoremProved here from basic factsNo axioms
    substitution_keysLean 4Proved here from basic factsFormally verified
    /-- A substitution key chooses a code letter for A (26 choices), then for B (25 left), and so
    on: 26 × 25 × ⋯ × 1 keys — more than 400 million million million million. -/
    theorem substitution_keys : factorial 26 = 403291461126605635584000000 := by
      decide
    Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor

Formal exercises

Each exercise contains a sorry for you to replace. Open it in the Lean web editor, or clone the repository and run lake build.

wrap_aroundsorry × 1Try it in the Lean web editor
/-- Check that a shift of 3 wraps X, Y and Z round to A, B and C.
Hint: Lean can work out these remainders itself — try `decide`. -/
theorem wrap_around : shift 3 23 = 0 ∧ shift 3 24 = 1 ∧ shift 3 25 = 2 ∧ shift 3 12 = 15 := by
  sorry
Show the compiled solution
wrap_aroundLean 4Proved here from basic factsFormally verified
/-- Shifting by 3 wraps round the end of the alphabet: X (23) → A (0), Y (24) → B (1),
Z (25) → C (2). And M (12) → P (15) needs no wrapping. -/
theorem wrap_around : shift 3 23 = 0 ∧ shift 3 24 = 1 ∧ shift 3 25 = 2 ∧ shift 3 12 = 15 := by
  decide
Axioms used: No axiomsformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
zebrasorry × 1Try it in the Lean web editor
/-- Encode ZEBRA with a shift of 3.
Hint: `decide` can run the cipher on the whole word. -/
theorem zebra : caesar 3 "ZEBRA" = "CHEUD" := by
  sorry
Show the compiled solution
zebraLean 4Proved here from basic factsFormally verified
/-- Wrapping round: with a shift of 3, ZEBRA becomes CHEUD (Z → C). -/
theorem zebra : caesar 3 "ZEBRA" = "CHEUD" := by
  decide
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor
shift_shiftsorry × 1Try it in the Lean web editor
/-- Show that two shifts make one shift.
Hint: `unfold shift` turns the goal into a statement about remainders; then `omega` can
finish it. -/
theorem shift_shift (a b n : Nat) : shift b (shift a n) = shift (a + b) n := by
  sorry
Show the compiled solution
shift_shiftLean 4Proved here from basic factsFormally verified
/-- Two shifts, one after the other, are one shift: by `a`, then by `b`, is the same as by
`a + b`. (So the order of two shifts does not matter.) -/
theorem shift_shift (a b n : Nat) : shift b (shift a n) = shift (a + b) n := by
  unfold shift
  omega
Axioms used: propext, Quot.soundformal/Lumenary/SecretCodes.leanTry it in the Lean web editor