Soundness
Proved: DKSAP's false-positive rate is EXACTLY 1 / |F|, for every hash h --
the leak is the uniform spending scalar, not a hash collision; and for the
KEM-based scheme, falsePositiveRate ≤ ε + decapsRoR, where ε bounds tag
collisions over a uniform secret and decapsRoR is the real-or-random gap of
recipient 0's decapsulated key (a KEM IND-CPA question, as everywhere else),
with decapsRoR = 0 under an ideal decapsulation; and the view-tag instance,
SoundWithin (1/256 + decapsRoR) for a one-byte tag.
Assumed: tag collision-freeness and the uniformity of viewTag on a uniform
key are hypotheses, as is the ideal decapsulation. See
docs/announcement-model.md.
probOutput_true_bind_decide_eq
Testing a draw against a fixed value returns true exactly as often as the
draw hits it.
theorem probOutput_true_bind_decide_eq {α : Type} [DecidableEq α] (p : ProbComp α) (t : α) :
Pr[= true | (do let x ← p; pure (decide (x = t)))] = Pr[= t | p] := byproof
rw [bind_pure_comp, probOutput_map]
simp only [decide_eq_true_eq]
exact probEvent_eq_eq_probOutput p tprobOutput_true_bind_le
theorem probOutput_true_bind_le {α : Type} (p : ProbComp α) (f : α → ProbComp Bool)
(c : ENNReal) (h : ∀ a, Pr[= true | f a] ≤ c) :
Pr[= true | p >>= f] ≤ c := byproof
rw [← probEvent_eq_eq_probOutput]
exact probEvent_bind_le_of_forall_le fun a _ => by
rw [probEvent_eq_eq_probOutput]; exact h aprobOutput_true_bind_eq
theorem probOutput_true_bind_eq {α : Type} (p : ProbComp α) (f : α → ProbComp Bool)
(c : ENNReal) (h : ∀ a, Pr[= true | f a] = c) :
Pr[= true | p >>= f] = c := byproof
rw [probOutput_bind_of_const p (fun a _ => h a), probFailure_of_liftM_PMF, tsub_zero, one_mul]Bookkeeping for Pr[= true | …] over a bind
probOutput_falsePositiveExp_dksap_eq
The false-positive probability of DKSAP is exactly |F|⁻¹, for every hash
h: injectivity of x ↦ x • g turns the scan test into a linear equation in
recipient 1's uniform spending scalar.
theorem probOutput_falsePositiveExp_dksap_eq (g : G) (h : G → F)
(hinj : Function.Injective (fun x : F => x • g)) :
Pr[= true | (dksap g h).FalsePositiveExp] = (Fintype.card F : ENNReal)⁻¹ := byproof
have key : ∀ m0 v0 m1 v1 r : F,
(m1 • g + h (r • v1 • g) • g = (m0 + h (v0 • r • g)) • g)
↔ (m1 = m0 + h (v0 • r • g) - h (r • v1 • g)) := by
intro m0 v0 m1 v1 r
rw [← add_smul, eq_sub_iff_add_eq]
exact ⟨fun hEq => hinj hEq, fun hEq => congrArg (fun x : F => x • g) hEq⟩
have base : ∀ c : F, Pr[= true | (do let m1 ← ($ᵗ F); pure (decide (m1 = c)))]
= (Fintype.card F : ENNReal)⁻¹ := fun c => by
rw [probOutput_true_bind_decide_eq, probOutput_uniformSample]
simp only [StealthScheme.FalsePositiveExp, dksap, bind_assoc, pure_bind, key]
refine probOutput_true_bind_eq _ _ _ fun m0 => ?_
refine probOutput_true_bind_eq _ _ _ fun v0 => ?_
rw [probOutput_bind_bind_swap]
refine probOutput_true_bind_eq _ _ _ fun v1 => ?_
rw [probOutput_bind_bind_swap]
refine probOutput_true_bind_eq _ _ _ fun r => ?_
exact base _dksap_falsePositiveRate_eq
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem dksap_falsePositiveRate_eq (g : G) (h : G → F)
(hinj : Function.Injective (fun x : F => x • g)) :
(dksap g h).falsePositiveRate = 1 / (Fintype.card F : ℝ) := byproof
rw [StealthScheme.falsePositiveRate, probOutput_falsePositiveExp_dksap_eq g h hinj,
ENNReal.toReal_inv, ENNReal.toReal_natCast, one_div]dksap_falsePositiveRate_le
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem dksap_falsePositiveRate_le (g : G) (h : G → F)
(hinj : Function.Injective (fun x : F => x • g)) :
(dksap g h).SoundWithin (1 / (Fintype.card F : ℝ)) :=
le_of_eq (dksap_falsePositiveRate_eq g h hinj)DKSAP: the exact false-positive rate
AuxCollisionFree
The tag is ε-collision-free: over a UNIFORM secret, auxGen hits any
prescribed value with probability at most ε. For a view tag this is a counting
argument on the tag alphabet.
def AuxCollisionFree (auxGen : K → PK → Aux) (ε : ℝ) : Prop :=
∀ (a : Aux) (pk : PK),
(Pr[= true | (do let k ← ($ᵗ K); pure (decide (a = auxGen k pk)))]).toReal ≤ εfalsePositiveIdeal
def falsePositiveIdeal : ProbComp Bool := do
let ks0 ← kem.keygen
let ks1 ← kem.keygen
let ck ← kem.encaps ks1.1
let k ← ($ᵗ K)
pure (decide (auxGen ck.2 ks1.1 = auxGen k ks0.1))decapsRoR
noncomputable def decapsRoR : ℝ :=
(StealthScheme.ofKEMFull kem auxGen).FalsePositiveExp.boolDistAdvantage
(falsePositiveIdeal kem auxGen)probOutput_falsePositiveIdeal_le
theorem probOutput_falsePositiveIdeal_le (ε : ℝ) (hε : 0 ≤ ε)
(hTag : AuxCollisionFree auxGen ε) :
(Pr[= true | falsePositiveIdeal kem auxGen]).toReal ≤ ε := byproof
have hbound : Pr[= true | falsePositiveIdeal kem auxGen] ≤ ENNReal.ofReal ε := by
refine probOutput_true_bind_le _ _ _ fun ks0 => ?_
refine probOutput_true_bind_le _ _ _ fun ks1 => ?_
refine probOutput_true_bind_le _ _ _ fun ck => ?_
exact (ENNReal.le_ofReal_iff_toReal_le
(ne_top_of_le_ne_top ENNReal.one_ne_top probOutput_le_one) hε).2
(hTag (auxGen ck.2 ks1.1) ks0.1)
exact (ENNReal.le_ofReal_iff_toReal_le
(ne_top_of_le_ne_top ENNReal.one_ne_top probOutput_le_one) hε).1 hboundfalsePositiveRate_ofKEMFull_le
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem falsePositiveRate_ofKEMFull_le (ε : ℝ) (hε : 0 ≤ ε)
(hTag : AuxCollisionFree auxGen ε) :
(StealthScheme.ofKEMFull kem auxGen).SoundWithin (ε + decapsRoR kem auxGen) := byproof
have hgap : (StealthScheme.ofKEMFull kem auxGen).falsePositiveRate
- (Pr[= true | falsePositiveIdeal kem auxGen]).toReal ≤ decapsRoR kem auxGen :=
le_abs_self _
have hideal := probOutput_falsePositiveIdeal_le kem auxGen ε hε hTag
unfold StealthScheme.SoundWithin
linarithdecapsRoR_eq_zero_of_decaps_uniform
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem decapsRoR_eq_zero_of_decaps_uniform
(hdec : ∀ sk c, kem.decaps sk c = (do let k ← ($ᵗ K); pure (some k))) :
decapsRoR kem auxGen = 0 := byproof
have hEq : (StealthScheme.ofKEMFull kem auxGen).FalsePositiveExp
= falsePositiveIdeal kem auxGen := by
simp only [StealthScheme.FalsePositiveExp, StealthScheme.ofKEMFull, falsePositiveIdeal,
hdec, bind_assoc, pure_bind, Option.elim_some]
simp only [decapsRoR, hEq, ProbComp.boolDistAdvantage, sub_self, abs_zero]falsePositiveRate_ofKEMFull_le_of_decaps_uniform
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem falsePositiveRate_ofKEMFull_le_of_decaps_uniform (ε : ℝ) (hε : 0 ≤ ε)
(hTag : AuxCollisionFree auxGen ε)
(hdec : ∀ sk c, kem.decaps sk c = (do let k ← ($ᵗ K); pure (some k))) :
(StealthScheme.ofKEMFull kem auxGen).SoundWithin ε := byproof
have h := falsePositiveRate_ofKEMFull_le kem auxGen ε hε hTag
rw [decapsRoR_eq_zero_of_decaps_uniform kem auxGen hdec, add_zero] at h
exact hThe KEM scheme: tag collisions plus a real-or-random term
vectorEquivPi
The equivalence underlying instFintypeVector, named so the fiber of a
coordinate projection can be transferred from Vector α n to Fin n → α.
noncomputable def vectorEquivPi (n : ℕ) : Vector α n ≃ (Fin n → α) where
toFun := fun v i => v.get i
invFun := Vector.ofFn
left_inv := fun v => Vector.ext fun i hi => by simp [Vector.ofFn, Vector.get]
right_inv := fun f => funext fun i => by simp [Vector.get, Vector.ofFn]card_fiber_eval
theorem card_fiber_eval' {α : Type} [Fintype α] [DecidableEq α] [Nonempty α] {n : ℕ}
(i : Fin n) (x : α) :
Fintype.card { f : Fin n → α // x = f i } = Fintype.card α ^ (n - 1) := byproof
classical
let e : { f : Fin n → α // x = f i } ≃ ({ j : Fin n // j ≠ i } → α) :=
{ toFun := fun f j => f.1 j.1
invFun := fun g =>
⟨fun j => if h : j = i then x else g ⟨j, h⟩, by simp⟩
left_inv := fun f => by
apply Subtype.ext; funext j
dsimp only
split_ifs with h
· subst h; simp only [f.2]
· rfl
right_inv := fun g => by
funext j
dsimp only
split_ifs with h
· exact (j.2 h).elim
· exact congrArg g (Subtype.ext rfl) }
rw [Fintype.card_congr e, Fintype.card_fun, Fintype.card_subtype_compl (· = i),
Fintype.card_fin, Fintype.card_subtype_eq i]probOutput_map_get_uniformSample_vector
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem probOutput_map_get_uniformSample_vector {n : ℕ} (i : Fin n) (x : α) :
Pr[= x | (fun v : Vector α n => v.get i) <$> ($ᵗ (Vector α n))]
= (Fintype.card α : ENNReal)⁻¹ := byproof
classical
rw [probOutput_map_eq_sum_fintype_ite]
simp only [probOutput_uniformSample]
rw [← Finset.sum_filter]
rw [Finset.sum_const, nsmul_eq_mul]
-- The filtered count is the fiber cardinality `|α| ^ (n - 1)`.
have hfilt : (Finset.univ.filter (fun v : Vector α n => x = v.get i)).card
= Fintype.card α ^ (n - 1) := by
have hcard_eq : (Finset.univ.filter (fun v : Vector α n => x = v.get i)).card
= (Finset.univ.filter (fun f : Fin n → α => x = f i)).card := by
apply Finset.card_equiv (vectorEquivPi n)
intro v
simp [vectorEquivPi, Finset.mem_filter, Finset.mem_univ, true_and]
rw [hcard_eq]
rw [← @Fintype.card_of_subtype (Fin n → α) (fun f => x = f i)
(Finset.univ.filter (fun f : Fin n → α => x = f i))
(fun f => by simp only [Finset.mem_filter, Finset.mem_univ, true_and])]
exact card_fiber_eval' i x
rw [hfilt, Nat.cast_pow]
-- The total count `|α| ^ n`; cast through and cancel one factor.
have hC0 : (Fintype.card α : ENNReal) ≠ 0 := by
exact_mod_cast Fintype.card_ne_zero
have hCtop : (Fintype.card α : ENNReal) ≠ (⊤ : ENNReal) := ENNReal.natCast_ne_top _
have hcard : (Fintype.card (Vector α n) : ENNReal) = (Fintype.card α : ENNReal) ^ n := by
have h1 : Fintype.card (Vector α n) = Fintype.card (Fin n → α) :=
Fintype.card_congr (vectorEquivPi n)
rw [h1, Fintype.card_fun, Fintype.card_fin, Nat.cast_pow]
have hCn : (Fintype.card α : ENNReal) ^ n
= (Fintype.card α : ENNReal) ^ (n - 1) * (Fintype.card α : ENNReal) := by
have hn : NeZero n := Fin.neZero i
rw [← pow_succ, Nat.sub_add_cancel hn.pos]
have hCpow0 : (Fintype.card α : ENNReal) ^ (n - 1) ≠ 0 := ENNReal.pow_ne_zero hC0 (n - 1)
have hCpowtop : (Fintype.card α : ENNReal) ^ (n - 1) ≠ (⊤ : ENNReal) := ENNReal.pow_ne_top hCtop
-- Algebra: C^(n-1) * (C^(n-1) * C)⁻¹ = C⁻¹.
rw [hcard, hCn]
rw [ENNReal.mul_inv (Or.inl hCpow0) (Or.inr hC0)]
rw [ENNReal.mul_inv_cancel_left hCpow0 hCpowtop]A coordinate of a uniform vector is uniform
taggedAux
Auxiliary data led by a view tag: the tag depends on the shared secret alone, the remainder (stealth address, ciphertext hash, …) may depend on both.
def taggedAux (viewTag : K → T) (rest : K → PK → Rest) : K → PK → T × Rest :=
fun k pk => (viewTag k, rest k pk)auxCollisionFree_taggedAux
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem auxCollisionFree_taggedAux (viewTag : K → T) (rest : K → PK → Rest)
(hUnif : ∀ t : T, Pr[= t | viewTag <$> ($ᵗ K)] = (Fintype.card T : ENNReal)⁻¹) :
AuxCollisionFree (taggedAux viewTag rest) (1 / (Fintype.card T : ℝ)) := byproof
intro a pk
haveI : Nonempty T := ⟨a.1⟩
have hmono : Pr[= true | (do let k ← ($ᵗ K); pure (decide (a = taggedAux viewTag rest k pk)))]
≤ Pr[= true | (do let k ← ($ᵗ K); pure (decide (viewTag k = a.1)))] := by
refine probOutput_bind_mono fun k _ => ?_
by_cases hk : a = taggedAux viewTag rest k pk
· have htag : viewTag k = a.1 := by simp only [hk, taggedAux]
simp only [hk, htag, decide_true, le_refl]
· simp only [hk, decide_false, probOutput_pure, Bool.true_eq_false, if_false, zero_le]
have hbridge : (do let x ← (viewTag <$> ($ᵗ K)); pure (decide (x = a.1)))
= (do let k ← ($ᵗ K); pure (decide (viewTag k = a.1))) := by
simp only [map_eq_bind_pure_comp, bind_assoc, pure_bind, Function.comp_apply]
have hval : Pr[= true | (do let k ← ($ᵗ K); pure (decide (viewTag k = a.1)))]
= (Fintype.card T : ENNReal)⁻¹ := by
rw [← hbridge, probOutput_true_bind_decide_eq, hUnif]
have hfin : ((Fintype.card T : ENNReal))⁻¹ ≠ ⊤ := by
simp only [ne_eq, ENNReal.inv_eq_top, Nat.cast_eq_zero, Fintype.card_ne_zero,
not_false_eq_true]
have hle := ENNReal.toReal_mono hfin (hmono.trans_eq hval)
rwa [ENNReal.toReal_inv, ENNReal.toReal_natCast, ← one_div] at hlesoundWithin_ofKEMFull_taggedAux
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem soundWithin_ofKEMFull_taggedAux {SK C : Type}
(kem : KEM PK SK C K) (viewTag : K → T) (rest : K → PK → Rest)
(hUnif : ∀ t : T, Pr[= t | viewTag <$> ($ᵗ K)] = (Fintype.card T : ENNReal)⁻¹) :
(StealthScheme.ofKEMFull kem (taggedAux viewTag rest)).SoundWithin
(1 / (Fintype.card T : ℝ) + decapsRoR kem (taggedAux viewTag rest)) :=
falsePositiveRate_ofKEMFull_le kem (taggedAux viewTag rest) _
(by positivity) (auxCollisionFree_taggedAux viewTag rest hUnif)The view tag
soundWithin_ofKEMFull_byteTag
The ERC's tag-length rationale, machine-checked. With an n-byte view
tag the false-positive rate is at most 2 ^ (-8n) plus the real-or-random term;
n = 1 gives 1/256.
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem soundWithin_ofKEMFull_byteTag (n : ℕ) (kem : KEM PK SK C K)
(viewTag : K → Fin (2 ^ (8 * n))) (rest : K → PK → Rest)
(hUnif : ∀ t : Fin (2 ^ (8 * n)),
Pr[= t | viewTag <$> ($ᵗ K)] = ((2 ^ (8 * n) : ℕ) : ENNReal)⁻¹) :
(StealthScheme.ofKEMFull kem (taggedAux viewTag rest)).SoundWithin
(1 / ((2 ^ (8 * n) : ℕ) : ℝ) + decapsRoR kem (taggedAux viewTag rest)) := byproof
have hcard : Fintype.card (Fin (2 ^ (8 * n))) = 2 ^ (8 * n) := Fintype.card_fin _
have := soundWithin_ofKEMFull_taggedAux kem viewTag rest
(by simpa only [hcard, Nat.cast_pow, Nat.cast_ofNat] using hUnif)
rwa [hcard] at thissoundWithin_ofKEMFull_oneByteTag
axioms: propext, Classical.choice, Quot.sound · sorry-free ✓
theorem soundWithin_ofKEMFull_oneByteTag (kem : KEM PK SK C K)
(viewTag : K → Fin 256) (rest : K → PK → Rest)
(hUnif : ∀ t : Fin 256, Pr[= t | viewTag <$> ($ᵗ K)] = (256 : ENNReal)⁻¹) :
(StealthScheme.ofKEMFull kem (taggedAux viewTag rest)).SoundWithin
(1 / 256 + decapsRoR kem (taggedAux viewTag rest)) := byproof
have hcard : Fintype.card (Fin 256) = 256 := Fintype.card_fin 256
have h := soundWithin_ofKEMFull_taggedAux kem viewTag rest
(by simpa only [hcard, Nat.cast_ofNat] using hUnif)
rw [hcard] at h
exact_mod_cast h