VCVio had no Algebraic Group Model. We built one — now vendored in zksecurity/bls-lean as Signatures/AlgebraicGroupModel/, split into Spec/ (what a reviewer must trust) and Proofs/ (everything derived) — and every pairing-based signature proof is built from it plus the shared LinearFunctional layer. This deck opens each definition, one per screen, in the order it's built, and breaks down what it is and why it's there. It's long on purpose — but every screen is small.
One premise: whenever the adversary hands you a group element, it also hands you a recipe — the coefficients showing how it was built from the elements it has already seen (the generator, the hashes, the signatures it received).
element it outputs = Σ (known coefficients) · (elements it has seen)
Our job on VCVio was to turn "a recipe exists" into machinery that reads the secret off it. The rest of this deck is that machinery, piece by piece.
Two components, Spec/ before Proofs/. Read top to bottom — each file uses the ones above it. We'll open every definition.
Our directory adds the algebraic-group machinery. Every other symbol comes from one of three places — the language, the math library, or the crypto framework we build on. The next three screens define each, so no term is a mystery.
x
fun a => …an anonymous function (lambda)
match e with | … => …branch on the shape of e
⟨a, b⟩build a structure / pair from its fields (anonymous constructor)
decide pturn a decidable proposition into a Bool
Prop · Bool · Option · Unita proposition · a boolean · maybe-a-value · the trivial type
by rw · simp · exact · linear_combinationenter proof mode; rewrite · simplify · apply · solve a linear identity
∀ · ∃ · → · (· • g)for-all · exists · implies · a hole: "the function x ↦ x•g"
α, ρ, c live here
AddCommGroup G · Module F Gan abelian group; F acts on it by scalars
x • g (SMul)scalar x times a group element g — the group "multiplication"
Fintype · DecidableEqfinitely many elements; equality is decidable
ENNRealthe extended reals [0,∞] — where probabilities and the bound live
Function.Injective / Bijectivea map is one-to-one / a bijection (hpair, hgen)
Function.update f a bchange f's value at a single point
∑ i, f i (Finset.sum)a finite sum over a Fintype
List.zipWith · sum · mappair two lists with a function · add up · transform
ZMod pintegers mod p (ArkLib's exponent type)
The AGM core imports exactly five VCVio modules. This is its whole borrowed surface — the probability/oracle language plus one generic signature game:
OracleComp over uniform coins — a purely probabilistic computation
$ᵗ S · SampleableType · uniformSamplesample uniformly from a finite type S
Pr[ p | e ] · probEvent · probOutputprobability event p holds / a value occurs, running e
support ethe set of outputs e can produce (used by hfresh)
QueryImpl · simulateQan oracle handler; run a computation under it
M →ₒ Sa single-oracle spec (domain M, range S) — e.g. the signing oracle
SignatureAlg · unforgeableAdv · .advantagethe generic signature scheme, its EUF-CMA adversary, and its success probability
OracleComp.ProbComp, Constructions.SampleableType, EvalDist.Bool, SimSemantics.QueryImpl, CryptoFoundations.SignatureAlg.class BilinearPairingclass BilinearPairing (F G₁ G₂ Gₜ) … where e : G₁ → G₂ → Gₜ e_smul_left (a x y) : e (a • x) y = a • e x y e_smul_right (a x y) : e x (a • y) = a • e x y e_add_left (x x' y) : e (x + x') y = e x y + e x' y e_add_right (x y y') : e x (y + y') = e x y + e x y'
e : G₁×G₂→Gₜ — no concrete curve.e_smul_left/right: a scalar pulls out of either argument. This is what lets α "hop" across a verification equation.e_add_left/right: additive in either argument. Type-3 (no G₂→G₁ map assumed).pairing + bilinearity lemmasdef pairing (x : G₁) (y : G₂) : Gₜ := inst.e x y @[simp] pairing_smul_left / _smul_right / _add_left / _add_right
pairing is a thin wrapper with implicit instance args (write pairing σ g₂). The four bilinearity axioms are restated as @[simp] lemmas on it, so simp automatically pushes scalars and sums through the pairing. No new content — just ergonomics.
DLog11Adversary & dlog11Expdef DLog11Adversary (F G₁ G₂) := G₁ → G₁ → G₂ → G₂ → ProbComp F def dlog11Exp (g₁ g₂) (adversary) : ProbComp Bool := do let α ← $ᵗ F let α' ← adversary g₁ (α • g₁) g₂ (α • g₂) return decide (α' = α)
The target problem, read line by line:
α uniformly from the field.α'.Pr[= true | dlog11Exp …] on the right of every AGM bound.AffineForm — a dlog as const + lin·αstructure AffineForm (F) where const : F -- the α-free part of the discrete log lin : F -- the coefficient of the secret α def eval (s : AffineForm F) (α : F) : F := s.const + s.lin * α
Every element's discrete log has this shape. The three canonical handles: generator ⟨1,0⟩, hash output ρ·g₁ is ⟨ρ,0⟩, signature ρ·(α·g₁) is ⟨0,ρ⟩. Note: signatures are the only source of lin — remember that for freshness.
extract_secret — four linestheorem extract_secret (hverify : out.eval α = c * α) (hnondeg : out.lin ≠ c) : α = out.const / (c - out.lin) := by have hc : c - out.lin ≠ 0 := sub_ne_zero.mpr hnondeg.symm rw [eq_div_iff hc]; simp only [AffineForm.eval] at hverify linear_combination -hverify
hverify — the forgery's dlog equals c·α (what a pairing check forces; PairingExtraction supplies it).hnondeg — lin ≠ c (what freshness forces; each scheme's Lcoeff non-degeneracy supplies it).const/(c − lin). This one lemma is the entire cryptographic core.muW & Lcoeff — one functional, three schemesdef muW (w : ι → M → F) (ρ : ι → F) (m : M) : F := ∑ i, ρ i * w i m def Lcoeff (w) (mstar) (v : Fin Q → M) (ξ : Fin Q → F) (i : ι) : F := w i mstar - ∑ q, ξ q * w i (v q)
w m′ m = [m = m′]. OOPS: message powers w i m = mⁱ. Proximity: coordinates. Everything downstream is generic in w.i is programmed as ρᵢ·g₁. This is the c that extract_secret divides by.μ* − lin equals Σᵢ ρᵢ·Lᵢ; if some coordinate Lᵢ ≠ 0 on a fresh forgery, a deferred uniform ρᵢ zeroes it only with probability 1/|F|. Each scheme's non-degeneracy lemma says exactly "some Lᵢ ≠ 0".Forgery + reprA — the representation, collapsedstructure Forgery (F M ι Q) where mstar : M -- the forged message v : Fin Q → M -- the signing-query messages β : F -- coefficient on g₁ γ : ι → F -- coefficients on the base points ξ : Fin Q → F -- coefficients on the signatures def reprA (w) (fo) (ρ) : AffineForm F := ⟨fo.β + ∑ i, fo.γ i * ρ i, ∑ q, fo.ξ q * muW w ρ (fo.v q)⟩
forgeryElem) — handing over a Forgery is handing over the recipe.Hᵢ = ρᵢ·g₁, the generator and hashes are α-free — they land in const.α — lin reads exactly the signed messages. And forgeryElem_red proves σ* = (reprA …).eval α • g₁: soundness comes with the data, not as a separate hypothesis.realGame vs redGameAdef realGame (w) (adv) := do let fo ← adv; let α ← $ᵗ F; let H ← $ᵗ (ι → G₁) pure (fo, α, H, realSign w α H) -- real bases, real signing with α def redGameA (adv) := do let fo ← adv; let α ← $ᵗ F; let ρ ← $ᵗ (ι → F) pure (fo, α, ρ) -- known dlogs: Hᵢ = ρᵢ·g₁
H, signatures from the secret α. No challenge anywhere. Success is forgerySucc: the pairing check plus freshness m* ≠ v_q.ρᵢ·g₁ with ρ retained, so the reduction can evaluate reprA and muW in the field. hgen (bijectivity of · • g₁) makes the two views distribute identically — that is the simulation.α first and hand α·g₂ to the adversary before the independent base scalars — the ordering that keeps the deferred-coordinate collision argument valid. Dynamic.lean/Adaptive.lean lift all of this to per-query adaptive transcripts.affine_uniform_collisiontheorem affine_uniform_collision (oa) (qH) (a b t) (ha : ∀ z ∈ …, a z ≠ 0) : Pr[fun w => a w.1 * w.2 + b w.1 = t w.1 | (do z ← oa; ρ ← $ᵗ F; pure (z, ρ))] ≤ qH / |F|
The statement has three moving parts; here's each:
a, b, t), then draw the fresh uniform ρ.a ≠ 0, it holds for exactly one ρ = (t−b)/a — a singleton.ρ hits that one point with tiny probability. This generalizes the plain singleton close to the affine (collision-slot) case OOPS/Proximity need.agm_forgery_extract_splittheorem agm_forgery_extract_split (oa) (success repr c secret) (hsound : … success z → (repr z).eval (secret z) = c z * secret z) : Pr[success | oa] ≤ Pr[(repr z).const / (c z - (repr z).lin) = secret z | oa] -- extraction + Pr[success z ∧ (repr z).lin = c z | oa] -- bad event
One hypothesis, and a bound with two terms:
α internally, so no external α is needed.)extract_secret applied to every good outcome.extract_secret can't fire. The kernels (next slide) bound it by qH/|F|.uniform_hits_finset · indep_uniform_collisiontheorem uniform_hits_finset (S : Finset F) : Pr[ρ ∈ S | $ᵗ F] = S.card / |F| theorem indep_uniform_collision (oa) (qH) (candidates) (… ≤ qH) : Pr[w.2 ∈ candidates w.1 | (do z←oa; ρ←$ᵗ F; pure (z,ρ))] ≤ qH / |F|
The two facts the bad event reduces to: a uniform sample hits a set S with probability exactly |S|/|F|; and after a deferred-sampling hop, a fresh uniform ρ hits one of the ≤ qH committed values with probability ≤ qH/|F|. This closes the up-to-bad term.
pairing_dlog_relation → pairing_extracttheorem pairing_dlog_relation (hsound) (hverify) (hpair) : repr.eval α = c * α theorem pairing_extract (hsound) (hverify) (hpair) (hnondeg) : repr.const / (c - repr.lin) = α -- = pairing_dlog_relation ∘ extract_secret
This is the glue that supplies extract_secret's hypotheses. Its inputs:
repr (the AGM premise, from forgeryElem_red / reprA).Lcoeff).repr.eval α = c·α (pairing_dlog_relation); add the fourth and extract_secret finishes. A scheme supplies only its c and non-degeneracy.With the layer done, a scheme supplies only its weights w and one Lcoeff non-degeneracy lemma. The generic reduction is five lines — and extract_secret's formula appears verbatim:
def functionalDlogReduction (w) (extracted) : DLog11Adversary F G₁ G₂ := fun _ _ _ _ => do let fo ← extracted let ρ ← $ᵗ (ι → F) pure ((reprA w fo ρ).const / (muW w ρ fo.mstar - (reprA w fo ρ).lin)) def blsDlogReduction (extracted) := functionalDlogReduction (blsW M) extracted
Forgery — message, queries, coefficients.α·g₁.const / (c − lin) — extract_secret's formula, verbatim. BLS is one line on top; OOPS and Proximity swap in their own w.On top of this sit the headline endpoints: bls_euf_cma_real / proximity_euf_cma_real_* (representation endpoints), and — through Dynamic/Adaptive — the full-AGM OOPS theorem full_agm_unforgeable_dlog in the original degree-bounded game, plus the standard-model bls_eufcma_le_cocdh_coron_lazy_pk on the co-CDH side.
extract_secret is the engine: α = const/(c − lin).AffineForm + Forgery/reprA build its input from the adversary's recipe; muW supplies the c.pairing_extract hands it hypothesis one (verify ⇒ eval α = c·α); each scheme's Lcoeff non-degeneracy hands it hypothesis two (freshness ⇒ lin ≠ c).agm_forgery_extract_split + the collision kernels package it as advantage ≤ Pr[DLog] + 1/|F| — reused by every scheme; Dynamic/Adaptive lift it to fully adaptive transcripts.full_agm_unforgeable_dlog, no free representation hypothesis — the AGM interface itself is the model), the representation endpoints for BLS/Proximity (the recipe is supplied, not compiled), and the standard-model lazy-ROM co-CDH bound for BLS. These notes explain the source project's formalization; its Lean proofs and audit tools are not included in this standalone game.