KEMAnonymity
Proved: KEM (VCVio's KEMScheme over ProbComp) and its anonymity game --
key privacy, which VCVio does not ship; ofKEM, whose unlinkability EQUALS the
KEM's anonymity advantage; ofKEMFull, the faithful scheme whose announcement
also carries auxGen sharedSecret pk and whose scan recomputes it, with
unlinkAdvantage_ofKEMFull_le bounding unlinkability by two
shared-secret-hiding terms, auxKeyIndependence, and anonymity; and detection
completeness of ofKEMFull from VCVio's KEMScheme.PerfectlyCorrect.
Assumed: nothing here. anonymity → MLWE for ML-KEM is the open piece
(AnonymityFromSPR). See docs/announcement-model.md.
KEM
A key encapsulation mechanism over ProbComp: key generation, encapsulation
(producing a ciphertext and a shared secret), and decapsulation.
abbrev KEM (PK SK C K : Type) := KEMScheme ProbComp K PK SK CAn abstract KEM
probOutput_true_eq_one_of_imp
Transfer a probability-one Boolean verdict along a pointwise implication:
VCVio's probEvent_mono read through probOutput_map. Used to derive detection
completeness from the strictly stronger verdict of KEM correctness.
theorem probOutput_true_eq_one_of_imp {α : Type} (oa : ProbComp α) {p q : α → Bool}
(h : ∀ a ∈ support oa, p a = true → q a = true)
(hp : Pr[= true | (do let a ← oa; pure (p a))] = 1) :
Pr[= true | (do let a ← oa; pure (q a))] = 1 := byproof
rw [bind_pure_comp, probOutput_map] at hp ⊢
exact le_antisymm probEvent_le_one (hp ▸ probEvent_mono h)A Pr[= true | …] = 1 transfer lemma
run
The draw prefix common to KEM correctness and to detection completeness: a keypair, an encapsulation to its public key, and a decapsulation of the result.
def run : ProbComp ((PK × SK) × (C × K) × Option K) := do
let ks ← kem.keygen
let ck ← kem.encaps ks.1
let k? ← kem.decaps ks.2 ck.1
pure (ks, ck, k?)correctExp_eq
theorem correctExp_eq [DecidableEq K] :
kem.CorrectExp = (do let a ← kem.run; pure (decide (a.2.2 = some a.2.1.2))) := byproof
simp only [KEMScheme.CorrectExp, KEM.run, bind_assoc, pure_bind]Correctness
anonSetup
Shared prefix: two keypairs, both public keys published.
def anonSetup : ProbComp (PK × PK) := do
let (pk0, _) ← kem.keygen
let (pk1, _) ← kem.keygen
pure (pk0, pk1)anonBranch
def anonBranch (b : Bool) (a : PK × PK) : ProbComp Bool := do
let (c, _) ← kem.encaps (if b then a.2 else a.1)
adv a.1 a.2 cAnonExp
def AnonExp : ProbComp Bool := do
let a ← kem.anonSetup
let b ← ($ᵗ Bool)
let z ← kem.anonBranch adv b a
pure (b == z)anonAdvantage
noncomputable def anonAdvantage : ℝ := (kem.AnonExp adv).boolBiasAdvantageanonAdvantage_eq_branchDistAdvantage
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem anonAdvantage_eq_branchDistAdvantage :
kem.anonAdvantage adv =
(kem.anonSetup >>= kem.anonBranch adv true).boolDistAdvantage
(kem.anonSetup >>= kem.anonBranch adv false) :=
ProbComp.boolBiasAdvantage_bind_uniformBool_branch kem.anonSetup (kem.anonBranch adv)Anonymity (key privacy)
StealthScheme.ofKEM
Stealth scheme from a KEM: announcement = ciphertext, detection = "did
decapsulation succeed". A teaching model, not the scheme -- on an
implicit-rejection KEM its scan is the constant true.
def StealthScheme.ofKEM (kem : KEM PK SK C K) : StealthScheme PK SK C where
keygen := kem.keygen
announce pk := do
let (c, _) ← kem.encaps pk
pure c
scan sk c := do
let k ← kem.decaps sk c
pure k.isSomeunlinkAdvantage_ofKEM_eq_anonAdvantage
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem unlinkAdvantage_ofKEM_eq_anonAdvantage
(kem : KEM PK SK C K) (adv : StealthScheme.UnlinkAdv PK C) :
(StealthScheme.ofKEM kem).unlinkAdvantage adv = kem.anonAdvantage adv := byproof
unfold StealthScheme.unlinkAdvantage KEM.anonAdvantage
congr 1
simp only [StealthScheme.UnlinkExp, KEM.AnonExp, StealthScheme.unlinkSetup,
KEM.anonSetup, StealthScheme.unlinkBranch, KEM.anonBranch,
StealthScheme.ofKEM, bind_assoc, pure_bind]The KEM-based stealth scheme and the reduction
StealthScheme.ofKEMFull
Stealth scheme from a KEM with a full announcement: ciphertext plus
auxGen sharedSecret pk. Detection recomputes the auxiliary data from the
decapsulated secret and the recipient's OWN key, hence the SK × PK state.
def StealthScheme.ofKEMFull [DecidableEq Aux] (kem : KEM PK SK C K)
(auxGen : K → PK → Aux) : StealthScheme PK (SK × PK) (C × Aux) where
keygen := do
let ks ← kem.keygen
pure (ks.1, (ks.2, ks.1))
announce pk := do
let ck ← kem.encaps pk
pure (ck.1, auxGen ck.2 pk)
scan sk ca := do
let k? ← kem.decaps sk.1 ca.1
pure (k?.elim false fun k => decide (ca.2 = auxGen k sk.2))perfectlyComplete_ofKEMFull
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem perfectlyComplete_ofKEMFull [DecidableEq K] [DecidableEq Aux]
(kem : KEM PK SK C K) (auxGen : K → PK → Aux)
(hkem : kem.PerfectlyCorrect ProbCompRuntime.probComp) :
(StealthScheme.ofKEMFull kem auxGen).PerfectlyComplete := byproof
have hCE : (StealthScheme.ofKEMFull kem auxGen).CorrectExp =
(do let a ← kem.run
pure (a.2.2.elim false fun k => decide (auxGen a.2.1.2 a.1.1 = auxGen k a.1.1))) := by
simp only [StealthScheme.CorrectExp, StealthScheme.ofKEMFull, KEM.run,
bind_assoc, pure_bind]
rw [StealthScheme.PerfectlyComplete, hCE]
refine probOutput_true_eq_one_of_imp (p := fun a => decide (a.2.2 = some a.2.1.2))
kem.run ?_ ?_
· intro a _ ha
simp only [decide_eq_true_eq] at ha
simp only [ha, Option.elim_some, decide_true]
· rw [← kem.correctExp_eq]
exact hkemunlinkSetup_ofKEMFull
theorem unlinkSetup_ofKEMFull [DecidableEq Aux] (kem : KEM PK SK C K)
(auxGen : K → PK → Aux) :
(StealthScheme.ofKEMFull kem auxGen).unlinkSetup = kem.anonSetup := byproof
simp only [StealthScheme.unlinkSetup, KEM.anonSetup, StealthScheme.ofKEMFull,
bind_assoc, pure_bind]StealthScheme.UnlinkAdv.cipherOf
The ciphertext-anonymity adversary induced by a full-announcement one: it
synthesizes the auxiliary data from a FRESH random shared secret and a FIXED
public key, recipient 0's. See docs/announcement-model.md.
def StealthScheme.UnlinkAdv.cipherOf
(adv : StealthScheme.UnlinkAdv PK (C × Aux)) (auxGen : K → PK → Aux) :
StealthScheme.UnlinkAdv PK C :=
fun pk0 pk1 c => do
let k' ← ($ᵗ K)
adv pk0 pk1 (c, auxGen k' pk0)randAuxBranch
The intermediate game on branch b: same ciphertext and same public key,
but the auxiliary data is built from a fresh random shared secret.
def randAuxBranch (b : Bool) (a : PK × PK) : ProbComp Bool := do
let (c, _) ← kem.encaps (if b then a.2 else a.1)
let k' ← ($ᵗ K)
adv a.1 a.2 (c, auxGen k' (if b then a.2 else a.1))randAuxBranch_false
theorem randAuxBranch_false :
randAuxBranch kem auxGen adv false = kem.anonBranch (adv.cipherOf auxGen) false := rflsharedSecretHiding
Shared-secret-hiding advantage on branch b: real shared secret versus a
fresh random key, from the SAME public key on both sides. A KEM IND-CPA
advantage (sharedSecretHiding_eq_indCpaAdvantage).
noncomputable def sharedSecretHiding (b : Bool) : ℝ :=
((StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
(StealthScheme.ofKEMFull kem auxGen).unlinkBranch adv b).boolDistAdvantage
((StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
randAuxBranch kem auxGen adv b)auxKeyIndependence
noncomputable def auxKeyIndependence : ℝ :=
((StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
randAuxBranch kem auxGen adv true).boolDistAdvantage
(kem.anonSetup >>= kem.anonBranch (adv.cipherOf auxGen) true)unlinkAdvantage_ofKEMFull_le
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem unlinkAdvantage_ofKEMFull_le :
(StealthScheme.ofKEMFull kem auxGen).unlinkAdvantage adv ≤
sharedSecretHiding kem auxGen adv true
+ auxKeyIndependence kem auxGen adv
+ kem.anonAdvantage (adv.cipherOf auxGen)
+ sharedSecretHiding kem auxGen adv false := byproof
have hQf : kem.anonSetup >>= kem.anonBranch (adv.cipherOf auxGen) false =
(StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
randAuxBranch kem auxGen adv false := by
rw [randAuxBranch_false, unlinkSetup_ofKEMFull]
rw [StealthScheme.unlinkAdvantage_eq_branchDistAdvantage,
KEM.anonAdvantage_eq_branchDistAdvantage]
unfold sharedSecretHiding auxKeyIndependence
rw [hQf, ProbComp.boolDistAdvantage_comm
((StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
(StealthScheme.ofKEMFull kem auxGen).unlinkBranch adv false)]
set Pt := (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
(StealthScheme.ofKEMFull kem auxGen).unlinkBranch adv true
set Pf := (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
(StealthScheme.ofKEMFull kem auxGen).unlinkBranch adv false
set Mt := (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
randAuxBranch kem auxGen adv true
set Mf := (StealthScheme.ofKEMFull kem auxGen).unlinkSetup >>=
randAuxBranch kem auxGen adv false
set Qt := kem.anonSetup >>= kem.anonBranch (adv.cipherOf auxGen) true
calc Pt.boolDistAdvantage Pf
≤ Pt.boolDistAdvantage Mt + Mt.boolDistAdvantage Pf :=
ProbComp.boolDistAdvantage_triangle Pt Mt Pf
_ ≤ Pt.boolDistAdvantage Mt + (Mt.boolDistAdvantage Qt + Qt.boolDistAdvantage Pf) := by
gcongr
exact ProbComp.boolDistAdvantage_triangle Mt Qt Pf
_ ≤ Pt.boolDistAdvantage Mt + (Mt.boolDistAdvantage Qt
+ (Qt.boolDistAdvantage Mf + Mf.boolDistAdvantage Pf)) := by
gcongr
exact ProbComp.boolDistAdvantage_triangle Qt Mf Pf
_ = Pt.boolDistAdvantage Mt + Mt.boolDistAdvantage Qt
+ Qt.boolDistAdvantage Mf + Mf.boolDistAdvantage Pf := by
ring