import Mathlib.Data.ZMod.Basic set_option autoImplicit false -- `p` is the field characteristic; a prime, so `ZMod p` is a field. variable {p : ℕ} [Fact p.Prime] --------- Expressions --------- /-- A circuit variable: a display `name` plus an optional powdr witness-column ID. -/ structure Variable where name : String powdrId? : Option Nat := none deriving DecidableEq, Repr instance : BEq Variable := ⟨fun a b => decide (a = b)⟩ /-- An arithmetic expression over structured variables and field constants. -/ inductive Expression (p : ℕ) where | const (n : ZMod p) | var (x : Variable) | add (e1 e2 : Expression p) | mul (e1 e2 : Expression p) /-- Evaluate an expression under an assignment `env` of variables to field elements. -/ def Expression.eval (e : Expression p) (env : Variable → ZMod p) : ZMod p := match e with | .const n => n | .var x => env x | .add e1 e2 => e1.eval env + e2.eval env | .mul e1 e2 => e1.eval env * e2.eval env /-- The multiplicative degree of an expression. -/ def Expression.degree : Expression p → Nat | .const _ => 0 | .var _ => 1 | .add e1 e2 => max e1.degree e2.degree | .mul e1 e2 => e1.degree + e2.degree /-- The variables occurring in an expression. -/ def Expression.vars : Expression p → List Variable | .const _ => [] | .var x => [x] | .add e1 e2 => e1.vars ++ e2.vars | .mul e1 e2 => e1.vars ++ e2.vars --------- Computation Methods --------- /-- A method for computing a *derived* variable's value from other variables, mirroring powdr's `ComputationMethod`. For newly introduced variables, this is interpreted by powdr's witness generator. `quotientOrZero num den` is `num / den` in the field, or `0` when `den = 0`; `ifEqZero cond thenM elseM` picks `thenM` when `cond` evaluates to `0`, else `elseM`. -/ inductive ComputationMethod (p : ℕ) where | const (c : ZMod p) | quotientOrZero (num den : Expression p) | ifEqZero (cond : Expression p) (thenM elseM : ComputationMethod p) /-- Evaluate a computation method under an assignment (cf. powdr's `evaluate_computation_method`). -/ def ComputationMethod.eval : ComputationMethod p → (Variable → ZMod p) → ZMod p | .const c, _ => c | .quotientOrZero num den, env => if den.eval env = 0 then 0 else (den.eval env)⁻¹ * num.eval env | .ifEqZero cond thenM elseM, env => if cond.eval env = 0 then thenM.eval env else elseM.eval env /-- The variables a computation method may read. -/ def ComputationMethod.vars : ComputationMethod p → List Variable | .const _ => [] | .quotientOrZero num den => num.vars ++ den.vars | .ifEqZero cond thenM elseM => cond.vars ++ thenM.vars ++ elseM.vars /-- A list of derived variables paired with how to compute each, in order — the extra output of the optimizer, consumed by witness generation. -/ abbrev Derivations (p : ℕ) := List (Variable × ComputationMethod p) --------- Bus Interactions --------- /-- A bus interaction. Typically, α is - an expression (=> symbolic bus interaction), or - a field element (=> bus interaction message). -/ structure BusInteraction (α : Type) where busId : Nat multiplicity : α payload : List α /-- Evaluate a bus interaction under an assignment `env`, turning a symbolic bus interaction into a bus interaction message. -/ def BusInteraction.eval (bi : BusInteraction (Expression p)) (env : Variable → ZMod p) : BusInteraction (ZMod p) := { busId := bi.busId, multiplicity := bi.multiplicity.eval env, payload := bi.payload.map (fun e => e.eval env) } /-- Per-zkVM bound on the multiplicative degree of a circuit's expressions. -/ structure DegreeBound where identities : Nat busInteractions : Nat /-- The bus semantics of the zkVM. -/ -- TODO: Rename to VmSemantics structure BusSemantics (p : ℕ) where /-- Whether the bus of the given ID changes the state of the VM. Stateless bus interactions are typically lookups. -/ isStateful (busId : Nat) : Bool /-- Whether sending this bus interaction message violates a constraint in *another* chip. An example of this is sending a message that conflicts with a lookup table entry. -/ violatesConstraint (busInteractionMessage : BusInteraction (ZMod p)) : Bool /-- Whether sending this bus interaction message breaks an invariant on which soundness of the system depends. For example, a memory bus might have the invariant that all sent values must be in a certain range. -/ breaksInvariant (busInteractionMessage : BusInteraction (ZMod p)) : Bool /-- A property on *stateful* bus messages with nonzero multiplicity. Completeness is only required for assignments whose stateful messages are `admissible`. One useful way to use this is to describe the semantics of memory buses, see ``ApcOptimizer/MemoryBus.lean``. -/ admissible (statefulBusMessages: List (BusInteraction (ZMod p))): Prop /-- The zkVM's degree bound. -/ degreeBound : DegreeBound /-- A concrete bus interaction message: which bus, and the tuple sent. -/ abbrev BusMessage (p : ℕ) := Nat × List (ZMod p) /-- The effect on the stateful buses: the messages sent, each with a multiplicity. -/ abbrev BusState (p : ℕ) := List (BusMessage p × ZMod p) /-- The net multiplicity with which `message` is sent in `state`. -/ def multiplicitySum (message : BusMessage p) (state : BusState p) : ZMod p := match state with | [] => 0 | (msg, mult) :: tl => (if msg = message then mult else 0) + multiplicitySum message tl /-- Two bus states are equal when every message is sent with the same net multiplicity. -/ instance : HasEquiv (BusState p) := ⟨fun s t => ∀ message, multiplicitySum message s = multiplicitySum message t⟩ --------- Constraint System --------- /-- A constraint system representing a single zkVM chip. -/ structure ConstraintSystem (p : ℕ) where algebraicConstraints : List (Expression p) busInteractions : List (BusInteraction (Expression p)) /-- The variables occurring anywhere in a constraint system. -/ def ConstraintSystem.vars (cs : ConstraintSystem p) : List Variable := cs.algebraicConstraints.flatMap Expression.vars ++ cs.busInteractions.flatMap (fun bi => bi.multiplicity.vars ++ bi.payload.flatMap Expression.vars) /-- The side effects of a constraint system under a given environment and bus semantics. The side effects are the tuples sent to the *stateful* buses.-/ def ConstraintSystem.sideEffects (cs : ConstraintSystem p) (busSemantics : BusSemantics p) (env : Variable → ZMod p) : BusState p := cs.busInteractions.filter (fun bi => busSemantics.isStateful bi.busId) |>.map (fun bi => let m := bi.eval env ((m.busId, m.payload), m.multiplicity)) --------- Derived variables --------- /-- The `ComputationMethod` witness generation uses for `v`: the **last** one `ds` lists for it (later derivations override earlier ones), or `none` if `v` is not derived. -/ def Derivations.methodFor : Derivations p → Variable → Option (ComputationMethod p) | [], _ => none | (u, cm) :: rest, v => (Derivations.methodFor rest v).orElse (fun _ => if u = v then some cm else none) /-- Whether `ds` lets witness generation produce every element of `outputVars` from `inputVars`: each output variable is either an input variable (reused) or a derived variable with a method that reads only input variables. -/ def Derivations.cover (ds : Derivations p) (inputVars outputVars : List Variable) : Prop := ∀ v ∈ outputVars, match v.powdrId? with | some _ => v ∈ inputVars | none => ∃ cm, ds.methodFor v = some cm ∧ ∀ x ∈ cm.vars, x ∈ inputVars /-- Witness generation: reconstruct an output assignment from an input assignment. Every powdr-ID (input) variable passes through unchanged; every other variable is computed by the method `ds` records for it, read from the input variables. This is what powdr runs to fill the optimized circuit's variables from an input trace. -/ def Derivations.witgen (ds : Derivations p) (inputEnv : Variable → ZMod p) : Variable → ZMod p := fun v => match v.powdrId? with -- Note that by `Derivations.cover`, if `v` appears in the output constraint system, -- it must also exist in the input constraint system, so this case is always well-defined. | some _ => inputEnv v | none => match Derivations.methodFor ds v with | some cm => cm.eval inputEnv -- Note that by `Derivations.cover`, if `v` appears in the output constraint system, -- this case is impossible. | none => inputEnv v --------- Constraint system implications --------- /-- Whether a given assignment is admissible under the bus semantics. -/ def ConstraintSystem.admissible (s : ConstraintSystem p) (busSemantics : BusSemantics p) (env : Variable → ZMod p) : Prop := busSemantics.admissible ((s.busInteractions.map (fun bi => bi.eval env)).filter (fun m => decide (m.multiplicity ≠ 0) && busSemantics.isStateful m.busId)) /-- Whether a constraint system is satisfied under a given environment and bus semantics, i.e., whether it satisfies all algebraic constraints and does not violate any bus constraints. -/ def ConstraintSystem.satisfies (s : ConstraintSystem p) (busSemantics : BusSemantics p) (env : Variable → ZMod p) : Prop := (∀ c ∈ s.algebraicConstraints, c.eval env = 0) ∧ (∀ bi ∈ s.busInteractions, let message := bi.eval env message.multiplicity ≠ 0 → busSemantics.violatesConstraint message = false) /-- Whether a constraint system guarantees that all invariants are maintained under a given bus semantics. -/ def ConstraintSystem.guaranteesInvariants (s : ConstraintSystem p) (busSemantics : BusSemantics p) : Prop := ∀ env, s.satisfies busSemantics env → ∀ bi ∈ s.busInteractions, let message := bi.eval env message.multiplicity ≠ 0 → busSemantics.breaksInvariant message = false /-- Whether an optimized constraint system is a sound replacement for an original constraint system. Informally, for any satisfying assignment of the optimized system, there exists a corresponding satisfying assignment of the original system *with equivalent side effects*. Also, the optimized system must maintain all invariants guaranteed by the original system. -/ def ConstraintSystem.isSoundReplacementOf (optimizedCS originalCS : ConstraintSystem p) (busSemantics : BusSemantics p) : Prop := (∀ env, optimizedCS.satisfies busSemantics env → ∃ env', originalCS.satisfies busSemantics env' ∧ optimizedCS.sideEffects busSemantics env ≈ originalCS.sideEffects busSemantics env') ∧ (originalCS.guaranteesInvariants busSemantics → optimizedCS.guaranteesInvariants busSemantics) /-- Whether an optimized constraint system is a complete replacement for an original one. Assuming every input variable carries a powdr ID, then for any admissible satisfying assignment of the original constraint system, there is a computable assignment of the optimized system that is itself satisfying and admissible, with equivalent side effects. -/ def ConstraintSystem.isCompleteReplacementOf (optimizedCS originalCS : ConstraintSystem p) (busSemantics : BusSemantics p) (ds : Derivations p) : Prop := (∀ v ∈ originalCS.vars, v.powdrId?.isSome) → ∀ env, originalCS.admissible busSemantics env → originalCS.satisfies busSemantics env → ds.cover originalCS.vars optimizedCS.vars ∧ let env' := Derivations.witgen ds env optimizedCS.satisfies busSemantics env' ∧ optimizedCS.admissible busSemantics env' ∧ originalCS.sideEffects busSemantics env ≈ optimizedCS.sideEffects busSemantics env' --------- Degree bound --------- /-- Whether a constraint system stays within a degree bound. -/ def ConstraintSystem.withinDegree (s : ConstraintSystem p) (b : DegreeBound) : Prop := (∀ c ∈ s.algebraicConstraints, c.degree ≤ b.identities) ∧ (∀ bi ∈ s.busInteractions, bi.multiplicity.degree ≤ b.busInteractions ∧ ∀ e ∈ bi.payload, e.degree ≤ b.busInteractions) /-- Whether an optimizer for the fixed `busSemantics` respects the zkVM's degree bound: a within-bound input always yields a within-bound output. -/ def optimizerRespectsDegreeBound (busSemantics : BusSemantics p) (optimizer : ConstraintSystem p → ConstraintSystem p × Derivations p) : Prop := ∀ constraintSystem : ConstraintSystem p, constraintSystem.withinDegree busSemantics.degreeBound → (optimizer constraintSystem).1.withinDegree busSemantics.degreeBound --------- Optimizer correctness --------- abbrev Optimizer (p : ℕ) := ConstraintSystem p → ConstraintSystem p × Derivations p /-- An optimizer is correct if, for every input constraint system, replacing it with the optimized system is both sound and complete, and the optimizer respects the degree bound. -/ def Optimizer.isCorrect (optimizer : Optimizer p) (busSemantics : BusSemantics p) : Prop := (∀ originalCS : ConstraintSystem p, let (optimizedCS, derivations) := optimizer originalCS (optimizedCS.isSoundReplacementOf originalCS busSemantics) ∧ (optimizedCS.isCompleteReplacementOf originalCS busSemantics derivations)) ∧ optimizerRespectsDegreeBound busSemantics optimizer