Skip to content
PQ Stealth Addresses

SPRTwoHop

Proved: the two decision-MLWE problems the SPR argument needs, both as VCVio LearningWithErrors.Problems over the seed rho; the two reduction adversaries built from an unlinkability distinguisher; that each hop's game gap IS the LearningWithErrors.advantage of its reduction adversary; and the resulting four-term bound on KEM.sprAdv for ML-KEM-768.

Also proved: the real anonymity branch may be reordered to encapsulate first and draw the other key afterwards (evalDist_anonSetup_bind_anonBranch, by VCVio's relational tactics), so that reorder costs nothing and is NOT folded into the idealization term.

Assumed: the two non-lattice terms of that bound stay unbounded here -- the primitive-idealization term (G and PRF_eta as independent samplers; the seeded shape keeps SampleNTT concrete) and the simulator term (compress-and-encode regularity, plus the honest key distribution). Neither is an MLWE statement. See docs/spr-two-hop.md.

instNeZeroMlkemModulus

The FIPS 203 modulus is nonzero -- needed to see ZMod modulus as FinEnum, which modulus being a def hides from instance search.

instance instNeZeroMlkemModulus : NeZero MLKEM.modulus := ⟨by decide⟩

SPRTwoHop.lean:37-38 ↗

instSampleableTypeRq

instance instSampleableTypeRq : SampleableType MLKEM.Rq :=
  inferInstanceAs (SampleableType (Vector MLKEM.Coeff MLKEM.ringDegree))

SPRTwoHop.lean:41-43 ↗

instSampleableTypeTq

instance instSampleableTypeTq : SampleableType MLKEM.Tq :=
  SampleableType.ofEquiv (α := MLKEM.Rq)
    ⟨fun f => ⟨f⟩, fun t => t.coeffs, fun _ => rfl, fun _ => rfl⟩

SPRTwoHop.lean:45-48 ↗

Uniform sampling on the ML-KEM ring

LearningWithErrors.advantage_eq_boolDistAdvantage

The decision-LWE advantage is the distance between the two named games, so it composes with the game-hopping triangle inequalities. (Scoped under PqStealth; upstream ask, docs/vcvio-upstream.md item 4.)

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem LearningWithErrors.advantage_eq_boolDistAdvantage {Sample Secret Output : Type}
    [Add Output] (problem : LearningWithErrors.Problem Sample Secret Output)
    (adv : LearningWithErrors.Adversary problem) :
    LearningWithErrors.advantage problem adv =
      (LearningWithErrors.game0 problem adv).boolDistAdvantage
        (LearningWithErrors.game1 problem adv) := by
proof
  have key := ProbComp.boolBiasAdvantage_bind_uniformBool_eq_boolDistAdvantage
    (pure () : ProbComp Unit) (fun _ => LearningWithErrors.game0 problem adv)
    (fun _ => LearningWithErrors.game1 problem adv)
  simp only [pure_bind] at key
  rw [LearningWithErrors.advantage, ← key]
  refine congrArg ProbComp.boolBiasAdvantage ?_
  simp only [LearningWithErrors.experiment]
  refine bind_congr fun b => ?_
  cases b <;>
    simp only [if_true, LearningWithErrors.game0, LearningWithErrors.game1, bind_assoc]

SPRTwoHop.lean:54-70 ↗

VCVio's LWE advantage as a game distance

pkOf

The K-PKE public key carrying rho and the encoding of t̂.

def pkOf (rho : Seed32) (tHat : TqVec params.k) : EncapsulationKey params encoding :=
  { tHatEncoded := encoding.byteEncode12Vec tHat, rho := rho }

SPRTwoHop.lean:80-82 ↗

ctOf

def ctOf (u : RqVec params.k) (v : Rq) : Ciphertext params encoding :=
  { uEncoded := encoding.byteEncodeDUVec (encoding.compressDU u)
    vEncoded := encoding.byteEncodeDV (encoding.compressDV v) }

SPRTwoHop.lean:84-87 ↗

sampleEta1Hat

def sampleEta1Hat (offset : ℕ) : ProbComp (TqVec params.k) := do
  let sigma ← $ᵗ Seed32
  pure (ring.nttVec (prims.sampleVecEta1 sigma offset))

SPRTwoHop.lean:91-94 ↗

sampleNoise

def sampleNoise : ProbComp (RqVec params.k × Rq) := do
  let c₁ ← $ᵗ Coins
  let c₂ ← $ᵗ Coins
  pure (prims.sampleVecEta2 c₁ params.k, prims.prfEta2 c₂ (2 * params.k))

SPRTwoHop.lean:96-100 ↗

idealKeygen

def idealKeygen : ProbComp (EncapsulationKey params encoding) := do
  let rho ← $ᵗ Seed32
  let s ← sampleEta1Hat ring encoding prims 0
  let e ← sampleEta1Hat ring encoding prims params.k
  pure (pkOf encoding rho (ring.matVecMul (prims.publicMatrix rho) s + e))

SPRTwoHop.lean:103-108 ↗

idealEncrypt

def idealEncrypt (ek : EncapsulationKey params encoding) :
    ProbComp (Ciphertext params encoding) := do
  let y ← sampleEta1Hat ring encoding prims 0
  let ne ← sampleNoise encoding prims
  let m ← $ᵗ Message
  pure (ctOf encoding
    (ring.invNTTVec (ring.matTransposeVecMul (prims.publicMatrix ek.rho) y) + ne.1)
    (ring.invNTT (ring.dot (encoding.byteDecode12Vec ek.tHatEncoded) y) + ne.2
      + encoding.decompress1 (encoding.byteDecode1 m)))

SPRTwoHop.lean:111-120 ↗

uniformKeygen

def uniformKeygen : ProbComp (EncapsulationKey params encoding) := do
  let rho ← $ᵗ Seed32
  let t ← $ᵗ (TqVec params.k)
  pure (pkOf encoding rho t)

SPRTwoHop.lean:122-126 ↗

randCt

def randCt : ProbComp (Ciphertext params encoding) := do
  let u ← $ᵗ (RqVec params.k)
  let v ← $ᵗ Rq
  let m ← $ᵗ Message
  pure (ctOf encoding u (v + encoding.decompress1 (encoding.byteDecode1 m)))

SPRTwoHop.lean:130-135 ↗

Semantic constructors and the idealized samplers

idealBranch

The anonymity branch in idealized form, parametric in how the challenge key and the challenge ciphertext are produced.

def idealBranch (adv : StealthScheme.UnlinkAdv (EncapsulationKey params encoding)
      (Ciphertext params encoding))
    (kb : ProbComp (EncapsulationKey params encoding))
    (ct : EncapsulationKey params encoding → ProbComp (Ciphertext params encoding))
    (b : Bool) : ProbComp Bool := do
  let pkb ← kb
  let c ← ct pkb
  let pkOther ← idealKeygen ring encoding prims
  adv (if b then pkOther else pkb) (if b then pkb else pkOther) c

SPRTwoHop.lean:144-153 ↗

The game chain

keyHopProblem

Key hop. Given (rho, t̂), decide whether t̂ = ·ŝ + ê for PRF_η₁-distributed (ŝ, ê) or t̂ is uniform.

def keyHopProblem :
    LearningWithErrors.Problem Seed32 (TqVec params.k) (TqVec params.k) where
  sampleChallenge := $ᵗ Seed32
  sampleSecret := sampleEta1Hat ring encoding prims 0
  sampleError := sampleEta1Hat ring encoding prims params.k
  noiseless := fun s rho => ring.matVecMul (prims.publicMatrix rho) s
  sampleUniform := $ᵗ (TqVec params.k)

SPRTwoHop.lean:161-168 ↗

ctHopProblem

def ctHopProblem :
    LearningWithErrors.Problem (Seed32 × TqVec params.k) (TqVec params.k)
      (RqVec params.k × Rq) where
  sampleChallenge := do
    let rho ← $ᵗ Seed32
    let t ← $ᵗ (TqVec params.k)
    pure (rho, t)
  sampleSecret := sampleEta1Hat ring encoding prims 0
  sampleError := sampleNoise encoding prims
  noiseless := fun y ch =>
    (ring.invNTTVec (ring.matTransposeVecMul (prims.publicMatrix ch.1) y),
      ring.invNTT (ring.dot ch.2 y))
  sampleUniform := do
    let u ← $ᵗ (RqVec params.k)
    let v ← $ᵗ Rq
    pure (u, v)

SPRTwoHop.lean:171-187 ↗

The two decision-MLWE problems

keyHopAdv

Key-hop reduction. Plant the MLWE challenge as recipient b's encapsulation key, generate the other recipient honestly, encrypt honestly to the planted key.

def keyHopAdv (b : Bool) : LearningWithErrors.Adversary (keyHopProblem ring encoding prims) :=
  fun chal => do
    let c ← idealEncrypt ring encoding prims (pkOf encoding chal.1 chal.2)
    let pkOther ← idealKeygen ring encoding prims
    adv (if b then pkOther else pkOf encoding chal.1 chal.2)
      (if b then pkOf encoding chal.1 chal.2 else pkOther) c

SPRTwoHop.lean:196-202 ↗

ctHopAdv

def ctHopAdv (b : Bool) : LearningWithErrors.Adversary (ctHopProblem ring encoding prims) :=
  fun chal => do
    let m ← $ᵗ Message
    let pkOther ← idealKeygen ring encoding prims
    adv (if b then pkOther else pkOf encoding chal.1.1 chal.1.2)
      (if b then pkOf encoding chal.1.1 chal.1.2 else pkOther)
      (ctOf encoding chal.2.1 (chal.2.2 + encoding.decompress1 (encoding.byteDecode1 m)))

SPRTwoHop.lean:206-213 ↗

The two reduction adversaries

idealEncrypt_pkOf

Encrypting to a planted key sees t̂ itself: the byteEncode12Vec / byteDecode12Vec roundtrip is an Encoding field, not a Laws hypothesis.

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem idealEncrypt_pkOf (rho : Seed32) (t : TqVec params.k) :
    idealEncrypt ring encoding prims (pkOf encoding rho t) = (do
      let y ← sampleEta1Hat ring encoding prims 0
      let ne ← sampleNoise encoding prims
      let m ← $ᵗ Message
      pure (ctOf encoding
        (ring.invNTTVec (ring.matTransposeVecMul (prims.publicMatrix rho) y) + ne.1)
        (ring.invNTT (ring.dot t y) + ne.2
          + encoding.decompress1 (encoding.byteDecode1 m)))) := by
proof
  simp only [idealEncrypt, pkOf, encoding.byteDecode12Vec_byteEncode12Vec]

SPRTwoHop.lean:218-228 ↗

game0_keyHopProblem

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game0_keyHopProblem (b : Bool) :
    LearningWithErrors.game0 (keyHopProblem ring encoding prims)
      (keyHopAdv ring encoding prims adv b) =
      idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
        (idealEncrypt ring encoding prims) b := by
proof
  simp only [LearningWithErrors.game0, LearningWithErrors.distr, keyHopProblem, keyHopAdv,
    idealBranch, idealKeygen, bind_assoc, pure_bind]

SPRTwoHop.lean:230-237 ↗

game1_keyHopProblem

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game1_keyHopProblem (b : Bool) :
    LearningWithErrors.game1 (keyHopProblem ring encoding prims)
      (keyHopAdv ring encoding prims adv b) =
      idealBranch ring encoding prims adv (uniformKeygen encoding)
        (idealEncrypt ring encoding prims) b := by
proof
  simp only [LearningWithErrors.game1, LearningWithErrors.uniformDistr, keyHopProblem, keyHopAdv,
    idealBranch, uniformKeygen, bind_assoc, pure_bind]

SPRTwoHop.lean:239-246 ↗

game0_ctHopProblem

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game0_ctHopProblem (b : Bool) :
    LearningWithErrors.game0 (ctHopProblem ring encoding prims)
      (ctHopAdv ring encoding prims adv b) =
      idealBranch ring encoding prims adv (uniformKeygen encoding)
        (idealEncrypt ring encoding prims) b := by
proof
  simp only [LearningWithErrors.game0, LearningWithErrors.distr, ctHopProblem, ctHopAdv,
    idealBranch, uniformKeygen, idealEncrypt_pkOf, bind_assoc, pure_bind, Prod.fst_add,
    Prod.snd_add]

SPRTwoHop.lean:248-256 ↗

game1_ctHopProblem

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game1_ctHopProblem (b : Bool) :
    LearningWithErrors.game1 (ctHopProblem ring encoding prims)
      (ctHopAdv ring encoding prims adv b) =
      idealBranch ring encoding prims adv (uniformKeygen encoding)
        (fun _ => randCt encoding) b := by
proof
  simp only [LearningWithErrors.game1, LearningWithErrors.uniformDistr, ctHopProblem, ctHopAdv,
    idealBranch, uniformKeygen, randCt, bind_assoc, pure_bind]

SPRTwoHop.lean:258-265 ↗

The hop identities

KEM.anonBranchReordered

The real anonymity branch in the idealized games' order: challenge key, its encapsulation, then the other key.

def KEM.anonBranchReordered (b : Bool) : ProbComp Bool := do
  let pkb ← Prod.fst <$> kem.keygen
  let c ← Prod.fst <$> kem.encaps pkb
  let pkOther ← Prod.fst <$> kem.keygen
  adv (if b then pkOther else pkb) (if b then pkb else pkOther) c

SPRTwoHop.lean:278-283 ↗

KEM.evalDist_anonSetup_bind_anonBranch

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem KEM.evalDist_anonSetup_bind_anonBranch (b : Bool) :
    𝒟[kem.anonSetup >>= kem.anonBranch adv b] = 𝒟[kem.anonBranchReordered adv b] := by
proof
  cases b
  · simp only [KEM.anonSetup, KEM.anonBranch, KEM.anonBranchReordered, bind_assoc, pure_bind,
      map_eq_bind_pure_comp, Function.comp_def, Bool.false_eq_true, if_false]
    by_equiv
    rvcstep
    intro a b hab
    cases hab
    rvcstep swap left
    rvcgen
  · simp only [KEM.anonSetup, KEM.anonBranch, KEM.anonBranchReordered, bind_assoc, pure_bind,
      map_eq_bind_pure_comp, Function.comp_def, if_true]
    by_equiv
    rvcstep swap left
    rvcstep
    intro a b hab
    cases hab
    rvcstep swap left
    rvcgen

SPRTwoHop.lean:285-305 ↗

KEM.boolDistAdvantage_anonBranch_anonBranchReordered

theorem KEM.boolDistAdvantage_anonBranch_anonBranchReordered (b : Bool) :
    (kem.anonSetup >>= kem.anonBranch adv b).boolDistAdvantage
      (kem.anonBranchReordered adv b) = 0 := by
proof
  rw [ProbComp.boolDistAdvantage, probOutput_congr rfl (kem.evalDist_anonSetup_bind_anonBranch adv b),
    sub_self, abs_zero]

SPRTwoHop.lean:307-312 ↗

The real branch, reordered

primitiveIdealization

The gap between the (reordered) real ML-KEM anonymity branch and its idealized form: G as a fresh-seed sampler (key seeds, and the encryption coins G(m ‖ H(ek))) and PRF_η as an independent sampler. SampleNTT stays concrete -- the MLWE problems below are seeded on rho. Not a lattice term -- it is the ROM/PRF step.

noncomputable def primitiveIdealization (b : Bool) : ℝ :=
  (KEM.anonBranchReordered (MLKEM.asKEMScheme ring encoding prims) adv b).boolDistAdvantage
    (idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
      (idealEncrypt ring encoding prims) b)

SPRTwoHop.lean:327-331 ↗

simulatorGap

noncomputable def simulatorGap (b : Bool) : ℝ :=
  (idealBranch ring encoding prims adv (uniformKeygen encoding)
      (fun _ => randCt encoding) b).boolDistAdvantage
    (KEM.anonSetup (MLKEM.asKEMScheme ring encoding prims) >>= KEM.simBranch sim adv)

SPRTwoHop.lean:335-339 ↗

encodingRegularity

noncomputable def encodingRegularity (b : Bool) : ℝ :=
  (idealBranch ring encoding prims adv (uniformKeygen encoding)
      (fun _ => randCt encoding) b).boolDistAdvantage
    (idealBranch ring encoding prims adv (uniformKeygen encoding) (fun _ => sim) b)

SPRTwoHop.lean:342-346 ↗

keyRestoration

noncomputable def keyRestoration (b : Bool) : ℝ :=
  (idealBranch ring encoding prims adv (uniformKeygen encoding) (fun _ => sim) b).boolDistAdvantage
    (KEM.anonSetup (MLKEM.asKEMScheme ring encoding prims) >>= KEM.simBranch sim adv)

SPRTwoHop.lean:349-352 ↗

simulatorGap_le

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem simulatorGap_le (b : Bool) :
    simulatorGap ring encoding prims adv sim b ≤
      encodingRegularity ring encoding prims adv sim b
        + keyRestoration ring encoding prims adv sim b :=
  ProbComp.boolDistAdvantage_triangle _ _ _

SPRTwoHop.lean:354-359 ↗

The four named terms

sprAdv_le_two_hop_decomposition

SPR of ML-KEM through the two MLWE hops. The per-branch SPR advantage is bounded by the primitive-idealization term, the two decision-MLWE advantages of the named reduction adversaries, and the simulator gap.

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem sprAdv_le_two_hop_decomposition (b : Bool) :
    KEM.sprAdv (MLKEM.asKEMScheme ring encoding prims) sim adv b ≤
      primitiveIdealization ring encoding prims adv b
        + LearningWithErrors.advantage (keyHopProblem ring encoding prims)
            (keyHopAdv ring encoding prims adv b)
        + LearningWithErrors.advantage (ctHopProblem ring encoding prims)
            (ctHopAdv ring encoding prims adv b)
        + simulatorGap ring encoding prims adv sim b := by
proof
  have hkey : LearningWithErrors.advantage (keyHopProblem ring encoding prims)
      (keyHopAdv ring encoding prims adv b) =
      (idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
          (idealEncrypt ring encoding prims) b).boolDistAdvantage
        (idealBranch ring encoding prims adv (uniformKeygen encoding)
          (idealEncrypt ring encoding prims) b) := by
    rw [LearningWithErrors.advantage_eq_boolDistAdvantage, game0_keyHopProblem,
      game1_keyHopProblem]
  have hct : LearningWithErrors.advantage (ctHopProblem ring encoding prims)
      (ctHopAdv ring encoding prims adv b) =
      (idealBranch ring encoding prims adv (uniformKeygen encoding)
          (idealEncrypt ring encoding prims) b).boolDistAdvantage
        (idealBranch ring encoding prims adv (uniformKeygen encoding)
          (fun _ => randCt encoding) b) := by
    rw [LearningWithErrors.advantage_eq_boolDistAdvantage, game0_ctHopProblem,
      game1_ctHopProblem]
  rw [KEM.sprAdv, hkey, hct, primitiveIdealization, simulatorGap]
  set kem : KEM (EncapsulationKey params encoding) (DecapsulationKey params encoding)
    (Ciphertext params encoding) SharedSecret := MLKEM.asKEMScheme ring encoding prims
  set G₀ : ProbComp Bool := KEM.anonSetup kem >>= KEM.anonBranch kem adv b
  set G₀' : ProbComp Bool := KEM.anonBranchReordered kem adv b
  set G₁ : ProbComp Bool := idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
    (idealEncrypt ring encoding prims) b
  set G₂ : ProbComp Bool := idealBranch ring encoding prims adv (uniformKeygen encoding)
    (idealEncrypt ring encoding prims) b
  set G₃ : ProbComp Bool := idealBranch ring encoding prims adv (uniformKeygen encoding)
    (fun _ => randCt encoding) b
  set G₄ : ProbComp Bool := KEM.anonSetup kem >>= KEM.simBranch sim adv
  calc ProbComp.boolDistAdvantage G₀ G₄
      ≤ ProbComp.boolDistAdvantage G₀ G₀' + ProbComp.boolDistAdvantage G₀' G₄ :=
        ProbComp.boolDistAdvantage_triangle _ _ _
    _ = ProbComp.boolDistAdvantage G₀' G₄ := by
        rw [KEM.boolDistAdvantage_anonBranch_anonBranchReordered kem adv b, zero_add]
    _ ≤ ProbComp.boolDistAdvantage G₀' G₁ + ProbComp.boolDistAdvantage G₁ G₄ :=
        ProbComp.boolDistAdvantage_triangle _ _ _
    _ ≤ ProbComp.boolDistAdvantage G₀' G₁ +
          (ProbComp.boolDistAdvantage G₁ G₂ + ProbComp.boolDistAdvantage G₂ G₄) := by
        gcongr
        exact ProbComp.boolDistAdvantage_triangle _ _ _
    _ ≤ ProbComp.boolDistAdvantage G₀' G₁ +
          (ProbComp.boolDistAdvantage G₁ G₂ +
            (ProbComp.boolDistAdvantage G₂ G₃ + ProbComp.boolDistAdvantage G₃ G₄)) := by
        gcongr
        exact ProbComp.boolDistAdvantage_triangle _ _ _
    _ = _ := by ring

SPRTwoHop.lean:365-418 ↗

The bound

mlkem768KeyHopProblem

The key hop at ML-KEM-768: t̂ = ·ŝ + ê against uniform, over the matrix SampleNTT expands from rho.

def mlkem768KeyHopProblem :
    LearningWithErrors.Problem Seed32 (TqVec mlkem768.k) (TqVec mlkem768.k) :=
  keyHopProblem concreteNTTRingOps mlkem768Encoding mlkem768Primitives

SPRTwoHop.lean:430-433 ↗

mlkem768CtHopProblem

def mlkem768CtHopProblem :
    LearningWithErrors.Problem (Seed32 × TqVec mlkem768.k) (TqVec mlkem768.k)
      (RqVec mlkem768.k × Rq) :=
  ctHopProblem concreteNTTRingOps mlkem768Encoding mlkem768Primitives

SPRTwoHop.lean:436-440 ↗

mlkem768_sprAdv_le_two_hop_decomposition

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem mlkem768_sprAdv_le_two_hop_decomposition (b : Bool) :
    mlkem768KEM.sprAdv mlkem768UniformCiphertext adv768 b ≤
      primitiveIdealization concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b
        + LearningWithErrors.advantage mlkem768KeyHopProblem
            (keyHopAdv concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b)
        + LearningWithErrors.advantage mlkem768CtHopProblem
            (ctHopAdv concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b)
        + simulatorGap concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768
            mlkem768UniformCiphertext b :=
  sprAdv_le_two_hop_decomposition concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768
    mlkem768UniformCiphertext b

SPRTwoHop.lean:444-455 ↗

At ML-KEM-768

primitiveIdealizationBound

ROM/PRF assumption: the primitive-idealization term is bounded by epsilonPrim.

def primitiveIdealizationBound (epsilonPrim : ℝ) : Prop :=
  ∀ b, primitiveIdealization ring encoding prims adv b ≤ epsilonPrim

SPRTwoHop.lean:475-477 ↗

encodingRegularityBound

def encodingRegularityBound (epsilonEnc : ℝ) : Prop :=
  ∀ b, encodingRegularity ring encoding prims adv sim b ≤ epsilonEnc

SPRTwoHop.lean:480-482 ↗

keyRestorationAdv

def keyRestorationAdv (b : Bool) :
    LearningWithErrors.Adversary (keyHopProblem ring encoding prims) :=
  fun chal => do
    let simCt ← sim
    let pkOther ← idealKeygen ring encoding prims
    adv (if b then pkOther else pkOf encoding chal.1 chal.2)
      (if b then pkOf encoding chal.1 chal.2 else pkOther) simCt
 
omit [DecidableEq encoding.EncodedTHat] [DecidableEq encoding.EncodedU]
  [DecidableEq encoding.EncodedV] in

SPRTwoHop.lean:489-498 ↗

game0_keyRestorationAdv

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game0_keyRestorationAdv (b : Bool) :
    LearningWithErrors.game0 (keyHopProblem ring encoding prims)
      (keyRestorationAdv ring encoding prims adv sim b) =
      idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
        (fun _ => sim) b := by
proof
  simp only [LearningWithErrors.game0, LearningWithErrors.distr, keyHopProblem,
    keyRestorationAdv, idealBranch, idealKeygen, bind_assoc, pure_bind]
 
omit [DecidableEq encoding.EncodedTHat] [DecidableEq encoding.EncodedU]
  [DecidableEq encoding.EncodedV] in

SPRTwoHop.lean:502-511 ↗

game1_keyRestorationAdv

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem game1_keyRestorationAdv (b : Bool) :
    LearningWithErrors.game1 (keyHopProblem ring encoding prims)
      (keyRestorationAdv ring encoding prims adv sim b) =
      idealBranch ring encoding prims adv (uniformKeygen encoding)
        (fun _ => sim) b := by
proof
  simp only [LearningWithErrors.game1, LearningWithErrors.uniformDistr, keyHopProblem,
    keyRestorationAdv, idealBranch, uniformKeygen, bind_assoc, pure_bind]

SPRTwoHop.lean:514-521 ↗

keyIdealization

noncomputable def keyIdealization (b : Bool) : ℝ :=
  (idealBranch ring encoding prims adv (idealKeygen ring encoding prims)
      (fun _ => sim) b).boolDistAdvantage
    (KEM.anonSetup (MLKEM.asKEMScheme ring encoding prims) >>= KEM.simBranch sim adv)

SPRTwoHop.lean:526-530 ↗

keyRestoration_le_mlwe_add_keyIdealization

axioms: propext, Classical.choice, Quot.sound · sorry-free ✓

theorem keyRestoration_le_mlwe_add_keyIdealization (b : Bool) :
    keyRestoration ring encoding prims adv sim b ≤
      LearningWithErrors.advantage (keyHopProblem ring encoding prims)
        (keyRestorationAdv ring encoding prims adv sim b)
        + keyIdealization ring encoding prims adv sim b := by
proof
  rw [keyRestoration, keyIdealization, LearningWithErrors.advantage_eq_boolDistAdvantage,
    game0_keyRestorationAdv, game1_keyRestorationAdv]
  set Gu : ProbComp Bool :=
    idealBranch ring encoding prims adv (uniformKeygen encoding) (fun _ => sim) b
  set Gi : ProbComp Bool :=
    idealBranch ring encoding prims adv (idealKeygen ring encoding prims) (fun _ => sim) b
  set Gr : ProbComp Bool :=
    KEM.anonSetup (MLKEM.asKEMScheme ring encoding prims) >>= KEM.simBranch sim adv
  calc ProbComp.boolDistAdvantage Gu Gr
      ≤ ProbComp.boolDistAdvantage Gu Gi + ProbComp.boolDistAdvantage Gi Gr :=
        ProbComp.boolDistAdvantage_triangle _ _ _
    _ = ProbComp.boolDistAdvantage Gi Gu + ProbComp.boolDistAdvantage Gi Gr := by
        rw [ProbComp.boolDistAdvantage_comm Gu Gi]

SPRTwoHop.lean:535-553 ↗

keyRestorationMLWE

def keyRestorationMLWE (epsilonRestore : ℝ) : Prop :=
  ∀ b, keyRestoration ring encoding prims adv sim b ≤ epsilonRestore

SPRTwoHop.lean:557-559 ↗

sprAdv_le_mlwe

theorem sprAdv_le_mlwe (b : Bool)
    (epsilonPrim epsilonEnc epsilonRestore : ℝ)
    (hPrim : ∀ b, primitiveIdealization ring encoding prims adv b ≤ epsilonPrim)
    (hEnc : ∀ b, encodingRegularity ring encoding prims adv sim b ≤ epsilonEnc)
    (hRestore : ∀ b, keyRestoration ring encoding prims adv sim b ≤ epsilonRestore) :
    KEM.sprAdv (MLKEM.asKEMScheme ring encoding prims) sim adv b ≤
      epsilonPrim
        + LearningWithErrors.advantage (keyHopProblem ring encoding prims)
            (keyHopAdv ring encoding prims adv b)
        + LearningWithErrors.advantage (ctHopProblem ring encoding prims)
            (ctHopAdv ring encoding prims adv b)
        + epsilonEnc
        + epsilonRestore := by
proof
  calc
    KEM.sprAdv (MLKEM.asKEMScheme ring encoding prims) sim adv b
      ≤ primitiveIdealization ring encoding prims adv b
          + LearningWithErrors.advantage (keyHopProblem ring encoding prims)
              (keyHopAdv ring encoding prims adv b)
          + LearningWithErrors.advantage (ctHopProblem ring encoding prims)
              (ctHopAdv ring encoding prims adv b)
          + simulatorGap ring encoding prims adv sim b :=
        sprAdv_le_two_hop_decomposition ring encoding prims adv sim b
    _ ≤ epsilonPrim + _ + _ + (encodingRegularity ring encoding prims adv sim b
          + keyRestoration ring encoding prims adv sim b) := by
        gcongr
        · exact hPrim b
        · exact simulatorGap_le ring encoding prims adv sim b
    _ ≤ epsilonPrim + _ + _ + (epsilonEnc + epsilonRestore) := by
        gcongr
        · exact hEnc b
        · exact hRestore b
    _ = _ := by ring

SPRTwoHop.lean:561-593 ↗

mlkem768_sprAdv_le_mlwe

ML-KEM-768 SPR ≤ named assumptions + three MLWE advantages, against the uniform-ciphertext-bytes simulator mlkem768UniformCiphertext (the same simulator as mlkem768_sprAdv_le_two_hop_decomposition).

theorem mlkem768_sprAdv_le_mlwe (b : Bool)
    (epsilonPrim epsilonEnc epsilonRestore : ℝ)
    (hPrim : ∀ b, primitiveIdealization concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b ≤ epsilonPrim)
    (hEnc : ∀ b, encodingRegularity concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768
      mlkem768UniformCiphertext b ≤ epsilonEnc)
    (hRestore : ∀ b, keyRestoration concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768
      mlkem768UniformCiphertext b ≤ epsilonRestore) :
    mlkem768KEM.sprAdv mlkem768UniformCiphertext adv768 b ≤
      epsilonPrim
        + LearningWithErrors.advantage mlkem768KeyHopProblem
            (keyHopAdv concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b)
        + LearningWithErrors.advantage mlkem768CtHopProblem
            (ctHopAdv concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768 b)
        + epsilonEnc
        + epsilonRestore :=
  sprAdv_le_mlwe concreteNTTRingOps mlkem768Encoding mlkem768Primitives adv768
    mlkem768UniformCiphertext b epsilonPrim epsilonEnc epsilonRestore hPrim hEnc hRestore

SPRTwoHop.lean:605-622 ↗