Skip to content
PQ Stealth Addresses

SharedSecretHiding

Proved: each shared-secret-hiding term of unlinkAdvantage_ofKEMFull_le is the bias of a real-or-random guessing game, and that bias EQUALS KEMScheme.IND_CPA_Advantage of the explicit reduction adversary indCpaAdv against the KEM itself. The two experiments draw the same independent samples in different orders, with one draw unused on one branch; bind is a syntactic constructor, so this holds after evalDist rather than on the nose, which is all an advantage sees. Hence unlinkAdvantage_ofKEMFull_le_indCpa.

Assumed: nothing. Bounding KEMScheme.IND_CPA_Advantage for ML-KEM by MLWE is the missing upstream lemma recorded in MLKEM.lean. See docs/spr-two-hop.md.

indCpaAdv

IND-CPA reduction adversary for branch b: the challenge key is embedded as recipient b, the other recipient is generated by the reduction, and the state carries both keys so the auxiliary data can be rebuilt from the challenge key.

def indCpaAdv (b : Bool) : kem.IND_CPA_Adversary where
  State := PK × PK
  preChallenge pk := do
    let (pk', _) ← kem.keygen
    pure (if b then (pk', pk) else (pk, pk'))
  postChallenge s cStar k := adv s.1 s.2 (cStar, auxGen k (if b then s.2 else s.1))

SharedSecretHiding.lean:35-41 ↗

The concrete VCVio IND-CPA reduction adversary

evalDist_bind_pull_front

An independent draw may be pulled to the front of a two-step prefix (the dependent-prefix form of Reorder.evalDist_pull₃).

theorem evalDist_bind_pull_front {α β δ ζ : Type}
    (oa : ProbComp α) (ob : α → ProbComp β) (od : ProbComp δ)
    (k : α → β → δ → ProbComp ζ) :
    𝒟[(do let a ← oa; let b ← ob a; let d ← od; k a b d)] =
      𝒟[(do let d ← od; let a ← oa; let b ← ob a; k a b d)] := by
proof
  calc 𝒟[(do let a ← oa; let b ← ob a; let d ← od; k a b d)]
      = 𝒟[(do let a ← oa; let d ← od; let b ← ob a; k a b d)] := by
        refine evalDist_bind_congr' _ fun a => ?_
        exact evalDist_bind_bind_swap (ob a) od fun b d => k a b d
    _ = 𝒟[(do let d ← od; let a ← oa; let b ← ob a; k a b d)] :=
        evalDist_bind_bind_swap oa od _

SharedSecretHiding.lean:51-62 ↗

boolBiasAdvantage_congr

theorem boolBiasAdvantage_congr {p q : ProbComp Bool} (h : 𝒟[p] = 𝒟[q]) :
    p.boolBiasAdvantage = q.boolBiasAdvantage := by
proof
  simp only [ProbComp.boolBiasAdvantage, probOutput_def, h]

SharedSecretHiding.lean:64-67 ↗

Distribution-level bridges

rorNormalForm

Normal form of the branch-b reduction game: hidden bit, keypair 0, keypair 1, the challenge encapsulation to recipient b, then the idealized shared secret -- drawn on both branches of the hidden bit.

noncomputable def rorNormalForm (b : Bool) : ProbComp Bool := do
  let hb ← ($ᵗ Bool)
  let pk0 ← kem.keygen
  let pk1 ← kem.keygen
  let ck ← kem.encaps (if b then pk1.1 else pk0.1)
  let kRand ← ($ᵗ K)
  let z ← adv pk0.1 pk1.1
    (ck.1, auxGen (if hb then ck.2 else kRand) (if b then pk1.1 else pk0.1))
  pure (hb == z)

SharedSecretHiding.lean:78-87 ↗

indCpaGame_eq_evalDist_normalForm

theorem indCpaGame_eq_evalDist_normalForm (b : Bool) :
    KEMScheme.IND_CPA_Game ProbCompRuntime.probComp (indCpaAdv kem auxGen adv b) =
      𝒟[rorNormalForm kem auxGen adv b] := by
proof
  cases b with
  | true =>
    have hgame : KEMScheme.IND_CPA_Game ProbCompRuntime.probComp
        (indCpaAdv kem auxGen adv true) =
        𝒟[(do
          let pk1 ← kem.keygen
          let pk0 ← kem.keygen
          let hb ← ($ᵗ Bool)
          let ck ← kem.encaps pk1.1
          let kRand ← ($ᵗ K)
          let z ← adv pk0.1 pk1.1 (ck.1, auxGen (if hb then ck.2 else kRand) pk1.1)
          pure (hb == z) : ProbComp Bool)] := by
      simp only [KEMScheme.IND_CPA_Game, indCpaAdv, bind_assoc, pure_bind,
        ProbCompRuntime.probComp, ProbCompRuntime.liftProbComp, ProbCompLift.id]
      rfl
    rw [hgame, evalDist_bind_bind_swap kem.keygen kem.keygen
      (fun pk1 pk0 => do
        let hb ← ($ᵗ Bool)
        let ck ← kem.encaps pk1.1
        let kRand ← ($ᵗ K)
        let z ← adv pk0.1 pk1.1 (ck.1, auxGen (if hb then ck.2 else kRand) pk1.1)
        pure (hb == z))]
    exact evalDist_bind_pull_front kem.keygen (fun _ => kem.keygen) ($ᵗ Bool)
      (fun pk0 pk1 hb => do
        let ck ← kem.encaps pk1.1
        let kRand ← ($ᵗ K)
        let z ← adv pk0.1 pk1.1 (ck.1, auxGen (if hb then ck.2 else kRand) pk1.1)
        pure (hb == z))
  | false =>
    have hgame : KEMScheme.IND_CPA_Game ProbCompRuntime.probComp
        (indCpaAdv kem auxGen adv false) =
        𝒟[(do
          let pk0 ← kem.keygen
          let pk1 ← kem.keygen
          let hb ← ($ᵗ Bool)
          let ck ← kem.encaps pk0.1
          let kRand ← ($ᵗ K)
          let z ← adv pk0.1 pk1.1 (ck.1, auxGen (if hb then ck.2 else kRand) pk0.1)
          pure (hb == z) : ProbComp Bool)] := by
      simp only [KEMScheme.IND_CPA_Game, indCpaAdv, bind_assoc, pure_bind,
        ProbCompRuntime.probComp, ProbCompRuntime.liftProbComp, ProbCompLift.id]
      rfl
    rw [hgame]
    exact evalDist_bind_pull_front kem.keygen (fun _ => kem.keygen) ($ᵗ Bool)
      (fun pk0 pk1 hb => do
        let ck ← kem.encaps pk0.1
        let kRand ← ($ᵗ K)
        let z ← adv pk0.1 pk1.1 (ck.1, auxGen (if hb then ck.2 else kRand) pk0.1)
        pure (hb == z))

SharedSecretHiding.lean:91-143 ↗

The hiding terms ARE VCVio IND-CPA advantages

rorGame

The shared-secret real-or-random game on branch b: real secret or fresh random key, from the SAME public key on both sides. Orientation is VCVio's -- the REAL secret sits on hidden bit true, for both values of b.

noncomputable def rorGame (b : Bool) : ProbComp Bool := do
  let hb ← ($ᵗ Bool)
  let z ← if hb then
      (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
        (StealthScheme.ofKEMFull kem auxGen).unlinkBranch adv b
    else
      (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
        randAuxBranch kem auxGen adv b
  pure (hb == z)

SharedSecretHiding.lean:151-160 ↗

sharedSecretHiding_eq_rorBias

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

theorem sharedSecretHiding_eq_rorBias (b : Bool) :
    sharedSecretHiding kem auxGen adv b = (rorGame kem auxGen adv b).boolBiasAdvantage := by
proof
  unfold sharedSecretHiding rorGame
  exact (ProbComp.boolBiasAdvantage_eq_boolDistAdvantage_uniformBool_branch _ _).symm

SharedSecretHiding.lean:163-167 ↗

evalDist_rorGame_eq_normalForm

theorem evalDist_rorGame_eq_normalForm (b : Bool) :
    𝒟[rorGame kem auxGen adv b] = 𝒟[rorNormalForm kem auxGen adv b] := by
proof
  unfold rorGame rorNormalForm
  refine evalDist_bind_congr' _ fun hb => ?_
  cases hb with
  | true =>
    simp only [StealthScheme.unlinkSetup, StealthScheme.unlinkBranch,
      StealthScheme.ofKEMFull, bind_assoc, pure_bind, if_true]
    refine evalDist_bind_congr' _ fun pk0 => evalDist_bind_congr' _ fun pk1 =>
      evalDist_bind_congr' _ fun ck => ?_
    exact (evalDist_uniformSample_bind_const _).symm
  | false =>
    simp only [StealthScheme.unlinkSetup, randAuxBranch,
      StealthScheme.ofKEMFull, bind_assoc, pure_bind, Bool.false_eq_true, if_false]

SharedSecretHiding.lean:170-184 ↗

sharedSecretHiding_eq_indCpaAdvantage

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

theorem sharedSecretHiding_eq_indCpaAdvantage (b : Bool) :
    sharedSecretHiding kem auxGen adv b =
      KEMScheme.IND_CPA_Advantage ProbCompRuntime.probComp (indCpaAdv kem auxGen adv b) := by
proof
  rw [sharedSecretHiding_eq_rorBias, KEMScheme.IND_CPA_Advantage,
    indCpaGame_eq_evalDist_normalForm,
    boolBiasAdvantage_congr (evalDist_rorGame_eq_normalForm kem auxGen adv b)]
  rfl

SharedSecretHiding.lean:187-194 ↗

unlinkAdvantage_ofKEMFull_le_indCpa

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

theorem unlinkAdvantage_ofKEMFull_le_indCpa :
    (StealthScheme.ofKEMFull kem auxGen).unlinkAdvantage adv ≤
      KEMScheme.IND_CPA_Advantage ProbCompRuntime.probComp (indCpaAdv kem auxGen adv true)
      + auxKeyIndependence kem auxGen adv
      + kem.anonAdvantage (adv.cipherOf auxGen)
      + KEMScheme.IND_CPA_Advantage ProbCompRuntime.probComp
        (indCpaAdv kem auxGen adv false) := by
proof
  rw [← sharedSecretHiding_eq_indCpaAdvantage, ← sharedSecretHiding_eq_indCpaAdvantage]
  exact unlinkAdvantage_ofKEMFull_le kem auxGen adv

SharedSecretHiding.lean:198-207 ↗