Walkthrough · the AGM layer, definition by definition
Every definition, in build order
The AGM layer,
brick by brick.

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.

Navigate with ← →, the buttons, or the dots. Code blocks scroll sideways on their own.
The idea

What the Algebraic Group Model says

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).

the AGM premise
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.

The map

The directory we added

Two components, Spec/ before Proofs/. Read top to bottom — each file uses the ones above it. We'll open every definition.

Spec/BilinearGroupthe abstract pairing
Spec/DLog11the target hardness problem
Spec/AffineForma dlog as const + lin·α
Proofs/Extractionthe core lemma extract_secret
Proofs/ForgeryGamethe generic split + collision kernels
Proofs/PairingExtractionverify ⇒ the affine DLog equation
LinearFunctional/Spec/Handlethe shared weights muW · Lcoeff
LinearFunctional/ProofsForgery · reprA · the two games (+ Dynamic/Adaptive)
Before the pieces · the borrowed vocabulary

Everything here is Lean, Mathlib, or VCVio

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.

Leanthe language: declarations, do-notation, pattern matching, tactics
Mathlibthe algebra: fields, groups, modules, scalar action, finite sums
VCViothe framework: probabilistic computations, oracles, the signature game
Vocabulary · the Lean language

Lean syntax you'll meet

structure · inductive · classdeclare a record · an enum · a typeclass def · theorem · lemmaa definition · a proved statement do   let x ← esequence a computation; bind its result to 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"
Vocabulary · Mathlib (the algebra)

Mathlib terms — the math

Field Fa field: add, multiply, divide — the scalars α, ρ, 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)
Vocabulary · what we use from VCVio

VCVio terms — the framework

The AGM core imports exactly five VCVio modules. This is its whole borrowed surface — the probability/oracle language plus one generic signature game:

OracleComp spec α · OracleSpeca computation with oracle access · a description of the oracles ProbComp α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
The line is clean:everything probabilistic is VCVio's; everything algebraic (the AGM) is ours. The five modules: OracleComp.ProbComp, Constructions.SampleableType, EvalDist.Bool, SimSemantics.QueryImpl, CryptoFoundations.SignatureAlg.
Spec/BilinearGroup.lean · piece 1

class BilinearPairing

class Spec/BilinearGroup.lean:20
class 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'
  • The abstract pairing 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).
Spec/BilinearGroup.lean · piece 1

pairing + bilinearity lemmas

def · simp lemmas Spec/BilinearGroup.lean:32–51
def 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.

Spec/DLog11.lean · piece 2

DLog11Adversary & dlog11Exp

def Spec/DLog11.lean:25, 32
def 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:

let α ← $ᵗ FSample the secret α uniformly from the field.
adversary g₁ (α • g₁) g₂ (α • g₂)Hand it the challenge — the four public elements — and let it return a guess α'.
return decide (α' = α)Win iff the guess is right. This whole game is the Pr[= true | dlog11Exp …] on the right of every AGM bound.
Spec/AffineForm.lean · piece 3

AffineForm — a dlog as const + lin·α

structure · def Spec/AffineForm.lean:23, 32
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.

Proofs/Extraction.lean · the whole engine

extract_secret — four lines

theorem Proofs/Extraction.lean:27
theorem 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).
  • ⇒ the secret is const/(c − lin). This one lemma is the entire cryptographic core.
LinearFunctional/Spec/Handle.lean · the shared weights

muW & Lcoeff — one functional, three schemes

def · def LinearFunctional/Spec/Handle.lean:23, 27
def 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 → FA scheme is a weight matrix. BLS: the indicator w m′ m = [m = m′]. OOPS: message powers w i m = mⁱ. Proximity: coordinates. Everything downstream is generic in w.
muW ρ m = Σᵢ ρᵢ·wᵢ(m)The target scalar — the dlog of the verification target when base i is programmed as ρᵢ·g₁. This is the c that extract_secret divides by.
Lcoeff … i = wᵢ(m*) − Σ_q ξ_q·wᵢ(v_q)The freshness functional. The denominator μ* − 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".
LinearFunctional/Proofs/EndToEnd.lean · the premise as data

Forgery + reprA — the representation, collapsed

structure · def LinearFunctional/Proofs/EndToEnd.lean:97, 162
structure 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)⟩
σ* = β·g₁ + Σᵢ γᵢ·Hᵢ + Σⱼ ξⱼ·σⱼThe AGM premise as fields: the forged element is exactly this combination (forgeryElem) — handing over a Forgery is handing over the recipe.
const = β + Σᵢ γᵢ·ρᵢUnder programmed bases Hᵢ = ρᵢ·g₁, the generator and hashes are α-free — they land in const.
lin = Σ_q ξ_q·μ(v_q)Signatures are the only source of α — lin reads exactly the signed messages. And forgeryElem_red proves σ* = (reprA …).eval α • g₁: soundness comes with the data, not as a separate hypothesis.
LinearFunctional/Proofs/EndToEnd.lean · the two games

realGame vs redGameA

def · def LinearFunctional/Proofs/EndToEnd.lean:127, 134
def 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₁
realGameThe uniform side: everything sampled honestly — uniform base points H, signatures from the secret α. No challenge anywhere. Success is forgerySucc: the pairing check plus freshness m* ≠ v_q.
redGameAThe reduction side: the base points become ρᵢ·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.
realGamePK · redGameAPKThe pk-aware variants sample α 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.
ForgeryGame.lean · the collision kernel

affine_uniform_collision

theorem Proofs/ForgeryGame.lean:95
theorem 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:

do z ← oa; ρ ← $ᵗ F; pure (z, ρ)Deferred sampling: run the adversary first (fixing a, b, t), then draw the fresh uniform ρ.
a z ≠ 0The one hypothesis: the leading coefficient is nonzero on every run.
a·ρ + b = tThe bad event. Since a ≠ 0, it holds for exactly one ρ = (t−b)/a — a singleton.
≤ qH / |F|Conclusion: a fresh uniform ρ hits that one point with tiny probability. This generalizes the plain singleton close to the affine (collision-slot) case OOPS/Proximity need.
ForgeryGame.lean · the generic split

agm_forgery_extract_split

theorem Proofs/ForgeryGame.lean:43
theorem 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:

hsound : success z → (repr z).eval (secret z) = c z · secret zA win satisfies the DLog relation on every run. (The game samples α internally, so no external α is needed.)
Pr[ const/(c − lin) = secret ]Extraction term: the reduction's output equals the secret — the DLog advantage. This is extract_secret applied to every good outcome.
Pr[ success ∧ lin = c ]Bad-event term: the one degenerate case where extract_secret can't fire. The kernels (next slide) bound it by qH/|F|.
ForgeryGame.lean · the probability kernels

uniform_hits_finset · indep_uniform_collision

theorems Proofs/ForgeryGame.lean:59, 71
theorem 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.

PairingExtraction.lean · verify ⇒ the equation

pairing_dlog_relation → pairing_extract

theorems Proofs/PairingExtraction.lean:29, 42
theorem 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:

hsound : σ = repr.eval α • g₁The forgery's dlog is the affine form repr (the AGM premise, from forgeryElem_red / reprA).
hverify : e(σ, g₂) = e(c·g₁, α·g₂)The pairing verification equation the forgery passes.
hpair : Injective (· • pairing g₁ g₂)Pairing injectivity on the exponent — lets you cancel the pairing and read the equation in the field.
hnondeg : repr.lin ≠ cThe non-degeneracy (from freshness, via Lcoeff).
⇒ const / (c − lin) = αResult: the first three give repr.eval α = c·α (pairing_dlog_relation); add the fourth and extract_secret finishes. A scheme supplies only its c and non-degeneracy.
The payoff · instantiating

A scheme just plugs in

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 LinearFunctional/Proofs/EndToEnd.lean:371 · BLS/Proofs/EndToEnd.lean:67
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
let fo ← extractedRun the algebraic forger and take its Forgery — message, queries, coefficients.
let ρ ← $ᵗ (ι → F)Sample the base-point scalars — the reduction knows them, so it can build every hash and sign every query off the public α·g₁.
(reprA …).const / (muW ρ m* − (reprA …).lin)Output 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.

Zooming back out

The whole layer, in one breath

  • 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.
That's the layer— a short stack, not a wall. On top of it: the full-AGM OOPS route (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.
1 / 30