Documentation

VCVio.OracleComp.QueryTracking.Birthday

ROM Birthday Bound #

Per-pair collision bounds and union bound birthday argument for random oracle collision probability. Covers both log-based and cache-based collision bounds, with per-index corollaries.

Per-Pair Collision Bound (Textbook Step 3) #

For each pair (i,j) of positions in the log with distinct inputs, Pr[outputs equal] ≤ 1/|C|. This is because in the evalDist model, each query returns an independent uniform sample.

theorem OracleComp.probEvent_log_entry_eq_le {ι : Type} {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} (oa : OracleComp spec α) (k : ) (entry : (t : spec.Domain) × spec.Range t) :
(probEvent (simulateQ loggingOracle oa).run fun (z : α × spec.QueryLog) => z.2[k]? = some entry) (↑(Fintype.card (spec.Range entry.fst)))⁻¹

ROM uniformity at a log position: For any loggingOracle trace, the probability that the k-th log entry matches a fixed sigma-typed value ⟨t, v⟩ is at most 1/|Range t|. Each query response is an independent uniform draw.

Proof by structural induction on the computation. For query t >>= mx:

  • The log is [⟨t, u⟩] ++ sub_log where u is uniform from Range t.
  • For k = 0: the event is ⟨t, u⟩ = entry, bounded by Pr[= v | query t] = 1/|Range t|.
  • For k > 0: the event is sub_log[k-1]? = entry, bounded by the inductive hypothesis.
theorem OracleComp.probEvent_log_output_heq_le {ι : Type} {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) (oa : OracleComp spec α) (k : ) (entry : (t : spec.Domain) × spec.Range t) :
(probEvent (simulateQ loggingOracle oa).run fun (z : α × spec.QueryLog) => z.2[k]? = some entry) (↑(Fintype.card (spec.Range default)))⁻¹

Uniformized log entry bound: the probability that position k of a loggingOracle trace equals a fixed sigma-typed entry is at most 1/|Range default|, assuming |Range default| is minimal across all oracle indices.

This is a corollary of probEvent_log_entry_eq_le (which gives 1/|Range entry.1|) combined with the hrange monotonicity hypothesis.

theorem OracleComp.probEvent_log_output_match_le {ι : Type} {spec : OracleSpec ι} [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) (oa : OracleComp spec α) (k : ) (t₀ : spec.Domain) (u₀ : spec.Range t₀) :
(probEvent (simulateQ loggingOracle oa).run fun (z : α × spec.QueryLog) => ∃ (s : spec.Domain) (v : spec.Range s), z.2[k]? = some s, v u₀ v) (↑(Fintype.card (spec.Range default)))⁻¹

Probability that the k-th log entry's output is HEq to a fixed value u₀ : spec.Range t₀. Unlike probEvent_log_entry_eq_le which matches the full sigma entry, this only constrains the output component. The bound uses hrange to get 1/|Range default|.

Proof by structural induction on the computation, same shape as probEvent_log_entry_eq_le.

theorem OracleComp.probEvent_pair_collision_le {ι : Type} {spec : OracleSpec ι} [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (oa : OracleComp spec α) (n : ) (_hbound : oa.IsTotalQueryBound n) (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) (i j : Fin n) (hij : i j) :
(probEvent (simulateQ loggingOracle oa).run fun (z : α × spec.QueryLog) => List.length z.2 > i List.length z.2 > j (z.2[i]?.bind fun (ei : (t : spec.Domain) × spec.Range t) => Option.map (fun (ej : (t : spec.Domain) × spec.Range t) => ei.fst ej.fst ei.snd ej.snd) z.2[j]?) = do let asome true pure (a = true)) (↑(Fintype.card (spec.Range default)))⁻¹

Per-pair collision bound: For any two positions in a loggingOracle trace with distinct inputs, the probability that their outputs are HEq-equal is ≤ 1/|C|.

This is the core ROM property: distinct oracle inputs yield independent uniform outputs. In the evalDist model, each query call returns a fresh uniform sample.

The proof is by structural induction on the computation. For query t >>= mx:

  • If both positions ≥ 1: they refer to sub-log positions, and the IH applies directly.
  • If one position is 0: the collision involves the fresh response u from query t and a sub-log entry. By probEvent_log_output_heq_le, sub-log position k matching any fixed entry has prob ≤ 1/|Range default|.

The hrange hypothesis ensures that |Range default| is minimal across all oracle indices, so the per-entry bound 1/|Range s| ≤ 1/|Range default| holds uniformly.

Union Bound Birthday (Textbook Steps 4-5) #

Collision = ∃ pair with collision. Union bound over C(n,2) pairs gives n²/(2|C|).

theorem OracleComp.probEvent_logCollision_le_birthday_total {ι : Type} {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (oa : OracleComp spec α) (n : ) (hbound : oa.IsTotalQueryBound n) (hC : 0 < Fintype.card (spec.Range default)) (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) :
(probEvent (simulateQ loggingOracle oa).run fun (z : α × spec.QueryLog) => LogHasCollision z.2) n ^ 2 / (2 * (Fintype.card (spec.Range default)))

Birthday bound for loggingOracle (total query bound): The probability of a collision in the query log is ≤ n²/(2|C|).

Proof: express collision as ∃ pair (i,j), then union bound using probEvent_pair_collision_le for each pair.

theorem OracleComp.probEvent_cacheCollision_le_birthday_total_tight {ι : Type} [DecidableEq ι] {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (oa : OracleComp spec α) (n : ) (_hbound : oa.IsTotalQueryBound n) (_hC : 0 < Fintype.card (spec.Range default)) (_hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) :
(probEvent ((simulateQ cachingOracle oa).run ) fun (z : α × spec.QueryCache) => CacheHasCollision z.2) ↑(n * (n - 1)) / (2 * (Fintype.card (spec.Range default)))

Tight birthday bound for cachingOracle (total query bound): The probability of a collision in the cache is ≤ n*(n-1)/(2|C|).

theorem OracleComp.probEvent_cacheCollision_le_birthday_total {ι : Type} [DecidableEq ι] {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} [Inhabited ι] (oa : OracleComp spec α) (n : ) (hbound : oa.IsTotalQueryBound n) (hC : 0 < Fintype.card (spec.Range default)) (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) :
(probEvent ((simulateQ cachingOracle oa).run ) fun (z : α × spec.QueryCache) => CacheHasCollision z.2) n ^ 2 / (2 * (Fintype.card (spec.Range default)))

Loose birthday bound for cachingOracle (total query bound): The probability of a collision in the cache is ≤ n²/(2|C|). Derived from the tight bound n*(n-1)/(2|C|) since n*(n-1) ≤ n².

Per-Index Bound Versions #

theorem OracleComp.probEvent_cacheCollision_le_birthday {ι : Type} [DecidableEq ι] {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} {t : } [Inhabited ι] [Fintype ι] (oa : OracleComp spec α) (hbound : oa.IsPerIndexQueryBound fun (x : ι) => t) (hC : 0 < Fintype.card (spec.Range default)) (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) :
(probEvent ((simulateQ cachingOracle oa).run ) fun (z : α × spec.QueryCache) => CacheHasCollision z.2) ((Fintype.card ι) * t) ^ 2 / (2 * (Fintype.card (spec.Range default)))

Birthday bound for cachingOracle with per-index query bound.

theorem OracleComp.probEvent_cacheCollision_le_birthday' {ι : Type} [DecidableEq ι] {spec : OracleSpec ι} [spec.DecidableEq] [spec.Fintype] [spec.Inhabited] {α : Type} {t : } [Inhabited ι] [Unique ι] (oa : OracleComp spec α) (hbound : oa.IsPerIndexQueryBound fun (x : ι) => t) (hC : 0 < Fintype.card (spec.Range default)) (hrange : ∀ (t : ι), Fintype.card (spec.Range default) Fintype.card (spec.Range t)) :
(probEvent ((simulateQ cachingOracle oa).run ) fun (z : α × spec.QueryCache) => CacheHasCollision z.2) t ^ 2 / (2 * (Fintype.card (spec.Range default)))

WARNING: vacuously true. The [Unique ι] hypothesis means ι has exactly one element, but CacheHasCollision requires two distinct oracle indices t₁ ≠ t₂ : ι, which is impossible. The event CacheHasCollision z.2 is therefore always false, making the bound trivially 0 ≤ ....

The non-vacuous birthday bound is probEvent_cacheCollision_le_birthday_total_tight.