Documentation

GaudisCrypt.Lib.Enc.HashedOTP

IND-security of the hashed one-time pad #

The encryption Enc(k, m) = H(k) ⊕ m with H a random oracle and key k ← input is left-or-right indistinguishable: an adversary making at most q oracle queries cannot tell an encryption of m₀ from one of m₁, beyond its chance of querying the key:

|Pr[A : guess | enc m₀] − Pr[A : guess | enc m₁]| ≤ 2 (q + 1) / |input|

Proof, by game hopping #

The challenger samples the key k, computes H(k) (a fresh uniform value on the empty cache), publishes the ciphertext c = H(k) ⊕ m_b, then runs the adversary's query loop. The two worlds differ only in m_b.

We reuse ow_challenge_x as the key register and chal_x_queried_gh as the "queried the key" flag, so lazy_query_tracked and the entire OW up-to-bad core (body_relE, InvUB, …) apply verbatim.

Result #

enc_ind_secure (fully proved, no sorry): for a q-query adversary, Pr[A guesses ∣ enc m₀] ≤ Pr[A guesses ∣ enc m₁] + (q+1)/|input|. By symmetry in m₀, m₁, |Pr₀ − Pr₁| ≤ (q+1)/|input|. The whole proof is game hopping in the relational calculus, reusing the OW up-to-bad core and guess-experiment framework wholesale.

A commutative group structure on the output type, modelling the one-time-pad mask (e.g. bitwise XOR). Axiomatized on the opaque output, consistent with its other instances.

The published ciphertext, readable by the adversary.

The adversary's guess bit.

The IND game: sample key, compute H(key) (the challenger's own, untracked query), publish H(key) ⊕ m, initialize the "queried the key" flag, run the adversary's tracked query loop, read its guess. The result bit is the adversary's guess.

Equations
  • One or more equations did not get rendered due to their size.
Instances For

    The preprogrammed form: H(key) on the empty oracle is a fresh uniform sample written into RO[key].

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For

      The win indicator and bad event #

      Stage 1: indistinguishability up to the bad event #

      The two encryption worlds, after preprogramming, are related by one relE judgment whose post is encPost. The loop reuses the OW up-to-bad body coupling body_relE verbatim.

      theorem enc_game_pre_relE (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (m₀ m₁ : output) (q : ℕ) :
      (enc_game_pre enc_adv m₀ q).relE (enc_game_pre enc_adv m₁ q) Eq encPost✝

      The game-level coupling: the two preprogrammed worlds relate at encPost — flags agree, and on good runs the guesses agree. Peels the shared prefix (lazy_init, key sample, set key) and applies the OTP tail.

      The preprogramming mini-hop #

      theorem enc_game_wp_eq_pre (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (m : output) (q : ℕ) (F : Bool × state → ENNReal) (σ : state) :
      (enc_game enc_adv m q).wp F σ = (enc_game_pre enc_adv m q).wp F σ

      H(key) on the empty oracle is a fresh uniform written into RO[key]: enc_game and enc_game_pre have equal wp.

      Stage 1 result: indistinguishability up to the bad event #

      theorem enc_guess_le_pre (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (m₀ m₁ : output) (q : ℕ) (σ : state) :
      (enc_game_pre enc_adv m₀ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ ≤ (enc_game_pre enc_adv m₁ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ + (enc_game_pre enc_adv m₀ q).wp (fun (bσ : Bool × state) => if chal_x_queried_gh.get bσ.2 = true then if bσ.1 = true then 1 else 0 else 0) σ

      One-sided up-to-bad bound: the guess probability in world m₀ is at most that in world m₁ plus the chance the adversary queries the key. (enc_game_pre-level; the mini-hop transfers it to enc_game.)

      theorem enc_guess_le (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (m₀ m₁ : output) (q : ℕ) (σ : state) :
      (enc_game enc_adv m₀ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ ≤ (enc_game enc_adv m₁ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ + (enc_game enc_adv m₀ q).wp (fun (bσ : Bool × state) => if chal_x_queried_gh.get bσ.2 = true then 1 else 0) σ

      Indistinguishability up to the bad event (at the enc_game level). The guess probability in world m₀ exceeds that in world m₁ by at most the chance the adversary queries the key.

      Stage 2: bounding the bad event #

      Pr[adversary queries the key] ≤ (q+1)/|input|. We drop the RO[key] preprogramming (the runs are identical until the key is queried), reindex the mask to a fresh independent ciphertext, and bound by a guess_experiment built from the OW body_game_1/final_game_1 — for which game_1_correspondence already supplies the schema inequality.

      The game with H(key) not preprogrammed into RO[key]: a lazy query to the key would sample fresh. Identical to enc_game_pre until the key is queried (the bad event).

      Equations
      • One or more equations did not get rendered due to their size.
      Instances For
        theorem enc_pre_bad_eq_nopre (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (m : output) (q : ℕ) (σ : state) :
        (enc_game_pre enc_adv m q).wp (fun (bσ : Bool × state) => if chal_x_queried_gh.get bσ.2 = true then 1 else 0) σ = (enc_game_nopre enc_adv m q).wp (fun (bσ : Bool × state) => if chal_x_queried_gh.get bσ.2 = true then 1 else 0) σ

        Dropping the preprogramming is invisible to the bad event. The two games differ only by set RO[key], so they sit at InvUB, and their bad flags agree (relE.bad_eq).

        The env of the bad-event guess experiment: lazy oracle, then publish a fresh uniform ciphertext (the mask reindex makes m disappear).

        Equations
        Instances For

          The guess-experiment bound for the bad event: the adversary hits the uniform key with probability ≤ (q+1)/|input|. Reuses OW's game_1_correspondence (env-generic) + the generic interim bound.

          The lazy-run bad event is bounded by the guess-experiment matched event: reindex the mask to a fresh ciphertext (m drops out), commute the sums, and apply the tail bound termwise.

          theorem enc_bad_bound (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (h_qi : enc_adv.inFootprint (GaudisCrypt.Lens.footprint queries_input)ᶜ) (m : output) (q : ℕ) (σ : state) :
          (enc_game enc_adv m q).wp (fun (bσ : Bool × state) => if chal_x_queried_gh.get bσ.2 = true then 1 else 0) σ ≤ (↑q + 1) / ↑(Fintype.card input)

          The bad-event bound: Pr[adversary queries the key] ≤ (q+1)/|input|. Mini-hop → drop preprogramming → guess-experiment bound.

          The headline theorem #

          theorem enc_ind_secure (enc_adv : GaudisCrypt.ProgramDenotation state Unit) (h_RO : enc_adv.inFootprint (GaudisCrypt.Lens.footprint random_oracle_state)ᶜ) (h_flag : enc_adv.inFootprint (GaudisCrypt.Lens.footprint chal_x_queried_gh)ᶜ) (h_cx : enc_adv.inFootprint (GaudisCrypt.Lens.footprint ow_challenge_x)ᶜ) (h_qi : enc_adv.inFootprint (GaudisCrypt.Lens.footprint queries_input)ᶜ) (h_mass : ∀ (σ : state), enc_adv.wp (fun (x : Unit × state) => 1) σ = 1) (m₀ m₁ : output) (q : ℕ) (σ : state) :
          (enc_game enc_adv m₀ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ ≤ (enc_game enc_adv m₁ q).wp (fun (bσ : Bool × state) => if bσ.1 = true then 1 else 0) σ + (↑q + 1) / ↑(Fintype.card input)

          IND-security of the hashed one-time pad. The adversary's guess probability in the m₀ world exceeds that in the m₁ world by at most (q+1)/|input| (its chance of querying the key). Symmetric in m₀, m₁, so |Pr₀ − Pr₁| ≤ (q+1)/|input|.