You are an expert Lean4 and formal verification agent proving a Lean4 theorem. Your output **must** strictly adhere to the specified format.

### Input Information
You will receive the following components, relevant to the target theorem:
* <all_available_defs>: Definitions available from imported modules (either from the current Lean repository or from Mathlib)
* <all_available_lemmas>: Lemmas/theorems available that may assist the proof.
* <local_ctx>...</local_ctx>: The local file context (imports, namespaces, definitions, comments, etc.) from the top of the file up to—but not including—the target theorem or any previously defined local lemmas/theorems.
* <target_theorem>...</target_theorem>: The target theorem statement, ending with `:=`.

**Notes**
1. Definitions include their full bodies, but for definitions whose bodies contain proofs, the proof parts are replaced by `admit` (i.e., admit /- proof elided -/), while the rest of the definition is kept unchanged; lemmas include only their statements.
2. You may freely reference any definition or lemma contained in these tags without redefining them.
3. The tags for available definitions and lemmas (all_available_defs, all_available_lemmas) may be omitted if empty.

### Output Format & Requirements
Your output MUST contain a complete, syntactically correct Lean 4 proof for the target theorem.

**Strict Output Structure:**
<reasoning> 
...
</reasoning> 

<lean4_invented_lemmas>
-- each lemma is a full Lean 4 declaration with a complete proof
...
</lean4_invented_lemmas>

<lean4_proof>
-- Lean 4 proof body of the target theorem
...
</lean4_proof>

**Requirements:**
1. <reasoning> and <lean4_invented_lemmas> are OPTIONAL. Include them only if they aid proof construction.
2. <lean4_proof> MUST contain only the proof body (tactics/terms), starting by "by", NOT the theorem declaration.
3. <lean4_invented_lemmas> (if present) MUST contain syntactically correct lemma declarations that type checks AND complete proof bodies. Defining a lemma using `axiom` is NOT allowed.
4. <reasoning> contains high-level proof planning.
5. If you create a lemma under <lean4_invented_lemmas> whose name is namespaced (e.g., X.Y.Z), you MUST always refer to it using its fully qualified name. Do NOT rely on shortened names. For example, write rw [X.Y.Z], not rw [Z] for invented lemmas.
6. All Lean 4 code MUST be syntactically correct. No `sorry`, `admit`, or non-Lean text. Comments allowed using `--` or `/- ... -/`.
7. Follow the tags and structure EXACTLY. No extra text before, between, or after the sections.
8. Do NOT wrap invented lemmas in a `mutual` block unless they have genuine mutual recursion. Declare each lemma independently.

### Examples are provided here (example input are under "User Prompt", and example assistant response are under "Agent Response"):
Example 1 ------------------- 
User Prompt:
<all_available_defs>
structure BitVec (w : Nat) where
  /-- Construct a `BitVec w` from a number less than `2^w`.
  O(1), because we use `Fin` as the internal representation of a bitvector. -/
  ofFin ::

  /-- Interpret a bitvector as a number less than `2^w`.
  O(1), because we use `Fin` as the internal representation of a bitvector. -/
  toFin : Fin (hPow 2 w)
</all_available_defs>

<all_available_lemmas>
theorem List.getElem_append_left{α : Type u_1} {l₁ l₂ : List α} {i : Nat} (hn : i < l₁.length) : (l₁ ++ l₂)[i] = l₁[i]
theorem getElem_eq_testBit_toNat (x : BitVec w) (i : Nat) (h : i < w) :
  x[i] = x.toNat.testBit i := rfl
@[simp, grind =] theorem toNat_cons (b : Bool) (x : BitVec w) :
    (cons b x).toNat = (b.toNat <<< w) ||| x.toNat
@[simp, grind =] theorem getLsbD_of_ge (x : BitVec w) (i : Nat) (ge : w ≤ i) : getLsbD x i = false
theorem testBit_toNat (x : BitVec w) : x.toNat.testBit i = x.getLsbD i
</all_available_lemmas>

<local_ctx>
import Mathlib.Computability.NFA
import Mathlib.Data.FinEnum
import Mathlib.Data.Rel
import Mathlib.Data.Vector.Basic
import Blase.AutoStructs.ForLean

open Set
open Mathlib
open SetRel

-- this is better because card is defeq to n
instance (α : Type) : Inter (Language α) := ⟨Set.inter⟩
instance (α : Type) : Union (Language α) := ⟨Set.union⟩

/--
The set of `n`-tuples of bit vectors of an arbitrary width.
-/
structure BitVecs (n : Nat) where
  w : Nat
  bvs : List.Vector (BitVec w) n

def BitVecs.cast (bvs : BitVecs n) (h : n = n') : BitVecs n' :=
  { w := bvs.w, bvs := h ▸ bvs.bvs }

abbrev BitVecs.empty : BitVecs n := ⟨0, List.Vector.replicate n .nil⟩
abbrev BitVecs.singleton {w : Nat} (bv : BitVec w) : BitVecs 1 := ⟨w, bv ::ᵥ .nil⟩
abbrev BitVecs.pair {w : Nat} (bv1 bv2 : BitVec w) : BitVecs 2 := ⟨w, bv1 ::ᵥ bv2 ::ᵥ .nil⟩

/--
The set of `n`-tuples of bit vectors of an arbitrary width, encoded as a list of
bit vectors of width `n`. The width of the encoded bit vectors is the length of
the list.
-/
abbrev BitVecs' (n : Nat) := List (BitVec n)

@[simps]
def dec (bvs' : BitVecs' n) : BitVecs n where
  w := bvs'.length
  bvs := List.Vector.ofFn fun k => BitVec.ofFn fun i => bvs'[i].getLsbD k

@[simp]
</local_ctx>

<target_theorem>
lemma dec_snoc n (bvs' : BitVecs' n) (a : BitVec n) :
  dec (bvs' ++ [a]) =
    { w := bvs'.length + 1
      bvs := List.Vector.ofFn fun k =>
        BitVec.cons (a.getLsbD k) ((dec bvs').bvs.get k) } :=
</target_theorem>

Agent Response:
<reasoning> 
The goal is to understand what `dec` does after appending one more bitvector `a` to `bvs'`. By definition, `dec` builds a width `= length` and a vector of bitvectors whose `i`-th bit at position `k` is `bvs'[i].getLsbD k`. So for `bvs' ++ [a]`, we expect the width to increase by 1 and each resulting bitvector to be `BitVec.cons` of the new bit coming from `a`.

First, I use `ext` twice: once on the record to focus on the `bvs` field, and once on the resulting bitvector. This reduces everything to a pointwise statement:
for fixed `k` and `i` with `i < bvs'.length + 1`,
the bit `(bvs' ++ [a])[i].getLsbD k` should match the bit at index `i` of `BitVec.cons (a.getLsbD k) ((dec bvs').bvs.get k)`.

At this point I need a clean description of indexing into `BitVec.cons`: if we have `x : BitVec n`, then `(cons b x)[i]` should give `b` at the new “last” index `n` and `x[i]` otherwise. That motivates an auxiliary lemma `getElem_cons` expressing exactly this behavior, proved via the `testBit`/`toNat` characterization and a case split on `i < n`, `i = n`, `i > n`.

Using `getElem_cons`, the RHS becomes an `if` on whether `i = bvs'.length`. So I split into cases:
- If `i = bvs'.length`, then `(bvs' ++ [a])[i]` is the appended `a`, so both sides reduce to `a.getLsbD k`.
- If `i ≠ bvs'.length`, the bound `i < bvs'.length + 1` forces `i < bvs'.length`. Then `(bvs' ++ [a])[i] = bvs'[i]` by `List.getElem_append_left`, and the RHS takes the “else” branch and likewise gives the bit from `bvs'[i]`. In both subcases, `simp` (plus the definition of `dec`) finishes.

So the whole proof is: `ext` to bit level, unfold `dec` with `simp_all`, rewrite with `getElem_cons`, split on the boundary index `i = length bvs'`, and in the “< length” branch use `List.getElem_append_left` and bounds to line up both sides.
</reasoning> 

<lean4_invented_lemmas>
@[grind =]
theorem getElem_cons {b : Bool} {n} {x : BitVec n} {i : Nat} (h : i < n + 1) :
    (cons b x)[i] = if h : i = n then b else x[i] := by
  simp only [BitVec.getElem_eq_testBit_toNat, BitVec.toNat_cons, Nat.testBit_or]
  rw [Nat.testBit_shiftLeft]
  rcases Nat.lt_trichotomy i n with i_lt_n | i_eq_n | n_lt_i
  · have p1 : ¬(n ≤ i) := by omega
    have p2 : i ≠ n := by omega
    simp [p1, p2]
  · simp only [i_eq_n, ge_iff_le, Nat.le_refl, decide_true, Nat.sub_self, Nat.testBit_zero,
    Bool.true_and, BitVec.testBit_toNat, BitVec.getLsbD_of_ge, Bool.or_false]
    cases b <;> trivial
  · have p1 : i ≠ n := by omega
    have p2 : i - n ≠ 0 := by omega
    simp [p1, p2, Nat.testBit_bool_toNat]
</lean4_invented_lemmas>

<lean4_proof>
by
  ext k i hi <;> simp_all
  rw [getElem_cons]
  split
  next heq => simp_all
  next h =>
    have hlt : i < List.length bvs' := by simp at hi; omega
    rw [List.getElem_append_left hlt]
    simp
</lean4_proof>

### Your task:
<all_available_defs>
namespace Gadgets.Rotation32
instance : Fact (p > 512) :=
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def main (offset : Fin 32) (x : Var U32 (F p)) : Circuit (F p) (Var U32 (F p)) := do
  let byte_offset : Fin 4 := ⟨ offset.val / 8, by admit /- proof elided -/
  ⟩
  let bit_offset : Fin 8 := ⟨ offset.val % 8, by admit /- proof elided -/
  ⟩

  
  let byte_rotated ← Rotation32Bytes.circuit byte_offset x
  Rotation32Bits.circuit bit_offset byte_rotated
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def Assumptions (input : U32 (F p)) := input.Normalized
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def Spec (offset : Fin 32) (x : U32 (F p)) (y : U32 (F p)) :=
  y.value = rotRight32 x.value offset.val
  ∧ y.Normalized
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def output (offset : Fin 32) (i0 : ℕ) : U32 (Expression (F p)) :=
  Rotation32Bits.output ⟨ offset.val % 8, by admit /- proof elided -/
  ⟩ i0
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def elaborated (off : Fin 32) : ElaboratedCircuit (F p) U32 U32 where
  main := main off
  localLength _ := 8
  output _inputs i0 := output off i0
end Gadgets.Rotation32

namespace Gadgets.Rotation32
def circuit (offset : Fin 32) : FormalCircuit (F p) U32 U32 := {
  elaborated offset with
  Assumptions
  Spec := Spec offset
  soundness := soundness offset
  completeness := completeness offset
}
end Gadgets.Rotation32

def FormalCircuit.toSubcircuit (circuit : FormalCircuit F β α)
    (n : ℕ) (input_var : Var β F) : Subcircuit F n :=
  let ops := circuit.main input_var |>.operations n
  have h_consistent : ops.SubcircuitsConsistent n := circuit.subcircuitsConsistent input_var n

  have imply_soundness : ∀ env : Environment F,
    let input := eval env input_var
    let output := eval env (circuit.output input_var n)
    ConstraintsHoldFlat env ops.toFlat → circuit.Assumptions input → circuit.Spec input output :=

def FormalAssertion.toSubcircuit (circuit : FormalAssertion F β)
    (n : ℕ) (input_var : Var β F) : Subcircuit F n :=
  let ops := circuit.main input_var |>.operations n
  have h_consistent : ops.SubcircuitsConsistent n := circuit.subcircuitsConsistent input_var n

  {
    ops := ops.toFlat,
    Soundness env := circuit.Assumptions (eval env input_var) → circuit.Spec (eval env input_var),
    Completeness env := circuit.Assumptions (eval env input_var) ∧ circuit.Spec (eval env input_var),
    UsesLocalWitnesses _ := True,
    localLength := circuit.localLength input_var

    imply_soundness := by admit /- proof elided -/

def GeneralFormalCircuit.toSubcircuit (circuit : GeneralFormalCircuit F β α)
    (n : ℕ) (input_var : Var β F) : Subcircuit F n :=
  let ops := circuit.main input_var |>.operations n
  have h_consistent : ops.SubcircuitsConsistent n := circuit.subcircuitsConsistent input_var n

  have imply_soundness : ∀ env : Environment F,
      let input := eval env input_var
      let output := eval env (circuit.output input_var n)
      ConstraintsHoldFlat env ops.toFlat → circuit.Spec input output :=

@[circuit_norm]
def subcircuit (circuit : FormalCircuit F β α) (b : Var β F) : Circuit F (Var α F) :=
  fun offset =>
    let a := circuit.output b offset
    let subcircuit := circuit.toSubcircuit offset b
    (a, [.subcircuit subcircuit])

@[circuit_norm]
def assertion (circuit : FormalAssertion F β) (b : Var β F) : Circuit F Unit :=
  fun offset =>
    let subcircuit := circuit.toSubcircuit offset b
    ((), [.subcircuit subcircuit])

@[circuit_norm]
def subcircuitWithAssertion (circuit : GeneralFormalCircuit F β α) (b : Var β F) : Circuit F (Var α F) :=
  fun offset =>
    let a := circuit.output b offset
    let subcircuit := circuit.toSubcircuit offset b
    (a, [.subcircuit subcircuit])

instance : CoeFun (FormalCircuit F β α) (fun _ => Var β F → Circuit F (Var α F)) where
  coe circuit input := subcircuit circuit input

instance : CoeFun (FormalAssertion F β) (fun _ => Var β F → Circuit F Unit) where
  coe circuit input := assertion circuit input

instance : CoeFun (GeneralFormalCircuit F β α) (fun _ => Var β F → Circuit F (Var α F)) where
  coe circuit input := subcircuitWithAssertion circuit input

namespace ElaboratedCircuit
def ComputableWitnesses' (circuit : ElaboratedCircuit F β α) : Prop :=
  ∀ (n : ℕ) (input : Var β F),
    Environment.OnlyAccessedBelow n (eval · input) →
      (circuit.main input).ComputableWitnesses n
end ElaboratedCircuit

namespace ElaboratedCircuit
def ComputableWitnesses (circuit : ElaboratedCircuit F β α) : Prop :=
  ∀ (n : ℕ) (input : Var β F) env env',
  circuit.main input |>.operations n |>.forAllFlat n {
    witness n _ compute :=
      env.AgreesBelow n env' → eval env input = eval env' input → compute env = compute env' }
end ElaboratedCircuit

namespace Gadgets.BLAKE3.FinalStateUpdate
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
structure Inputs (F : Type) where
  state : BLAKE3State F
  chaining_value : Vector (U32 F) 8
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
instance : ProvableStruct Inputs where
  components := [BLAKE3State, ProvableVector U32 8]
  toComponents := fun { state, chaining_value } => .cons state (.cons chaining_value .nil)
  fromComponents := fun (.cons state (.cons chaining_value .nil)) => { state, chaining_value }
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
def main (input : Var Inputs (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  let { state, chaining_value } := input

  
  let s0 ← Xor32.circuit ⟨state[0], state[8]⟩
  let s1 ← Xor32.circuit ⟨state[1], state[9]⟩
  let s2 ← Xor32.circuit ⟨state[2], state[10]⟩
  let s3 ← Xor32.circuit ⟨state[3], state[11]⟩
  let s4 ← Xor32.circuit ⟨state[4], state[12]⟩
  let s5 ← Xor32.circuit ⟨state[5], state[13]⟩
  let s6 ← Xor32.circuit ⟨state[6], state[14]⟩
  let s7 ← Xor32.circuit ⟨state[7], state[15]⟩

  
  let s8 ← Xor32.circuit ⟨chaining_value[0], state[8]⟩
  let s9 ← Xor32.circuit ⟨chaining_value[1], state[9]⟩
  let s10 ← Xor32.circuit ⟨chaining_value[2], state[10]⟩
  let s11 ← Xor32.circuit ⟨chaining_value[3], state[11]⟩
  let s12 ← Xor32.circuit ⟨chaining_value[4], state[12]⟩
  let s13 ← Xor32.circuit ⟨chaining_value[5], state[13]⟩
  let s14 ← Xor32.circuit ⟨chaining_value[6], state[14]⟩
  let s15 ← Xor32.circuit ⟨chaining_value[7], state[15]⟩

  return #v[s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15]
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
instance elaborated : ElaboratedCircuit (F p) Inputs BLAKE3State where
  main := main
  localLength _ := 64
  output inputs i0 := #v[
    varFromOffset U32 (i0 + 0),
    varFromOffset U32 (i0 + 4),
    varFromOffset U32 (i0 + 8),
    varFromOffset U32 (i0 + 12),
    varFromOffset U32 (i0 + 16),
    varFromOffset U32 (i0 + 20),
    varFromOffset U32 (i0 + 24),
    varFromOffset U32 (i0 + 28),
    varFromOffset U32 (i0 + 32),
    varFromOffset U32 (i0 + 36),
    varFromOffset U32 (i0 + 40),
    varFromOffset U32 (i0 + 44),
    varFromOffset U32 (i0 + 48),
    varFromOffset U32 (i0 + 52),
    varFromOffset U32 (i0 + 56),
    varFromOffset U32 (i0 + 60)
  ]

  localLength_eq _ n := by admit /- proof elided -/
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
def Assumptions (input : Inputs (F p)) :=
  let { state, chaining_value } := input
  state.Normalized ∧ (∀ i : Fin 8, chaining_value[i].Normalized)
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
def Spec (input : Inputs (F p)) (out : BLAKE3State (F p)) :=
  let { state, chaining_value } := input
  out.value = finalStateUpdate state.value (chaining_value.map U32.value) ∧ out.Normalized
end Gadgets.BLAKE3.FinalStateUpdate

namespace Gadgets.BLAKE3.FinalStateUpdate
def circuit : FormalCircuit (F p) Inputs BLAKE3State := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.BLAKE3.FinalStateUpdate

namespace ProvableStructNaming
def getStructureFieldNames (structName : Name) : MetaM (Array Name) := do
  let env ← getEnv
  match getStructureInfo? env structName with
  | some _ =>
    
    return getStructureFields env structName
  | none => throwError "Not a structure: {structName}"
end ProvableStructNaming

namespace ProvableStructNaming
def generateFieldBasedNames (baseName : Name) (fieldNames : Array Name) : Array Name :=
  fieldNames.map fun fieldName => baseName.appendAfter s!"_{fieldName}"
end ProvableStructNaming

namespace ProvableStructNaming
def generateStructFieldNames (fvarId : FVarId) : MetaM AltVarNames := do
  let localDecl ← fvarId.getDecl
  let userName := localDecl.userName

  
  let varType ← inferType (.fvar fvarId)
  let varType' ← whnf varType

  
  let structName ← match varType' with
  | .app (.const name _) _ => pure name
  | .const name _ => pure name
  | _ => throwError "Cannot extract structure name from type: {varType'}"

  
  let fieldNames ← getStructureFieldNames structName

  
  let customNames := generateFieldBasedNames userName fieldNames

  
  return { varNames := customNames.toList }
end ProvableStructNaming

namespace Specs.Keccak256
def roundConstants : Vector UInt64 24 := #v[
  0x0000000000000001, 0x0000000000008082,
  0x800000000000808a, 0x8000000080008000,
  0x000000000000808b, 0x0000000080000001,
  0x8000000080008081, 0x8000000000008009,
  0x000000000000008a, 0x0000000000000088,
  0x0000000080008009, 0x000000008000000a,
  0x000000008000808b, 0x800000000000008b,
  0x8000000000008089, 0x8000000000008003,
  0x8000000000008002, 0x8000000000000080,
  0x000000000000800a, 0x800000008000000a,
  0x8000000080008081, 0x8000000000008080,
  0x0000000080000001, 0x8000000080008008
]
end Specs.Keccak256

namespace Specs.Keccak256
def bits2bytes (x : ℕ) : ℕ :=
  (x + 7) / 8
end Specs.Keccak256

namespace Specs.Keccak256
def thetaC (state : Vector ℕ 25) : Vector ℕ 5 :=
  #v[
    state[0] ^^^ state[1] ^^^ state[2] ^^^ state[3] ^^^ state[4],
    state[5] ^^^ state[6] ^^^ state[7] ^^^ state[8] ^^^ state[9],
    state[10] ^^^ state[11] ^^^ state[12] ^^^ state[13] ^^^ state[14],
    state[15] ^^^ state[16] ^^^ state[17] ^^^ state[18] ^^^ state[19],
    state[20] ^^^ state[21] ^^^ state[22] ^^^ state[23] ^^^ state[24]
  ]
end Specs.Keccak256

namespace Specs.Keccak256
def thetaD (c : Vector ℕ 5) : Vector ℕ 5 :=
  #v[
    c[4] ^^^ (rotLeft64 c[1] 1),
    c[0] ^^^ (rotLeft64 c[2] 1),
    c[1] ^^^ (rotLeft64 c[3] 1),
    c[2] ^^^ (rotLeft64 c[4] 1),
    c[3] ^^^ (rotLeft64 c[0] 1)
  ]
end Specs.Keccak256

namespace Specs.Keccak256
def thetaXor (state : Vector ℕ 25) (d : Vector ℕ 5) : Vector ℕ 25 :=
  #v[
    state[0] ^^^ d[0],
    state[1] ^^^ d[0],
    state[2] ^^^ d[0],
    state[3] ^^^ d[0],
    state[4] ^^^ d[0],
    state[5] ^^^ d[1],
    state[6] ^^^ d[1],
    state[7] ^^^ d[1],
    state[8] ^^^ d[1],
    state[9] ^^^ d[1],
    state[10] ^^^ d[2],
    state[11] ^^^ d[2],
    state[12] ^^^ d[2],
    state[13] ^^^ d[2],
    state[14] ^^^ d[2],
    state[15] ^^^ d[3],
    state[16] ^^^ d[3],
    state[17] ^^^ d[3],
    state[18] ^^^ d[3],
    state[19] ^^^ d[3],
    state[20] ^^^ d[4],
    state[21] ^^^ d[4],
    state[22] ^^^ d[4],
    state[23] ^^^ d[4],
    state[24] ^^^ d[4]
  ]
end Specs.Keccak256

namespace Specs.Keccak256
def theta (state : Vector ℕ 25) : Vector ℕ 25 :=
  let c := thetaC state
  let d := thetaD c
  thetaXor state d
end Specs.Keccak256

namespace Specs.Keccak256
def rhoPi (state : Vector ℕ 25) : Vector ℕ 25 :=
  #v[
    rotLeft64 state[0] 0,
    rotLeft64 state[15] 28,
    rotLeft64 state[5] 1,
    rotLeft64 state[20] 27,
    rotLeft64 state[10] 62,
    rotLeft64 state[6] 44,
    rotLeft64 state[21] 20,
    rotLeft64 state[11] 6,
    rotLeft64 state[1] 36,
    rotLeft64 state[16] 55,
    rotLeft64 state[12] 43,
    rotLeft64 state[2] 3,
    rotLeft64 state[17] 25,
    rotLeft64 state[7] 10,
    rotLeft64 state[22] 39,
    rotLeft64 state[18] 21,
    rotLeft64 state[8] 45,
    rotLeft64 state[23] 8,
    rotLeft64 state[13] 15,
    rotLeft64 state[3] 41,
    rotLeft64 state[24] 14,
    rotLeft64 state[14] 61,
    rotLeft64 state[4] 18,
    rotLeft64 state[19] 56,
    rotLeft64 state[9] 2
  ]
end Specs.Keccak256

namespace Specs.Keccak256
def chi (b : Vector ℕ 25) : Vector ℕ 25 :=
  #v[
    b[0] ^^^ ((not64 b[5]) &&& b[10]),
    b[1] ^^^ ((not64 b[6]) &&& b[11]),
    b[2] ^^^ ((not64 b[7]) &&& b[12]),
    b[3] ^^^ ((not64 b[8]) &&& b[13]),
    b[4] ^^^ ((not64 b[9]) &&& b[14]),
    b[5] ^^^ ((not64 b[10]) &&& b[15]),
    b[6] ^^^ ((not64 b[11]) &&& b[16]),
    b[7] ^^^ ((not64 b[12]) &&& b[17]),
    b[8] ^^^ ((not64 b[13]) &&& b[18]),
    b[9] ^^^ ((not64 b[14]) &&& b[19]),
    b[10] ^^^ ((not64 b[15]) &&& b[20]),
    b[11] ^^^ ((not64 b[16]) &&& b[21]),
    b[12] ^^^ ((not64 b[17]) &&& b[22]),
    b[13] ^^^ ((not64 b[18]) &&& b[23]),
    b[14] ^^^ ((not64 b[19]) &&& b[24]),
    b[15] ^^^ ((not64 b[20]) &&& b[0]),
    b[16] ^^^ ((not64 b[21]) &&& b[1]),
    b[17] ^^^ ((not64 b[22]) &&& b[2]),
    b[18] ^^^ ((not64 b[23]) &&& b[3]),
    b[19] ^^^ ((not64 b[24]) &&& b[4]),
    b[20] ^^^ ((not64 b[0]) &&& b[5]),
    b[21] ^^^ ((not64 b[1]) &&& b[6]),
    b[22] ^^^ ((not64 b[2]) &&& b[7]),
    b[23] ^^^ ((not64 b[3]) &&& b[8]),
    b[24] ^^^ ((not64 b[4]) &&& b[9])
  ]
end Specs.Keccak256

namespace Specs.Keccak256
def iota (state : Vector ℕ 25) (rc : UInt64) : Vector ℕ 25 :=
  state.set 0 ((state[0]) ^^^ rc.toFin)
end Specs.Keccak256

namespace Specs.Keccak256
def keccakRound (state : Vector ℕ 25) (rc : UInt64) : Vector ℕ 25 :=
  let theta_state := theta state
  let rho_pi_state := rhoPi theta_state
  let chi_state := chi rho_pi_state
  iota chi_state rc
end Specs.Keccak256

namespace Specs.Keccak256
def keccakPermutation (state : Vector ℕ 25): Vector ℕ 25 :=
  roundConstants.foldl keccakRound state
end Specs.Keccak256

namespace Specs.Keccak256
@[reducible] def CAPACITY := 8
end Specs.Keccak256

namespace Specs.Keccak256
@[reducible] def RATE := 17
example : RATE + CAPACITY = 25 := rfl
end Specs.Keccak256

namespace Specs.Keccak256
def initialState : Vector ℕ 25 := .fill 25 0
end Specs.Keccak256

namespace Specs.Keccak256
def absorbBlock (state : Vector ℕ 25) (block : Vector ℕ RATE) : Vector ℕ 25 :=
  
  let state' := Vector.mapFinRange 25 fun i => state[i] ^^^ (if _ : i.val < RATE then block[i] else 0)
  
  keccakPermutation state'
end Specs.Keccak256

namespace Specs.Keccak256
def absorbBlocks (blocks : List (Vector ℕ RATE)) : Vector ℕ 25 :=
  blocks.foldl absorbBlock initialState
end Specs.Keccak256

namespace Specs.Keccak256.Tests
def state : Vector (U64 ℕ) 25 := #v[
  ⟨67, 168, 144, 181, 2, 173, 144, 47⟩,
  ⟨114, 52, 107, 105, 171, 22, 114, 75⟩,
  ⟨196, 118, 22, 253, 100, 162, 87, 52⟩,
  ⟨50, 65, 171, 81, 229, 6, 172, 155⟩,
  ⟨178, 167, 68, 225, 82, 73, 216, 194⟩,
  ⟨193, 5, 52, 193, 148, 168, 64, 147⟩,
  ⟨212, 142, 107, 244, 55, 237, 100, 203⟩,
  ⟨101, 34, 195, 62, 133, 216, 64, 34⟩,
  ⟨240, 214, 204, 27, 17, 231, 66, 179⟩,
  ⟨136, 37, 228, 137, 64, 208, 27, 90⟩,
  ⟨177, 229, 130, 4, 191, 7, 25, 117⟩,
  ⟨124, 168, 245, 7, 222, 138, 168, 16⟩,
  ⟨115, 130, 213, 74, 217, 123, 172, 109⟩,
  ⟨128, 149, 137, 6, 45, 133, 77, 101⟩,
  ⟨104, 90, 153, 237, 72, 44, 164, 84⟩,
  ⟨129, 177, 235, 28, 82, 30, 150, 201⟩,
  ⟨52, 55, 32, 241, 142, 211, 246, 68⟩,
  ⟨149, 124, 124, 204, 34, 220, 229, 69⟩,
  ⟨215, 168, 47, 96, 70, 5, 220, 2⟩,
  ⟨53, 224, 38, 18, 110, 66, 70, 9⟩,
  ⟨213, 122, 200, 196, 186, 122, 207, 42⟩,
  ⟨141, 103, 32, 88, 244, 160, 37, 76⟩,
  ⟨99, 242, 138, 24, 4, 30, 100, 196⟩,
  ⟨141, 253, 136, 54, 8, 21, 204, 152⟩,
  ⟨93, 161, 29, 12, 44, 252, 49, 57⟩
]
end Specs.Keccak256.Tests

namespace Specs.Keccak256.Tests
def state' := state.map U64.valueNat
end Specs.Keccak256.Tests

namespace Specs.Keccak256.Tests
def rc : U64 ℕ := ⟨235, 226, 178, 113, 2, 17, 87, 249⟩
end Specs.Keccak256.Tests

namespace TestSplitProvableStructEq
structure TestInputs (F : Type) where
  x : F
  y : F
  z : F
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
instance : ProvableStruct TestInputs where
  components := [field, field, field]
  toComponents := fun { x, y, z } => .cons x (.cons y (.cons z .nil))
  fromComponents := fun (.cons x (.cons y (.cons z .nil))) => { x, y, z }
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
structure NonProvableStruct (F : Type) where
  a : F
  b : F
end TestSplitProvableStructEq

def InductiveTable.Soundness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
      (eval env acc_var = acc) ∧ (eval env x_var = x) →
    
    Circuit.ConstraintsHold.Soundness env (step acc_var x_var |>.operations ((size State) + (size Input))) →
    
    Spec initialState xs row_index xs_len acc →
    
    Spec initialState (xs.concat x) (row_index + 1) (xs_len ▸ List.length_concat) (eval env (step acc_var x_var |>.output ((size State) + (size Input))))

def InductiveTable.Completeness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (InputAssumptions : ℕ → Input F → Prop) (InitialStateAssumptions : State F → Prop)
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
    (eval env acc_var = acc) ∧ (eval env x_var = x) →
  
  env.UsesLocalWitnessesCompleteness ((size State) + (size Input)) (step acc_var x_var |>.operations ((size State) + (size Input))) →
  
  InitialStateAssumptions initialState ∧
  Spec initialState xs row_index xs_len acc ∧ InputAssumptions row_index x →
  
  Circuit.ConstraintsHold.Completeness env (step acc_var x_var |>.operations ((size State) + (size Input)))

structure InductiveTable (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input] where
   
  step : Var State F → Var Input F → Circuit F (Var State F)

   
  Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop

   
  InputAssumptions : ℕ → Input F → Prop := fun _ _ => True
  InitialStateAssumptions : State F → Prop := fun _ => True

  soundness : InductiveTable.Soundness F State Input Spec step

  completeness : InductiveTable.Completeness F State Input InputAssumptions InitialStateAssumptions Spec step

  subcircuitsConsistent : ∀ acc x, ((step acc x).operations ((size State) + (size Input))).SubcircuitsConsistent ((size State) + (size Input))
    := by admit /- proof elided -/

namespace InductiveTable
def inductiveConstraint (table : InductiveTable F State Input) : TableConstraint 2 (ProvablePair State Input) F Unit := do
  let (acc, x) ← getCurrRow
  let output ← table.step acc x
  let (output', _) ← getNextRow
  
  output' === output
end InductiveTable

namespace InductiveTable
def equalityConstraint (Input : TypeMap) [ProvableType Input] (target : State F) : SingleRowConstraint (ProvablePair State Input) F := do
  let (actual, _) ← getCurrRow
  actual === (const target)
end InductiveTable

namespace InductiveTable
def tableConstraints (table : InductiveTable F State Input) (input_state output_state : State F) :
  List (TableOperation (ProvablePair State Input) F) := [
    .everyRowExceptLast table.inductiveConstraint,
    .boundary (.fromStart 0) (equalityConstraint Input input_state),
    .boundary (.fromEnd 0) (equalityConstraint Input output_state),
  ]
end InductiveTable

namespace InductiveTable
def traceInputs {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) : List (Input F) :=
  trace.val.toList.map Prod.snd
end InductiveTable

namespace InductiveTable
def toFormal (table : InductiveTable F State Input) (input output : State F) : FormalTable F (ProvablePair State Input) where
  constraints := table.tableConstraints input output
  Assumption N := N > 0 ∧ table.Spec input [] 0 rfl input
  Spec {N} trace := table.Spec input (traceInputs trace.tail) (N-1) (traceInputs_length trace.tail) output

  soundness N trace env assumption constraints :=
    table.table_soundness input output ⟨N, assumption.left⟩ trace env assumption.right constraints

  offset_consistent := by admit /- proof elided -/
end InductiveTable

namespace Gadgets.And.And64
structure Inputs (F : Type) where
  x: U64 F
  y: U64 F
end Gadgets.And.And64

namespace Gadgets.And.And64
instance : ProvableStruct Inputs where
  components := [U64, U64]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.And.And64

namespace Gadgets.And.And64
def main (input : Var Inputs (F p)) : Circuit (F p) (Var U64 (F p))  := do
  let ⟨x, y⟩ := input
  let z0 ← And8.circuit ⟨ x.x0, y.x0 ⟩
  let z1 ← And8.circuit ⟨ x.x1, y.x1 ⟩
  let z2 ← And8.circuit ⟨ x.x2, y.x2 ⟩
  let z3 ← And8.circuit ⟨ x.x3, y.x3 ⟩
  let z4 ← And8.circuit ⟨ x.x4, y.x4 ⟩
  let z5 ← And8.circuit ⟨ x.x5, y.x5 ⟩
  let z6 ← And8.circuit ⟨ x.x6, y.x6 ⟩
  let z7 ← And8.circuit ⟨ x.x7, y.x7 ⟩
  return U64.mk z0 z1 z2 z3 z4 z5 z6 z7
end Gadgets.And.And64

namespace Gadgets.And.And64
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized
end Gadgets.And.And64

namespace Gadgets.And.And64
def Spec (input : Inputs (F p)) (z : U64 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = x.value &&& y.value ∧ z.Normalized
end Gadgets.And.And64

namespace Gadgets.And.And64
instance elaborated : ElaboratedCircuit (F p) Inputs U64 where
  main
  localLength _ := 8
  output _ i := varFromOffset U64 i
end Gadgets.And.And64

namespace Gadgets.And.And64
def circuit : FormalCircuit (F p) Inputs U64 where
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.And.And64

namespace Circomlib
@[reducible]
def BinSubInput (n : ℕ) := ProvableVector (fields n) 2
end Circomlib

namespace Circomlib.BinSub
def main (n : ℕ) [NeZero n] (inp : BinSubInput n (Expression (F p))) := do
  
  let lin := Fin.foldl n (fun lin i =>
      let e2 : Expression (F p) := (2^i.val : F p)
      lin + inp[0][i] * e2 - inp[1][i] * e2)
    (2^n : F p)

  
  let out ← witnessVector n fun env =>
    fieldToBits n (lin.eval env)

  
  let aux ← witness fun env =>
    let lin_val := lin.eval env
    
    if (lin_val.val / (2^n)) % 2 = 1 then (1 : F p) else (0 : F p)

  
  let (lout, _) ← Circuit.foldlRange n ((0 : Expression (F p)), (1 : Expression (F p))) fun (lout, e2) i => do
    
    out[i] * (out[i] - (1 : Expression (F p))) === (0 : Expression (F p))
    let lout := lout + out[i] * e2
    return (lout, e2 + e2)

  
  aux * (aux - (1 : Expression (F p))) === (0 : Expression (F p))

  
  let lout := lout + aux * ((2^n : F p) : Expression (F p))

  
  lin === lout

  return out
end Circomlib.BinSub

namespace Circomlib.BinSub
def circuit (n : ℕ) [hn : NeZero n] [NonEmptyProvableType (fields n)] (hnout : 2^(n+1) < p) :
    FormalCircuit (F p) (BinSubInput n) (fields n) where
  main input := main n input

  localLength _ := n
  localLength_eq := by admit /- proof elided -/
end Circomlib.BinSub

namespace Gadgets.IsZero
def main (input : Var M F) : Circuit F (Var field F) := do
  let elemVars := toVars input
  
  
  let result ← Circuit.foldlRange (size M) (1 : Expression F) fun acc i => do
    let isZeroElem ← IsZeroField.circuit elemVars[i]
    return acc * isZeroElem
  return result
end Gadgets.IsZero

namespace Gadgets.IsZero
instance elaborated : ElaboratedCircuit F M field where
  main
  localLength _ := 2 * size M
  localLength_eq := by admit /- proof elided -/
end Gadgets.IsZero

namespace Gadgets.IsZero
def Assumptions (_ : M F) : Prop := True
end Gadgets.IsZero

namespace Gadgets.IsZero
def Spec [DecidableEq (M F)] (input : M F) (output : F) : Prop :=
  output = if input = 0 then 1 else 0
end Gadgets.IsZero

namespace Gadgets.IsZero
def circuit [DecidableEq (M F)] : FormalCircuit F M field := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.IsZero

namespace Gadgets.Rotation64
instance : Fact (p > 512) :=
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def main (offset : Fin 64) (x : Var U64 (F p)) : Circuit (F p) (Var U64 (F p)) := do
  let byte_offset : Fin 8 := ⟨ offset.val / 8, by admit /- proof elided -/
  ⟩
  let bit_offset : Fin 8 := ⟨ offset.val % 8, by admit /- proof elided -/
  ⟩

  
  let byte_rotated ← Rotation64Bytes.circuit byte_offset x
  Rotation64Bits.circuit bit_offset byte_rotated
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def Assumptions (input : U64 (F p)) := input.Normalized
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def Spec (offset : Fin 64) (x : U64 (F p)) (y : U64 (F p)) :=
  y.value = rotRight64 x.value offset.val
  ∧ y.Normalized
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def output (offset : Fin 64) (i0 : ℕ) : U64 (Expression (F p)) :=
  Rotation64Bits.output ⟨ offset.val % 8, by admit /- proof elided -/
  ⟩ i0
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def elaborated (off : Fin 64) : ElaboratedCircuit (F p) U64 U64 where
  main := main off
  localLength _ := 16
  output _ i0 := output off i0
end Gadgets.Rotation64

namespace Gadgets.Rotation64
def circuit (offset : Fin 64) : FormalCircuit (F p) U64 U64 := {
  elaborated offset with
  Assumptions
  Spec := Spec offset
  soundness := soundness offset
  completeness := completeness offset
}
end Gadgets.Rotation64

class ExplicitCircuit (circuit : Circuit F α) where
   
  output : ℕ → α
  localLength : ℕ → ℕ
  operations : ℕ → Operations F

   
  output_eq : ∀ n : ℕ, circuit.output n = output n := by admit /- proof elided -/

class ExplicitCircuits (circuit : α → Circuit F β) where
  output : α → ℕ → β
  localLength : α → ℕ → ℕ
  operations : α → ℕ → Operations F
  output_eq : ∀ (a : α) (n : ℕ), (circuit a).output n = output a n := by admit /- proof elided -/

instance ExplicitCircuits.from_single {circuit : α → Circuit F β}
    (explicit : ∀ a, ExplicitCircuit (circuit a)) : ExplicitCircuits circuit where
  output a n := (explicit a).output n
  localLength a n := (explicit a).localLength n
  operations a n := (explicit a).operations n
  output_eq a n := (explicit a).output_eq n
  localLength_eq a n := (explicit a).localLength_eq n
  operations_eq a n := (explicit a).operations_eq n
  subcircuitsConsistent a n := (explicit a).subcircuitsConsistent n

instance ExplicitCircuits.to_single (circuit : α → Circuit F β) (a : α)
    [explicit : ExplicitCircuits circuit] : ExplicitCircuit (circuit a) where
  output n := output circuit a n
  localLength n := explicit.localLength a n
  operations n := operations circuit a n
  output_eq n := output_eq a n
  localLength_eq n := localLength_eq a n
  operations_eq n := operations_eq a n
  subcircuitsConsistent n := subcircuitsConsistent a n

instance ExplicitCircuit.from_pure {a : α} : ExplicitCircuit (pure a : Circuit F α) where
  output _ := a
  localLength _ := 0
  operations _ := []

instance ExplicitCircuits.from_pure {f : α → β} : ExplicitCircuits (fun a => pure (f a) : α → Circuit F β) where
  output a _ := f a
  localLength _ _ := 0
  operations _ _ := []

instance ExplicitCircuit.from_bind {f : Circuit F α} {g : α → Circuit F β}
    (f_explicit : ExplicitCircuit f) (g_explicit : ∀ a : α, ExplicitCircuit (g a)) : ExplicitCircuit (f >>= g) where
  output n :=
    let a := output f n
    output (g a) (n + localLength f n)

  localLength n :=
    let a := output f n
    localLength f n + localLength (g a) (n + localLength f n)

  operations n :=
    let a := output f n
    operations f n ++ operations (g a) (n + localLength f n)

  output_eq n := by admit /- proof elided -/

instance ExplicitCircuit.from_map {f : α → β} {g : Circuit F α}
    (g_explicit : ExplicitCircuit g) : ExplicitCircuit (f <$> g) where
  output n := output g n |> f
  localLength n := localLength g n
  operations n := operations g n

  output_eq n := by admit /- proof elided -/

instance : ExplicitCircuits (F:=F) witnessVar where
  output _ n := ⟨ n ⟩
  localLength _ _ := 1
  operations c n := [.witness 1 fun env => #v[c env]]

instance {k : ℕ} {c : Environment F → Vector F k} : ExplicitCircuit (witnessVars k c) where
  output n := .mapRange k fun i => ⟨n + i⟩
  localLength _ := k
  operations n := [.witness k c]

instance {α : TypeMap} [ProvableType α] : ExplicitCircuits (ProvableType.witness (α:=α) (F:=F)) where
  output _ n := varFromOffset α n
  localLength _ _ := size α
  operations c n := [.witness (size α) (toElements ∘ c)]

instance {value var : TypeMap} [ProvableType value] [inst : Witnessable F value var] :
    ExplicitCircuits (witness (F:=F) (value:=value) (var:=var)) where
  output _ n := inst.var_eq ▸ varFromOffset value n
  output_eq c n := by admit /- proof elided -/

instance : ExplicitCircuits (F:=F) assertZero where
  output _ _ := ()
  localLength _ _ := 0
  operations e n := [.assert e]

instance {α : TypeMap} [ProvableType α] {table : Table F α} : ExplicitCircuits (F:=F) (lookup table) where
  output _ _ := ()
  localLength _ _ := 0
  operations entry n := [.lookup { table := table.toRaw, entry := toElements entry }]

instance {β α: TypeMap} [ProvableType α] [ProvableType β] {circuit : FormalCircuit F β α} {input} :
    ExplicitCircuit (subcircuit circuit input) where
  output n := circuit.output input n
  localLength _ := circuit.localLength input
  operations n := [.subcircuit (circuit.toSubcircuit n input)]

instance {β : TypeMap} [ProvableType β] {circuit : FormalAssertion F β} {input} :
    ExplicitCircuit (assertion circuit input) where
  output n := ()
  localLength _ := circuit.localLength input
  operations n := [.subcircuit (circuit.toSubcircuit n input)]

namespace Gadgets.BLAKE3.Compress
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
def main (input : Var ApplyRounds.Inputs (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  
  let state ← ApplyRounds.circuit input
  
  FinalStateUpdate.circuit ⟨state, input.chaining_value⟩
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
instance elaborated : ElaboratedCircuit (F p) ApplyRounds.Inputs BLAKE3State where
  main
  localLength input := ApplyRounds.circuit.localLength input + FinalStateUpdate.circuit.localLength ⟨default, input.chaining_value⟩
  output := fun input offset =>
    let applyRounds_out := ApplyRounds.circuit.output input offset
    FinalStateUpdate.circuit.output
      ⟨applyRounds_out, input.chaining_value⟩
      (offset + ApplyRounds.circuit.localLength input)
  output_eq := by admit /- proof elided -/
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
def Assumptions (input : ApplyRounds.Inputs (F p)) : Prop :=
  ApplyRounds.Assumptions input
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
def Spec (input : ApplyRounds.Inputs (F p)) (output : BLAKE3State (F p)) : Prop :=
  let { chaining_value, block_words, counter_high, counter_low, block_len, flags } := input
  output.value = compress
    (chaining_value.map U32.value)
    (block_words.map U32.value)
    (counter_low.value + 2^32 * counter_high.value)
    block_len.value
    flags.value ∧
  output.Normalized
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
def circuit : FormalCircuit (F p) ApplyRounds.Inputs BLAKE3State := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.BLAKE3.Compress

private def isStructLiteral (e : Expr) : MetaM Bool := do
  
  try
    let type ← inferType e
    if ← hasProvableStructInstance type then
      
      isMkConstructor e
    else
      return false
  catch _ => return false

private partial def collectStructEvalPattern (e : Expr) : MetaM (List Expr) := do
  match e with
  | .app (.app (.const ``And _) left) right =>
    
    let leftStruct ← collectStructEvalPattern left
    let rightStruct ← collectStructEvalPattern right
    return leftStruct ++ rightStruct
  | _ =>
    
    if e.isAppOf `Eq then
      if let (some lhs, some rhs) := (e.getArg? 1, e.getArg? 2) then
        let lhsIsEval := hasEvalPattern lhs
        let rhsIsEval := hasEvalPattern rhs

        if lhsIsEval || rhsIsEval then
          let evalSide := if lhsIsEval then lhs else rhs
          let otherSide := if lhsIsEval then rhs else lhs
          let otherSideIsEval := if lhsIsEval then rhsIsEval else lhsIsEval

          
          let otherIsLiteral ← if otherSideIsEval then
              if let some otherExpr := otherSide.getArg? 5 then
                isStructLiteral otherExpr
              else
                pure false
            else
              isStructLiteral otherSide
          if otherIsLiteral then
            
            if let some structExpr := evalSide.getArg? 5 then
              return [structExpr]
            else
              return []

          
          
          if let some evalArg := evalSide.getArg? 5   then
            let isLit ← isStructLiteral evalArg
            if isLit then
              return [evalArg]
            else
              return []
          else
            return []
        else
          return []
      else
        return []
    else
      return []

namespace Gadgets.ByteDecomposition.Theorems
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.ByteDecomposition.Theorems

namespace Vector
def fromList (l : List α) : Vector α l.length := ⟨ .mk l, rfl ⟩
end Vector

namespace Vector
def len (_ : Vector α n) : ℕ := n
end Vector

namespace Vector
def cons (a : α) (v : Vector α n) : Vector α (n + 1) :=
  ⟨ .mk (a :: v.toList), by admit /- proof elided -/
  ⟩
end Vector

namespace Vector
def set? (v : Vector α n) (i : ℕ) (a : α) : Vector α n :=
  ⟨ .mk <| v.toList.set i a, by admit /- proof elided -/
  ⟩
end Vector

namespace Vector
def update (v : Vector α n) (i : Fin n) (f : α → α) : Vector α n :=
  v.set i (f (v.get i))
end Vector

namespace Vector
def mapMonad {M : Type → Type} {n} [Monad M] (v : Vector (M α) n) : M (Vector α n) :=
  match (v : Vector (M α) n) with
  | ⟨ .mk [], h ⟩ => pure ⟨ #[], h ⟩
  | ⟨ .mk (a :: as), (h : as.length + 1 = n) ⟩ => do
    let hd ← a
    let tl ← mapMonad ⟨ .mk as, rfl ⟩
    pure ⟨ .mk <| hd :: tl.toList, by admit /- proof elided -/
    ⟩
end Vector

namespace Vector
def induct {motive : {n : ℕ} → Vector α n → Sort u}
    (nil : motive #v[])
    (cons: ∀ {n : ℕ} (a : α) (as : Vector α n), motive as → motive (cons a as))
    {n : ℕ} (v : Vector α n) : motive v :=
  match v with
  | ⟨ .mk [], h ⟩ => by admit /- proof elided -/
  | ⟨ .mk (a :: as), h ⟩ => by admit /- proof elided -/
end Vector

namespace Vector
def toPush (v : Vector α (n + 1)) :
    (as : Vector α n) ×' (a : α) ×' (as.push a = v) :=
  ⟨ v.pop, v.back, v.push_pop_back ⟩
end Vector

namespace Vector
def inductPush {motive : {n : ℕ} → Vector α n → Sort u}
  (nil : motive #v[])
  (push: ∀ {n : ℕ} (as : Vector α n) (a : α), motive as → motive (as.push a))
  {n : ℕ} (v : Vector α n) : motive v :=
  match v with
  | ⟨ .mk [], (h : 0 = n) ⟩ =>
    cast (by admit /- proof elided -/
    ) nil
  | ⟨ .mk (a :: as), h ⟩ =>
    have hlen : as.length + 1 = n := by admit /- proof elided -/
end Vector

namespace Vector
def mapFinRange (n : ℕ) (create : Fin n → α) : Vector α n := finRange n |>.map create
end Vector

namespace Vector
def mapRange (n : ℕ) (create : ℕ → α) : Vector α n :=
  match n with
  | 0 => #v[]
  | k + 1 => mapRange k create |>.push (create k)
end Vector

namespace Vector
def fill (n : ℕ) (a : α) : Vector α n :=
  match n with
  | 0 => #v[]
  | k + 1 => (fill k a).push a
end Vector

namespace Vector
instance [Inhabited α] {n : ℕ} : Inhabited (Vector α n) where
  default := fill n default
end Vector

def Composition.ofProductLength (m : ℕ+) {α : Type} {l : List α} (hl : l.length = n * m.val) : Composition l.length := {
  blocks := List.replicate n m.val
  blocks_pos hi := (List.mem_replicate.mp hi).right ▸ m.pos
  blocks_sum := hl ▸ List.sum_replicate_nat
}

namespace Vector
def toChunks (m : ℕ+) {α : Type} (v : Vector α (n*m)) : Vector (Vector α m) n :=
  let comp := Composition.ofProductLength m v.length_toList
  let list : List (Vector α m) := v.toList.splitWrtComposition comp
    |>.attachWith (List.length · = m) (comp.ofProductLength_mem_length rfl)
    |>.map fun ⟨ l, hl ⟩ => .mk (.mk l) hl
  .mk (.mk list) (by admit /- proof elided -/
  )
end Vector

namespace Vector
def mapRangeM (n : ℕ) {m : Type → Type} [Monad m] (f : ℕ → m β) : m (Vector β n) := (range n).mapM f
end Vector

namespace Vector
def mapFinRangeM (n : ℕ) {m : Type → Type} [Monad m] (f : Fin n → m β) : m (Vector β n) := (finRange n).mapM f
end Vector

namespace Vector
def rotate {α : Type} {n : ℕ} (v : Vector α n) (off : ℕ) : Vector α n :=
  ⟨(v.toList.rotate off).toArray, by admit /- proof elided -/
  ⟩
end Vector

namespace Vector
def takeShort {α : Type} {n : ℕ} (v : Vector α n) (j : ℕ) (h_j : j < n) : Vector α j :=
  (v.take j).cast (by admit /- proof elided -/
  )
end Vector

def circuit := do
  let x ← witnessField (F := F pBabybear) fun _ => 246
  let y ← witnessField fun _ => 20
  let z ← Gadgets.Addition8.circuit.main { x, y }
  Gadgets.Addition8.circuit.main { x, y := z }

namespace Gadgets.Xor
def ByteXorTable : Table (F p) fieldTriple := .fromStatic {
  name := "ByteXor"
  length := 256*256

  row i :=
    let (x, y) := splitTwoBytes i
    (fromByte x, fromByte y, fromByte (x ^^^ y))

  index := fun (x, y, _) => x.val * 256 + y.val

  Spec := fun (x, y, z) =>
    x.val < 256 ∧ y.val < 256 ∧ z.val = x.val ^^^ y.val

  contains_iff := by admit /- proof elided -/
end Gadgets.Xor

namespace Circomlib.XOR
def main (input : Expression (F p) × Expression (F p)) := do
  let a := input.1
  let b := input.2
  let out <== a + b - 2*a*b
  return out
end Circomlib.XOR

namespace Circomlib.XOR
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.XOR

namespace Circomlib.AND
def main (input : Expression (F p) × Expression (F p)) := do
  let a := input.1
  let b := input.2
  let out <== a*b
  return out
end Circomlib.AND

namespace Circomlib.AND
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.AND

namespace Circomlib.OR
def main (input : Expression (F p) × Expression (F p)) := do
  let a := input.1
  let b := input.2
  let out <== a + b - a*b
  return out
end Circomlib.OR

namespace Circomlib.OR
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.OR

namespace Circomlib.NOT
def main (input : Expression (F p)) := do
  let inp := input
  let out <== 1 + inp - 2*inp
  return out
end Circomlib.NOT

namespace Circomlib.NOT
def circuit : FormalCircuit (F p) field field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.NOT

namespace Circomlib.NAND
def main (input : Expression (F p) × Expression (F p)) := do
  let a := input.1
  let b := input.2
  let out <== 1 - a*b
  return out
end Circomlib.NAND

namespace Circomlib.NAND
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.NAND

namespace Circomlib.NOR
def main (input : Expression (F p) × Expression (F p)) := do
  let a := input.1
  let b := input.2
  let out <== a*b + 1 - a - b
  return out
end Circomlib.NOR

namespace Circomlib.NOR
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.NOR

namespace Circomlib.MultiAND
def main : {n : ℕ} → Vector (Expression (F p)) n → Circuit (F p) (Expression (F p))
  | 0, _ =>
    return (1 : F p)
  | 1, input =>
    return input[0]
  | 2, input =>
    AND.circuit.main (input[0], input[1])
  | n + 3, input => do
    let n1 := (n + 3) / 2
    let n2 := (n + 3) - n1

    let input1 : Vector (Expression (F p)) n1 := input.take n1 |>.cast (by admit /- proof elided -/
    )
    let input2 : Vector (Expression (F p)) n2 := input.drop n1 |>.cast (by admit /- proof elided -/
    )

    let out1 ← main input1
    let out2 ← main input2

    AND.circuit.main (out1, out2)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
def Assumptions (n : ℕ) (input : fields n (F p)) : Prop :=
  ∀ (i : ℕ) (h : i < n), IsBool input[i]
end Circomlib.MultiAND

namespace Circomlib.MultiAND
def Spec (n : ℕ) (input : fields n (F p)) (output : F p) : Prop :=
  output.val = (input.map (·.val)).foldl (· &&& ·) 1 ∧ IsBool output
end Circomlib.MultiAND

namespace Circomlib.MultiAND
def circuit (n : ℕ) : FormalCircuit (F p) (fields n) field where
  main
  localLength _ := n - 1
  localLength_eq := localLength_eq n
  subcircuitsConsistent := subcircuitsConsistent n

  Assumptions := Assumptions n
  Spec := Spec n

  soundness := by admit /- proof elided -/
end Circomlib.MultiAND

def InductiveTable.Soundness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
      (eval env acc_var = acc) ∧ (eval env x_var = x) →
    
    Circuit.ConstraintsHold.Soundness env (step acc_var x_var |>.operations ((size State) + (size Input))) →
    
    Spec initialState xs row_index xs_len acc →
    
    Spec initialState (xs.concat x) (row_index + 1) (xs_len ▸ List.length_concat) (eval env (step acc_var x_var |>.output ((size State) + (size Input))))

def InductiveTable.Completeness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (InputAssumptions : ℕ → Input F → Prop) (InitialStateAssumptions : State F → Prop)
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
    (eval env acc_var = acc) ∧ (eval env x_var = x) →
  
  env.UsesLocalWitnessesCompleteness ((size State) + (size Input)) (step acc_var x_var |>.operations ((size State) + (size Input))) →
  
  InitialStateAssumptions initialState ∧
  Spec initialState xs row_index xs_len acc ∧ InputAssumptions row_index x →
  
  Circuit.ConstraintsHold.Completeness env (step acc_var x_var |>.operations ((size State) + (size Input)))

structure InductiveTable (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input] where
   
  step : Var State F → Var Input F → Circuit F (Var State F)

   
  Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop

   
  InputAssumptions : ℕ → Input F → Prop := fun _ _ => True
  InitialStateAssumptions : State F → Prop := fun _ => True

  soundness : InductiveTable.Soundness F State Input Spec step

  completeness : InductiveTable.Completeness F State Input InputAssumptions InitialStateAssumptions Spec step

  subcircuitsConsistent : ∀ acc x, ((step acc x).operations ((size State) + (size Input))).SubcircuitsConsistent ((size State) + (size Input))
    := by admit /- proof elided -/

namespace InductiveTable
def inductiveConstraint (table : InductiveTable F State Input) : TableConstraint 2 (ProvablePair State Input) F Unit := do
  let (acc, x) ← getCurrRow
  let output ← table.step acc x
  let (output', _) ← getNextRow
  
  output' === output
end InductiveTable

namespace InductiveTable
def equalityConstraint (Input : TypeMap) [ProvableType Input] (target : State F) : SingleRowConstraint (ProvablePair State Input) F := do
  let (actual, _) ← getCurrRow
  actual === (const target)
end InductiveTable

namespace InductiveTable
def tableConstraints (table : InductiveTable F State Input) (input_state output_state : State F) :
  List (TableOperation (ProvablePair State Input) F) := [
    .everyRowExceptLast table.inductiveConstraint,
    .boundary (.fromStart 0) (equalityConstraint Input input_state),
    .boundary (.fromEnd 0) (equalityConstraint Input output_state),
  ]
end InductiveTable

namespace InductiveTable
def traceInputs {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) : List (Input F) :=
  trace.val.toList.map Prod.snd
end InductiveTable

namespace Tables.Fibonacci32Inductive
def fib32 : ℕ → ℕ
  | 0 => 0
  | 1 => 1
  | n + 2 => (fib32 n + fib32 (n + 1)) % 2^32
end Tables.Fibonacci32Inductive

namespace Tables.Fibonacci32Inductive
structure Row (F : Type) where
  x: U32 F
  y: U32 F
end Tables.Fibonacci32Inductive

namespace Tables.Fibonacci32Inductive
instance : ProvableStruct Row where
  components := [U32, U32]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Tables.Fibonacci32Inductive

namespace Tables.Fibonacci32Inductive
def table : InductiveTable (F p) Row unit where
  step row _ := do
    let z ← Addition32.circuit { x := row.x, y := row.y }
    return { x := row.y, y := z }

  Spec _ _ i _ row : Prop :=
    row.x.value = fib32 i ∧
    row.y.value = fib32 (i + 1) ∧
    row.x.Normalized ∧ row.y.Normalized

  soundness := by admit /- proof elided -/
end Tables.Fibonacci32Inductive

namespace Tables.Fibonacci32Inductive
def formalTable (output : Row (F p)) := table.toFormal { x := U32.fromByte 0, y := U32.fromByte 1 } output
end Tables.Fibonacci32Inductive

namespace Gadgets.Rotation64.Theorems
def rotRight64_bytes (xs : Vector ℕ 8) (o : ℕ) : Vector ℕ 8 :=
  .ofFn fun ⟨ i, hi ⟩ => xs[i] / 2^o + (xs[(i + 1) % 8] % 2^o) * 2^(8-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
def rotRight64_u64 : U64 ℕ → ℕ → U64 ℕ
  | ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩, o => ⟨
    (x0 / 2^o) + (x1 % 2^o) * 2^(8-o),
    (x1 / 2^o) + (x2 % 2^o) * 2^(8-o),
    (x2 / 2^o) + (x3 % 2^o) * 2^(8-o),
    (x3 / 2^o) + (x4 % 2^o) * 2^(8-o),
    (x4 / 2^o) + (x5 % 2^o) * 2^(8-o),
    (x5 / 2^o) + (x6 % 2^o) * 2^(8-o),
    (x6 / 2^o) + (x7 % 2^o) * 2^(8-o),
    (x7 / 2^o) + (x0 % 2^o) * 2^(8-o),
  ⟩
end Gadgets.Rotation64.Theorems

namespace Gadgets.Keccak256.ThetaC
def main (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakRow (F p)) :=
  .mapFinRange 5 fun i => do
    let c ← Xor64.circuit ⟨state[5*i.val], state[5*i.val + 1]⟩
    let c ← Xor64.circuit ⟨c, state[5*i.val + 2]⟩
    let c ← Xor64.circuit ⟨c, state[5*i.val + 3]⟩
    let c ← Xor64.circuit ⟨c, state[5*i.val + 4]⟩
    return c
end Gadgets.Keccak256.ThetaC

namespace Gadgets.Keccak256.ThetaC
def Assumptions (state : KeccakState (F p)) := state.Normalized
end Gadgets.Keccak256.ThetaC

namespace Gadgets.Keccak256.ThetaC
def Spec (state : KeccakState (F p)) (out : KeccakRow (F p)) :=
  out.Normalized
  ∧ out.value = Specs.Keccak256.thetaC state.value
end Gadgets.Keccak256.ThetaC

namespace Gadgets.Keccak256.ThetaC
instance elaborated : ElaboratedCircuit (F p) KeccakState KeccakRow where
  main
  localLength _ := 160
  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.Keccak256.ThetaC

namespace Gadgets.Keccak256.ThetaC
def circuit : FormalCircuit (F p) KeccakState KeccakRow :=
 { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.ThetaC

namespace Gadgets.TwoPowerLookup
def fromByte_limb {two_exponent : Fin 9} (x : Fin (2 ^ two_exponent.val)) : F p :=
  FieldUtils.natToField x.val (by admit /- proof elided -/
    )
end Gadgets.TwoPowerLookup

namespace Gadgets.TwoPowerLookup
def ByteLessThanTwoPower (two_exponent : Fin 9) : Table (F p) field := .fromStatic {
  name := "ByteLessThanTwoPower"
  length := 2^two_exponent.val

  row i := fromByte_limb i
  index x := x.val

  Spec x := x.val < 2^two_exponent.val

  contains_iff x := by admit /- proof elided -/
end Gadgets.TwoPowerLookup

def buildAuxMap (as : CellAssignment W S) : Std.HashMap ℕ ℕ := Id.run do
  let (_, _, map) :=
    as.vars.foldl
      fun (idx, offset, m) cell =>
        match cell with
        | .aux _ => (idx + 1, offset + 1, m.insert idx offset)
        | _ => (idx + 1, offset, m)
      (0, size S, Std.HashMap.emptyWithCapacity)

  map

def generateNextRow (tc : TableConstraint W S F Unit) (cur_row : Array F) : Array F :=
  let ctx := (tc .empty).2

  let assignment := ctx.assignment
  let generators := ctx.circuit.witnessGenerators

    let aux_map := buildAuxMap assignment
  let next_row := Array.replicate cur_row.size 0

  
  let env i :=
    if h : i < assignment.offset then
      match assignment.vars[i] with
      | .input ⟨r, c⟩ =>
        
          if r = 0 then cur_row[c]! else next_row[c]!
      | .aux _ =>
        
        
        
        next_row[aux_map[i]!]!
    
    else panic! s!"Invalid variable index {i} in environment"

  
  let (_, next_row) := generators.foldl (fun (idx, next_row) compute =>
      let wit := compute ⟨ env ⟩

      
      let next_row := if h : idx < assignment.offset then
        let var := assignment.vars[idx]

        match var with
          | .input ⟨r, c⟩ => if r = 1 then next_row.set! c wit else next_row
          | .aux _ => next_row.set! aux_map[idx]! wit
      
      else panic! s!"Invalid variable index {idx} in environment"

      (idx + 1, next_row)
    )
    (0, next_row)

  next_row

def witnesses
    (tc : TableConstraint W S F Unit) (init_row : Row F S) (n : ℕ) : Array (Array F) := Id.run do

  
  let aux_cols := Array.replicate tc.finalAssignment.numAux 0
  let cur_row := (toElements init_row).toArray ++ aux_cols

  let mut trace := #[cur_row]
  let mut current := cur_row

  for _ in [: n-1] do
    let next := generateNextRow tc current
    trace := trace.push next
    current := next
  trace

namespace Gadgets
def allZero {n} (xs : Vector (Expression F) n) : Circuit F Unit := .forEach xs assertZero
end Gadgets

namespace Gadgets.Equality
def main {α : TypeMap} [ProvableType α] (input : Var α F × Var α F) : Circuit F Unit := do
  let (x, y) := input
  let diffs := (toVars x).zip (toVars y) |>.map (fun (xi, yi) => xi - yi)
  .forEach diffs assertZero
end Gadgets.Equality

namespace Gadgets.Equality
@[reducible]
instance elaborated (α : TypeMap) [ProvableType α] : ElaboratedCircuit F (ProvablePair α α) unit where
  main
  localLength _ := 0
  output _ _ := ()

  localLength_eq _ n := by admit /- proof elided -/
end Gadgets.Equality

namespace Gadgets.Equality
@[simps! (attr := circuit_norm) (config := {isSimp := false})]
def circuit (α : TypeMap) [ProvableType α] : FormalAssertion F (ProvablePair α α) where
  Assumptions _ := True

  Spec : α F × α F → Prop
  | (x, y) => x = y

  soundness := by admit /- proof elided -/
end Gadgets.Equality

@[circuit_norm]
def assertEquals {F : Type} [Field F] {α : TypeMap} [ProvableType α]
    (x y : α (Expression F)) : Circuit F Unit :=
  Gadgets.Equality.circuit α (x, y)

@[circuit_norm, reducible]
def Expression.assertEquals {F : Type} [Field F]
    (x y : Expression F) : Circuit F Unit :=
  Gadgets.Equality.circuit id (x, y)

class HasAssertEq (β : Type) (F : outParam Type) [Field F] where
  assert_eq : β → β → Circuit F Unit

instance {F : Type} [Field F] : HasAssertEq (Expression F) F where
  assert_eq := Expression.assertEquals

instance {F : Type} [Field F] {α : TypeMap} [ProvableType α] :
  HasAssertEq (α (Expression F)) F where
  assert_eq := @assertEquals F _ α _

class HasAssignEq (β : Type) (F : outParam Type) [Field F] where
  assignEq : β → Circuit F β

instance {F : Type} [Field F] : HasAssignEq (Expression F) F where
  assignEq := fun rhs => do
    let witness ← witnessField fun env => rhs.eval env
    witness === rhs
    return witness

instance {F : Type} [Field F] {α : TypeMap} [ProvableType α] :
  HasAssignEq (α (Expression F)) F where
  assignEq := fun rhs => do
    let witness ← ProvableType.witness fun env => eval env rhs
    witness === rhs
    return witness

instance {F : Type} [Field F] {n : ℕ} : HasAssignEq (Vector (Expression F) n) F :=
  inferInstanceAs (HasAssignEq (fields n (Expression F)) F)

def IsBool {α : Type*} [Zero α] [One α] (x : α) : Prop := x = 0 ∨ x = 1

instance {α : Type*} [Zero α] [One α] [DecidableEq α] {x : α} : Decidable (IsBool x) :=
  inferInstanceAs (Decidable (x = 0 ∨ x = 1))

inductive Boolean (F : Type) where
  | private mk : Variable F → Boolean F

namespace Boolean
def witness (compute : Environment (F p) → F p) := do
  let x ← witnessVar compute
  assertZero (var x * (var x - 1))
  return Boolean.mk x
end Boolean

namespace Boolean
def var (b : Boolean (F p)) := Expression.var b.1
end Boolean

namespace Boolean
instance : Coe (Boolean (F p)) (Expression (F p)) where
  coe x := x.var
end Boolean

namespace Boolean
@[circuit_norm]
def assertBool : FormalAssertion (F p) field where
  main (x : Expression (F p)) := assertZero (x * (x - 1))
  Assumptions _ := True
  Spec (x : F p) := IsBool x

  soundness := by admit /- proof elided -/
end Boolean

namespace Gadgets.Keccak256
@[reducible] def KeccakState := ProvableVector U64 25
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakState.Normalized (state : KeccakState (F p)) :=
  ∀ i : Fin 25, state[i.val].Normalized
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakState.value (state : KeccakState (F p)) := state.map U64.value
end Gadgets.Keccak256

namespace Gadgets.Keccak256
@[reducible] def KeccakRow := ProvableVector U64 5
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakRow.Normalized (row : KeccakRow (F p)) :=
  ∀ i : Fin 5, row[i.val].Normalized
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakRow.value (row : KeccakRow (F p)) := row.map U64.value
end Gadgets.Keccak256

namespace Gadgets.Keccak256
@[reducible] def KeccakBlock := ProvableVector U64 RATE
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakBlock.Normalized (block : KeccakBlock (F p)) :=
  ∀ i : Fin RATE, block[i.val].Normalized
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakBlock.value (block : KeccakBlock (F p)) := block.map U64.value
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakRow.normalized_iff (row : KeccakRow (F p)) :
    row.Normalized ↔
    row[0].Normalized ∧
    row[1].Normalized ∧
    row[2].Normalized ∧
    row[3].Normalized ∧
    row[4].Normalized :=
end Gadgets.Keccak256

namespace Gadgets.Keccak256
def KeccakBlock.normalized : FormalAssertion (F p) KeccakBlock where
  main block := .forEach block (assertion U64.AssertNormalized.circuit)
  Assumptions _ := True
  Spec block := block.Normalized
  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.Keccak256

def not64 (a : ℕ) : ℕ := a ^^^ 0xffffffffffffffff

def add32 (a b : ℕ) : ℕ := (a + b) % 2^32

def rotRight8 (x : Fin 256) (offset : Fin 8) : Fin 256 :=
  let low := x % (2^offset.val)
  let high := x / (2^offset.val)
  low * (2^(8-offset.val)) + high

def rotLeft8 (x : Fin 256) (offset : Fin 8) : Fin 256 :=
  let low := x % (2^(8-offset.val))
  let high := x / (2^(8-offset.val))
  low * (2^offset.val) + high

def rotRight64 (x : ℕ) (offset : ℕ) : ℕ :=
  let offset := offset % 64
  let low := x % (2^offset)
  let high := x / (2^offset)
  low * (2^(64-offset)) + high

def rotRight32 (x : ℕ) (offset : ℕ) : ℕ :=
  let offset := offset % 32
  let low := x % (2^offset)
  let high := x / (2^offset)
  low * (2^(32-offset)) + high

def rotLeft64 (value : ℕ) (left : Fin 64) : ℕ:=
  let right := (64 - left) % 64
  rotRight64 value right

namespace Circuit
instance : LawfulMonad (Circuit F) where
  bind_pure_comp {α β} (g : α → β) (f : Circuit F α) := by admit /- proof elided -/
end Circuit

namespace Circuit
@[reducible, circuit_norm]
def forAll (circuit : Circuit F α) (n : ℕ) (prop : Condition F) :=
  (circuit.operations n).forAll n prop
end Circuit

def FormalCircuit.isGeneralFormalCircuit (F : Type) (Input Output : TypeMap) [Field F] [ProvableType Output] [ProvableType Input]
    (orig : FormalCircuit F Input Output): GeneralFormalCircuit F Input Output :=

def FormalAssertion.isGeneralFormalCircuit (F : Type) (Input : TypeMap) [Field F] [ProvableType Input]
    (orig : FormalAssertion F Input) : GeneralFormalCircuit F Input unit :=

namespace Tables.Fibonacci8Table
structure RowType (F : Type) where
  x: F
  y: F
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
instance : ProvableType RowType where
  size := 2
  toElements x := #v[x.x, x.y]
  fromElements v :=
    let ⟨ .mk [x, y], _ ⟩ := v
    ⟨ x, y ⟩
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def fibRelation : TwoRowsConstraint RowType (F p) := do
  let curr ← TableConstraint.getCurrRow
  let next_x ← copyToVar curr.y
  let next_y ← Gadgets.Addition8.circuit { x := curr.x, y := curr.y }
  assignVar (.next 0) next_x
  assign (.next 1) next_y
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def boundaryFib : SingleRowConstraint RowType (F p) :=
  assignCurrRow { x := 0, y := 1 }
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def fibTable : List (TableOperation RowType (F p)) := [
  boundary (.fromStart 0) boundaryFib,
  everyRowExceptLast fibRelation,
]
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def fib8 : ℕ -> ℕ
  | 0 => 0
  | 1 => 1
  | (n + 2) => (fib8 n + fib8 (n + 1)) % 256
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def Spec {N : ℕ} (trace : TraceOfLength (F p) RowType N) : Prop :=
  trace.ForAllRowsOfTraceWithIndex fun row index =>
    (row.x.val = fib8 index) ∧
    (row.y.val = fib8 (index + 1))
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
def formalFibTable : FormalTable (F p) RowType := {
  constraints := fibTable
  Spec := Spec

  soundness := by admit /- proof elided -/
end Tables.Fibonacci8Table

namespace Circuit
def ConstantLength.fromConstantLength' [Inhabited β] (body : β × Fin m → Circuit F β)
    (h : ∀ (acc : β) (i i' : Fin m) n,
      (body (acc, i)).localLength n = (body (default, i')).localLength 0) :
    ConstantLength body where
  localLength := match m with
    | 0 => 0
    | m + 1 => (body (default, 0)).localLength 0
  localLength_eq := by admit /- proof elided -/
end Circuit

namespace Circuit.FoldlM
@[reducible]
def prod (circuit : β → α → Circuit F β) : β × α → Circuit F β := fun t => circuit t.1 t.2
end Circuit.FoldlM

namespace Circuit.FoldlM
def foldlAcc (n : ℕ) (xs : Vector α m) (circuit : β → α → Circuit F β) (init : β) (j : Fin m) : β :=
  Fin.foldl j (fun acc i => (circuit acc xs[i.val]).output (n + i*(circuit acc xs[i.val]).localLength)) init
end Circuit.FoldlM

namespace Circuit
def forEach {m : ℕ} (xs : Vector α m) [Inhabited α] (body : α → Circuit F Unit)
    (_constant : ConstantLength body := by admit /- proof elided -/
    ) : Circuit F Unit :=
  xs.forM body
end Circuit

namespace Circuit
def map {m : ℕ} (xs : Vector α m) (body : α → Circuit F β)
    (_constant : ConstantLength body := by admit /- proof elided -/
    ) : Circuit F (Vector β m) :=
  xs.mapM body
end Circuit

namespace Circuit
def mapFinRange (m : ℕ) [NeZero m] (body : Fin m → Circuit F β)
    (_constant : ConstantLength body := by admit /- proof elided -/
    ) : Circuit F (Vector β m) :=
  Vector.mapFinRangeM m body
end Circuit

namespace Circuit
def foldl {m : ℕ} [Inhabited β] [Inhabited α] (xs : Vector α m) (init : β) (body : β → α → Circuit F β)
  (_const_out : ConstantOutput (fun (s, a) => body s a) := by admit /- proof elided -/
    )
  (_constant : ConstantLength (fun (s, a) => body s a) := by admit /- proof elided -/
  )
   : Circuit F β :=
  xs.foldlM body init
end Circuit

namespace Circuit
def foldlRange (m : ℕ) [Inhabited β] (init : β) (body : β → Fin m → Circuit F β)
  (_constant : ConstantLength (fun (s, a) => body s a) := by admit /- proof elided -/
    ) : Circuit F β :=
  (Vector.finRange m).foldlM body init
end Circuit

def fibRelationBabybear := fibRelation (p:=p_babybear)

def initRow : RowType (F p_babybear) := { x := 0, y := 1 }

namespace Gadgets.Keccak256.ThetaD
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.Keccak256.ThetaD

namespace Gadgets.Keccak256.ThetaD
def main (row : Var KeccakRow (F p)) : Circuit (F p) (Var KeccakRow (F p)) := do
  let c0 ← Rotation64.circuit (64 - 1) row[1]
  let c0 ← Xor64.circuit ⟨row[4], c0⟩

  let c1 ← Rotation64.circuit (64 - 1) row[2]
  let c1 ← Xor64.circuit ⟨row[0], c1⟩

  let c2 ← Rotation64.circuit (64 - 1) row[3]
  let c2 ← Xor64.circuit ⟨row[1], c2⟩

  let c3 ← Rotation64.circuit (64 - 1) row[4]
  let c3 ← Xor64.circuit ⟨row[2], c3⟩

  let c4 ← Rotation64.circuit (64 - 1) row[0]
  let c4 ← Xor64.circuit ⟨row[3], c4⟩

  return #v[c0, c1, c2, c3, c4]
end Gadgets.Keccak256.ThetaD

namespace Gadgets.Keccak256.ThetaD
instance elaborated : ElaboratedCircuit (F p) KeccakRow KeccakRow where
  main
  localLength _ := 120
end Gadgets.Keccak256.ThetaD

namespace Gadgets.Keccak256.ThetaD
def Assumptions (state : KeccakRow (F p)) := state.Normalized
end Gadgets.Keccak256.ThetaD

namespace Gadgets.Keccak256.ThetaD
def Spec (row : KeccakRow (F p)) (out : KeccakRow (F p)) : Prop :=
  out.Normalized
  ∧ out.value = Specs.Keccak256.thetaD row.value
end Gadgets.Keccak256.ThetaD

namespace Gadgets.Keccak256.ThetaD
def circuit : FormalCircuit (F p) KeccakRow KeccakRow :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.ThetaD

namespace Gadgets
inductive Byte (F : Type) where
  | private mk : (Variable F) → Byte F
end Gadgets

namespace Gadgets.Byte
def var (b : Byte (F p)) := Expression.var b.1
end Gadgets.Byte

namespace Gadgets.Byte
def witness (compute : Environment (F p) → F p) := do
  let x ← witnessVar compute
  lookup ByteTable (.var x)
  return Byte.mk x
end Gadgets.Byte

namespace Gadgets.Byte
instance : Coe (Byte (F p)) (Expression (F p)) where
  coe x := x.var
end Gadgets.Byte

namespace Gadgets.IsZeroField
def main (x : Var field F) : Circuit F (Var field F) := do
  
  
  let xInv ← witness fun env =>
    if x.eval env = 0 then 0 else (x.eval env : F)⁻¹

  let isZero <== 1 - x*xInv 
  isZero * x === 0  

  return isZero
end Gadgets.IsZeroField

namespace Gadgets.IsZeroField
instance elaborated : ElaboratedCircuit F field field where
  main
  localLength _ := 2  
end Gadgets.IsZeroField

namespace Gadgets.IsZeroField
def Assumptions (_ : F) : Prop := True
end Gadgets.IsZeroField

namespace Gadgets.IsZeroField
def Spec (x : F) (output : F) : Prop :=
  output = if x = 0 then 1 else 0
end Gadgets.IsZeroField

namespace Gadgets.IsZeroField
def circuit : FormalCircuit F field field := {
  elaborated with Assumptions, Spec := Spec (F:=F), soundness, completeness
}
end Gadgets.IsZeroField

namespace Gadgets.Addition32Full
structure Inputs (F : Type) where
  x: U32 F
  y: U32 F
  carryIn: F
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
instance : ProvableStruct Inputs where
  components := [U32, U32, field]
  toComponents := fun {x, y, carryIn} => .cons x ( .cons y ( .cons carryIn .nil))
  fromComponents := fun (.cons x ( .cons y ( .cons carryIn .nil))) => ⟨ x, y, carryIn ⟩
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
structure Outputs (F : Type) where
  z: U32 F
  carryOut: F
deriving Repr
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
instance : ProvableStruct Outputs where
  components := [U32, field]
  toComponents := fun {z, carryOut} => .cons z ( .cons carryOut .nil)
  fromComponents := fun (.cons z ( .cons carryOut .nil)) => ⟨ z, carryOut ⟩
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
def main (input : Var Inputs (F p)) : Circuit (F p) (Var Outputs (F p)) := do
  let ⟨x, y, carryIn⟩ := input
  let { z := z0, carryOut := c0 } ← Addition8FullCarry.main ⟨ x.x0, y.x0, carryIn ⟩
  let { z := z1, carryOut := c1 } ← Addition8FullCarry.main ⟨ x.x1, y.x1, c0 ⟩
  let { z := z2, carryOut := c2 } ← Addition8FullCarry.main ⟨ x.x2, y.x2, c1 ⟩
  let { z := z3, carryOut := c3 } ← Addition8FullCarry.main ⟨ x.x3, y.x3, c2 ⟩
  return { z := U32.mk z0 z1 z2 z3, carryOut := c3 }
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y, carryIn⟩ := input
  x.Normalized ∧ y.Normalized ∧ IsBool carryIn
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
def Spec (input : Inputs (F p)) (out : Outputs (F p)) :=
  let ⟨x, y, carryIn⟩ := input
  let ⟨z, carryOut⟩ := out
  z.value = (x.value + y.value + carryIn.val) % 2^32
  ∧ carryOut.val = (x.value + y.value + carryIn.val) / 2^32
  ∧ z.Normalized ∧ IsBool carryOut
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
instance elaborated : ElaboratedCircuit (F p) Inputs Outputs where
  main
  localLength _ := 8
  
  localLength_eq _ i0 := by admit /- proof elided -/
end Gadgets.Addition32Full

namespace Gadgets.Addition32Full
def circuit : FormalCircuit (F p) Inputs Outputs where
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.Addition32Full

namespace Gadgets.Keccak256.AbsorbBlock
structure Input (F : Type) where
  state : KeccakState F
  block : KeccakBlock F
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
instance : ProvableStruct Input where
  components := [KeccakState, KeccakBlock]
  toComponents := fun { state, block } => .cons state (.cons block .nil)
  fromComponents := fun (.cons state (.cons block .nil)) => { state, block }
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
def main (input : Var Input (F p)) : Circuit (F p) (Var KeccakState (F p)) := do
  let { state, block } := input
  
  let state_rate ← Circuit.mapFinRange RATE fun i => Xor64.circuit ⟨state[i.val], block[i.val]⟩

  
  let state_capacity := Vector.mapFinRange (25 - RATE) fun i => state[RATE + i.val]
  let state' : Vector _ 25 := state_rate ++ state_capacity

  
  Permutation.circuit state'
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
instance elaborated : ElaboratedCircuit (F p) Input KeccakState where
  main
  localLength _ := 31048
  output _ i0 := Permutation.stateVar (i0 + 136) 23

  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
@[reducible] def Assumptions (input : Input (F p)) :=
  input.state.Normalized ∧ input.block.Normalized
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
@[reducible] def Spec (input : Input (F p)) (out_state : KeccakState (F p)) :=
  out_state.Normalized ∧
  out_state.value = absorbBlock input.state.value input.block.value
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.AbsorbBlock
def circuit : FormalCircuit (F p) Input KeccakState :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.AbsorbBlock

namespace Circomlib.Num2Bits_strict
def main (input : Expression (F p)) := do
  
  let bits ← Num2Bits.main 254 input

  
  AliasCheck.circuit bits

  return bits
end Circomlib.Num2Bits_strict

namespace Circomlib.Num2Bits_strict
def circuit : FormalCircuit (F p) field (fields 254) where
  main
  localLength _ := 254 + 127 + 1 + 135 + 1 
  localLength_eq := by admit /- proof elided -/
end Circomlib.Num2Bits_strict

namespace Circomlib.Bits2Num_strict
def main (input : Vector (Expression (F p)) 254) := do
  
  AliasCheck.circuit input

  
  Bits2Num.main 254 input
end Circomlib.Bits2Num_strict

namespace Circomlib.Bits2Num_strict
def circuit : FormalCircuit (F p) (fields 254) field where
  main
  localLength _ := (127 + 1 + 135 + 1) + 1  
  localLength_eq := by admit /- proof elided -/
end Circomlib.Bits2Num_strict

namespace Circomlib.Num2BitsNeg
def main (n : ℕ) (input : Expression (F p)) := do
  
  let out ← witnessVector n fun env =>
    fieldToBits n (if n = 0 then 0 else (2^n : F p) - input.eval env)

  
  let lc1 ← Circuit.foldlRange n 0 fun lc1 i => do
    assertBool out[i]
    return lc1 + out[i] * (2^i.val : F p)

  
  let isZero_out ← IsZero.circuit input

  
  lc1 + isZero_out * (2^n : F p) === (2^n : F p) - input

  return out
end Circomlib.Num2BitsNeg

namespace Circomlib.Num2BitsNeg
def circuit (n : ℕ) (hn : 2^n < p) : FormalCircuit (F p) field (fields n) where
  main := main n
  localLength _ := n + 2 
  localLength_eq := by admit /- proof elided -/
end Circomlib.Num2BitsNeg

namespace Circomlib.RotR
def main (n r : ℕ) [NeZero n] (inp : Vector (Expression (F p)) n) := do
  let out <== Vector.mapFinRange n fun i => inp.get (i + Fin.ofNat n r)
  return out
end Circomlib.RotR

namespace Circomlib.RotR
def circuit (n r : ℕ) [NeZero n] : FormalCircuit (F p) (fields n) (fields n) where
  main := main n r

  localLength _ := n


  Spec input output := output = input.rotate r

  soundness := by admit /- proof elided -/
end Circomlib.RotR

namespace Gadgets.Keccak256.RhoPi
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def rhoPiIndices : Vector (Fin 25) 25 := #v[
  0, 15, 5, 20, 10, 6, 21, 11, 1, 16, 12, 2, 17, 7, 22, 18, 8, 23, 13, 3, 24, 14, 4, 19, 9
]
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def rhoPiShifts : Vector (Fin 64) 25 := #v[
  0, 28, 1, 27, 62, 44, 20, 6, 36, 55, 43, 3, 25, 10, 39, 21, 45, 8, 15, 41, 14, 61, 18, 56, 2
]
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def rhoPiConstants := rhoPiIndices.zip rhoPiShifts
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def main (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakState (F p)) :=
  .map rhoPiConstants fun (i, s) =>
    Rotation64.circuit (-s) state[i.val]
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def Assumptions := KeccakState.Normalized (p:=p)
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def Spec (state : KeccakState (F p)) (out_state : KeccakState (F p)) :=
  out_state.Normalized
  ∧ out_state.value = Specs.Keccak256.rhoPi state.value
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
instance elaborated : ElaboratedCircuit (F p) KeccakState KeccakState where
  main
  localLength _ := 400
  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
def circuit : FormalCircuit (F p) KeccakState KeccakState :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.RhoPi

structure LookupCircuit (F : Type) [Field F] (α β : TypeMap) [ProvableType α] [ProvableType β]
    extends circuit : FormalCircuit F α β where
  name : String
  computableWitnesses : circuit.ComputableWitnesses

namespace LookupCircuit
def proverEnvironment (circuit : LookupCircuit F α β) (input : α F) : Environment F :=
  circuit.main (const input) |>.proverEnvironment
end LookupCircuit

namespace LookupCircuit
def constantOutput (circuit : LookupCircuit F α β) (input : α F) : β F :=
  circuit.output (const input) 0 |> eval (circuit.proverEnvironment input)
end LookupCircuit

namespace LookupCircuit
def toTable (circuit : LookupCircuit F α β) : Table F (ProvablePair α β) where
  name := circuit.name

  
  Contains := fun (input, output) =>
    
    ∃ n env,
    
    Circuit.ConstraintsHold env (circuit.main (const input) |>.operations n)
    
    ∧ output = eval env (circuit.output (const input) n)

  Soundness := fun (input, output) => circuit.Assumptions input → circuit.Spec input output
  Completeness := fun (input, output) => circuit.Assumptions input ∧ output = circuit.constantOutput input

  imply_soundness := by admit /- proof elided -/
end LookupCircuit

namespace LookupCircuit
@[circuit_norm]
def lookupCircuit (circuit : LookupCircuit F α β) : FormalCircuit F α β where
  main (input : Var α F) := do
    
    let output ← witness fun env => circuit.constantOutput (eval env input)

    lookup circuit.toTable (input, output)
    return output

  localLength n := size β
  output _ n := varFromOffset β n

  Assumptions := circuit.Assumptions
  Spec := circuit.Spec

  soundness := by admit /- proof elided -/
end LookupCircuit

namespace LookupCircuit
@[circuit_norm]
def lookup (circuit : LookupCircuit F α β) (input : Var α F) : Circuit F (Var β F) :=
  lookupCircuit circuit input
end LookupCircuit

namespace Utils.StateTransition
def Transition (S : Type*) := S × S
end Utils.StateTransition

namespace Utils.StateTransition
instance [Fintype S] : Fintype (Transition S) := instFintypeProd S S
end Utils.StateTransition

namespace Utils.StateTransition
instance [DecidableEq S] : DecidableEq (Transition S) := instDecidableEqProd
end Utils.StateTransition

namespace Utils.StateTransition
def Run (S : Type*) := Transition S → ℕ
end Utils.StateTransition

namespace Utils.StateTransition
noncomputable def Run.netFlow {S : Type*} [Fintype S] [DecidableEq S] (R : Run S) (x : S) : ℤ :=
  (∑ y : S, (R (x, y) : ℤ)) - (∑ y : S, (R (y, x) : ℤ))
end Utils.StateTransition

namespace Utils.StateTransition
noncomputable def Run.size {S : Type*} [Fintype S] [DecidableEq S] (R : Run S) : ℕ :=
  ∑ t : Transition S, R t
end Utils.StateTransition

namespace Utils.StateTransition
def countTransitionInPath [DecidableEq S] (t : Transition S) (path : List S) : ℕ :=
  (path.zip path.tail).count t
end Utils.StateTransition

namespace Utils.StateTransition
def Run.containsPath [DecidableEq S] (R : Run S) (path : List S) : Prop :=
  ∀ t : Transition S, countTransitionInPath t path ≤ R t
end Utils.StateTransition

namespace Utils.StateTransition
def Run.hasCycle [DecidableEq S] (R : Run S) : Prop :=
  ∃ (cycle : List S), cycle.length ≥ 2 ∧
    cycle.head? = cycle.getLast? ∧
    R.containsPath cycle
end Utils.StateTransition

namespace Utils.StateTransition
def Run.isAcyclic [DecidableEq S] (R : Run S) : Prop :=
  ¬R.hasCycle
end Utils.StateTransition

namespace Utils.StateTransition
def Run.removeCycle (R : Run S) (cycle : List S) : Run S :=
  fun t => R t - countTransitionInPath t cycle
end Utils.StateTransition

namespace Utils.StateTransition
def Run.reachable [DecidableEq S] (R : Run S) (start finish : S) : Prop :=
  ∃ (path : List S), path.head? = some start ∧ path.getLast? = some finish ∧
    path ≠ [] ∧ R.containsPath path
end Utils.StateTransition

namespace Utils.StateTransition
def Run.isLeaf (R : Run S) (root leaf : S) : Prop :=
  R.reachable root leaf ∧ ∀ y, R (leaf, y) = 0
end Utils.StateTransition

namespace Utils.StateTransition
def countAsFirst [DecidableEq S] (xs : List S) (x : S) : ℕ :=
  (xs.zip xs.tail).countP (fun p => p.1 = x)
end Utils.StateTransition

namespace Utils.StateTransition
def countAsSecond [DecidableEq S] (xs : List S) (x : S) : ℕ :=
  (xs.zip xs.tail).countP (fun p => p.2 = x)
end Utils.StateTransition

namespace Gadgets.Not
def not64_bytewise (x : Var U64 (F p)) : Var U64 (F p) := U64.map x (fun x => 255 - x)
end Gadgets.Not

namespace Gadgets.Not
def not64_bytewise_value (x : U64 (F p)) : U64 (F p) := x.map (fun x => 255 - x)
end Gadgets.Not

namespace Gadgets.Not
def circuit : FormalCircuit (F p) U64 U64 where
  main x := pure (not64_bytewise x)
  Assumptions x := x.Normalized
  Spec x z := z.value = not64 x.value ∧ z.Normalized

  localLength _ := 0
  output x _ := not64_bytewise x

  soundness := by admit /- proof elided -/
end Gadgets.Not

@[reducible]
def Row (F : Type) (S : Type → Type) [ProvableType S] := S F

@[table_norm, table_assignment_norm]
def Row.get (row : Row F S) (i : Fin (size S)) : F :=
  (toElements row)[i.val]

inductive Trace (F : Type) (S : Type → Type) [ProvableType S] where
   
  | empty : Trace F S
   
  | cons (rest : Trace F S) (row : Row F S) : Trace F S

namespace Trace
@[table_norm, table_assignment_norm]
def len : Trace F S → ℕ
  | <+> => 0
  | rest +> _ => rest.len + 1
end Trace

namespace Trace
@[table_assignment_norm]
def getLeFromBottom :
    (trace : Trace F S) → (row : Fin trace.len) → (col : Fin (size S)) → F
  | _ +> currRow, ⟨0, _⟩, j => currRow.get j
  | rest +> _, ⟨i + 1, h⟩, j => getLeFromBottom rest ⟨i, Nat.le_of_succ_le_succ h⟩ j
end Trace

namespace Trace
@[table_norm]
def lastRow : (trace : Trace F S) → (hlen : trace.len > 0) → S F
  | <+>, h  => nomatch h
  | _ +> row, _ => row
end Trace

namespace Trace
def tail : Trace F S → Trace F S
  | <+>  => <+>
  | rest +> _ => rest
end Trace

namespace Trace
@[table_norm]
def ForAllRowsOfTraceWithIndex
    (trace : Trace F S) (prop : Row F S → ℕ → Prop) : Prop :=
  inner trace prop
  where
end Trace

namespace Trace
def ForAllRowsWithPrevious  : Trace F S → (Row F S → Trace F S → Prop) → Prop
  | <+>, _ => true
  | rest +> row, prop => (prop row rest) ∧ ForAllRowsWithPrevious rest prop
end Trace

namespace Trace
def toList : Trace F S → List (Row F S)
  | <+> => []
  | rest +> row => rest.toList.concat row
end Trace

def TraceOfLength (F : Type) (S : Type → Type) [ProvableType S] (N : ℕ) : Type :=
  { env : Trace F S // env.len = N }

namespace TraceOfLength
@[table_assignment_norm]
def get {M : ℕ} :
    (env : TraceOfLength F S M) → (i : Fin M) → (j : Fin (size S)) → F
  | ⟨env, h⟩, i, j => env.getLeFromBottom ⟨
      M - 1 - i,
      by admit /- proof elided -/
    ⟩ j
end TraceOfLength

namespace TraceOfLength
@[table_norm]
def lastRow {M : ℕ+} (trace : TraceOfLength F S M) : S F :=
  trace.val.lastRow (by admit /- proof elided -/
  )
end TraceOfLength

namespace TraceOfLength
def tail {M : ℕ} (trace : TraceOfLength F S M) : TraceOfLength F S (M - 1) :=
  ⟨ trace.val.tail, by admit /- proof elided -/
  ⟩
end TraceOfLength

namespace TraceOfLength
@[table_norm]
def ForAllRowsOfTrace {N : ℕ}
    (trace : TraceOfLength F S N) (prop : Row F S → Prop) : Prop :=
  inner trace.val prop
  where
end TraceOfLength

namespace TraceOfLength
@[table_norm]
def ForAllRowsOfTraceExceptLast {N : ℕ}
    (trace : TraceOfLength F S N) (prop : Row F S → Prop) : Prop :=
  inner trace.val prop
  where
  inner : Trace F S → (Row F S → Prop) → Prop
    | <+>, _ => true
    | <+> +> _, _ => true
    | rest +> curr +> _, prop => prop curr ∧ inner (rest +> curr) prop
end TraceOfLength

namespace TraceOfLength
@[table_norm]
def ForAllRowsOfTraceWithIndex {N : ℕ}
    (trace : TraceOfLength F S N) (prop : Row F S → ℕ → Prop) : Prop :=
  trace.val.ForAllRowsOfTraceWithIndex prop
end TraceOfLength

namespace TraceOfLength
def ForAllRowsWithPrevious {N : ℕ}
    (trace : TraceOfLength F S N) (prop : Row F S → (i : ℕ) → TraceOfLength F S i → Prop) : Prop :=
  trace.val.ForAllRowsWithPrevious fun row rest => prop row rest.len ⟨ rest, rfl ⟩
end TraceOfLength

namespace TraceOfLength
def toList {N : ℕ} (trace : TraceOfLength F S N) : List.Vector (Row F S) N :=
  ⟨ trace.val.toList, by admit /- proof elided -/
  ⟩
end TraceOfLength

namespace TraceOfLength
def take : {N : ℕ} → TraceOfLength F S N → (i : Fin (N + 1)) → TraceOfLength F S i
  | 0, ⟨ <+>, h0 ⟩, i => ⟨ <+>, i.val_eq_zero.symm ▸ rfl ⟩
  | N + 1, ⟨ rest +> row, h ⟩, ⟨ i, hi ⟩ =>
    if hi' : i = N + 1 then
      
      ⟨ rest +> row, hi' ▸ h ⟩
    else
      take ⟨ rest, Nat.succ_inj.mp h ⟩ ⟨ i, by admit /- proof elided -/
      ⟩
end TraceOfLength

structure CellOffset (W : ℕ+) (S : Type → Type) [ProvableType S]  where
  row: Fin W
  column: Fin (size S)

instance : Repr (CellOffset W S) where
  reprPrec off _ := "⟨" ++ reprStr off.row ++ ", " ++ reprStr off.column ++ "⟩"

namespace CellOffset
@[table_assignment_norm]
def curr {W : ℕ+} (j : Fin (size S)) : CellOffset W S := ⟨0, j⟩
end CellOffset

namespace CellOffset
@[table_assignment_norm]
def next {W : ℕ+} (j : Fin (size S)) : CellOffset W S := ⟨1, j⟩
end CellOffset

inductive Cell (W : ℕ+) (S : Type → Type) [ProvableType S] where
  | input : CellOffset W S → Cell W S
  | aux : ℕ → Cell W S

instance : Repr (Cell W S) where
  reprPrec cell _ := match cell with
    | .input off => ".input " ++ reprStr off
    | .aux i => ".aux " ++ reprStr i

structure CellAssignment (W : ℕ+) (S : Type → Type) [ProvableType S] where
  offset : ℕ 
  aux_length : ℕ 

   
  vars : Vector (Cell W S) offset

namespace CellAssignment
instance : Repr (CellAssignment W S) where
  reprPrec := fun { offset, aux_length, vars } _ =>
    "{ offset := " ++ reprStr offset ++ ", aux_length := " ++ reprStr aux_length ++ ", vars := " ++ reprStr vars ++ "}"
end CellAssignment

namespace CellAssignment
@[table_assignment_norm, reducible]
def empty (W : ℕ+) : CellAssignment W S where
  offset := 0
  aux_length := 0
  vars := #v[]
end CellAssignment

namespace CellAssignment
@[table_assignment_norm]
def pushVarAux (assignment : CellAssignment W S) : CellAssignment W S where
  offset := assignment.offset + 1
  aux_length := assignment.aux_length + 1
  vars := assignment.vars.push (.aux assignment.aux_length)
end CellAssignment

namespace CellAssignment
@[table_assignment_norm]
def pushVarsAux (assignment : CellAssignment W S) (n : ℕ) : CellAssignment W S where
  offset := assignment.offset + n
  aux_length := assignment.aux_length + n
  vars := assignment.vars ++ (.mapRange n fun i => .aux (assignment.aux_length + i) : Vector (Cell W S) n)
end CellAssignment

namespace CellAssignment
@[table_assignment_norm]
def pushVarInput (assignment : CellAssignment W S) (off : CellOffset W S) : CellAssignment W S where
  offset := assignment.offset + 1
  aux_length := assignment.aux_length
  vars := assignment.vars.push (.input off)
end CellAssignment

namespace CellAssignment
@[table_assignment_norm]
def pushRow (assignment : CellAssignment W S) (row : Fin W) : CellAssignment W S :=
  let row_vars : Vector (Cell W S) (size S) := .mapFinRange _ fun col => .input ⟨ row, col ⟩
  {
    offset := assignment.offset + size S
    aux_length := assignment.aux_length
    vars := assignment.vars ++ row_vars
  }
end CellAssignment

namespace CellAssignment
@[table_assignment_norm]
def setVarInput (assignment : CellAssignment W S) (off : CellOffset W S) (var : ℕ) : CellAssignment W S :=
  let vars := assignment.vars.set? var (.input off)
  
  
  { assignment with vars }
end CellAssignment

namespace CellAssignment
def numAux (assignment : CellAssignment W S) : ℕ :=
  assignment.vars.foldl (fun acc cell =>
    match cell with
    | .input _ => acc
    | .aux _ => acc + 1
  ) 0
end CellAssignment

structure TableContext (W : ℕ+) (S : Type → Type) (F : Type) [Field F] [ProvableType S] where
  circuit : Operations F
  assignment : CellAssignment W S
deriving Repr

namespace TableContext
@[reducible, table_norm, table_assignment_norm]
def empty : TableContext W S F where
  circuit := []
  assignment := .empty W
end TableContext

namespace TableContext
@[reducible, table_norm, table_assignment_norm]
def offset (table : TableContext W S F) : ℕ := table.circuit.localLength
end TableContext

@[reducible, table_norm, table_assignment_norm]
def TableConstraint (W : ℕ+) (S : Type → Type) (F : Type) [Field F] [ProvableType S] :=
  StateM (TableContext W S F)

instance [Repr F] : Repr (TableConstraint W S F α) where
  reprPrec table _ := reprStr (table .empty).2

@[table_assignment_norm]
def assignmentFromCircuit (as : CellAssignment W S) : Operations F → CellAssignment W S
  | [] => as
  | .witness m _ :: ops => assignmentFromCircuit (as.pushVarsAux m) ops
  | .assert _ :: ops => assignmentFromCircuit as ops
  | .lookup _ :: ops => assignmentFromCircuit as ops
  | .subcircuit s :: ops => assignmentFromCircuit (as.pushVarsAux s.localLength) ops

def assignmentFromCircuit' (as : CellAssignment W S) (ops : Operations F) : CellAssignment W S where
  offset := as.offset + ops.localLength
  aux_length := as.aux_length + ops.localLength
  vars := as.vars ++ (.mapRange _ fun i => .aux (as.aux_length + i) : Vector (Cell W S) _)

@[reducible, table_norm, table_assignment_norm]
instance : MonadLift (Circuit F) (TableConstraint W S F) where
  monadLift circuit ctx :=
    let (a, ops) := circuit ctx.circuit.localLength
    (a, {
      circuit := ctx.circuit ++ ops,
      assignment := assignmentFromCircuit ctx.assignment ops
    })

namespace TableConstraint
@[reducible, table_norm, table_assignment_norm]
def finalOffset (table : TableConstraint W S F α) : ℕ :=
  table .empty |>.snd.circuit.localLength
end TableConstraint

namespace TableConstraint
@[table_norm]
def operations (table : TableConstraint W S F α) : Operations F :=
  table .empty |>.snd.circuit
end TableConstraint

namespace TableConstraint
@[table_assignment_norm]
def finalAssignment (table : TableConstraint W S F α) : CellAssignment W S :=
  table .empty |>.snd.assignment
end TableConstraint

namespace TableConstraint
@[table_assignment_norm]
def OffsetConsistent (table : TableConstraint W S F α) : Prop :=
  table.finalOffset = table.finalAssignment.offset
end TableConstraint

namespace TableConstraint
def windowEnv (table : TableConstraint W S F Unit)
  (window : TraceOfLength F S W) (aux_env : Environment F) : Environment F :=
  let assignment := table.finalAssignment
  .mk fun i =>
    if hi : i < assignment.offset then
      match assignment.vars[i] with
      | .input ⟨i, j⟩ => window.get i j
      | .aux k => aux_env.get k
    else aux_env.get (i + assignment.aux_length)
end TableConstraint

namespace TableConstraint
@[table_norm]
def ConstraintsHoldOnWindow (table : TableConstraint W S F Unit)
  (window : TraceOfLength F S W) (aux_env : Environment F) : Prop :=
  let env := windowEnv table window aux_env
  Circuit.ConstraintsHold.Soundness env table.operations
end TableConstraint

namespace TableConstraint
@[table_norm]
def output {α : Type} (table : TableConstraint W S F α) : α :=
  table .empty |>.fst
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def getRow (row : Fin W) : TableConstraint W S F (Var S F) :=
  modifyGet fun ctx =>
    let ctx' : TableContext W S F := {
      circuit := ctx.circuit ++ [.witness (size S) fun env => .mapRange _ fun i => env.get (ctx.offset + i)],
      assignment := ctx.assignment.pushRow row
    }
    (varFromOffset S ctx.offset, ctx')
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def getCurrRow : TableConstraint W S F (Var S F) := getRow 0
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def getNextRow : TableConstraint W S F (Var S F) := getRow 1
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def assignVar (off : CellOffset W S) (v : Variable F) : TableConstraint W S F Unit :=
  modify fun ctx =>
    let assignment := ctx.assignment.setVarInput off v.index
    { ctx with assignment }
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def assign (off : CellOffset W S) : Expression F → TableConstraint W S F Unit
  
  | .var v => assignVar off v
  
  | x => do
    let new_var ← witnessVar x.eval
    assertZero (x - var new_var)
    assignVar off new_var
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def assignCurrRow {W : ℕ+} (curr : Var S F) : TableConstraint W S F Unit :=
  let vars := toVars curr
  forM (List.finRange (size S)) fun i =>
    assign (.curr i) vars[i]
end TableConstraint

namespace TableConstraint
@[table_norm, table_assignment_norm]
def assignNextRow {W : ℕ+} (next : Var S F) : TableConstraint W S F Unit :=
  let vars := toVars next
  forM (List.finRange (size S)) fun i =>
    assign (.next i) vars[i]
end TableConstraint

@[reducible]
def SingleRowConstraint (S : Type → Type) (F : Type) [Field F] [ProvableType S] := TableConstraint 1 S F Unit

@[reducible]
def TwoRowsConstraint (S : Type → Type) (F : Type) [Field F] [ProvableType S] := TableConstraint 2 S F Unit

inductive RowIndex where
  | fromStart : ℕ → RowIndex
  | fromEnd : ℕ → RowIndex

inductive TableOperation (S : Type → Type) (F : Type) [Field F] [ProvableType S] where
   
  | boundary: RowIndex → SingleRowConstraint S F → TableOperation S F

   
  | everyRow: SingleRowConstraint S F → TableOperation S F

   
  | everyRowExceptLast: TwoRowsConstraint S F → TableOperation S F

instance : Repr RowIndex where
  reprPrec
    | .fromStart i, _ => reprStr (i : ℤ)
    | .fromEnd i, _ => reprStr (-i-1 : ℤ)

instance [Repr F] : Repr (TableOperation S F) where
  reprPrec op _ := match op with
    | .boundary i c => "boundary " ++ reprStr i ++ " " ++ reprStr c
    | .everyRow c => "everyRow " ++ reprStr c
    | .everyRowExceptLast c => "everyRowExceptLast " ++ reprStr c

@[table_norm]
def TableConstraintsHold {N : ℕ} (constraints : List (TableOperation S F))
  (trace : TraceOfLength F S N) (env : ℕ → ℕ → Environment F) : Prop :=
  let constraints_and_envs := constraints.mapIdx (fun i cs => (cs, env i))
  foldl N constraints_and_envs trace.val constraints_and_envs
  where

structure FormalTable (F : Type) [Field F] (S : Type → Type) [ProvableType S] where
   
  constraints : List (TableOperation S F)

   
  Assumption : ℕ → Prop := fun _ => True

   
  Spec {N : ℕ} : TraceOfLength F S N → Prop

   
  soundness :
    ∀ (N : ℕ) (trace : TraceOfLength F S N) (env : ℕ → ℕ → Environment F),
    Assumption N →
    TableConstraintsHold constraints trace env →
    Spec trace

   
  offset_consistent :
    constraints.Forall fun cs =>
      match cs with
      | .boundary _ constraint => constraint.OffsetConsistent
      | .everyRow constraint => constraint.OffsetConsistent
      | .everyRowExceptLast constraint => constraint.OffsetConsistent
    := by admit /- proof elided -/

def FormalTable.statement (table : FormalTable F S) (N : ℕ) (trace : TraceOfLength F S N) : Prop :=
  table.Assumption N → table.Spec trace

instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)

namespace Gadgets.ByteDecomposition
structure Outputs (F : Type) where
  low : F
  high : F
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
instance : ProvableStruct Outputs where
  components := [field, field]
  toComponents := fun { low, high } => .cons low (.cons high .nil)
  fromComponents := fun (.cons low (.cons high .nil)) => { low, high }
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
def main (offset : Fin 8) (x : Expression (F p)) : Circuit (F p) (Var Outputs (F p)) := do
  let low ← witness fun env => mod (env x) (2^offset.val) (by admit /- proof elided -/
  )
  let high ← witness fun env => floorDiv (env x) (2^offset.val)

  lookup ByteTable ((2^(8-offset.val) : F p) * low)
  lookup ByteTable high

  x === low + high * (2^offset.val : F p)

  return { low, high }
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
def Assumptions (x : F p) := x.val < 256
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
def Spec (offset : Fin 8) (x : F p) (out : Outputs (F p)) :=
  let ⟨low, high⟩ := out
  (low.val = x.val % (2^offset.val) ∧ high.val = x.val / (2^offset.val))
  ∧ (low.val < 2^offset.val ∧ high.val < 2^(8-offset.val))
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
def elaborated (offset : Fin 8) : ElaboratedCircuit (F p) field Outputs where
  main := main offset
  localLength _ := 2
  output _ i0 := varFromOffset Outputs i0
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
def circuit (offset : Fin 8) : FormalCircuit (F p) field Outputs := {
  elaborated offset with
  main := main offset
  Assumptions
  Spec := Spec offset
  soundness := soundness offset
  completeness := completeness offset
}
end Gadgets.ByteDecomposition

namespace Specs.BLAKE3
def outLen : ℕ := 32
end Specs.BLAKE3

namespace Specs.BLAKE3
def keyLen : ℕ := 32
end Specs.BLAKE3

namespace Specs.BLAKE3
def blockLen : ℕ := 64
end Specs.BLAKE3

namespace Specs.BLAKE3
def chunkLen : ℕ := 1024
end Specs.BLAKE3

namespace Specs.BLAKE3
def chunkStart : ℕ := 2^0
end Specs.BLAKE3

namespace Specs.BLAKE3
def chunkEnd : ℕ := 2^1
end Specs.BLAKE3

namespace Specs.BLAKE3
def parent : ℕ := 2^2
end Specs.BLAKE3

namespace Specs.BLAKE3
def root : ℕ := 2^3
end Specs.BLAKE3

namespace Specs.BLAKE3
def keyedHash : ℕ := 2^4
end Specs.BLAKE3

namespace Specs.BLAKE3
def deriveKeyContext : ℕ := 2^5
end Specs.BLAKE3

namespace Specs.BLAKE3
def deriveKeyMaterial : ℕ := 2^6
end Specs.BLAKE3

namespace Specs.BLAKE3
def iv : Vector UInt32 8 := #v[
  0x6a09e667,
  0xbb67ae85,
  0x3c6ef372,
  0xa54ff53a,
  0x510e527f,
  0x9b05688c,
  0x1f83d9ab,
  0x5be0cd19
]
end Specs.BLAKE3

namespace Specs.BLAKE3
def msgPermutation : Vector (Fin 16) 16 :=

  #v[2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]
end Specs.BLAKE3

namespace Specs.BLAKE3
def g (state : Vector ℕ 16) (a b c d : Fin 16) (mx my : ℕ) : Vector ℕ 16 :=
  let state_a := add32 (state[a]) (add32 state[b] mx)
  let state_d := rotRight32 (state[d] ^^^ state_a) 16
  let state_c := add32 (state[c]) state_d
  let state_b := rotRight32 (state[b] ^^^ state_c) 12

  let state_a := add32 state_a (add32 state_b my)
  let state_d := rotRight32 (state_d ^^^ state_a) 8
  let state_c := add32 state_c state_d
  let state_b := rotRight32 (state_b ^^^ state_c) 7

  state.set a state_a
        |>.set b state_b
        |>.set c state_c
        |>.set d state_d
end Specs.BLAKE3

namespace Specs.BLAKE3
def roundConstants : Vector (Fin 16 × Fin 16 × Fin 16 × Fin 16 × Fin 16 × Fin 16) 8 := #v[
  (0, 4, 8, 12, 0, 1),
  (1, 5, 9, 13, 2, 3),
  (2, 6, 10, 14, 4, 5),
  (3, 7, 11, 15, 6, 7),
  (0, 5, 10, 15, 8, 9),
  (1, 6, 11, 12, 10, 11),
  (2, 7, 8, 13, 12, 13),
  (3, 4, 9, 14, 14, 15)
]
end Specs.BLAKE3

namespace Specs.BLAKE3
def round (state : Vector ℕ 16) (m : Vector ℕ 16) : Vector ℕ 16 :=
  roundConstants.foldl (fun state (a, b, c, d, i, j) =>
    g state a b c d m[i] m[j]
  ) state
end Specs.BLAKE3

namespace Specs.BLAKE3
def permute (state : Vector ℕ 16) : Vector ℕ 16 :=
  Vector.ofFn (fun i => state[msgPermutation[i]])
end Specs.BLAKE3

namespace Specs.BLAKE3
def roundWithPermute (acc : Vector ℕ 16 × Vector ℕ 16) (round_num : ℕ) : Vector ℕ 16 × Vector ℕ 16 :=
  let (state, block_words) := acc
  let new_state := round state block_words
  
  let new_block_words := if round_num < 6 then permute block_words else block_words
  (new_state, new_block_words)
end Specs.BLAKE3

namespace Specs.BLAKE3
def applyRounds (chaining_value : Vector ℕ 8) (block_words : Vector ℕ 16) (counter : ℕ) (block_len : ℕ) (flags : ℕ) : Vector ℕ 16 :=
  
  let counter_low := counter % 2^32
  let counter_high := counter / 2^32

  
  let state := #v[
    chaining_value[0], chaining_value[1], chaining_value[2], chaining_value[3],
    chaining_value[4], chaining_value[5], chaining_value[6], chaining_value[7],
    iv[0].toNat, iv[1].toNat, iv[2].toNat, iv[3].toNat,
    counter_low, counter_high, block_len, flags
  ]

  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words
  let block_words := permute block_words
  let state := round state block_words

  state
end Specs.BLAKE3

namespace Specs.BLAKE3
def finalStateUpdate (state : Vector ℕ 16) (chaining_value : Vector ℕ 8) : Vector ℕ 16 :=
  #v[
    state[0] ^^^ state[8],
    state[1] ^^^ state[9],
    state[2] ^^^ state[10],
    state[3] ^^^ state[11],
    state[4] ^^^ state[12],
    state[5] ^^^ state[13],
    state[6] ^^^ state[14],
    state[7] ^^^ state[15],
    chaining_value[0] ^^^ state[8],
    chaining_value[1] ^^^ state[9],
    chaining_value[2] ^^^ state[10],
    chaining_value[3] ^^^ state[11],
    chaining_value[4] ^^^ state[12],
    chaining_value[5] ^^^ state[13],
    chaining_value[6] ^^^ state[14],
    chaining_value[7] ^^^ state[15]
  ]
end Specs.BLAKE3

namespace Specs.BLAKE3
def compress (chaining_value : Vector ℕ 8) (block_words : Vector ℕ 16) (counter : ℕ) (block_len : ℕ) (flags : ℕ) : Vector ℕ 16 :=
  let state := applyRounds chaining_value block_words counter block_len flags
  finalStateUpdate state chaining_value
end Specs.BLAKE3

namespace Specs.BLAKE3
structure ChunkState where
  chaining_value : Vector ℕ 8      
  chunk_counter : ℕ                
  blocks_compressed : ℕ            
  block_buffer : List ℕ            
end Specs.BLAKE3

namespace Specs.BLAKE3
def initialChunkState (cv : Vector ℕ 8) (counter : ℕ) : ChunkState :=
  { chaining_value := cv
  , chunk_counter := counter
  , blocks_compressed := 0
  , block_buffer := []
  }
end Specs.BLAKE3

namespace Specs.BLAKE3
def bytesToWords (bytes : List ℕ) : Vector ℕ 16 :=
  let paddedBytes : List ℕ := bytes ++ List.replicate (64 - bytes.length) 0
  have : 64 ≤ paddedBytes.length := by admit /- proof elided -/
end Specs.BLAKE3

namespace Specs.BLAKE3
def startFlag (state : ChunkState) : ℕ :=
  if state.blocks_compressed = 0 then chunkStart else 0
end Specs.BLAKE3

namespace Specs.BLAKE3
def processBlockWords (state : ChunkState) (block_words : Vector ℕ 16) : ChunkState :=
  let flags := startFlag state
  let new_cv := compress state.chaining_value block_words state.chunk_counter blockLen flags
  { state with
    chaining_value := new_cv.takeShort 8 (by admit /- proof elided -/
    )
    blocks_compressed := state.blocks_compressed + 1
    block_buffer := []
  }
end Specs.BLAKE3

namespace Specs.BLAKE3
def splitIntoBlockWords (bytes : List ℕ) : (List (Vector ℕ 16) × List ℕ) :=
  splitIntoBlockWords.go bytes []
where
end Specs.BLAKE3

namespace Specs.BLAKE3
def processBlocksWords (state : ChunkState) (blocks : List (Vector ℕ 16)) : ChunkState :=
  blocks.foldl processBlockWords state
end Specs.BLAKE3

namespace Specs.BLAKE3
def updateChunk (state : ChunkState) (input_bytes : List ℕ) : ChunkState :=
  let combined := state.block_buffer ++ input_bytes
  let (blockWords, remainder) := splitIntoBlockWords combined
  let newState := processBlocksWords state blockWords
  { newState with block_buffer := remainder }
end Specs.BLAKE3

namespace Specs.BLAKE3
def finalizeChunk (state : ChunkState) (base_flags : ℕ) : Vector ℕ 8 :=
  let block_words := bytesToWords state.block_buffer
  let flags := base_flags ||| startFlag state ||| chunkEnd
  let final_state := compress state.chaining_value block_words state.chunk_counter state.block_buffer.length flags
  final_state.take 8
end Specs.BLAKE3

namespace Specs.BLAKE3
def processChunk (cv : Vector ℕ 8) (chunk_counter : ℕ) (chunk_bytes : List ℕ) (base_flags : ℕ) : Vector ℕ 8 :=
  let initial := initialChunkState cv chunk_counter
  let updated := updateChunk initial chunk_bytes
  finalizeChunk updated base_flags
end Specs.BLAKE3

namespace Specs.BLAKE3.Tests
def stateInitG : Vector ℕ 16 := #v[1321565287, 1539917118, 1918974978, 1109417770, 1286102396, 687960962, 441968613, 3595364146, 3111632159, 1102204962, 944689943, 3680149627, 3129663845, 3265095166, 606420953, 4183330326]
example : g stateInitG 0 1 2 3 4 5 = #v[3279123572, 367480655, 3947042124, 3663589532, 1286102396, 687960962, 441968613, 3595364146, 3111632159, 1102204962, 944689943, 3680149627, 3129663845, 3265095166, 606420953, 4183330326] := rfl
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def stateInitRound : Vector ℕ 16 := #v[1048429017, 869689525, 3373747814, 3881173978, 867318181, 93804160, 1095841330, 3806666906, 1528071400, 2951122214, 4271188711, 3509256835, 40453064, 3578515354, 1456976626, 243768026]
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def m : Vector ℕ 16 := #v[3959934058, 3329161910, 3688806782, 3025089236, 897128991, 1111177342, 4132823147, 2420086736, 1951041921, 2483382132, 1478626316, 2397174491, 1858261849, 1494602388, 4275385857, 3719915132]
example : round stateInitRound m = #v[2183394319, 368400627, 2705018986, 1532359963, 184541119, 4093912516, 344508834, 154001542, 2580533130, 866577463, 1629990543, 2086044263, 618301763, 3154665623, 3243728413, 699478374] := rfl
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def stateInitPermute : Vector ℕ 16 := #v[3383581781, 3743774256, 2003572531, 1426274751, 826242452, 1591270934, 3844308220, 2585707362, 2245261223, 142878727, 3284326898, 338750343, 4278730886, 3963897632, 4264855050, 15597940]
example : permute stateInitPermute = #v[2003572531, 3844308220, 1426274751, 3284326898, 2585707362, 3383581781, 826242452, 3963897632, 3743774256, 338750343, 4278730886, 1591270934, 142878727, 4264855050, 15597940, 2245261223] := rfl
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def chainingValue : Vector ℕ 8 := #v[671114869, 2251103971, 1125212539, 2996205183, 1286164105, 2483632496, 367841012, 3199388477]
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def blockWords : Vector ℕ 16 := #v[1260152445, 449952550, 2837099038, 716667674, 3544843723, 387900774, 3257147430, 2088822348, 4202301432, 2249467574, 1521610824, 186847680, 2726995727, 3572868764, 1936257617, 3338044720]
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def counter : ℕ := 953581910
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def blockLen : ℕ := 2437728858
end Specs.BLAKE3.Tests

namespace Specs.BLAKE3.Tests
def flags : ℕ := 2498436276
end Specs.BLAKE3.Tests

namespace Gadgets.Keccak256.ThetaXor
structure Inputs (F : Type) where
  state : KeccakState F
  d : KeccakRow F
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
instance : ProvableStruct Inputs where
  components := [KeccakState, KeccakRow]
  toComponents := fun { state, d } => .cons state (.cons d .nil)
  fromComponents := fun (.cons state (.cons d .nil)) => { state, d }
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
def main : Var Inputs (F p) → Circuit (F p) (Var KeccakState (F p))
  | { state, d } => .mapFinRange 25 fun i =>
    Xor64.circuit ⟨state[i.val], d[i.val / 5]⟩
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
instance elaborated : ElaboratedCircuit (F p) Inputs KeccakState where
  main
  localLength _ := 200

  localLength_eq _ n := by admit /- proof elided -/
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
def Assumptions (inputs : Inputs (F p)) : Prop :=
  let ⟨state, d⟩ := inputs
  state.Normalized ∧ d.Normalized
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
def Spec (inputs : Inputs (F p)) (out : KeccakState (F p)) : Prop :=
  let ⟨state, d⟩ := inputs
  out.Normalized
  ∧ out.value = Specs.Keccak256.thetaXor state.value d.value
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
def circuit : FormalCircuit (F p) Inputs KeccakState :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.ThetaXor

namespace Circomlib.MultiMux1
structure Inputs (n : ℕ) (F : Type) where
  c : ProvableVector (ProvablePair field field) n F  
  s : F                 
end Circomlib.MultiMux1

namespace Circomlib.MultiMux1
instance {n : ℕ} : ProvableStruct (Inputs n) where
  components := [ProvableVector (ProvablePair field field) n, field]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.MultiMux1

namespace Circomlib.MultiMux1
def main (n : ℕ) (input : Var (Inputs n) (F p)) := do
  let { c, s } := input

  
  let out <== c.map fun (c0, c1) =>
    (c1 - c0) * s + c0
  return out
end Circomlib.MultiMux1

namespace Circomlib.MultiMux1
def circuit (n : ℕ) : FormalCircuit (F p) (Inputs n) (fields n) where
  main := main n

  localLength _ := n

  Assumptions input :=
    let ⟨c, s⟩ := input
    IsBool s

  Spec input output :=
    let ⟨c, s⟩ := input
    ∀ i (_ : i < n),
      output[i] = if s = 0 then (c[i]).1 else (c[i]).2

  soundness := by admit /- proof elided -/
end Circomlib.MultiMux1

namespace Circomlib.Mux1
structure Inputs (F : Type) where
  c : Vector F 2  
  s : F           
end Circomlib.Mux1

namespace Circomlib.Mux1
instance : ProvableStruct Inputs where
  components := [fields 2, field]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.Mux1

namespace Circomlib.Mux1
def main (input : Var Inputs (F p)) := do
  let { c, s } := input

  
  let mux_out ← MultiMux1.circuit 1 { c := #v[(c[0], c[1])], s }
  return mux_out[0]
end Circomlib.Mux1

namespace Circomlib.Mux1
def circuit : FormalCircuit (F p) Inputs field where
  main := main

  localLength _ := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.Mux1

namespace Gadgets.Or32
structure Inputs (F : Type) where
  x : U32 F
  y : U32 F
end Gadgets.Or32

namespace Gadgets.Or32
instance : ProvableStruct Inputs where
  components := [U32, U32]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.Or32

namespace Gadgets.Or32
def main (input : Var Inputs (F p)) : Circuit (F p) (Var U32 (F p))  := do
  let ⟨x, y⟩ := input

  let z0 ← Or8.circuit ⟨x.x0, y.x0⟩
  let z1 ← Or8.circuit ⟨x.x1, y.x1⟩
  let z2 ← Or8.circuit ⟨x.x2, y.x2⟩
  let z3 ← Or8.circuit ⟨x.x3, y.x3⟩

  return ⟨z0, z1, z2, z3⟩
end Gadgets.Or32

namespace Gadgets.Or32
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized
end Gadgets.Or32

namespace Gadgets.Or32
def Spec (input : Inputs (F p)) (z : U32 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = x.value ||| y.value ∧ z.Normalized
end Gadgets.Or32

namespace Gadgets.Or32
instance elaborated : ElaboratedCircuit (F p) Inputs U32 where
  main
  localLength _ := 4
end Gadgets.Or32

namespace Gadgets.Or32
def circuit : FormalCircuit (F p) Inputs U32 :=
  { Assumptions, Spec, soundness, completeness }
end Gadgets.Or32

namespace Gadgets.BLAKE3
@[reducible] def BLAKE3State := ProvableVector U32 16
end Gadgets.BLAKE3

namespace Gadgets.BLAKE3
def BLAKE3State.Normalized (state : BLAKE3State (F p)) :=
  ∀ i : Fin 16, state[i.val].Normalized
end Gadgets.BLAKE3

namespace Gadgets.BLAKE3
def BLAKE3State.value (state : BLAKE3State (F p)) := state.map U32.value
end Gadgets.BLAKE3

namespace Gadgets.BLAKE3
instance (F : Type) [Inhabited F] : Inhabited (BLAKE3State F) where
  default := .replicate 16 default
end Gadgets.BLAKE3

partial def circuitProofStartCore : TacticM Unit := do
  withMainContext do
    let goal ← getMainGoal
    let goalType ← goal.getType

    
    
    let headConst? := goalType.getAppFn.constName?
    let isSoundness := headConst? == some ``Soundness ||
                       headConst? == some ``FormalAssertion.Soundness ||
                       headConst? == some ``GeneralFormalCircuit.Soundness
    let isCompleteness := headConst? == some ``Completeness ||
                          headConst? == some ``FormalAssertion.Completeness ||
                          headConst? == some ``GeneralFormalCircuit.Completeness

    if isSoundness then
      match headConst? with
      | some ``Soundness =>
        evalTactic (← `(tactic| unfold Soundness))
        let names := [`i₀, `env, `input_var, `input, `h_input, `h_assumptions, `h_holds]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | some ``FormalAssertion.Soundness =>
        evalTactic (← `(tactic| unfold FormalAssertion.Soundness))
        let names := [`i₀, `env, `input_var, `input, `h_input, `h_assumptions, `h_holds]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | some ``GeneralFormalCircuit.Soundness =>
        evalTactic (← `(tactic| unfold GeneralFormalCircuit.Soundness))
        let names := [`i₀, `env, `input_var, `input, `h_input, `h_holds]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | _ => pure ()
      return
    else if isCompleteness then
      
      match headConst? with
      | some ``Completeness =>
        evalTactic (← `(tactic| unfold Completeness))
        let names := [`i₀, `env, `input_var, `h_env, `input, `h_input, `h_assumptions]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | some ``FormalAssertion.Completeness =>
        evalTactic (← `(tactic| unfold FormalAssertion.Completeness))
        let names := [`i₀, `env, `input_var, `h_env, `input, `h_input, `h_assumptions, `h_spec]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | some ``GeneralFormalCircuit.Completeness =>
        evalTactic (← `(tactic| unfold GeneralFormalCircuit.Completeness))
        let names := [`i₀, `env, `input_var, `h_env, `input, `h_input, `h_assumptions]
        for name in names do
          evalTactic (← `(tactic| intro $(mkIdent name):ident))
      | _ => pure ()
      return
    else
      
      throwError "circuitProofStartCore can only be used on Soundness, Completeness, FormalAssertion.Soundness, FormalAssertion.Completeness, GeneralFormalCircuit.Soundness, or GeneralFormalCircuit.Completeness goals"

@[reducible]
def TypeMap := Type → Type

@[reducible] def Var (M : TypeMap) (F : Type) := M (Expression F)

namespace Trace
def every_row_two_rows_induction {P : Trace F S → Sort*}
    (zero : P (<+>))
    (one : ∀ row : Row F S, P (empty +> row))
    (more : ∀ curr next : Row F S,
      ∀ rest : Trace F S, P (rest) → P (rest +> curr) → P (rest +> curr +> next))
    : ∀ trace, P trace
  | <+> => zero
  | <+> +> first => one first
  | rest +> curr +> _ => more _ _ _
    (every_row_two_rows_induction zero one more (rest))
    (every_row_two_rows_induction zero one more (rest +> curr))
end Trace

namespace Trace
def everyRowTwoRowsInduction' {P : (t : Trace F S) → t.len ≥ 2 → Sort*}
    (base : ∀ first second, P (<+> +> first +> second) (show 2 ≤ 2 by admit /- proof elided -/
    ))
    (more : ∀ (row : Row F S) (rest : Trace F S) (h : rest.len ≥ 2),
      P rest h → P (rest +> row) (Nat.le_succ_of_le h))
    : ∀ (trace : Trace F S) (h : trace.len ≥ 2), P trace h
  
  
  | <+> => by admit /- proof elided -/
  | <+> +> first => by admit /- proof elided -/
  | <+> +> first +> second => fun _ => base first second
  | rest +> prev +> curr +> next => fun h =>
    have h_len : 2 ≤ rest.len + 2 := by admit /- proof elided -/
end Trace

namespace TraceOfLength
def every_row_two_rows_induction {P : (N : ℕ) → TraceOfLength F S N → Sort*}
    (zero : P 0 ⟨<+>, rfl⟩)
    (one : ∀ row : Row F S, P 1 ⟨<+> +> row, rfl⟩)
    (more : ∀ (N : ℕ) (curr next : Row F S),
      ∀ rest : TraceOfLength F S N, P N (rest) →
        P (N + 1) ⟨rest.val +> curr, by admit /- proof elided -/
        ⟩ →
        P (N + 2) ⟨rest.val +> curr +> next, by admit /- proof elided -/
        ⟩)
    : ∀ N trace, P N trace
  | 0, ⟨<+>, _⟩ => zero
  | 1, ⟨<+> +> first, _⟩ => one first
  | N + 2, ⟨rest +> curr +> next, (h : rest.len + 2 = N + 2)⟩ => by admit /- proof elided -/
end TraceOfLength

namespace TraceOfLength
def everyRowTwoRowsInduction' {P : (N : ℕ+) → TraceOfLength F S N → Prop}
    (one : ∀ row : Row F S, P 1 ⟨<+> +> row, rfl⟩)
    (more : ∀ (N : ℕ) (curr next : Row F S) (rest : TraceOfLength F S N),
      P ⟨N + 1, Nat.succ_pos N⟩ ⟨rest.val +> curr, by simp [Trace.len, rest.property]⟩ →
      P ⟨N + 2, Nat.succ_pos (N + 1)⟩ ⟨rest.val +> curr +> next, by simp [Trace.len, rest.property]⟩)
    : ∀ N trace, P N trace :=
end TraceOfLength

namespace TraceOfLength
def two_row_induction {prop : Row F S → ℕ → Prop}
    (zero : ∀ first_row : Row F S, prop first_row 0)
    (succ : ∀ (N : ℕ) (curr next : Row F S), prop curr N → prop next (N + 1))
    : ∀ N (trace : TraceOfLength F S N), ForAllRowsOfTraceWithIndex trace prop :=
end TraceOfLength

namespace CellAssignment
def pushVarInput_offset (assignment : CellAssignment W S) (off : CellOffset W S) :
  (assignment.pushVarInput off).offset = assignment.offset + 1 :=
end CellAssignment

namespace Examples.FemtoCairo.Types
structure State (F : Type) where
  pc : F
  ap : F
  fp : F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure RawInstruction (F : Type) where
  rawInstrType : F
  op1 : F
  op2 : F
  op3 : F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure DecodedInstructionType (F : Type) where
  isAdd : F
  isMul : F
  isStoreState : F
  isLoadState : F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure DecodedAddressingMode (F : Type) where
  isDoubleAddressing : F
  isApRelative : F
  isFpRelative : F
  isImmediate : F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure DecodedInstruction (F : Type) where
  instrType : DecodedInstructionType F
  mode1 : DecodedAddressingMode F
  mode2 : DecodedAddressingMode F
  mode3 : DecodedAddressingMode F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure MemoryReadInput (F : Type) where
  state : State F
  offset : F
  mode : DecodedAddressingMode F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
structure StateTransitionInput (F : Type) where
  state : State F
  decoded : DecodedInstruction F
  v1 : F
  v2 : F
  v3 : F
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableType State where
  size := 3
  toElements := fun { pc, ap, fp } => #v[pc, ap, fp]
  fromElements := fun elements => {
    pc := elements[0],
    ap := elements[1],
    fp := elements[2]
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableType RawInstruction where
  size := 4
  toElements := fun { rawInstrType, op1, op2, op3 } => #v[rawInstrType, op1, op2, op3]
  fromElements := fun elements => {
    rawInstrType := elements[0],
    op1 := elements[1],
    op2 := elements[2],
    op3 := elements[3]
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableType DecodedInstructionType where
  size := 4
  toElements := fun { isAdd, isMul, isStoreState, isLoadState } => #v[isAdd, isMul, isStoreState, isLoadState]
  fromElements := fun elements => {
    isAdd := elements[0],
    isMul := elements[1],
    isStoreState := elements[2],
    isLoadState := elements[3]
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableType DecodedAddressingMode where
  size := 4
  toElements := fun { isDoubleAddressing, isApRelative, isFpRelative, isImmediate } => #v[isDoubleAddressing, isApRelative, isFpRelative,
    isImmediate]
  fromElements := fun elements => {
    isDoubleAddressing := elements[0],
    isApRelative := elements[1],
    isFpRelative := elements[2],
    isImmediate := elements[3]
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableStruct DecodedInstruction where
  components := [DecodedInstructionType, DecodedAddressingMode, DecodedAddressingMode, DecodedAddressingMode]
  toComponents := fun { instrType, mode1, mode2, mode3 } => .cons instrType (.cons mode1 (.cons mode2 (.cons mode3 .nil)))
  fromComponents := fun (.cons instrType (.cons mode1 (.cons mode2 (.cons mode3 .nil)))) => {
    instrType, mode1, mode2, mode3
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableStruct MemoryReadInput where
  components := [State, field, DecodedAddressingMode]
  toComponents := fun { state, offset, mode } => .cons state (.cons offset (.cons mode .nil))
  fromComponents := fun (.cons state (.cons offset (.cons mode .nil))) => {
    state, offset, mode
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
instance : ProvableStruct StateTransitionInput where
  components := [State, DecodedInstruction, field, field, field]
  toComponents := fun { state, decoded, v1, v2, v3 } =>
    .cons state (.cons decoded (.cons v1 (.cons v2 (.cons v3 .nil))))
  fromComponents := fun (.cons state (.cons decoded (.cons v1 (.cons v2 (.cons v3 .nil))))) => {
    state, decoded, v1, v2, v3
  }
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
def DecodedInstructionType.val : DecodedInstructionType (F p) → ℕ := fun instrType =>
  if instrType.isAdd = 1 then 0
  else if instrType.isMul = 1 then 1
  else if instrType.isStoreState = 1 then 2
  else 3
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
def DecodedInstructionType.isEncodedCorrectly (instrType : DecodedInstructionType (F p)) : Prop :=
  (instrType.isAdd = 1 ∧ instrType.isMul = 0 ∧ instrType.isStoreState = 0 ∧ instrType.isLoadState = 0) ∨
  (instrType.isAdd = 0 ∧ instrType.isMul = 1 ∧ instrType.isStoreState = 0 ∧ instrType.isLoadState = 0) ∨
  (instrType.isAdd = 0 ∧ instrType.isMul = 0 ∧ instrType.isStoreState = 1 ∧ instrType.isLoadState = 0) ∨
  (instrType.isAdd = 0 ∧ instrType.isMul = 0 ∧ instrType.isStoreState = 0 ∧ instrType.isLoadState = 1)
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
def DecodedAddressingMode.val : DecodedAddressingMode (F p) → ℕ := fun mode =>
  if mode.isDoubleAddressing = 1 then 0
  else if mode.isApRelative = 1 then 1
  else if mode.isFpRelative = 1 then 2
  else 3
end Examples.FemtoCairo.Types

namespace Examples.FemtoCairo.Types
def DecodedAddressingMode.isEncodedCorrectly (mode : DecodedAddressingMode (F p)) : Prop :=
  (mode.isDoubleAddressing = 1 ∧ mode.isApRelative = 0 ∧ mode.isFpRelative = 0 ∧ mode.isImmediate = 0) ∨
  (mode.isDoubleAddressing = 0 ∧ mode.isApRelative = 1 ∧ mode.isFpRelative = 0 ∧ mode.isImmediate = 0) ∨
  (mode.isDoubleAddressing = 0 ∧ mode.isApRelative = 0 ∧ mode.isFpRelative = 1 ∧ mode.isImmediate = 0) ∨
  (mode.isDoubleAddressing = 0 ∧ mode.isApRelative = 0 ∧ mode.isFpRelative = 0 ∧ mode.isImmediate = 1)
end Examples.FemtoCairo.Types

def FormalCircuit.concat
    {F : Type} [Field F]
    {Input Mid Output : TypeMap} [ProvableType Input] [ProvableType Mid] [ProvableType Output]
    (circuit1 : FormalCircuit F Input Mid)
    (circuit2 : FormalCircuit F Mid Output)
    (h_compat : ∀ input mid, circuit1.Assumptions input → circuit1.Spec input mid → circuit2.Assumptions mid)
    (h_localLength_stable : ∀ mid mid', circuit2.localLength mid = circuit2.localLength mid') :
    FormalCircuit F Input Output := {
  elaborated := {
    main := (circuit1 · >>= circuit2)
    localLength input := circuit1.localLength input + circuit2.localLength (circuit1.output input 0)
    localLength_eq := by admit /- proof elided -/

def FormalCircuit.weakenSpec
    {F : Type} [Field F]
    {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : FormalCircuit F Input Output)
    (WeakerSpec : Input F → Output F → Prop)
    (h_spec_implication : ∀ input output,
      circuit.Assumptions input →
      circuit.Spec input output →
      WeakerSpec input output) :
    FormalCircuit F Input Output := {
  elaborated := circuit.elaborated
  Assumptions := circuit.Assumptions
  Spec := WeakerSpec
  soundness := by admit /- proof elided -/

namespace Gadgets.Or.Or8
structure Inputs (F : Type) where
  x: F
  y: F
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
instance : ProvableStruct Inputs where
  components := [field, field]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.val < 256 ∧ y.val < 256
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
def Spec (input : Inputs (F p)) (z : F p) :=
  let ⟨x, y⟩ := input
  z.val = x.val ||| y.val ∧ z.val < 256
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
def main (input : Var Inputs (F p)) : Circuit (F p) (fieldVar (F p)) := do
  let ⟨x, y⟩ := input
  let or ← witness fun eval => (eval x).val ||| (eval y).val
  
  let xor := 2*or - x - y
  lookup ByteXorTable (x, y, xor)
  return or
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
instance elaborated : ElaboratedCircuit (F p) Inputs field where
  main
  localLength _ := 1
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
def circuit : FormalCircuit (F p) Inputs field :=
  { Assumptions, Spec, soundness, completeness }
end Gadgets.Or.Or8

namespace TestCircuitProofStart.UnfoldTest1
def TestAssumptions (_ : unit (F p)) : Prop := True
end TestCircuitProofStart.UnfoldTest1

namespace TestCircuitProofStart.UnfoldTest1
def TestSpec (_ : unit (F p)) (_ : unit (F p)) : Prop := True
end TestCircuitProofStart.UnfoldTest1

namespace TestCircuitProofStart.UnfoldTest1
def Assumptions (input : unit (F p)) : Prop :=
  TestAssumptions input
end TestCircuitProofStart.UnfoldTest1

namespace TestCircuitProofStart.UnfoldTest1
def Spec (input : unit (F p)) (output : unit (F p)) : Prop :=
  TestSpec input output
end TestCircuitProofStart.UnfoldTest1

namespace TestCircuitProofStart.UnfoldTest1
def testCircuit : ElaboratedCircuit (F p) unit unit :=
  { main := fun _ => pure (), output := fun _ _ => (), localLength := 0, output_eq := by simp }

example : Soundness (F p) testCircuit Assumptions Spec :=
end TestCircuitProofStart.UnfoldTest1

namespace TestCircuitProofStart.UnfoldTest2
def TestAssumptions (_ : unit (F p)) : Prop := True
end TestCircuitProofStart.UnfoldTest2

namespace TestCircuitProofStart.UnfoldTest2
def TestSpec (_ : unit (F p)) (_ : unit (F p)) : Prop := True
end TestCircuitProofStart.UnfoldTest2

namespace TestCircuitProofStart.UnfoldTest2
def Assumptions (input : unit (F p)) : Prop :=
  TestAssumptions input ∧
  TestAssumptions input
end TestCircuitProofStart.UnfoldTest2

namespace TestCircuitProofStart.UnfoldTest2
def Spec (input : unit (F p)) (output : unit (F p)) : Prop :=
  TestSpec input output ∧
  TestSpec input output
end TestCircuitProofStart.UnfoldTest2

namespace TestCircuitProofStart.UnfoldTest2
def testCircuit : ElaboratedCircuit (F p) unit unit :=
  { main := fun _ => pure (), output := fun _ _ => (), localLength := 0, output_eq := by simp }

example : Soundness (F p) testCircuit Assumptions Spec :=
end TestCircuitProofStart.UnfoldTest2

namespace TestCircuitProofStart.UnfoldTest3
def testCircuit : ElaboratedCircuit (F p) unit unit :=
  { main := fun _ => pure (), output := fun _ _ => (), localLength := 0, output_eq := by admit /- proof elided -/
end TestCircuitProofStart.UnfoldTest3

namespace TestCircuitProofStart.UnfoldTest3
def elaborated : ElaboratedCircuit (F p) unit unit :=
  testCircuit
end TestCircuitProofStart.UnfoldTest3

namespace TestCircuitProofStart.UnfoldTest3
def TestAssumptions (_ : unit (F p)) : Prop := True
end TestCircuitProofStart.UnfoldTest3

namespace TestCircuitProofStart.UnfoldTest3
def TestSpec (_ : unit (F p)) (_ : unit (F p)) : Prop := True

example : Soundness (F p) elaborated TestAssumptions TestSpec :=
end TestCircuitProofStart.UnfoldTest3

namespace Tables.Addition8
structure RowType (F : Type) where
  x: F
  y: F
  z: F
end Tables.Addition8

namespace Tables.Addition8
instance : ProvableType RowType where
  size := 3
  toElements x := #v[x.x, x.y, x.z]
  fromElements v :=
    let ⟨ .mk [x, y, z], _ ⟩ := v
    ⟨ x, y, z ⟩
end Tables.Addition8

namespace Tables.Addition8
def add8Inline : SingleRowConstraint RowType (F p) := do
  let row ← TableConstraint.getCurrRow
  lookup ByteTable row.x
  lookup ByteTable row.y
  let z ← Gadgets.Addition8.circuit { x := row.x, y := row.y }
  assign (.curr 2) z
end Tables.Addition8

namespace Tables.Addition8
def add8Table : List (TableOperation RowType (F p)) := [
  .everyRow add8Inline
]
end Tables.Addition8

namespace Tables.Addition8
def Spec_add8 {N : ℕ} (trace : TraceOfLength (F p) RowType N) : Prop :=
  trace.ForAllRowsOfTrace (fun row => (row.z.val = (row.x.val + row.y.val) % 256))
end Tables.Addition8

namespace Tables.Addition8
def formalAdd8Table : FormalTable (F p) RowType := {
  constraints := add8Table,
  Spec := Spec_add8,
  soundness := by admit /- proof elided -/
end Tables.Addition8

namespace Gadgets.Rotation32Bits
instance : Fact (p > 512) :=
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def main (offset : Fin 8) (x : U32 (Expression (F p))) : Circuit (F p) (Var U32 (F p)) := do
  let parts ← Circuit.map x.toLimbs (ByteDecomposition.circuit offset)
  let lows := parts.map Outputs.low
  let highs := parts.map Outputs.high

  let rotated := highs.zip (lows.rotate 1) |>.map fun (high, low) =>
    high + low * ((2^(8-offset.val) : ℕ) : F p)

  return U32.fromLimbs rotated
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def Assumptions (input : U32 (F p)) := input.Normalized
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def Spec (offset : Fin 8) (x : U32 (F p)) (y : U32 (F p)) :=
  y.value = rotRight32 x.value offset.val
  ∧ y.Normalized
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def output (offset : Fin 8) (i0 : ℕ) : U32 (Expression (F p)) :=
  U32.fromLimbs (.ofFn fun ⟨i,_⟩ =>
    (var ⟨i0 + i*2 + 1⟩) + var ⟨i0 + (i + 1) % 4 * 2⟩ * .const ((2^(8-offset.val) : ℕ) : F p))
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def elaborated (off : Fin 8) : ElaboratedCircuit (F p) U32 U32 where
  main := main off
  localLength _ := 8
  output _inputs i0 := output off i0
  localLength_eq _ i0 := by admit /- proof elided -/
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
def circuit (offset : Fin 8) : FormalCircuit (F p) U32 U32 := {
  elaborated offset with
  Assumptions
  Spec := Spec offset
  soundness := soundness offset
  completeness := completeness offset
}
end Gadgets.Rotation32Bits

def findStructVarsInEqualities : TacticM (List FVarId) := do
  withMainContext do
    let ctx ← getLCtx
    let mut structVarsToCase := []

    
    for localDecl in ctx do
      if localDecl.isImplementationDetail then
        continue

      let type := localDecl.type

      
      let equalities ← extractEqualities type

      for (_, lhs, rhs) in equalities do
        
        let lhsIsConstructor ← isMkConstructor lhs
        let rhsIsConstructor ← isMkConstructor rhs

        if lhsIsConstructor && !rhsIsConstructor then
          
          match rhs with
          | .fvar fvarId =>
            
            let varType ← inferType rhs
            if ← hasProvableStructInstance varType then
              structVarsToCase := fvarId :: structVarsToCase
          | _ => pure ()
        else if rhsIsConstructor && !lhsIsConstructor then
          
          match lhs with
          | .fvar fvarId =>
            
            let varType ← inferType lhs
            if ← hasProvableStructInstance varType then
              structVarsToCase := fvarId :: structVarsToCase
          | _ => pure ()

    return structVarsToCase.eraseDups

def splitProvableStructEq : TacticM Unit := do
  withMainContext do
    
    let varsToCase ← findStructVarsInEqualities

    if !varsToCase.isEmpty then
      let mut currentGoal ← getMainGoal
      for fvarId in varsToCase do
        try
          
          let altVarNames ← generateStructFieldNames fvarId
          
          
          let casesResult ← currentGoal.cases fvarId #[altVarNames]
          
          match casesResult.toList with
          | [subgoal] =>
            currentGoal := subgoal.mvarId
          | _ => continue
        catch e =>
          let ldecl ← fvarId.getDecl
          trace[Meta.Tactic] "Failed to apply cases to struct variable {ldecl.userName}: {e.toMessageData}"
          continue

      
      replaceMainGoal [currentGoal]

    
    withMainContext do
      
      let env ← getEnv
      let mut lemmasToApply : List Ident := []

      
      let ctx ← getLCtx
      for localDecl in ctx do
        if localDecl.isImplementationDetail then
          continue

        let type := localDecl.type

        
        let equalities ← extractEqualities type

        for (eqExpr, _, _) in equalities do
          
          if let some typeExpr := eqExpr.getArg? 0 then
            
            let typeExpr' ← whnf typeExpr
            match typeExpr'.getAppFn with
            | .const typeName _ =>
              
              let mkInjEqName := typeName ++ `mk ++ `injEq
              if env.contains mkInjEqName then
                
                
                if ← hasProvableStructInstance typeExpr' then
                  let lemmaIdent := mkIdent mkInjEqName
                  if !lemmasToApply.any (fun l => l.getId == mkInjEqName) then
                    lemmasToApply := lemmaIdent :: lemmasToApply
            | _ => pure ()

      
      for lemmaIdent in lemmasToApply do
        try
          evalTactic (← `(tactic| simp only [$lemmaIdent:ident] at *))
        catch e =>
          trace[Meta.Tactic] "Failed to apply lemma {lemmaIdent}: {e.toMessageData}"
          continue

structure Table (F : Type) (Row : TypeMap) [ProvableType Row] where
  name : String
   
  Contains : Row F → Prop

   
  Soundness : Row F → Prop
  Completeness : Row F → Prop

  imply_soundness : ∀ row, Contains row → Soundness row
  implied_by_completeness : ∀ row, Completeness row → Contains row

structure RawTable (F : Type) where
  name : String
  arity : ℕ
  Contains : Vector F arity → Prop
  Soundness : Vector F arity → Prop
  Completeness : Vector F arity → Prop
  imply_soundness : ∀ row, Contains row → Soundness row
  implied_by_completeness : ∀ row, Completeness row → Contains row

structure Lookup (F : Type) where
  table : RawTable F
  entry : Vector (Expression F) table.arity

instance [Repr F] : Repr (Lookup F) where
  reprPrec l _ := "(Lookup " ++ l.table.name ++ " " ++ repr l.entry ++ ")"

@[circuit_norm]
def Table.toRaw (table : Table F Row) : RawTable F where
  name := table.name
  arity := size Row
  Contains row := table.Contains (fromElements row)
  Soundness row := table.Soundness (fromElements row)
  Completeness row := table.Completeness (fromElements row)
  imply_soundness row := table.imply_soundness (fromElements row)
  implied_by_completeness row := table.implied_by_completeness (fromElements row)

structure StaticTable (F : Type) (Row : TypeMap) [ProvableType Row] where
  name : String
  length : ℕ
  row : Fin length → Row F
  
  
  
  index : Row F → ℕ
  Spec : Row F → Prop
  contains_iff : ∀ t, (∃ i, t = row i) ↔ Spec t

namespace StaticTable
def Contains (table : StaticTable F Row) (row : Row F) :=
  ∃ i : Fin table.length, row = table.row i
end StaticTable

namespace StaticTable
@[circuit_norm]
def toTable (table : StaticTable F Row) : Table F Row where
  name := table.name
  Contains := table.Contains
  Soundness := table.Spec
  Completeness := table.Spec
  imply_soundness t := (table.contains_iff t).mp
  implied_by_completeness t := (table.contains_iff t).mpr
end StaticTable

@[circuit_norm]
def Table.fromStatic (table : StaticTable F Row) : Table F Row :=
  StaticTable.toTable table

namespace Circomlib.Num2Bits
def main (n : ℕ) (inp : Expression (F p)) := do
  let out ← witnessVector n fun env => fieldToBits n (inp.eval env)

  let (lc1, _) ← Circuit.foldlRange n (0, 1) fun (lc1, e2) i => do
    out[i] * (out[i] - 1) === 0
    let lc1 := lc1 + out[i] * e2
    return (lc1, e2 + e2)

  lc1 === inp
  return out
end Circomlib.Num2Bits

namespace Circomlib.Num2Bits
def arbitraryBitLengthCircuit (n : ℕ) : GeneralFormalCircuit (F p) field (fields n) where
  main := main n
  localLength _ := n
  localLength_eq := by admit /- proof elided -/
end Circomlib.Num2Bits

namespace Circomlib.Num2Bits
def circuit (n : ℕ) (hn : 2^n < p) : GeneralFormalCircuit (F p) field (fields n) where
  main input := arbitraryBitLengthCircuit n input
  localLength _ := n
  output _ i := varFromOffset (fields n) i

  Assumptions input := input.val < 2^n

  Spec input output :=
    input.val < 2^n ∧ output = fieldToBits n input

  soundness := by admit /- proof elided -/
end Circomlib.Num2Bits

namespace Circomlib.Bits2Num
def main (n : ℕ) (input : Vector (Expression (F p)) n) := do
  let (lc1, _) := Fin.foldl n (fun (lc1, e2) i =>
    let lc1 := lc1 + input[i] * e2
    let e2 := e2 + e2
    (lc1, e2)) (0, 1)

  let out <== lc1
  return out
end Circomlib.Bits2Num

namespace Circomlib.Bits2Num
def circuit (n : ℕ) : FormalCircuit (F p) (fields n) field where
  main := main n
  localLength _  := 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.Bits2Num

def InductiveTable.Soundness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
      (eval env acc_var = acc) ∧ (eval env x_var = x) →
    
    Circuit.ConstraintsHold.Soundness env (step acc_var x_var |>.operations ((size State) + (size Input))) →
    
    Spec initialState xs row_index xs_len acc →
    
    Spec initialState (xs.concat x) (row_index + 1) (xs_len ▸ List.length_concat) (eval env (step acc_var x_var |>.output ((size State) + (size Input))))

def InductiveTable.Completeness (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input]
    (InputAssumptions : ℕ → Input F → Prop) (InitialStateAssumptions : State F → Prop)
    (Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop)
    (step : Var State F → Var Input F → Circuit F (Var State F)) :=
  ∀ (initialState : State F) (row_index : ℕ) (env : Environment F),
  
  ∀ (acc_var : Var State F) (x_var : Var Input F)
    (acc : State F) (x : Input F) (xs : List (Input F)) (xs_len : xs.length = row_index),
    (eval env acc_var = acc) ∧ (eval env x_var = x) →
  
  env.UsesLocalWitnessesCompleteness ((size State) + (size Input)) (step acc_var x_var |>.operations ((size State) + (size Input))) →
  
  InitialStateAssumptions initialState ∧
  Spec initialState xs row_index xs_len acc ∧ InputAssumptions row_index x →
  
  Circuit.ConstraintsHold.Completeness env (step acc_var x_var |>.operations ((size State) + (size Input)))

structure InductiveTable (F : Type) [Field F] (State Input : Type → Type) [ProvableType State] [ProvableType Input] where
   
  step : Var State F → Var Input F → Circuit F (Var State F)

   
  Spec : (initialState : State F) → (xs : List (Input F)) → (i : ℕ) → (xs.length = i) → (currentState : State F) → Prop

   
  InputAssumptions : ℕ → Input F → Prop := fun _ _ => True
  InitialStateAssumptions : State F → Prop := fun _ => True

  soundness : InductiveTable.Soundness F State Input Spec step

  completeness : InductiveTable.Completeness F State Input InputAssumptions InitialStateAssumptions Spec step

  subcircuitsConsistent : ∀ acc x, ((step acc x).operations ((size State) + (size Input))).SubcircuitsConsistent ((size State) + (size Input))
    := by admit /- proof elided -/

namespace InductiveTable
def inductiveConstraint (table : InductiveTable F State Input) : TableConstraint 2 (ProvablePair State Input) F Unit := do
  let (acc, x) ← getCurrRow
  let output ← table.step acc x
  let (output', _) ← getNextRow
  
  output' === output
end InductiveTable

namespace InductiveTable
def equalityConstraint (Input : TypeMap) [ProvableType Input] (target : State F) : SingleRowConstraint (ProvablePair State Input) F := do
  let (actual, _) ← getCurrRow
  actual === (const target)
end InductiveTable

namespace InductiveTable
def tableConstraints (table : InductiveTable F State Input) (input_state output_state : State F) :
  List (TableOperation (ProvablePair State Input) F) := [
    .everyRowExceptLast table.inductiveConstraint,
    .boundary (.fromStart 0) (equalityConstraint Input input_state),
    .boundary (.fromEnd 0) (equalityConstraint Input output_state),
  ]
end InductiveTable

namespace InductiveTable
def traceInputs {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) : List (Input F) :=
  trace.val.toList.map Prod.snd
end InductiveTable

partial def getMaxMatchingHyp (e : Lean.Expr) : Lean.Elab.Tactic.TacticM (List Lean.Expr) := do
  
  let e ← whnf e
  match e with
  | (.forallE _ typ body _) => do
    
    let ctx ← Lean.MonadLCtx.getLCtx
    let option_matching_expr ← ctx.findDeclM? fun decl: Lean.LocalDecl => do
      let declExpr := decl.toExpr
      let declType ← Lean.Meta.inferType declExpr
      if ← Lean.Meta.isExprDefEq declType typ
        then return Option.some declExpr
        else return Option.none

    
    match option_matching_expr with
    | some res => do
      let other_hyps ← getMaxMatchingHyp body
      return [res] ++ other_hyps
    | none => do return []
  | _ => return []

partial def SpecializeAuto (e_term : Lean.Term): Lean.Elab.Tactic.TacticM Unit := do
  let (e, mvarIds') ← elabTermWithHoles e_term none `specialize_auto (allowNaturalHoles := true)

  
  if e.isFVar then
    let localDecl ← e.fvarId!.getDecl
    let declType ← Lean.Meta.inferType localDecl.toExpr

    
    let matching ← getMaxMatchingHyp declType

    
    let h' := Lean.mkAppN e matching.toArray

    
    let goal ← getMainGoal
    let mvarId ← goal.assert (← e.fvarId!.getDecl).userName (← inferType h').headBeta h'
    let (_, mvarId) ← mvarId.intro1P
    let mvarId ← mvarId.tryClear e.fvarId!
    replaceMainGoal (mvarIds' ++ [mvarId])
  else
    throwError "'specialize_auto' requires a term that appears in the local context"

def generateTrace (steps : ℕ) (output_path : String) : IO Unit := do
  let fib_relation_babybear := fib_relation (p:=p_babybear)
  let init_row : RowType (F p_babybear) := { x := 0, y := 1 }

  let trace_data := witnesses fib_relation_babybear init_row steps
  let json_data := Lean.toJson trace_data

  IO.FS.writeFile output_path json_data.pretty
  IO.println s!"Generated trace with {steps} steps -> {output_path}"

def main (args : List String) : IO Unit := do
  match args with
  | [steps_str, output_path] =>
    
    match steps_str.toNat? with
    | some steps => generateTrace steps output_path
    | none => IO.println "Error: Invalid number of steps"
  | _ =>
    IO.println "Usage: lake lean TraceGen.lean 

namespace Gadgets.BLAKE3.G
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
structure Inputs (F : Type) where
  state : BLAKE3State F
  x : U32 F
  y : U32 F
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
instance : ProvableStruct Inputs where
  components := [BLAKE3State, U32, U32]
  toComponents := fun { state, x, y } => .cons state (.cons x (.cons y .nil))
  fromComponents := fun (.cons state (.cons x (.cons y .nil))) => { state, x, y }
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
def main (a b c d : Fin 16) (input : Var Inputs (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  let { state, x, y } := input

  let state_a ← Addition32.circuit ⟨state[a], ← Addition32.circuit ⟨state[b], x⟩⟩

  let state_d ← Rotation32.circuit 16 <|
    ← Xor32.circuit ⟨state[d], state_a⟩

  let state_c ← Addition32.circuit ⟨state[c], state_d⟩

  let state_b ← Rotation32.circuit 12 <|
    ← Xor32.circuit ⟨state[b], state_c⟩

  let state_a ← Addition32.circuit ⟨state_a, ← Addition32.circuit ⟨state_b, y⟩⟩

  let state_d ← Rotation32.circuit 8 <|
    ← Xor32.circuit ⟨state_d, state_a⟩

  let state_c ← Addition32.circuit ⟨state_c, state_d⟩

  let state_b ← Rotation32.circuit 7 <|
    ← Xor32.circuit ⟨state_b, state_c⟩

  return state
    |>.set a state_a
    |>.set b state_b
    |>.set c state_c
    |>.set d state_d
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
instance elaborated (a b c d : Fin 16): ElaboratedCircuit (F p) Inputs BLAKE3State where
  main := main a b c d
  localLength _ := 96
  output inputs i0 := (inputs.state : Vector (U32 (Expression (F p))) 16)
    |>.set a (⟨var ⟨i0 + 56⟩, var ⟨i0 + 58⟩, var ⟨i0 + 60⟩, var ⟨i0 + 62⟩⟩) a.is_lt
    |>.set b (Rotation32.output 7 (i0 + 88)) b.is_lt
    |>.set c (⟨var ⟨i0 + 76⟩, var ⟨i0 + 78⟩, var ⟨i0 + 80⟩, var ⟨i0 + 82⟩⟩) c.is_lt
    |>.set d (Rotation32.output 8 (i0 + 68)) d.is_lt

  localLength_eq _ n := by admit /- proof elided -/
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
def Assumptions (input : Inputs (F p)) :=
  let { state, x, y } := input
  state.Normalized ∧ x.Normalized ∧ y.Normalized
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
def Spec (a b c d : Fin 16) (input : Inputs (F p)) (out : BLAKE3State (F p)) :=
  let { state, x, y } := input
  out.value = g state.value a b c d x.value y.value ∧ out.Normalized
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
def circuit (a b c d : Fin 16) : FormalCircuit (F p) Inputs BLAKE3State := {
  elaborated a b c d with
  Assumptions
  Spec := Spec a b c d
  soundness := soundness a b c d
  completeness := completeness a b c d
}
end Gadgets.BLAKE3.G

instance : ToJson (CellOffset W S) where
  toJson off := Json.mkObj [
    ("row", off.row),
    ("column", off.column)
  ]

instance : ToJson (Cell W S) where
  toJson
    | .input off => toJson off
    | .aux i => Json.mkObj [("aux", toJson i)]

instance : ToJson (CellAssignment W S) where
  toJson assignment :=
    let aux_map := buildAuxMap assignment
    
    let vars := assignment.vars.mapIdx fun idx cell =>
      match cell with
      | Cell.input off => Cell.input off
      | Cell.aux _ =>
        let col := aux_map[idx]!
        if h: col < (size S)
          then Cell.input { row := 1, column := ⟨col, h⟩ }
          else cell 

    Json.mkObj [
      ("offset", toJson assignment.offset),
      ("aux_length", toJson assignment.aux_length),
      ("vars", toJson vars.toArray),
    ]

instance : ToJson (TableContext W S F) where
  toJson ctx := Json.mkObj [
    ("circuit", toJson ctx.circuit),
    ("assignment", toJson ctx.assignment)
  ]

instance : ToJson (TableConstraint W S F α) where
  toJson table := toJson (table .empty).2

instance : ToJson (TableOperation S F) where
  toJson
    | .boundary i c => Json.mkObj [
      ("type", Json.str "Boundary"),
      ("row", reprStr i),
      ("context", toJson c)
    ]
    | .everyRow c => Json.mkObj [
      ("type", Json.str "EveryRow"),
      ("context", toJson c)
    ]
    | .everyRowExceptLast c => Json.mkObj [
      ("type", Json.str "EveryRowExceptLast"),
      ("context", toJson c)
    ]

def fib8json := Lean.toJson (Tables.Fibonacci8Table.fibTable (p:=pBabybear))

def fib32json := Lean.toJson ((Tables.Fibonacci32Inductive.table (p:=pBabybear)).tableConstraints
  { x := U32.fromByte 0, y := U32.fromByte 1 }
  { x := U32.fromByte 0, y := U32.fromByte 0 })

namespace Utils.Bits
def toBits (n : ℕ) (x : ℕ) : Vector ℕ n :=
  .mapRange n fun i => if x.testBit i then 1 else 0
end Utils.Bits

namespace Utils.Bits
def fromBits {n : ℕ} (bits : Vector ℕ n) : ℕ :=
  Fin.foldl n (fun acc ⟨i, _⟩ => acc + bits[i] * 2^i) 0
end Utils.Bits

namespace Utils.Bits
def fieldToBits (n : ℕ) (x : F p) : Vector (F p) n :=
  .map (↑) (toBits n x.val)
end Utils.Bits

namespace Utils.Bits
def fieldFromBits {n : ℕ} (bits : Vector (F p) n) : F p :=
  fromBits <| bits.map ZMod.val
end Utils.Bits

namespace Utils.Bits
def fieldFromBitsExpr {n : ℕ} (bits : Vector (Expression (F p)) n) : Expression (F p) :=
  Fin.foldl n (fun acc ⟨i, _⟩ => acc + bits[i] * (2^i : F p)) 0
end Utils.Bits

namespace Gadgets.Rotation64.Theorems
def rotRight64_bytes (xs : Vector ℕ 8) (o : ℕ) : Vector ℕ 8 :=
  .ofFn fun ⟨ i, hi ⟩ => xs[i] / 2^o + (xs[(i + 1) % 8] % 2^o) * 2^(8-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
def rotRight64_u64 : U64 ℕ → ℕ → U64 ℕ
  | ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩, o => ⟨
    (x0 / 2^o) + (x1 % 2^o) * 2^(8-o),
    (x1 / 2^o) + (x2 % 2^o) * 2^(8-o),
    (x2 / 2^o) + (x3 % 2^o) * 2^(8-o),
    (x3 / 2^o) + (x4 % 2^o) * 2^(8-o),
    (x4 / 2^o) + (x5 % 2^o) * 2^(8-o),
    (x5 / 2^o) + (x6 % 2^o) * 2^(8-o),
    (x6 / 2^o) + (x7 % 2^o) * 2^(8-o),
    (x7 / 2^o) + (x0 % 2^o) * 2^(8-o),
  ⟩
end Gadgets.Rotation64.Theorems

namespace Gadgets.BLAKE3.Permute
def main (state : Var BLAKE3State (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  return Vector.ofFn (fun i => state[msgPermutation[i]])
end Gadgets.BLAKE3.Permute

namespace Gadgets.BLAKE3.Permute
instance elaborated: ElaboratedCircuit (F p) BLAKE3State BLAKE3State where
  main := main
  localLength _ := 0
  output state i0 := Vector.ofFn (fun i => state[msgPermutation[i]])
end Gadgets.BLAKE3.Permute

namespace Gadgets.BLAKE3.Permute
def Assumptions (state : BLAKE3State (F p)) := state.Normalized
end Gadgets.BLAKE3.Permute

namespace Gadgets.BLAKE3.Permute
def Spec (state : BLAKE3State (F p)) (out : BLAKE3State (F p)) :=
  out.value = permute state.value ∧ out.Normalized
end Gadgets.BLAKE3.Permute

namespace Gadgets.BLAKE3.Permute
def circuit : FormalCircuit (F p) BLAKE3State BLAKE3State :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.BLAKE3.Permute

namespace Circomlib.MultiMux3
structure Inputs (n : ℕ) (F : Type) where
  c : ProvableVector (fields 8) n F  
  s : Vector F 3                      
end Circomlib.MultiMux3

namespace Circomlib.MultiMux3
instance {n : ℕ} : ProvableStruct (Inputs n) where
  components := [ProvableVector (fields 8) n, fields 3]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.MultiMux3

namespace Circomlib.MultiMux3
def main (n : ℕ) (input : Var (Inputs n) (F p)) := do
  let { c, s } := input

  let s10 <== s[1] * s[0]

  
  let out <== c.map fun ci =>
    let a210 := (ci[7] - ci[6] - ci[5] + ci[4] - ci[3] + ci[2] + ci[1] - ci[0]) * s10
    let a21 := (ci[6] - ci[4] - ci[2] + ci[0]) * s[1]
    let a20 := (ci[5] - ci[4] - ci[1] + ci[0]) * s[0]
    let a2 := (ci[4] - ci[0])

    let a10 := (ci[3] - ci[2] - ci[1] + ci[0]) * s10;
    let a1 := (ci[2] - ci[0]) * s[1];
    let a0 := (ci[1] - ci[0]) * s[0];
    let a := (ci[0]);

    (a210 + a21 + a20 + a2) * s[2] + (a10 + a1 + a0 + a);

  return out
end Circomlib.MultiMux3

namespace Circomlib.MultiMux3
def circuit (n : ℕ) : FormalCircuit (F p) (Inputs n) (fields n) where
  main := main n

  localLength _ := n + 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.MultiMux3

namespace Circomlib.Mux3
structure Inputs (F : Type) where
  c : Vector F 8  
  s : Vector F 3  
end Circomlib.Mux3

namespace Circomlib.Mux3
instance : ProvableStruct Inputs where
  components := [fields 8, fields 3]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.Mux3

namespace Circomlib.Mux3
def main (input : Var Inputs (F p)) := do
  let { c, s } := input

  
  let mux_out ← MultiMux3.circuit 1 { c := #v[c], s }
  return mux_out[0]
end Circomlib.Mux3

namespace Circomlib.Mux3
def circuit : FormalCircuit (F p) Inputs field where
  main := main

  localLength _ := 2
  localLength_eq := by admit /- proof elided -/
end Circomlib.Mux3

def p1009 := 1009

def pBabybear := 15 * 2^27 + 1

def pMersenne := 2^31 - 1

instance prime1009 : Fact (p1009.Prime) :=

instance primeBabybear : Fact (pBabybear.Prime) :=

instance primeMersenne : Fact (pMersenne.Prime) :=

instance : Fact (pBabybear > 512) :=

instance : Fact (pBabybear > 2^16 + 2^8) :=

instance : Fact (pMersenne > 512) :=

instance : Fact (pBabybear > 2^16 + 2^8) :=

instance : Fact (pMersenne > 2^16 + 2^8) :=

structure Variable (F : Type) where
  index : ℕ

instance : Repr (Variable F) where
  reprPrec v _ := "var ⟨" ++ repr v.index ++ "⟩"

inductive Expression (F : Type) where
  | var : Variable F -> Expression F
  | const : F -> Expression F
  | add : Expression F -> Expression F -> Expression F
  | mul : Expression F -> Expression F -> Expression F

structure Environment (F : Type) where
  get : ℕ → F

namespace Expression
@[circuit_norm]
def eval (env : Environment F) : Expression F → F
  | var v => env.get v.index
  | const c => c
  | add x y => eval env x + eval env y
  | mul x y => eval env x * eval env y
end Expression

namespace Expression
def toString [Repr F] : Expression F → String
  | var v => reprStr v
  | const c => reprStr c
  | add x y => "(" ++ toString x ++ " + " ++ toString y ++ ")"
  | mul x y => "(" ++ toString x ++ " * " ++ toString y ++ ")"
end Expression

namespace Expression
instance [Repr F] : Repr (Expression F) where
  reprPrec e _ := toString e
end Expression

namespace Expression
instance : Zero (Expression F) where
  zero := const 0
end Expression

namespace Expression
instance : One (Expression F) where
  one := const 1
end Expression

namespace Expression
instance : Add (Expression F) where
  add := add
end Expression

namespace Expression
instance : Neg (Expression F) where
  neg e := mul (const (-1)) e
end Expression

namespace Expression
instance : Sub (Expression F) where
  sub e₁ e₂ := add e₁ (-e₂)
end Expression

namespace Expression
instance : Mul (Expression F) where
  mul := mul
end Expression

namespace Expression
instance : Coe F (Expression F) where
  coe f := const f
end Expression

namespace Expression
instance {n : ℕ} [OfNat F n] : OfNat (Expression F) n where
  ofNat := const (OfNat.ofNat n)
end Expression

namespace Expression
instance : HMul F (Expression F) (Expression F) where
  hMul f e := mul f e
end Expression

namespace Expression
instance : HDiv (Expression F) F (Expression F) where
  hDiv e f := mul (f⁻¹ : F) e
end Expression

namespace Expression
instance : HDiv (Expression F) ℕ (Expression F) where
  hDiv e f := mul (f⁻¹ : F) e
end Expression

namespace Expression
instance {n : ℕ} : OfNat (Variable F) n where
  ofNat := { index := n }
end Expression

instance [Field F] : CoeFun (Environment F) (fun _ => (Expression F) → F) where
  coe env x := x.eval env

instance [Field F] : Inhabited F where
  default := 0

instance [Field F] : Inhabited (Expression F) where
  default := .const 0

@[reducible]
def TypeMap := Type → Type

@[reducible] def Var (M : TypeMap) (F : Type) := M (Expression F)

class ProvableType (M : TypeMap) where
  size : ℕ
  toElements {F : Type} : M F -> Vector F size
  fromElements {F : Type} : Vector F size -> M F

  toElements_fromElements {F : Type} : ∀ v : Vector F size, toElements (fromElements v) = v := by admit /- proof elided -/

class NonEmptyProvableType (M : TypeMap) extends ProvableType M where
  nonempty : size > 0 := by admit /- proof elided -/

@[circuit_norm]
def toVars (var : M (Expression F)) := toElements var

@[circuit_norm]
def fromVars (vars : Vector (Expression F) (size M)) := fromElements vars

namespace ProvableType
@[explicit_provable_type]
def eval (env : Environment F) (x : Var α F) : α F :=
  let vars := toVars x
  let values := vars.map (Expression.eval env)
  fromElements values
end ProvableType

namespace Gadgets.Addition8FullCarry
structure Inputs (F : Type) where
  x: F
  y: F
  carryIn: F
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
instance : ProvableStruct Inputs where
  components := [field, field, field]
  toComponents := fun { x, y, carryIn } => .cons x (.cons y (.cons carryIn .nil))
  fromComponents := fun (.cons x (.cons y (.cons carryIn .nil))) => { x, y, carryIn }
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
structure Outputs (F : Type) where
  z: F
  carryOut: F
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
instance : ProvableStruct Outputs where
  components := [field, field]
  toComponents := fun { z, carryOut } => .cons z (.cons carryOut .nil)
  fromComponents := fun (.cons z (.cons carryOut .nil)) => { z, carryOut }
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
def main (input : Var Inputs (F p)) : Circuit (F p) (Var Outputs (F p)) := do
  let ⟨x, y, carryIn⟩ := input

  
  let z ← witness fun eval => mod256 (eval (x + y + carryIn))
  lookup ByteTable z

  
  let carryOut ← witness fun eval => floorDiv256 (eval (x + y + carryIn))
  assertBool carryOut

  assertZero (x + y + carryIn - z - carryOut * 256)

  return { z, carryOut }
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y, carryIn⟩ := input
  x.val < 256 ∧ y.val < 256 ∧ IsBool carryIn
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
def Spec (input : Inputs (F p)) (out : Outputs (F p)) :=
  let ⟨x, y, carryIn⟩ := input
  out.z.val = (x.val + y.val + carryIn.val) % 256 ∧
  out.carryOut.val = (x.val + y.val + carryIn.val) / 256
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
def circuit : FormalCircuit (F p) Inputs Outputs where
  main
  Assumptions
  Spec
  localLength _ := 2
  output _ i0 := { z := var ⟨i0⟩, carryOut := var ⟨i0 + 1⟩ }

  soundness := by admit /- proof elided -/
end Gadgets.Addition8FullCarry

namespace Gadgets.Addition8FullCarry
def lookupCircuit : LookupCircuit (F p) Inputs Outputs := {
  circuit with
  name := "Addition8FullCarry"

  computableWitnesses n input := by admit /- proof elided -/
end Gadgets.Addition8FullCarry

namespace TestDecomposeProvableStruct
structure TestInputs (F : Type) where
  x : F
  y : F
  z : F
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
instance : ProvableStruct TestInputs where
  components := [field, field, field]
  toComponents := fun { x, y, z } => .cons x (.cons y (.cons z .nil))
  fromComponents := fun (.cons x (.cons y (.cons z .nil))) => { x, y, z }
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
structure NestedInputs (F : Type) where
  first : TestInputs F
  second : TestInputs F
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
instance : ProvableStruct NestedInputs where
  components := [TestInputs, TestInputs]
  toComponents := fun { first, second } => .cons first (.cons second .nil)
  fromComponents := fun (.cons first (.cons second .nil)) => { first, second }
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
def processStruct {F : Type} [Field F] (input : TestInputs F) : F :=
  input.x + input.y + input.z
end TestDecomposeProvableStruct

namespace Gadgets.Rotation32.Theorems
def rotRight32_bytes (xs : Vector ℕ 4) (o : ℕ) : Vector ℕ 4 :=
  .ofFn fun ⟨ i, hi ⟩ => xs[i] / 2^o + (xs[(i + 1) % 4] % 2^o) * 2^(8-o)
end Gadgets.Rotation32.Theorems

namespace Gadgets.Rotation32.Theorems
def rotRight32_u32 : U32 ℕ → ℕ → U32 ℕ
  | ⟨ x0, x1, x2, x3 ⟩, o => ⟨
    (x0 / 2^o) + (x1 % 2^o) * 2^(8-o),
    (x1 / 2^o) + (x2 % 2^o) * 2^(8-o),
    (x2 / 2^o) + (x3 % 2^o) * 2^(8-o),
    (x3 / 2^o) + (x0 % 2^o) * 2^(8-o),
  ⟩
end Gadgets.Rotation32.Theorems

namespace Specs.BLAKE3.ChunkProcessing.Tests
def testCV : Vector ℕ 8 := iv.map (·.toNat)


example :
    let state := initialChunkState testCV 0
    let expected := compress testCV (bytesToWords []) 0 0 (chunkStart ||| chunkEnd)
    finalizeChunk state 0 = expected.take 8 := rfl
end Specs.BLAKE3.ChunkProcessing.Tests

namespace Specs.BLAKE3.ChunkProcessing.Tests
def testBlock64 : List ℕ := List.range 64

-- Test single block processing
-- With the fix, 64 bytes now stay in the buffer (matching Python behavior)
example :
    let state := initialChunkState testCV 0
    let updated := updateChunk state testBlock64
    updated.blocks_compressed = 0 ∧ updated.block_buffer = testBlock64 :=
end Specs.BLAKE3.ChunkProcessing.Tests

namespace Specs.BLAKE3.ChunkProcessing.Tests
def testChunk65 : List ℕ := List.range 65

example :
    let state := initialChunkState testCV 0
    let updated := updateChunk state testChunk65
    updated.blocks_compressed = 1 ∧ updated.block_buffer.length = 1 :=
end Specs.BLAKE3.ChunkProcessing.Tests

namespace Specs.BLAKE3.ChunkProcessing.Tests
def testChunk1024 : List ℕ := List.range 1024

-- Verify bytesToWords handles padding correctly for small input
example :
    let bytes := [0x01, 0x02, 0x03, 0x04, 0x05]  -- 5 bytes
    let words := bytesToWords bytes
    -- First word is little-endian: 0x04030201
    words[0] = 0x04030201 ∧
    -- Second word has only one byte: 0x00000005
    words[1] = 0x00000005 ∧
    -- Rest are zeros
    words[2] = 0 :=
end Specs.BLAKE3.ChunkProcessing.Tests

instance {α : TypeMap} [ProvableType α] : Inhabited (Circuit F (Var α F)) where
  default := witness default

def copyToVar (x : Expression F) : Circuit F (Variable F) := do
  let x' ← witnessVar x.eval
  assertZero (x - (var x'))
  return x'

def toVar : Expression F → Circuit F (Variable F)
  | var v => pure v
  | x => copyToVar x

def getOffset : Circuit F ℕ := fun n => (n, [])

def computeValueFromOffset (α : TypeMap) [ProvableType α] (offset : ℕ) (env : Environment F) : α F :=
  fromElements <| .mapRange _ fun i => env.get (offset + i)

def ProvableType.witnessAny (α: TypeMap) [ProvableType α] : Circuit F (Var α F) := do
  let offset ← getOffset
  witness (computeValueFromOffset α offset)

def MemoryAccess := ℕ × ℕ × ℕ × ℕ 

def MemoryAccessList := List MemoryAccess

abbrev timestamp_ordering (x y : MemoryAccess) := match x, y with
| (t2, _a2, _r2, _w2), (t1, _a1, _r1, _w1) => t1 < t2

def MemoryAccessList.isTimestampSorted (accesses : MemoryAccessList) : Prop :=
  accesses.Sorted timestamp_ordering

def TimestampSortedMemoryAccessList := {accesses : MemoryAccessList // accesses.isTimestampSorted}

def MemoryAccessList.timestamps_neq (x y: MemoryAccess) : Prop :=
  match x, y with
  | (t_x, _a_x, _r_x, _w_x), (t_y, _a_y, _r_y, _w_y) => t_x ≠ t_y

def MemoryAccessList.Notimestampdup (accesses : MemoryAccessList) : Prop :=
  List.Pairwise timestamps_neq accesses

abbrev address_timestamp_ordering (x y : MemoryAccess) := match x, y with
| (t2, a2, _, _), (t1, a1, _, _) => if a1 = a2 then t1 ≤ t2 else a1 < a2

abbrev address_strict_timestamp_ordering (x y : MemoryAccess) := match x, y with
| (t2, a2, _, _), (t1, a1, _, _) => if a1 = a2 then t1 < t2 else a1 < a2

instance (x y : MemoryAccess) : Decidable (address_timestamp_ordering x y) :=

instance : IsTrans MemoryAccess address_timestamp_ordering :=

instance : IsTotal MemoryAccess address_timestamp_ordering :=

instance : IsAntisymm MemoryAccess timestamp_ordering :=

instance : IsIrrefl MemoryAccess timestamp_ordering :=

instance : IsIrrefl MemoryAccess address_strict_timestamp_ordering :=

instance : IsAntisymm MemoryAccess address_strict_timestamp_ordering :=

instance {x y : MemoryAccess} : Decidable (address_strict_timestamp_ordering x y) :=

@[reducible]
def MemoryAccessList.isAddressTimestampSorted (accesses : MemoryAccessList) : Prop :=
  accesses.Sorted address_timestamp_ordering

@[reducible]
def MemoryAccessList.isAddressStrictTimestampSorted (accesses : MemoryAccessList) : Prop :=
  accesses.Sorted address_strict_timestamp_ordering

def AddressSortedMemoryAccessList := {accesses : MemoryAccessList // accesses.isAddressTimestampSorted}

def MemoryAccessList.addressTimestampSort (accesses : MemoryAccessList) : MemoryAccessList :=
  List.insertionSort address_timestamp_ordering accesses

def MemoryAccessList.lastWriteValue (accesses : MemoryAccessList) (h : accesses.isTimestampSorted) (addr : ℕ) : ℕ := match accesses with
  
  | [] => 0
  | (_t, addr', _readValue, writeValue) :: rest =>
    if addr' = addr then
      
      writeValue
    else
      MemoryAccessList.lastWriteValue rest (List.Sorted.of_cons h) addr

def MemoryAccessList.isConsistentOnline (accesses : MemoryAccessList) (h : accesses.isTimestampSorted) : Prop := match accesses with
  | [] => True 
  | (_timestamp, addr, readValue, _writeValue) :: rest =>
    
    readValue = MemoryAccessList.lastWriteValue rest (List.Sorted.of_cons h) addr
    ∧ MemoryAccessList.isConsistentOnline rest (List.Sorted.of_cons h)

example : MemoryAccessList.isConsistentOnline [] (by admit /- proof elided -/
) := by admit /- proof elided -/

def MemoryAccessList.filterAddress (accesses : MemoryAccessList) (addr : ℕ) : MemoryAccessList :=
  accesses.filter (fun (_timestamp, addr', _readValue, _writeValue) => addr' = addr)

def MemoryAccessList.isConsistentSingleAddress (accesses : MemoryAccessList) (h_sorted : accesses.isTimestampSorted) : Prop := match accesses with
  
  | [] => True
  
  | (_timestamp, _addr, readValue, _writeValue) :: [] => readValue = 0
  
  | (_t2, _addr2, readValue2, _writeValue2) :: (t1, addr1, readValue1, writeValue1) :: rest =>
    readValue2 = writeValue1 ∧
    MemoryAccessList.isConsistentSingleAddress ((t1, addr1, readValue1, writeValue1) :: rest) (List.Sorted.of_cons h_sorted)

def MemoryAccessList.isConsistentOffline (accesses : MemoryAccessList) (h_sorted : accesses.isAddressTimestampSorted) : Prop := match accesses with
  | [] => True 
  | (_timestamp, _addr, readValue, _writeValue) :: [] => readValue = 0
  | (_t2, addr2, readValue2, _writeValue2) :: (t1, addr1, readValue1, writeValue1) :: rest =>
    (if addr1 = addr2 then readValue2 = writeValue1 else readValue2 = 0) ∧
    MemoryAccessList.isConsistentOffline ((t1, addr1, readValue1, writeValue1) :: rest) (List.Sorted.of_cons h_sorted)

instance explicit : ExplicitCircuits (Gadgets.Addition32Full.main (p:=pBabybear)) :=

@[reducible] def circuit32 input := Gadgets.Addition32Full.main (p:=pBabybear) input

example : ExplicitCircuit.localLength (circuit32 default) 0 = 8 :=

namespace Circomlib.CompConstant
def main (ct : ℕ) (input : Vector (Expression (F p)) 254) := do
  let parts : fields 127 (Expression (F p)) <== Vector.ofFn fun i =>
    let clsb := (ct >>> (i.val * 2)) &&& 1
    let cmsb := (ct >>> (i.val * 2 + 1)) &&& 1
    let slsb := input[i.val * 2]
    let smsb := input[i.val * 2 + 1]

    
    let b_val : ℤ := 2^128 - 2^i.val
    let a_val : ℤ := 2^i.val

    if cmsb == 0 && clsb == 0 then
      -(b_val : F p) * smsb * slsb + (b_val : F p) * smsb + (b_val : F p) * slsb
    else if cmsb == 0 && clsb == 1 then
      (a_val : F p) * smsb * slsb - (a_val : F p) * slsb + (b_val : F p) * smsb - (a_val : F p) * smsb + (a_val : F p)
    else if cmsb == 1 && clsb == 0 then
      (b_val : F p) * smsb * slsb - (a_val : F p) * smsb + (a_val : F p)
    else
      -(a_val : F p) * smsb * slsb + (a_val : F p)

  
  let sout <== parts.sum

  
  have hp : p > 2^135 := by admit /- proof elided -/
end Circomlib.CompConstant

namespace Circomlib.CompConstant
def circuit (c : ℕ) : FormalCircuit (F p) (fields 254) field where
  main := main c
  localLength _ := 127 + 1 + 135 + 1  
  localLength_eq := by admit /- proof elided -/
end Circomlib.CompConstant

namespace Circomlib.IsZero
def main (input : Expression (F p)) := do
  let inv ← witness fun env =>
    let x := input.eval env
    if x ≠ 0 then x⁻¹ else 0

  let out <== -input * inv + 1
  input * out === 0
  return out
end Circomlib.IsZero

namespace Circomlib.IsZero
def circuit : FormalCircuit (F p) field field where
  main
  localLength _ := 2

  Spec input output :=
    output = (if input = 0 then 1 else 0)

  soundness := by admit /- proof elided -/
end Circomlib.IsZero

namespace Circomlib.IsEqual
def main (input : Expression (F p) × Expression (F p)) := do
  let diff := input.1 - input.2
  let out ← IsZero.circuit diff
  return out
end Circomlib.IsEqual

namespace Circomlib.IsEqual
def circuit : FormalCircuit (F p) fieldPair field where
  main
  localLength _ := 2

  Spec input output :=
    output = (if input.1 = input.2 then 1 else 0)

  completeness := by admit /- proof elided -/
end Circomlib.IsEqual

namespace Circomlib.ForceEqualIfEnabled
structure Inputs (F : Type) where
  enabled : F
  inp : fieldPair F
end Circomlib.ForceEqualIfEnabled

namespace Circomlib.ForceEqualIfEnabled
instance : ProvableStruct Inputs where
  components := [field, fieldPair]
  toComponents := fun { enabled, inp } => .cons enabled (.cons inp .nil)
  fromComponents := fun (.cons enabled (.cons inp .nil)) => { enabled, inp }
end Circomlib.ForceEqualIfEnabled

namespace Circomlib.ForceEqualIfEnabled
def main (inputs : Var Inputs (F p)) := do
  let { enabled, inp } := inputs
  let isz ← IsZero.circuit (inp.2 - inp.1)
  enabled * (1 - isz) === 0
end Circomlib.ForceEqualIfEnabled

namespace Circomlib.ForceEqualIfEnabled
def circuit : FormalAssertion (F p) Inputs where
  main
  localLength _ := 2

  Assumptions := fun { enabled, inp } =>
    enabled = 0 ∨ enabled = 1

  Spec := fun { enabled, inp } =>
    enabled = 1 → inp.1 = inp.2

  soundness := by admit /- proof elided -/
end Circomlib.ForceEqualIfEnabled

namespace Circomlib.LessThan
def main (n : ℕ) (hn : 2^(n+1) < p) (input : Expression (F p) × Expression (F p)) := do
  let diff := input.1 + (2^n : F p) - input.2
  let bits ← Num2Bits.circuit (n + 1) hn diff
  let out <== 1 - bits[n]
  return out
end Circomlib.LessThan

namespace Circomlib.LessThan
def circuit (n : ℕ) (hn : 2^(n+1) < p) : FormalCircuit (F p) fieldPair field where
  main := main n hn
  localLength _ := n + 2
  localLength_eq := by admit /- proof elided -/
end Circomlib.LessThan

namespace Circomlib.LessEqThan
def circuit (n : ℕ) (hn : 2^(n+1) < p) : FormalCircuit (F p) fieldPair field where
  main := fun (x, y) =>
    LessThan.circuit n hn (x, y + 1)

  localLength _ := n + 2

  Assumptions := fun (x, y) => x.val < 2^n ∧ y.val < 2^n
  Spec := fun (x, y) output =>
    output = (if x.val <= y.val then 1 else 0)

  soundness := by admit /- proof elided -/
end Circomlib.LessEqThan

namespace Circomlib.GreaterThan
def circuit (n : ℕ) (hn : 2^(n+1) < p) : FormalCircuit (F p) fieldPair field where
  main := fun (x, y) =>
    LessThan.circuit n hn (y, x)

  localLength _ := n + 2

  Assumptions := fun (x, y) => x.val < 2^n ∧ y.val < 2^n

  Spec := fun (x, y) output =>
    output = (if x.val > y.val then 1 else 0)

  soundness := by admit /- proof elided -/
end Circomlib.GreaterThan

namespace Circomlib.GreaterEqThan
def circuit (n : ℕ) (hn : 2^(n+1) < p) : FormalCircuit (F p) fieldPair field where
  main := fun (x, y) =>
    LessThan.circuit n hn (y, x + 1)

  localLength _ := n + 2

  Assumptions := fun (x, y) => x.val < 2^n ∧ y.val < 2^n
  Spec := fun (x, y) output =>
    output = (if x.val >= y.val then 1 else 0)

  soundness := by admit /- proof elided -/
end Circomlib.GreaterEqThan

def Circuit (F : Type) [Field F] (α : Type) := ℕ → α × List (Operation F)

namespace Circuit
def bind {α β} (f : Circuit F α) (g : α → Circuit F β) : Circuit F β := fun (n : ℕ) =>
  
  let (b, ops') := g (f n).1 (n + Operations.localLength (f n).2)
  (b, (f n).2 ++ ops')
end Circuit

namespace Circuit
instance : Monad (Circuit F) where
  map {α β} (f : α → β) (circuit : Circuit F α) := fun (n : ℕ) =>
    let (a, ops) := circuit n
    (f a, ops)
  pure {α} (a : α) := fun _ => (a, [])
  bind := bind
end Circuit

namespace Circuit
@[reducible, circuit_norm]
def operations (circuit : Circuit F α) (offset : ℕ) : Operations F :=
  (circuit offset).2
end Circuit

namespace Circuit
@[reducible, circuit_norm]
def output (circuit : Circuit F α) (offset : ℕ) : α :=
  (circuit offset).1
end Circuit

namespace Circuit
@[reducible, circuit_norm]
def localLength (circuit : Circuit F α) (offset := 0) : ℕ :=
  Operations.localLength (circuit offset).2
end Circuit

namespace Circuit
@[circuit_norm]
def witnessVar (compute : Environment F → F) : Circuit F (Variable F) :=
  fun (offset : ℕ) =>
    let var : Variable F := ⟨ offset ⟩
    (var, [.witness 1 fun env => #v[compute env]])
end Circuit

namespace Circuit
@[circuit_norm]
def witnessField (compute : Environment F → F) := do
  let v ← witnessVar compute
  return var v
end Circuit

namespace Circuit
@[circuit_norm]
def witnessVars (m : ℕ) (compute : Environment F → Vector F m) : Circuit F (Vector (Variable F) m) :=
  fun (offset : ℕ) =>
    let vars := .mapRange m fun i => ⟨offset + i⟩
    (vars, [.witness m compute])
end Circuit

namespace Circuit
@[circuit_norm]
def witnessVector (m : ℕ) (compute : Environment F → Vector F m) : Circuit F (Vector (Expression F) m) :=
  fun (offset : ℕ) =>
    let vars := varFromOffset (fields m) offset
    (vars, [.witness m compute])
end Circuit

namespace Circuit
@[circuit_norm]
def assertZero (e : Expression F) : Circuit F Unit := fun _ =>
  ((), [.assert e])
end Circuit

namespace Circuit
@[circuit_norm]
def lookup {Row : TypeMap} [ProvableType Row] (table : Table F Row)  (entry : Row (Expression F)) : Circuit F Unit := fun _ =>
  ((), [.lookup { table := table.toRaw, entry := toElements entry }])
end Circuit

@[circuit_norm]
def ProvableType.witness {α : TypeMap} [ProvableType α] (compute : Environment F → α F) : Circuit F (α (Expression F)) :=
  fun (offset : ℕ) =>
    let var := varFromOffset α offset
    (var, [.witness (size α) (fun env => compute env |> toElements)])

@[circuit_norm]
def ProvableVector.witness {α : TypeMap} [NonEmptyProvableType α] (m : ℕ)
    (compute : Environment F → Vector (α F) m) : Circuit F (Vector (α (Expression F)) m) :=
  ProvableType.witness (α:=ProvableVector α m) compute

namespace Circuit
@[circuit_norm]
def ConstraintsHold (eval : Environment F) : List (Operation F) → Prop
  | [] => True
  | .witness _ _ :: ops => ConstraintsHold eval ops
  | .assert e :: ops => eval e = 0 ∧ ConstraintsHold eval ops
  | .lookup { table, entry, .. } :: ops =>
    table.Contains (entry.map eval) ∧ ConstraintsHold eval ops
  | .subcircuit s :: ops =>
    ConstraintsHoldFlat eval s.ops ∧ ConstraintsHold eval ops
end Circuit

namespace Circuit
@[circuit_norm]
def ConstraintsHold.Soundness (eval : Environment F) : List (Operation F) → Prop
  | [] => True
  | .witness _ _ :: ops => ConstraintsHold.Soundness eval ops
  | .assert e :: ops => eval e = 0 ∧ ConstraintsHold.Soundness eval ops
  | .lookup { table, entry } :: ops =>
    table.Soundness (entry.map eval) ∧ ConstraintsHold.Soundness eval ops
  | .subcircuit s :: ops =>
    s.Soundness eval ∧ ConstraintsHold.Soundness eval ops
end Circuit

namespace Circuit
@[circuit_norm]
def ConstraintsHold.Completeness (eval : Environment F) : List (Operation F) → Prop
  | [] => True
  | .witness _ _ :: ops => ConstraintsHold.Completeness eval ops
  | .assert e :: ops => eval e = 0 ∧ ConstraintsHold.Completeness eval ops
  | .lookup { table, entry } :: ops =>
    table.Completeness (entry.map eval) ∧ ConstraintsHold.Completeness eval ops
  | .subcircuit s :: ops =>
    s.Completeness eval ∧ ConstraintsHold.Completeness eval ops
end Circuit

def Environment.UsesLocalWitnesses (env : Environment F) (offset : ℕ) (ops : Operations F) : Prop :=
  ops.forAllFlat offset { witness n _ compute := env.ExtendsVector (compute env) n }

@[circuit_norm]
def Environment.UsesLocalWitnessesCompleteness (env : Environment F) (offset : ℕ) : List (Operation F) → Prop
  | [] => True
  | .witness m c :: ops => env.ExtendsVector (c env) offset ∧ env.UsesLocalWitnessesCompleteness (offset + m) ops
  | .assert _ :: ops => env.UsesLocalWitnessesCompleteness offset ops
  | .lookup _ :: ops => env.UsesLocalWitnessesCompleteness offset ops
  | .subcircuit s :: ops => s.UsesLocalWitnesses env ∧ env.UsesLocalWitnessesCompleteness (offset + s.localLength) ops

def Environment.UsesLocalWitnessesFlat (env : Environment F) (n : ℕ) (ops : List (FlatOperation F)) : Prop :=
  FlatOperation.forAll n { witness n _ compute := env.ExtendsVector (compute env) n } ops

class ElaboratedCircuit (F : Type) (Input Output : TypeMap) [Field F] [ProvableType Input] [ProvableType Output] where
  main : Var Input F → Circuit F (Var Output F)

   
  localLength : Var Input F → ℕ

   
  localLength_eq : ∀ input offset, (main input).localLength offset = localLength input
    := by admit /- proof elided -/

@[circuit_norm]
def Soundness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input Output)
    (Assumptions : Input F → Prop) (Spec : Input F → Output F → Prop) :=
  
  ∀ offset : ℕ, ∀ env,
  
  ∀ input_var : Var Input F, ∀ input : Input F, eval env input_var = input →
  Assumptions input →
  
  ConstraintsHold.Soundness env (circuit.main input_var |>.operations offset) →
  
  let output := eval env (circuit.output input_var offset)
  Spec input output

@[circuit_norm]
def Completeness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input Output)
    (Assumptions : Input F → Prop) :=
  
  ∀ offset : ℕ, ∀ env, ∀ input_var : Var Input F,
  env.UsesLocalWitnessesCompleteness offset (circuit.main input_var |>.operations offset) →
  
  ∀ input : Input F, eval env input_var = input →
  Assumptions input →
  
  ConstraintsHold.Completeness env (circuit.main input_var |>.operations offset)

structure FormalCircuit (F : Type) [Field F] (Input Output : TypeMap) [ProvableType Input] [ProvableType Output]
    extends elaborated : ElaboratedCircuit F Input Output where
  Assumptions (_ : Input F) : Prop := True
  Spec : Input F → Output F → Prop
  soundness : Soundness F elaborated Assumptions Spec
  completeness : Completeness F elaborated Assumptions

structure DeterministicFormalCircuit (F : Type) [Field F] (Input Output : TypeMap) [ProvableType Input] [ProvableType Output]
    extends circuit : FormalCircuit F Input Output where
  uniqueness : ∀ (input : Input F) (out1 out2 : Output F),
    circuit.Assumptions input → circuit.Spec input out1 → circuit.Spec input out2 → out1 = out2

@[circuit_norm]
def FormalAssertion.Soundness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input unit)
    (Assumptions : Input F → Prop) (Spec : Input F → Prop) :=
  
  ∀ offset : ℕ, ∀ env,
  
  ∀ input_var : Var Input F, ∀ input : Input F, eval env input_var = input →
  Assumptions input →
  
  ConstraintsHold.Soundness env (circuit.main input_var |>.operations offset) →
  
  Spec input

@[circuit_norm]
def FormalAssertion.Completeness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input unit)
    (Assumptions : Input F → Prop) (Spec : Input F → Prop) :=
  
  ∀ offset, ∀ env, ∀ input_var : Var Input F,
  env.UsesLocalWitnessesCompleteness offset (circuit.main input_var |>.operations offset) →
  
  ∀ input : Input F, eval env input_var = input →
  Assumptions input → Spec input →
  
  ConstraintsHold.Completeness env (circuit.main input_var |>.operations offset)

structure FormalAssertion (F : Type) (Input : TypeMap) [Field F] [ProvableType Input]
    extends elaborated : ElaboratedCircuit F Input unit where
  Assumptions : Input F → Prop
  Spec : Input F → Prop
  soundness : FormalAssertion.Soundness F elaborated Assumptions Spec
  completeness : FormalAssertion.Completeness F elaborated Assumptions Spec

  
  localLength _ := 0
  
  output _ _ := ()

@[circuit_norm]
def GeneralFormalCircuit.Soundness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input Output) (Spec : Input F → Output F → Prop) :=
  
  ∀ offset : ℕ, ∀ env,
  
  ∀ input_var : Var Input F, ∀ input : Input F, eval env input_var = input →
  
  ConstraintsHold.Soundness env (circuit.main input_var |>.operations offset) →
  
  let output := eval env (circuit.output input_var offset)
  Spec input output

@[circuit_norm]
def GeneralFormalCircuit.Completeness (F : Type) [Field F] (circuit : ElaboratedCircuit F Input Output) (Assumptions : Input F → Prop) :=
  
  ∀ offset : ℕ, ∀ env, ∀ input_var : Var Input F,
  env.UsesLocalWitnessesCompleteness offset (circuit.main input_var |>.operations offset) →
  
  ∀ input : Input F, eval env input_var = input →
  Assumptions input →
  
  ConstraintsHold.Completeness env (circuit.main input_var |>.operations offset)

structure GeneralFormalCircuit (F : Type) (Input Output : TypeMap) [Field F] [ProvableType Input] [ProvableType Output]
    extends elaborated : ElaboratedCircuit F Input Output where
  Assumptions : Input F → Prop 
  Spec : Input F → Output F → Prop 
  soundness : GeneralFormalCircuit.Soundness F elaborated Spec
  completeness : GeneralFormalCircuit.Completeness F elaborated Assumptions

class Witnessable (F : Type) [Field F] (value : outParam TypeMap) (var : TypeMap) [ProvableType value] where
  witness : ((Environment F) → value F) → Circuit F (var F)
  var_eq : var F = value (Expression F) := by admit /- proof elided -/

instance : Witnessable F field Expression where
  witness := witnessField

instance {m : ℕ} : Witnessable F (Vector · m) (fun F => Vector (Expression F) m) where
  witness := witnessVector m

instance (α : TypeMap) [ProvableType α] : Witnessable F α (Var α) where
  witness := ProvableType.witness

instance {m : ℕ} (α : TypeMap) [NonEmptyProvableType α] :
    Witnessable F (ProvableVector α m) (Var (ProvableVector α m)) where
  witness := ProvableVector.witness m

def Environment.fromList (witnesses : List F) : Environment F :=
  .mk fun i => witnesses[i]?.getD 0

def FlatOperation.dynamicWitness (op : FlatOperation F) (acc : List F) : List F := match op with
  | .witness _ compute => (compute (.fromList acc)).toList
  | .assert _ => []
  | .lookup _ => []

def FlatOperation.dynamicWitnesses (ops : List (FlatOperation F)) (init : List F) : List F :=
  ops.foldl (fun (acc : List F) (op : FlatOperation F) =>
    acc ++ op.dynamicWitness acc
  ) init

def FlatOperation.proverEnvironment (ops : List (FlatOperation F)) (init : List F) : Environment F :=
  .fromList (FlatOperation.dynamicWitnesses ops init)

def Environment.AgreesBelow (n : ℕ) (env env' : Environment F) :=
  ∀ i < n, env.get i = env'.get i

def Environment.OnlyAccessedBelow (n : ℕ) (f : Environment F → α) :=
  ∀ env env', env.AgreesBelow n env' → f env = f env'

def Operations.ComputableWitnesses (ops : Operations F) (n : ℕ) (env env' : Environment F) : Prop :=
  ops.forAllFlat n { witness n _ compute := env.AgreesBelow n env' → compute env = compute env' }

def Circuit.ComputableWitnesses (circuit : Circuit F α) (n : ℕ) :=
  ∀ env env', (circuit.operations n).ComputableWitnesses n env env'

def Circuit.proverEnvironment (circuit : Circuit F α) (init : List F := []) : Environment F :=
  .fromList (FlatOperation.dynamicWitnesses (circuit.operations init.length).toFlat init)

def FlatOperation.witnessGenerators : (l : List (FlatOperation F)) → Vector (Environment F → F) (localLength l)
  | [] => #v[]
  | .witness m c :: ops => Vector.mapFinRange m (fun i env => (c env)[i.val]) ++ witnessGenerators ops
  | .assert _ :: ops => witnessGenerators ops
  | .lookup _ :: ops => witnessGenerators ops

def Operations.witnessGenerators : (ops : Operations F) → Vector (Environment F → F) ops.localLength
  | [] => #v[]
  | .witness m c :: ops => Vector.mapFinRange m (fun i env => (c env)[i.val]) ++ witnessGenerators ops
  | .assert _ :: ops => witnessGenerators ops
  | .lookup _ :: ops => witnessGenerators ops
  | .subcircuit s :: ops => (s.localLength_eq ▸ FlatOperation.witnessGenerators s.ops) ++ witnessGenerators ops

namespace Circuit
class ConstantLength (circuit : α → Circuit F β) where
  localLength : ℕ
  localLength_eq : ∀ (a : α) (n : ℕ), (circuit a).localLength n = localLength
end Circuit

namespace Circuit
def ConstantLength.fromConstantLength {circuit : α → Circuit F β} [Inhabited α]
    (h : ∀ (a : α) n, (circuit a).localLength n = (circuit default).localLength 0) : ConstantLength circuit where
  localLength := (circuit default).localLength 0
  localLength_eq a n := h a n
end Circuit

namespace Circuit
@[circuit_norm]
def ConstantOutput (circuit : α → Circuit F β) [Inhabited α] :=
  ∀ (x : α) (n : ℕ), (circuit x).output n = (circuit default).output n
end Circuit

namespace Gadgets.And.And8
structure Inputs (F : Type) where
  x: F
  y: F
end Gadgets.And.And8

namespace Gadgets.And.And8
instance : ProvableStruct Inputs where
  components := [field, field]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.And.And8

namespace Gadgets.And.And8
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.val < 256 ∧ y.val < 256
end Gadgets.And.And8

namespace Gadgets.And.And8
def Spec (input : Inputs (F p)) (z : F p) :=
  let ⟨x, y⟩ := input
  z.val = x.val &&& y.val
end Gadgets.And.And8

namespace Gadgets.And.And8
def main (input : Var Inputs (F p)) : Circuit (F p) (fieldVar (F p)) := do
  let ⟨x, y⟩ := input
  let and ← witness fun eval => (eval x).val &&& (eval y).val
  
  let xor := x + y - 2*and
  lookup ByteXorTable (x, y, xor)
  return and
end Gadgets.And.And8

namespace Gadgets.And.And8
instance elaborated : ElaboratedCircuit (F p) Inputs field where
  main
  localLength _ := 1
  output _ i := var ⟨i⟩
end Gadgets.And.And8

namespace Gadgets.And.And8
def circuit : FormalCircuit (F p) Inputs field :=
  { Assumptions, Spec, soundness, completeness }
end Gadgets.And.And8

namespace Circomlib.MultiMux2
structure Inputs (n : ℕ) (F : Type) where
  c : ProvableVector (fields 4) n F  
  s : Vector F 2                      
end Circomlib.MultiMux2

namespace Circomlib.MultiMux2
instance {n : ℕ} : ProvableStruct (Inputs n) where
  components := [ProvableVector (fields 4) n, fields 2]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.MultiMux2

namespace Circomlib.MultiMux2
def main (n : ℕ) (input : Var (Inputs n) (F p)) := do
  let { c, s } := input

  let s10 <== s[1] * s[0]

  
  let out <== c.map fun ci =>
    let a10 := (ci[3] - ci[2] - ci[1] + ci[0]) * s10
    let a1 := (ci[2] - ci[0]) * s[1]
    let a0 := (ci[1] - ci[0]) * s[0]
    let a := ci[0]
    a10 + a1 + a0 + a

  return out
end Circomlib.MultiMux2

namespace Circomlib.MultiMux2
def circuit (n : ℕ) : FormalCircuit (F p) (Inputs n) (fields n) where
  main := main n

  localLength _ := n + 1
  localLength_eq := by admit /- proof elided -/
end Circomlib.MultiMux2

namespace Circomlib.Mux2
structure Inputs (F : Type) where
  c : Vector F 4  
  s : Vector F 2  
end Circomlib.Mux2

namespace Circomlib.Mux2
instance : ProvableStruct Inputs where
  components := [fields 4, fields 2]
  toComponents := fun {c, s} => .cons c (.cons s .nil)
  fromComponents := fun (.cons c (.cons s .nil)) => ⟨c, s⟩
end Circomlib.Mux2

namespace Circomlib.Mux2
def main (input : Var Inputs (F p)) := do
  let { c, s } := input

  
  let mux_out ← MultiMux2.circuit 1 { c := #v[c], s }
  return mux_out[0]
end Circomlib.Mux2

namespace Circomlib.Mux2
def circuit : FormalCircuit (F p) Inputs field where
  main := main

  localLength _ := 2
  localLength_eq := by admit /- proof elided -/
end Circomlib.Mux2

namespace Gadgets.Keccak256.Chi
def main (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakState (F p)) :=
  .mapFinRange 25 fun i => do
    let state_not ← Not.circuit (state[i + 5])
    let state_and ← And.And64.circuit ⟨state_not, state[i + 10]⟩
    Xor64.circuit ⟨state[i], state_and⟩
end Gadgets.Keccak256.Chi

namespace Gadgets.Keccak256.Chi
def Assumptions := KeccakState.Normalized (p:=p)
end Gadgets.Keccak256.Chi

namespace Gadgets.Keccak256.Chi
def Spec (state : KeccakState (F p)) (out_state : KeccakState (F p)) :=
  out_state.Normalized
  ∧ out_state.value = Specs.Keccak256.chi state.value
end Gadgets.Keccak256.Chi

namespace Gadgets.Keccak256.Chi
instance elaborated : ElaboratedCircuit (F p) KeccakState KeccakState where
  main
  localLength _ := 400
  output _ i0 := Vector.mapRange 25 fun i => varFromOffset U64 (i0 + i*16 + 8)

  localLength_eq state i0 := by admit /- proof elided -/
end Gadgets.Keccak256.Chi

namespace Gadgets.Keccak256.Chi
def circuit : FormalCircuit (F p) KeccakState KeccakState :=
  { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.Chi

namespace TestSimplifyProvableStructEval
structure TestInputs (F : Type) where
  x : F
  y : F
  z : F
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
instance : ProvableStruct TestInputs where
  components := [field, field, field]
  toComponents := fun { x, y, z } => .cons x (.cons y (.cons z .nil))
  fromComponents := fun (.cons x (.cons y (.cons z .nil))) => { x, y, z }
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
structure SimpleStruct (F : Type) where
  a : F
  b : F
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
instance : ProvableStruct SimpleStruct where
  components := [field, field]
  toComponents := fun { a, b } => .cons a (.cons b .nil)
  fromComponents := fun (.cons a (.cons b .nil)) => { a, b }
end TestSimplifyProvableStructEval

namespace Tables.BLAKE3.ProcessBlocksInductive
instance : Fact (p > 2^16 + 2^8) := .mk (by admit /- proof elided -/
)
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
structure ProcessBlocksState (F : Type) where
  chaining_value : Vector (U32 F) 8    
  chunk_counter : U32 F                 
  blocks_compressed : U32 F             
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
instance : ProvableStruct ProcessBlocksState where
  components := [ProvableVector U32 8, U32, U32]
  toComponents := fun { chaining_value, chunk_counter, blocks_compressed } =>
    .cons chaining_value (.cons chunk_counter (.cons blocks_compressed .nil))
  fromComponents := fun xss =>
    match xss with
    | .cons cv (.cons cc (.cons bc .nil)) =>
      { chaining_value := cv, chunk_counter := cc, blocks_compressed := bc }
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def ProcessBlocksState.toChunkState (state : ProcessBlocksState (F p)) : ChunkState :=
  { chaining_value := state.chaining_value.map (·.value)
  , chunk_counter := state.chunk_counter.value
  , blocks_compressed := state.blocks_compressed.value
  , block_buffer := []  
  }
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def ProcessBlocksState.Normalized (state : ProcessBlocksState (F p)) : Prop :=
  (∀ i : Fin 8, state.chaining_value[i].Normalized) ∧
  state.chunk_counter.Normalized ∧
  state.blocks_compressed.Normalized
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive.BLAKE3ProcessBlocksStateNormalized
def main (x : Var ProcessBlocksState (F p)) : Circuit (F p) Unit := do
  Circuit.forEach x.chaining_value U32.AssertNormalized.circuit
  U32.AssertNormalized.circuit x.chunk_counter
  U32.AssertNormalized.circuit x.blocks_compressed
end Tables.BLAKE3.ProcessBlocksInductive.BLAKE3ProcessBlocksStateNormalized

namespace Tables.BLAKE3.ProcessBlocksInductive.BLAKE3ProcessBlocksStateNormalized
def circuit : FormalAssertion (F p) ProcessBlocksState where
  main
  localLength_eq := by admit /- proof elided -/
end Tables.BLAKE3.ProcessBlocksInductive.BLAKE3ProcessBlocksStateNormalized

namespace Tables.BLAKE3.ProcessBlocksInductive
structure BlockInput (F : Type) where
  block_exists : F                      
  block_data : Vector (U32 F) 16        
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
instance : ProvableStruct BlockInput where
  components := [field, ProvableVector U32 16]
  toComponents := fun { block_exists, block_data } =>
    .cons block_exists (.cons block_data .nil)
  fromComponents := fun xss =>
    match xss with
    | .cons block_exists (.cons data .nil) =>
      { block_exists := block_exists, block_data := data }
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def BlockInput.Normalized (input : BlockInput (F p)) : Prop :=
  IsBool input.block_exists ∧
  (∀ i : Fin 16, input.block_data[i].Normalized)
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive.BLAKE3BlockInputNormalized
def main (x : Var BlockInput (F p)) : Circuit (F p) Unit := do
  assertBool x.block_exists
  Circuit.forEach x.block_data U32.AssertNormalized.circuit
end Tables.BLAKE3.ProcessBlocksInductive.BLAKE3BlockInputNormalized

namespace Tables.BLAKE3.ProcessBlocksInductive.BLAKE3BlockInputNormalized
def circuit : FormalAssertion (F p) BlockInput where
  main
  localLength_eq := by admit /- proof elided -/
end Tables.BLAKE3.ProcessBlocksInductive.BLAKE3BlockInputNormalized

namespace Tables.BLAKE3.ProcessBlocksInductive
def step (state : Var ProcessBlocksState (F p)) (input : Var BlockInput (F p)) :
    Circuit (F p) (Var ProcessBlocksState (F p)) := do

  BLAKE3ProcessBlocksStateNormalized.circuit state 
  BLAKE3BlockInputNormalized.circuit input

  
  let isFirstBlock ← IsZero.circuit state.blocks_compressed

  let startFlagU32 : Var U32 (F p) :=  ⟨isFirstBlock * (Expression.const (F:=F p) chunkStart), 0, 0, 0⟩

  
  let zeroU32 : Var U32 (F p) := ⟨0, 0, 0, 0⟩
  let blockLenU32 : Var U32 (F p) := ⟨(blockLen : F p), 0, 0, 0⟩

  
  let compressInput : Var BLAKE3.ApplyRounds.Inputs (F p) := {
    chaining_value := state.chaining_value
    block_words := input.block_data
    counter_high := zeroU32
    counter_low := state.chunk_counter
    block_len := blockLenU32
    flags := startFlagU32
  }

  
  let newCV16 ← BLAKE3.Compress.circuit compressInput

  
  let one32 : Var U32 (F p) := ⟨1, 0, 0, 0⟩
  let newBlocksCompressed ← Addition32.circuit { x := state.blocks_compressed, y := one32 }

  
  
  let muxedCV ← Conditional.circuit (M := ProvableVector U32 8) {
    selector := input.block_exists
    ifTrue := newCV16.takeShort 8 (by admit /- proof elided -/
    )
    ifFalse := state.chaining_value
  }

  let muxedBlocksCompressed ← Conditional.circuit {
    selector := input.block_exists
    ifTrue := newBlocksCompressed
    ifFalse := state.blocks_compressed
  }

  return {
    chaining_value := muxedCV
    chunk_counter := state.chunk_counter  
    blocks_compressed := muxedBlocksCompressed
  }
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def Spec (initialState : ProcessBlocksState (F p)) (inputs : List (BlockInput (F p))) i (_ : inputs.length = i) (state : ProcessBlocksState (F p)) :=
    inputs.length < 2^32 →
    initialState.Normalized ∧
    (∀ input ∈ inputs, input.Normalized) ∧
    
    
    let validBlocks := inputs |>.filter (·.block_exists = 1)
    let blockWords := validBlocks.map (fun b => b.block_data.map (·.value))
    let finalState := processBlocksWords initialState.toChunkState blockWords
    
    state.toChunkState.blocks_compressed < inputs.length ∧
    state.toChunkState = finalState ∧
    state.Normalized
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def InitialStateAssumptions (initialState : ProcessBlocksState (F p)) := initialState.Normalized
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def InputAssumptions (i : ℕ) (input : BlockInput (F p)) :=
    input.Normalized ∧ i < 2^32
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
def table : InductiveTable (F p) ProcessBlocksState BlockInput where
  step
  Spec
  InitialStateAssumptions initialState := initialState.Normalized
  InputAssumptions i input := input.Normalized ∧ i < 2^32
  soundness
  completeness
  subcircuitsConsistent := by admit /- proof elided -/
end Tables.BLAKE3.ProcessBlocksInductive

namespace Gadgets.BLAKE3.FinalizeChunk
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
structure Inputs (F : Type) where
  state : ProcessBlocksState F          
  buffer_len : F                        
  buffer_data : Vector F 64             
  base_flags : U32 F                    
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
instance : ProvableStruct Inputs where
  components := [ProcessBlocksState, field, ProvableVector field 64, U32]
  toComponents := fun { state, buffer_len, buffer_data, base_flags } =>
    .cons state (.cons buffer_len (.cons buffer_data (.cons base_flags .nil)))
  fromComponents := fun xss =>
    match xss with
    | .cons state (.cons buffer_len (.cons buffer_data (.cons base_flags .nil))) =>
      { state, buffer_len, buffer_data, base_flags }
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
def bytesToWords {F} (bytes : Vector F 64) : Vector (U32 F) 16 :=
  Vector.ofFn fun (i : Fin 16) =>
    let base := i.val*4
    U32.mk
      bytes[base]
      bytes[base + 1]
      bytes[base + 2]
      bytes[base + 3]
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
def main (input : Var Inputs (F p)) : Circuit (F p) (Var (ProvableVector U32 8) (F p)) := do

  
  let block_words := bytesToWords input.buffer_data

  
  let isFirstBlock ← IsZero.circuit input.state.blocks_compressed
  let start_flag : Var U32 (F p) := ⟨isFirstBlock*(chunkStart : F p), 0, 0, 0⟩

  
  let chunk_end_flag : Var U32 (F p) := ⟨(chunkEnd : F p), 0, 0, 0⟩
  let flags_with_start ← Or32.circuit { x := input.base_flags, y := start_flag }
  let final_flags ← Or32.circuit { x := flags_with_start, y := chunk_end_flag }

  
  let compress_input : Var ApplyRounds.Inputs (F p) := {
    chaining_value := input.state.chaining_value
    block_words := block_words
    counter_high := ⟨0, 0, 0, 0⟩
    counter_low := input.state.chunk_counter
    block_len := ⟨input.buffer_len, 0, 0, 0⟩  
    flags := final_flags
  }
  let final_state ← Compress.circuit compress_input
  return final_state.take 8
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
instance elaborated : ElaboratedCircuit (F p) Inputs (ProvableVector U32 8) where
  main
  localLength input := 2*4 + (4 + (4 + (5376 + 64)))
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
def Assumptions (input : Inputs (F p)) : Prop :=
  input.state.Normalized ∧
  input.buffer_len.val ≤ 64 ∧
  (∀ i : Fin 64, input.buffer_data[i].val < 256) ∧
  (∀ i : Fin 64, i.val ≥ input.buffer_len.val → input.buffer_data[i] = 0) ∧
  input.base_flags.Normalized
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
def Spec (input : Inputs (F p)) (output : ProvableVector U32 8 (F p)) : Prop :=
  let chunk_state : ChunkState := {
    chaining_value := input.state.chaining_value.map U32.value
    chunk_counter := input.state.chunk_counter.value
    blocks_compressed := input.state.blocks_compressed.value
    block_buffer := (input.buffer_data.take input.buffer_len.val).toList.map (·.val)
  }
  output.map U32.value = Specs.BLAKE3.finalizeChunk chunk_state input.base_flags.value ∧
  (∀ i : Fin 8, output[i].Normalized)
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
def circuit : FormalCircuit (F p) Inputs (ProvableVector U32 8) := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.Keccak256.Theta
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.Keccak256.Theta

namespace Gadgets.Keccak256.Theta
def main (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakState (F p)) := do
  let c ← ThetaC.circuit state
  let d ← ThetaD.circuit c
  ThetaXor.circuit ⟨state, d⟩
end Gadgets.Keccak256.Theta

namespace Gadgets.Keccak256.Theta
instance elaborated : ElaboratedCircuit (F p) KeccakState KeccakState where
  main
  localLength _ := 480
end Gadgets.Keccak256.Theta

namespace Gadgets.Keccak256.Theta
def Assumptions (state : KeccakState (F p)) := state.Normalized
end Gadgets.Keccak256.Theta

namespace Gadgets.Keccak256.Theta
def Spec (state : KeccakState (F p)) (out_state : KeccakState (F p)) : Prop :=
  out_state.Normalized
  ∧ out_state.value = Specs.Keccak256.theta state.value
end Gadgets.Keccak256.Theta

namespace Gadgets.Keccak256.Theta
def circuit : FormalCircuit (F p) KeccakState KeccakState :=
 { elaborated with Assumptions, Spec, soundness, completeness }
end Gadgets.Keccak256.Theta

@[reducible]
def TypeMap := Type → Type

@[reducible] def Var (M : TypeMap) (F : Type) := M (Expression F)

class ProvableType (M : TypeMap) where
  size : ℕ
  toElements {F : Type} : M F -> Vector F size
  fromElements {F : Type} : Vector F size -> M F

  toElements_fromElements {F : Type} : ∀ v : Vector F size, toElements (fromElements v) = v := by admit /- proof elided -/

class NonEmptyProvableType (M : TypeMap) extends ProvableType M where
  nonempty : size > 0 := by admit /- proof elided -/

@[circuit_norm]
def toVars (var : M (Expression F)) := toElements var

@[circuit_norm]
def fromVars (vars : Vector (Expression F) (size M)) := fromElements vars

namespace ProvableType
@[explicit_provable_type]
def eval (env : Environment F) (x : Var α F) : α F :=
  let vars := toVars x
  let values := vars.map (Expression.eval env)
  fromElements values
end ProvableType

namespace ProvableType
def const (x : α F) : Var α F :=
  let values : Vector F _ := toElements x
  fromVars (values.map .const)
end ProvableType

namespace ProvableType
def synthesizeValue : α F :=
  let zeros := Vector.fill (size α) 0
  fromElements zeros
end ProvableType

namespace ProvableType
instance [Field F] : Inhabited (α F) where
  default := synthesizeValue
end ProvableType

namespace ProvableType
def synthesizeConstVar : Var α F :=
  let zeros := Vector.fill (size α) 0
  fromVars (zeros.map .const)
end ProvableType

namespace ProvableType
instance [Field F] : Inhabited (Var α F) where
  default := synthesizeConstVar
end ProvableType

namespace ProvableType
@[explicit_provable_type]
def varFromOffset (α : TypeMap) [ProvableType α] (offset : ℕ) : Var α F :=
  let vars := Vector.mapRange (size α) fun i => var ⟨offset + i⟩
  fromVars vars
end ProvableType

@[reducible]
def unit (_ : Type) := Unit

instance : ProvableType unit where
  size := 0
  toElements _ := #v[]
  fromElements _ := ()

@[reducible] def field : TypeMap := id

@[reducible] def fieldVar (F : Type) := field (Expression F)

@[circuit_norm]
instance : ProvableType field where
  size := 1
  toElements x := #v[x]
  fromElements := fun ⟨⟨[x]⟩, _⟩ => x

instance : NonEmptyProvableType field where

@[reducible]
def ProvablePair (α β : TypeMap) := fun F => α F × β F

@[reducible]
def fieldPair : TypeMap := fun F => F × F

@[reducible]
def fieldTriple : TypeMap := fun F => F × F × F

instance : ProvableType fieldPair where
  size := 2
  toElements := fun (x, y) => #v[x, y]
  fromElements := fun ⟨⟨[x, y]⟩, _ ⟩ => (x, y)

instance : NonEmptyProvableType fieldPair where

instance : ProvableType fieldTriple where
  size := 3
  toElements := fun (x, y, z) => #v[x, y, z]
  fromElements := fun ⟨⟨[x, y, z]⟩, _ ⟩ => (x, y, z)

instance : NonEmptyProvableType fieldTriple where

@[reducible]
def ProvableVector (α : TypeMap) (n : ℕ) := fun F => Vector (α F) n

@[reducible]
def fields (n : ℕ) := fun F => Vector F n

@[circuit_norm]
instance : ProvableType (fields n) where
  size := n
  toElements x := x
  fromElements v := v

instance {n : ℕ} : NonEmptyProvableType (fields (n + 1)) where
nonempty := Nat.zero_lt_succ n

namespace ProvableStruct
structure WithProvableType where
  type : TypeMap
  provableType : ProvableType type := by admit /- proof elided -/
end ProvableStruct

namespace ProvableStruct
instance {c : WithProvableType} : ProvableType c.type := c.provableType
end ProvableStruct

namespace ProvableStruct
instance {α : TypeMap} [ProvableType α] : CoeDep TypeMap (α) WithProvableType where
  coe := { type := α }
end ProvableStruct

namespace ProvableStruct
inductive ProvableTypeList (F : Type) : List WithProvableType → Type 1 where
| nil : ProvableTypeList F []
| cons : ∀ {a : WithProvableType} {as : List WithProvableType}, a.type F → ProvableTypeList F as → ProvableTypeList F (a :: as)
end ProvableStruct

namespace ProvableStruct
@[reducible]
def combinedSize' (cs : List WithProvableType) : ℕ := cs.map (fun x => x.provableType.size) |>.sum
end ProvableStruct

class ProvableStruct (α : TypeMap) where
  components : List WithProvableType
  toComponents {F : Type} : α F → ProvableTypeList F components
  fromComponents {F : Type} : ProvableTypeList F components → α F

  combinedSize : ℕ := combinedSize' components
  combinedSize_eq : combinedSize = combinedSize' components := by admit /- proof elided -/

namespace ProvableStruct
@[circuit_norm]
def componentsToElements {F : Type} : (cs : List WithProvableType) → ProvableTypeList F cs → Vector F (combinedSize' cs)
  | [], .nil => #v[]
  | _ :: cs, .cons a as => toElements a ++ componentsToElements cs as
end ProvableStruct

namespace ProvableStruct
@[circuit_norm]
def componentsFromElements {F : Type} : (cs : List WithProvableType) → Vector F (combinedSize' cs) → ProvableTypeList F cs
  | [], _ => .nil
  | c :: cs, (v : Vector F (size c.type + combinedSize' cs)) =>
    let head_size := size c.type
    let tail_size := combinedSize' cs
    have h_head : head_size ⊓ (head_size + tail_size) = head_size := Nat.min_add_right_self
    have h_tail : head_size + tail_size - head_size = tail_size := Nat.add_sub_self_left _ _
    let head : Vector F head_size := (v.take head_size).cast h_head
    let tail : Vector F tail_size := (v.drop head_size).cast h_tail
    .cons (fromElements head) (componentsFromElements cs tail)
end ProvableStruct

instance ProvableType.fromStruct {α : TypeMap} [ProvableStruct α] : ProvableType α where
  size := combinedSize α
  toElements x :=
    toComponents x |> componentsToElements (components α) |>.cast combinedSize_eq.symm
  fromElements v :=
    v.cast combinedSize_eq |> componentsFromElements (components α) |> fromComponents
  fromElements_toElements x := by admit /- proof elided -/

namespace ProvableStruct
@[circuit_norm]
def eval (env : Environment F) (var : α (Expression F)) : α F :=
  toComponents var |> go (components α) |> fromComponents
where
end ProvableStruct

namespace ProvableStruct
@[circuit_norm]
def varFromOffset (α : TypeMap) [ProvableStruct α] (offset : ℕ) : Var α F :=
  go (components α) offset |> fromComponents (F:=Expression F)
where
end ProvableStruct

@[reducible]
def psize (α : TypeMap) [NonEmptyProvableType α] : ℕ+ :=
  ⟨ size α, NonEmptyProvableType.nonempty⟩

instance ProvableVector.instance : ProvableType (ProvableVector α n) where
  size := n * size α
  toElements x := x.map toElements |>.flatten
  fromElements v := v.toChunks (psize α) |>.map fromElements
  fromElements_toElements x := by admit /- proof elided -/

instance ProvablePair.instance {α β: TypeMap} [ProvableType α] [ProvableType β] : ProvableType (ProvablePair α β) where
  size := size α + size β
  toElements := fun (a, b) => toElements a ++ toElements b
  fromElements {F} v :=
    let a : α F := v.take (size α) |>.cast Nat.min_add_right_self |> fromElements
    let b : β F := v.drop (size α) |>.cast (Nat.add_sub_self_left _ _) |> fromElements
    (a, b)
  fromElements_toElements x := by admit /- proof elided -/

instance {α β: TypeMap} [NonEmptyProvableType α] [ProvableType β] :
  NonEmptyProvableType (ProvablePair α β) where
  nonempty := by admit /- proof elided -/

instance {α β: TypeMap} [ProvableType α] [NonEmptyProvableType β] :
  NonEmptyProvableType (ProvablePair α β) where
  nonempty := by admit /- proof elided -/

def ProvablePair.fromElements {α β: TypeMap} [ProvableType α] [ProvableType β] (xs : Vector F (size α + size β)) : α F × β F :=
  (ProvableType.fromElements xs : ProvablePair α β F)

def ProvablePair.toElements {α β: TypeMap} [ProvableType α] [ProvableType β] (pair : α F × β F) : Vector F (size α + size β) :=
  ProvableType.toElements (M:=ProvablePair α β) pair

instance {α : TypeMap} [ProvableType α] : Zero (α F) where
  zero := fromElements (Vector.replicate _ 0)

instance : HAdd (field (Expression F)) (Expression F) (Expression F) where
  hAdd (x : Expression F) y := x + y

instance : HAdd (Expression F) (field (Expression F)) (Expression F) where
  hAdd x (y : Expression F) := x + y

instance : HAdd (field (Expression F)) (field (Expression F)) (field (Expression F)) where
  hAdd (a : Expression F) (b : Expression F) := a + b

instance : HSub (field (Expression F)) (Expression F) (Expression F) where
  hSub (x : Expression F) y := x - y

instance : HSub (Expression F) (field (Expression F)) (Expression F) where
  hSub x (y : Expression F) := x - y

instance : HSub (field (Expression F)) (field (Expression F)) (field (Expression F)) where
  hSub (a : Expression F) (b : Expression F) := a - b

instance : HMul (field (Expression F)) (Expression F) (Expression F) where
  hMul (x : Expression F) y := x * y

instance : HMul (Expression F) (field (Expression F)) (Expression F) where
  hMul x (y : Expression F) := x * y

instance : HMul F (field (Expression F)) (field (Expression F)) where
  hMul x y : Expression F := x * y

instance : HMul (field (Expression F)) F (field (Expression F)) where
  hMul x y : Expression F := x * y

instance : HMul (field (Expression F)) (field (Expression F)) (field (Expression F)) where
  hMul (a : Expression F) (b : Expression F) := a * b

instance : Inv (field F) where
  inv (x : F) : F := x⁻¹

instance {n : ℕ} [OfNat F n] : OfNat (field F) n where
  ofNat : F := OfNat.ofNat n

instance [Coe ℕ F] : Coe ℕ (field F) where
  coe n : F := n

instance [CoeOut ℕ F] : CoeOut ℕ (field F) where
  coe n : F := n

instance [CoeTail ℕ F] : CoeTail ℕ (field F) where
  coe n : F := n

instance [CoeHead ℕ F] : CoeHead ℕ (field F) where
  coe n : F := n

instance [DecidableEq F] : DecidableEq (field F) :=
  inferInstanceAs (DecidableEq F)

namespace Gadgets.BLAKE3.ApplyRounds
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def roundWithPermute : FormalCircuit (F p) Round.Inputs Round.Inputs where
  main := fun input => do
    let state ← subcircuit Round.circuit input
    let permuted_message ← subcircuit Permute.circuit input.message
    return ⟨state, permuted_message⟩
  localLength := fun _ => Round.circuit.localLength _ + Permute.circuit.localLength _
  localLength_eq := by admit /- proof elided -/
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def twoRoundsWithPermute : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  roundWithPermute.concat roundWithPermute (by admit /- proof elided -/
  ) (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def applyTwoRounds (state : Vector ℕ 16) (message : Vector ℕ 16) : Vector ℕ 16 × Vector ℕ 16 :=
  let state1 := round state message
  let msg1 := permute message
  let state2 := round state1 msg1
  let msg2 := permute msg1
  (state2, msg2)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def TwoRoundsSpec (input : Round.Inputs (F p)) (output : Round.Inputs (F p)) : Prop :=
  let (final_state, final_message) := applyTwoRounds input.state.value (input.message.map U32.value)
  output.state.value = final_state ∧
  output.message.map U32.value = final_message ∧
  output.state.Normalized ∧
  (∀ i : Fin 16, output.message[i].Normalized)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def twoRoundsApplyStyle : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  twoRoundsWithPermute.weakenSpec TwoRoundsSpec (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def fourRoundsWithPermute : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  twoRoundsWithPermute.concat twoRoundsWithPermute (by admit /- proof elided -/
  ) (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def applyFourRounds (state : Vector ℕ 16) (message : Vector ℕ 16) : Vector ℕ 16 × Vector ℕ 16 :=
  let state1 := round state message
  let msg1 := permute message
  let state2 := round state1 msg1
  let msg2 := permute msg1
  let state3 := round state2 msg2
  let msg3 := permute msg2
  let state4 := round state3 msg3
  let msg4 := permute msg3
  (state4, msg4)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def FourRoundsSpec (input : Round.Inputs (F p)) (output : Round.Inputs (F p)) : Prop :=
  let (final_state, final_message) := applyFourRounds input.state.value (input.message.map U32.value)
  output.state.value = final_state ∧
  output.message.map U32.value = final_message ∧
  output.state.Normalized ∧
  (∀ i : Fin 16, output.message[i].Normalized)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def fourRoundsApplyStyle : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  fourRoundsWithPermute.weakenSpec FourRoundsSpec (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def sixRoundsWithPermute : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  fourRoundsWithPermute.concat twoRoundsWithPermute (by admit /- proof elided -/
  ) (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def applySixRounds (state : Vector ℕ 16) (message : Vector ℕ 16) : Vector ℕ 16 × Vector ℕ 16 :=
  let state1 := round state message
  let msg1 := permute message
  let state2 := round state1 msg1
  let msg2 := permute msg1
  let state3 := round state2 msg2
  let msg3 := permute msg2
  let state4 := round state3 msg3
  let msg4 := permute msg3
  let state5 := round state4 msg4
  let msg5 := permute msg4
  let state6 := round state5 msg5
  let msg6 := permute msg5
  (state6, msg6)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def SixRoundsSpec (input : Round.Inputs (F p)) (output : Round.Inputs (F p)) : Prop :=
  let (final_state, final_message) := applySixRounds input.state.value (input.message.map U32.value)
  output.state.value = final_state ∧
  output.message.map U32.value = final_message ∧
  output.state.Normalized ∧
  (∀ i : Fin 16, output.message[i].Normalized)
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def sixRoundsApplyStyle : FormalCircuit (F p) Round.Inputs Round.Inputs :=
  sixRoundsWithPermute.weakenSpec SixRoundsSpec (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def sevenRoundsFinal : FormalCircuit (F p) Round.Inputs BLAKE3State :=
  sixRoundsApplyStyle.concat Round.circuit (by admit /- proof elided -/
  ) (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def applySevenRounds (state : Vector ℕ 16) (message : Vector ℕ 16) : Vector ℕ 16 :=
  let state1 := round state message
  let msg1 := permute message
  let state2 := round state1 msg1
  let msg2 := permute msg1
  let state3 := round state2 msg2
  let msg3 := permute msg2
  let state4 := round state3 msg3
  let msg4 := permute msg3
  let state5 := round state4 msg4
  let msg5 := permute msg4
  let state6 := round state5 msg5
  let msg6 := permute msg5
  let state7 := round state6 msg6
  state7
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def SevenRoundsSpec (input : Round.Inputs (F p)) (output : BLAKE3State (F p)) : Prop :=
  let final_state := applySevenRounds input.state.value (input.message.map U32.value)
  output.value = final_state ∧
  output.Normalized
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def sevenRoundsApplyStyle : FormalCircuit (F p) Round.Inputs BLAKE3State :=
  sevenRoundsFinal.weakenSpec SevenRoundsSpec (by admit /- proof elided -/
  )
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
structure Inputs (F : Type) where
  chaining_value : Vector (U32 F) 8
  block_words : Vector (U32 F) 16
  counter_high : U32 F
  counter_low : U32 F
  block_len : U32 F
  flags : U32 F
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
instance : ProvableStruct Inputs where
  components := [ProvableVector U32 8, ProvableVector U32 16, U32, U32, U32, U32]
  toComponents := fun { chaining_value, block_words, counter_high, counter_low, block_len, flags } =>
    .cons chaining_value (.cons block_words (.cons counter_high (.cons counter_low (.cons block_len (.cons flags .nil)))))
  fromComponents := fun (.cons chaining_value (.cons block_words (.cons counter_high (.cons counter_low (.cons block_len (.cons flags .nil)))))) =>
    { chaining_value, block_words, counter_high, counter_low, block_len, flags }
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def initializeStateVector (input_var : Var Inputs (F p)) : Var BLAKE3State (F p) :=
  let { chaining_value, block_words, counter_high, counter_low, block_len, flags } := input_var
  #v[
    chaining_value[0], chaining_value[1], chaining_value[2], chaining_value[3],
    chaining_value[4], chaining_value[5], chaining_value[6], chaining_value[7],
    const (U32.fromUInt32 iv[0]), const (U32.fromUInt32 iv[1]),
    const (U32.fromUInt32 iv[2]), const (U32.fromUInt32 iv[3]),
    counter_low, counter_high, block_len, flags
  ]
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def main (input : Var Inputs (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  let state := initializeStateVector input
  
  sevenRoundsApplyStyle ⟨state, input.block_words⟩
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
instance elaborated : ElaboratedCircuit (F p) Inputs BLAKE3State where
  main := main
  localLength _ := 5376
  localLength_eq input i0 := by admit /- proof elided -/
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def Assumptions (input : Inputs (F p)) :=
  let { chaining_value, block_words, counter_high, counter_low, block_len, flags } := input
  (∀ i : Fin 8, chaining_value[i].Normalized) ∧
  (∀ i : Fin 16, block_words[i].Normalized) ∧
  counter_high.Normalized ∧ counter_low.Normalized ∧ block_len.Normalized ∧ flags.Normalized
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def Spec (input : Inputs (F p)) (out : BLAKE3State (F p)) :=
  let { chaining_value, block_words, counter_high, counter_low, block_len, flags } := input
  out.value = applyRounds
    (chaining_value.map U32.value)
    (block_words.map U32.value)
    (counter_low.value + 2^32 * counter_high.value)
    block_len.value
    flags.value ∧
  out.Normalized
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
def circuit : FormalCircuit (F p) Inputs BLAKE3State := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.ToBits
def main (n : ℕ) (x : Expression (F p)) := do
  
  let bits ← witnessVector n fun env => fieldToBits n (x.eval env)

  
  Circuit.forEach bits assertBool

  
  x === fieldFromBitsExpr bits
  return bits
end Gadgets.ToBits

namespace Gadgets.ToBits
def toBits (n : ℕ) (hn : 2^n < p) : GeneralFormalCircuit (F p) field (fields n) where
  main := main n
  localLength _ := n
  output _ i := varFromOffset (fields n) i

  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.ToBits

namespace Gadgets.ToBits
def rangeCheck (n : ℕ) (hn : 2^n < p) : FormalAssertion (F p) field where
  main x := do
    
    let _ ← toBits n hn x

  localLength _ := n

  Assumptions _ := True
  Spec (x : F p) := x.val < 2^n

  soundness := by admit /- proof elided -/
end Gadgets.ToBits

namespace Gadgets.BLAKE3.Round
instance : Fact (p > 512) := .mk (by admit /- proof elided -/
)
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
structure Inputs (F : Type) where
  state : BLAKE3State F
  message : Vector (U32 F) 16
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
instance : ProvableStruct Inputs where
  components := [BLAKE3State, ProvableVector U32 16]
  toComponents := fun { state, message } => .cons state (.cons message .nil)
  fromComponents := fun (.cons state (.cons message .nil)) => { state, message }
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
def main (input : Var Inputs (F p)) : Circuit (F p) (Var BLAKE3State (F p)) := do
  let { state, message } := input
  
  let state ← G.circuit 0 4 8 12 ⟨state, message[0], message[1]⟩
  let state ← G.circuit 1 5 9 13 ⟨state, message[2], message[3]⟩
  let state ← G.circuit 2 6 10 14 ⟨state, message[4], message[5]⟩
  let state ← G.circuit 3 7 11 15 ⟨state, message[6], message[7]⟩
  let state ← G.circuit 0 5 10 15 ⟨state, message[8], message[9]⟩
  let state ← G.circuit 1 6 11 12 ⟨state, message[10], message[11]⟩
  let state ← G.circuit 2 7 8 13 ⟨state, message[12], message[13]⟩
  let state ← G.circuit 3 4 9 14 ⟨state, message[14], message[15]⟩
  return state
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
instance elaborated : ElaboratedCircuit (F p) Inputs BLAKE3State where
  main := main
  localLength _ := 768
  localLength_eq input i0 := by admit /- proof elided -/
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
def Assumptions (input : Inputs (F p)) :=
  let { state, message } := input
  state.Normalized ∧ (∀ i : Fin 16, message[i].Normalized)
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
def Spec (input : Inputs (F p)) (out : BLAKE3State (F p)) :=
  let { state, message } := input
  out.value = round state.value (message.map U32.value) ∧ out.Normalized
end Gadgets.BLAKE3.Round

namespace Gadgets.BLAKE3.Round
def circuit : FormalCircuit (F p) Inputs BLAKE3State := {
  elaborated with Assumptions, Spec, soundness, completeness
}
end Gadgets.BLAKE3.Round

namespace Gadgets.Rotation64Bits
instance : Fact (p > 512) :=
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def main (offset : Fin 8) (x : Var U64 (F p)) : Circuit (F p) (Var U64 (F p)) := do
  let parts ← Circuit.map x.toLimbs (ByteDecomposition.circuit offset)
  let lows := parts.map Outputs.low
  let highs := parts.map Outputs.high

  let rotated := highs.zip (lows.rotate 1) |>.map fun (high, low) =>
    high + low * ((2^(8-offset.val) : ℕ) : F p)

  return U64.fromLimbs rotated
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def Assumptions (input : U64 (F p)) := input.Normalized
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def Spec (offset : Fin 8) (x : U64 (F p)) (y : U64 (F p)) :=
  y.value = rotRight64 x.value offset.val
  ∧ y.Normalized
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def output (offset : Fin 8) (i0 : ℕ) : U64 (Expression (F p)) :=
  U64.fromLimbs (.ofFn fun ⟨i,_⟩ =>
    (var ⟨i0 + i*2 + 1⟩) + var ⟨i0 + (i + 1) % 8 * 2⟩ * .const ((2^(8-offset.val) : ℕ) : F p))
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def elaborated (off : Fin 8) : ElaboratedCircuit (F p) U64 U64 where
  main := main off
  localLength _ := 16
  output _ i0 := output off i0
  localLength_eq _ i0 := by admit /- proof elided -/
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
def circuit (offset : Fin 8) : FormalCircuit (F p) U64 U64 := {
  elaborated offset with
  Assumptions
  Spec := Spec offset
  soundness := soundness offset
  completeness := completeness offset
}
end Gadgets.Rotation64Bits

def isMkConstructor (e : Expr) : MetaM Bool := do
  let e' ← withTransparency .all (whnf e)
  match e'.getAppFn with
  | .const name _ =>
    
    return name.components.getLast? == some `mk
  | _ => return false

partial def extractEqualities (e : Expr) : MetaM (List (Expr × Expr × Expr)) := do
  
  match e with
  | .app (.app (.const ``And _) left) right =>
    
    let leftEqs ← extractEqualities left
    let rightEqs ← extractEqualities right
    return leftEqs ++ rightEqs
  | _ =>
    
    if e.isAppOf `Eq then
      if let (some lhs, some rhs) := (e.getArg? 1, e.getArg? 2) then
        return [(e, lhs, rhs)]
    return []

def hasProvableStructInstance (type : Expr) : MetaM Bool := do
  let type' ← withTransparency .reducible (whnf type)
  
  match type' with
  | .app typeCtor _ =>
    try
      let instType ← mkAppM ``ProvableStruct #[typeCtor]
      match ← trySynthInstance instType with
      | .some _ => return true
      | _ => return false
    catch _ => return false
  | _ => return false

def hasEvalPattern (e : Expr) : Bool :=
  e.isAppOf ``ProvableType.eval || e.isAppOf ``Expression.eval || e.isAppOf ``ProvableStruct.eval

def extractTypeMapCandidates (type : Expr) : MetaM (List Expr) := do
  
  
  match type with
  | .app f _ => return [f]
  | _ => return []

def isExpressionType (type : Expr) : MetaM Bool := do
  match type with
  | .app (.const ``Expression _) fieldType =>
    
    let fieldClass ← mkAppM ``Field #[fieldType]
    match ← trySynthInstance fieldClass with
    | .some _ => return true
    | _ => return false
  | _ => return false

def hasFieldInstance (type : Expr) : MetaM Bool := do
  let fieldClass ← mkAppM ``Field #[type]
  match ← trySynthInstance fieldClass with
  | .some _ => return true
  | _ => return false

namespace Utils.Rotation
def rotRight64_eq_bv_rotate (x : ℕ) (h : x < 2^64) (offset : ℕ) :
    rotRight64 x offset = (x.toUInt64.toBitVec.rotateRight offset).toNat :=
end Utils.Rotation

namespace Utils.Rotation
def rotRight32_eq_bv_rotate (x : ℕ) (h : x < 2^32) (offset : ℕ) :
    rotRight32 x offset = (x.toUInt32.toBitVec.rotateRight offset).toNat :=
end Utils.Rotation

namespace Gadgets.Xor32
structure Inputs (F : Type) where
  x: U32 F
  y: U32 F
end Gadgets.Xor32

namespace Gadgets.Xor32
instance : ProvableStruct Inputs where
  components := [U32, U32]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.Xor32

namespace Gadgets.Xor32
def main (input : Var Inputs (F p)) : Circuit (F p) (Var U32 (F p))  := do
  let ⟨x, y⟩ := input
  let z ← witness fun env =>
    let z0 := (env x.x0).val ^^^ (env y.x0).val
    let z1 := (env x.x1).val ^^^ (env y.x1).val
    let z2 := (env x.x2).val ^^^ (env y.x2).val
    let z3 := (env x.x3).val ^^^ (env y.x3).val
    U32.mk z0 z1 z2 z3

  lookup ByteXorTable (x.x0, y.x0, z.x0)
  lookup ByteXorTable (x.x1, y.x1, z.x1)
  lookup ByteXorTable (x.x2, y.x2, z.x2)
  lookup ByteXorTable (x.x3, y.x3, z.x3)
  return z
end Gadgets.Xor32

namespace Gadgets.Xor32
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized
end Gadgets.Xor32

namespace Gadgets.Xor32
def Spec (input : Inputs (F p)) (z : U32 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = x.value ^^^ y.value ∧ z.Normalized
end Gadgets.Xor32

namespace Gadgets.Xor32
instance elaborated : ElaboratedCircuit (F p) Inputs U32 where
  main := main
  localLength _ := 4
  output _ i0 := varFromOffset U32 i0
end Gadgets.Xor32

namespace Gadgets.Xor32
def circuit : FormalCircuit (F p) Inputs U32 where
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.Xor32

namespace Tables.KeccakInductive
def table : InductiveTable (F p) KeccakState KeccakBlock where
  step state block := do
    KeccakBlock.normalized block
    AbsorbBlock.circuit { state, block }

  Spec _ blocks i _ state : Prop :=
    state.Normalized
    ∧ state.value = absorbBlocks (blocks.map KeccakBlock.value)

  InputAssumptions i block := block.Normalized

  soundness := by admit /- proof elided -/
end Tables.KeccakInductive

namespace Tables.KeccakInductive
def initialState : KeccakState (F p) := .fill 25 (U64.fromByte 0)
end Tables.KeccakInductive

namespace Tables.KeccakInductive
def formalTable (output : KeccakState (F p)) := table.toFormal initialState output
end Tables.KeccakInductive

partial def findStructVars (e : Lean.Expr) : Lean.MetaM (List Lean.FVarId) := do
  let rec go (e : Lean.Expr) (acc : List Lean.FVarId) : Lean.MetaM (List Lean.FVarId) := do
    match e with
    | .proj _ _ base =>
      
      match base with
      | .fvar fvarId =>
        return fvarId :: acc
      | _ => go base acc
    | .app _ _ =>
      
      
      let f := e.getAppFn
      let args := e.getAppArgs
      match f with
      | .const name _ =>
        
        let env ← getEnv
        if let some projInfo := env.getProjectionFnInfo? name then
          
          
          if h : projInfo.numParams < args.size then
            let structArg := args[projInfo.numParams]
            match structArg with
            | .fvar fvarId =>
              
              let acc' ← args.foldlM (fun acc arg => go arg acc) acc
              return fvarId :: acc'
            | _ =>
              
              args.foldlM (fun acc arg => go arg acc) acc
          else
            
            args.foldlM (fun acc arg => go arg acc) acc
        else
          
          let acc' ← go f acc
          args.foldlM (fun acc arg => go arg acc) acc'
      | _ =>
        
        let acc' ← go f acc
        args.foldlM (fun acc arg => go arg acc) acc'
    | .lam _ _ body _ | .forallE _ _ body _ =>
      
      go body acc
    | .letE _ _ val body _ =>
      
      let acc' ← go val acc
      go body acc'
    | .mdata _ expr => go expr acc
    | _ => return acc

  go e []

def findProvableStructVars : Lean.Elab.Tactic.TacticM (List Lean.FVarId) := do
  withMainContext do
    let ctx ← Lean.MonadLCtx.getLCtx
    let mut result := []

    
    let hasProvableStructInstance (fvarId : Lean.FVarId) : Lean.MetaM Bool := do
      let type ← inferType (.fvar fvarId)
      let type' ← withTransparency .reducible (whnf type)
      
      
      match type' with
      | .app typeCtor _ =>
        
        
        try
          let instType ← mkAppM ``ProvableStruct #[typeCtor]
          if let .some _ ← trySynthInstance instType then
            return true
          else
            return false
        catch _ => return false
      | _ => return false

    
    for localDecl in ctx do
      if localDecl.isImplementationDetail then
        continue

      
      let varsInType ← findStructVars localDecl.type
      
      for fvarId in varsInType do
        if ← hasProvableStructInstance fvarId then
          result := fvarId :: result

    
    let goal ← getMainGoal
    let target ← goal.getType
    let varsInGoal ← findStructVars target
    
    for fvarId in varsInGoal do
      if ← hasProvableStructInstance fvarId then
        result := fvarId :: result

    
    return result.reverse.eraseDups

def decomposeProvableStruct : Lean.Elab.Tactic.TacticM Unit := do
  withMainContext do
    let goal ← getMainGoal

    
    let fvarIds ← findProvableStructVars

    if fvarIds.isEmpty then
      throwError "decompose_provable_struct: No variables with ProvableStruct instances appear in field projections. This tactic only decomposes variables that appear in expressions like 'x.field_name'."

    
    let mut currentGoal := goal
    let mut decomposed := false

    for fvarId in fvarIds do
      let localDecl ← fvarId.getDecl
      let userName := localDecl.userName

      try
        
        let altVarNames ← generateStructFieldNames fvarId
        
        
        let casesResult ← currentGoal.cases fvarId #[altVarNames]

        
        match casesResult.toList with
        | [subgoal] =>
          currentGoal := subgoal.mvarId
          decomposed := true
        | _ =>
          throwError s!"Unexpected result from cases on {userName}"
      catch e =>
        
        
        trace[Meta.Tactic] "Failed to decompose struct variable {userName}: {e.toMessageData}"
        continue

    if not decomposed then
      throwError "Failed to decompose any variables with ProvableStruct instances"

    replaceMainGoal [currentGoal]

namespace Gadgets
def fromByte (x : Fin 256) : F p :=
  FieldUtils.natToField x.val (by admit /- proof elided -/
  )
end Gadgets

namespace Gadgets
def ByteTable : Table (F p) field := .fromStatic {
  name := "Bytes"
  length := 256

  row i := fromByte i
  index x := x.val

  Spec x := x.val < 256

  contains_iff := by admit /- proof elided -/
end Gadgets

namespace Gadgets.Addition32
structure Inputs (F : Type) where
  x: U32 F
  y: U32 F
end Gadgets.Addition32

namespace Gadgets.Addition32
instance : ProvableStruct Inputs where
  components := [U32, U32]
  toComponents := fun {x, y} => .cons x ( .cons y .nil)
  fromComponents := fun (.cons x ( .cons y .nil)) => ⟨ x, y ⟩
end Gadgets.Addition32

namespace Gadgets.Addition32
def main (input : Var Inputs (F p)) : Circuit (F p) (Var U32 (F p)) := do
  let ⟨x, y⟩ := input
  let ⟨z, _⟩ ← Addition32Full.circuit {x, y, carryIn := 0}
  return z
end Gadgets.Addition32

namespace Gadgets.Addition32
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized
end Gadgets.Addition32

namespace Gadgets.Addition32
def Spec (input : Inputs (F p)) (z : U32 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = (x.value + y.value) % 2^32 ∧ z.Normalized
end Gadgets.Addition32

namespace Gadgets.Addition32
instance elaborated : ElaboratedCircuit (F p) Inputs U32 where
  main := main
  localLength _ := 8
  output _ i0 := ⟨var ⟨i0⟩, var ⟨i0 + 2⟩, var ⟨i0 + 4⟩, var ⟨i0 + 6⟩ ⟩
end Gadgets.Addition32

namespace Gadgets.Addition32
def circuit : FormalCircuit (F p) Inputs U32 where
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.Addition32

namespace Gadgets.Keccak256.Permutation
def main (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakState (F p)) :=
  .foldl roundConstants state
    fun state rc => KeccakRound.circuit rc state
end Gadgets.Keccak256.Permutation

namespace Gadgets.Keccak256.Permutation
def Assumptions (state : KeccakState (F p)) := state.Normalized
end Gadgets.Keccak256.Permutation

namespace Gadgets.Keccak256.Permutation
def Spec (state : KeccakState (F p)) (out_state : KeccakState (F p)) :=
  out_state.Normalized
  ∧ out_state.value = keccakPermutation state.value
end Gadgets.Keccak256.Permutation

namespace Gadgets.Keccak256.Permutation
def stateVar (n : ℕ) (i : ℕ) : Var KeccakState (F p) :=
  Vector.mapRange 25 (fun j => varFromOffset U64 (n + i * 1288 + j * 16 + 888))
  |>.set 0 (varFromOffset U64 (n + i * 1288 + 1280))
end Gadgets.Keccak256.Permutation

namespace Gadgets.Keccak256.Permutation
instance elaborated : ElaboratedCircuit (F p) KeccakState KeccakState where
  main
  localLength _ := 30912
  output _ i0 := stateVar i0 23

  localLength_eq state i0 := by admit /- proof elided -/
end Gadgets.Keccak256.Permutation

namespace Gadgets.Keccak256.Permutation
def circuit : FormalCircuit (F p) KeccakState KeccakState := {
  elaborated with
  Assumptions, Spec, soundness
  
  
  completeness := by admit /- proof elided -/
end Gadgets.Keccak256.Permutation

structure U32 (T : Type) where
  x0 : T
  x1 : T
  x2 : T
  x3 : T
deriving DecidableEq

instance : ProvableType U32 where
  size := 4
  toElements x := #v[x.x0, x.x1, x.x2, x.x3]
  fromElements v :=
    let ⟨ .mk [x0, x1, x2, x3], _ ⟩ := v
    ⟨ x0, x1, x2, x3 ⟩

instance : NonEmptyProvableType U32 where
  nonempty := by admit /- proof elided -/

instance (T : Type) [Inhabited T] : Inhabited (U32 T) where
  default := ⟨ default, default, default, default ⟩

instance (T : Type) [Repr T] : Repr (U32 T) where
  reprPrec x _ := "⟨" ++ repr x.x0 ++ ", " ++ repr x.x1 ++ ", " ++ repr x.x2 ++ ", " ++ repr x.x3 ++ "⟩"

namespace U32
def toLimbs {F} (x : U32 F) : Vector F 4 := toElements x
end U32

namespace U32
def fromLimbs {F} (v : Vector F 4) : U32 F := fromElements v
end U32

namespace U32
def map {α β : Type} (x : U32 α) (f : α → β) : U32 β :=
  ⟨ f x.x0, f x.x1, f x.x2, f x.x3 ⟩
end U32

namespace U32
def vals (x : U32 (F p)) : U32 ℕ := x.map ZMod.val
end U32

namespace U32
def Normalized (x : U32 (F p)) :=
  x.x0.val < 256 ∧ x.x1.val < 256 ∧ x.x2.val < 256 ∧ x.x3.val < 256
end U32

namespace U32
def value (x : U32 (F p)) :=
  x.x0.val + x.x1.val * 256 + x.x2.val * 256^2 + x.x3.val * 256^3
end U32

namespace U32
def valueNat (x : U32 ℕ) :=
  x.x0 + x.x1 * 256 + x.x2 * 256^2 + x.x3 * 256^3
end U32

namespace U32
def decomposeNat (x : ℕ) : U32 (F p) :=
  let x0 := x % 256
  let x1 : ℕ := (x / 256) % 256
  let x2 : ℕ := (x / 256^2) % 256
  let x3 : ℕ := (x / 256^3) % 256
  ⟨ x0, x1, x2, x3 ⟩
end U32

namespace U32
def decomposeNatNat (x : ℕ) : U32 ℕ :=
  let x0 := x % 256
  let x1 : ℕ := (x / 256) % 256
  let x2 : ℕ := (x / 256^2) % 256
  let x3 : ℕ := (x / 256^3) % 256
  ⟨ x0, x1, x2, x3 ⟩
end U32

namespace U32
def decomposeNatExpr (x : ℕ) : U32 (Expression (F p)) :=
  let (⟨x0, x1, x2, x3⟩ : U32 (F p)) := decomposeNat x
  ⟨ x0, x1, x2, x3 ⟩
end U32

namespace U32
def fromUInt32 (x : UInt32) : U32 (F p) :=
  decomposeNat x.toFin
end U32

namespace U32
def valueU32 (x : U32 (F p)) (h : x.Normalized) : UInt32 :=
  UInt32.ofNatLT x.value (value_lt_of_normalized h)
end U32

namespace U32
def fromByte (x : Fin 256) : U32 (F p) :=
  ⟨ x.val, 0, 0, 0 ⟩
end U32

namespace U32.AssertNormalized
def main (inputs : Var U32 (F p)) : Circuit (F p) Unit  := do
  let ⟨ x0, x1, x2, x3 ⟩ := inputs
  lookup ByteTable x0
  lookup ByteTable x1
  lookup ByteTable x2
  lookup ByteTable x3
end U32.AssertNormalized

namespace U32.AssertNormalized
def circuit : FormalAssertion (F p) U32 where
  main
  Assumptions _ := True
  Spec inputs := inputs.Normalized

  soundness := by admit /- proof elided -/
end U32.AssertNormalized

def U32.witness (compute : Environment (F p) → U32 (F p)) := do
  let x ← ProvableType.witness compute
  U32.AssertNormalized.circuit x
  return x

namespace Gadgets.Rotation64Bytes
def main (offset : Fin 8) (input : Var U64 (F p)) : Circuit (F p) (Var U64 (F p)) := do
  let ⟨x0, x1, x2, x3 , x4, x5, x6, x7⟩ := input

  if offset = 0 then
    return ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩
  else if offset = 1 then
    return ⟨ x1, x2, x3, x4, x5, x6, x7, x0 ⟩
  else if offset = 2 then
    return ⟨ x2, x3, x4, x5, x6, x7, x0, x1 ⟩
  else if offset = 3 then
    return ⟨ x3, x4, x5, x6, x7, x0, x1, x2 ⟩
  else if offset = 4 then
    return ⟨ x4, x5, x6, x7, x0, x1, x2, x3 ⟩
  else if offset = 5 then
    return ⟨ x5, x6, x7, x0, x1, x2, x3, x4 ⟩
  else if offset = 6 then
    return ⟨ x6, x7, x0, x1, x2, x3, x4, x5 ⟩
  else
    return ⟨ x7, x0, x1, x2, x3, x4, x5, x6 ⟩
end Gadgets.Rotation64Bytes

namespace Gadgets.Rotation64Bytes
def Assumptions (input : U64 (F p)) := input.Normalized
end Gadgets.Rotation64Bytes

namespace Gadgets.Rotation64Bytes
def Spec (offset : Fin 8) (x : U64 (F p)) (y : U64 (F p)) :=
  y.value = rotRight64 x.value (offset.val * 8) ∧ y.Normalized
end Gadgets.Rotation64Bytes

namespace Gadgets.Rotation64Bytes
instance elaborated (off : Fin 8): ElaboratedCircuit (F p) U64 U64 where
  main := main off
  localLength _ := 0
  output input i0 :=
    let ⟨x0, x1, x2, x3, x4, x5, x6, x7⟩ := input
    match off with
    | 0 => ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩
    | 1 => ⟨ x1, x2, x3, x4, x5, x6, x7, x0 ⟩
    | 2 => ⟨ x2, x3, x4, x5, x6, x7, x0, x1 ⟩
    | 3 => ⟨ x3, x4, x5, x6, x7, x0, x1, x2 ⟩
    | 4 => ⟨ x4, x5, x6, x7, x0, x1, x2, x3 ⟩
    | 5 => ⟨ x5, x6, x7, x0, x1, x2, x3, x4 ⟩
    | 6 => ⟨ x6, x7, x0, x1, x2, x3, x4, x5 ⟩
    | 7 => ⟨ x7, x0, x1, x2, x3, x4, x5, x6 ⟩
  subcircuitsConsistent x i0 := by admit /- proof elided -/
end Gadgets.Rotation64Bytes

namespace Gadgets.Rotation64Bytes
def circuit (off : Fin 8) : FormalCircuit (F p) U64 U64 := {
  elaborated off with
  main := main off
  Assumptions
  Spec := Spec off
  soundness := soundness off
  completeness := completeness off
}
end Gadgets.Rotation64Bytes

def F p := ZMod p

instance (p : ℕ) [Fact p.Prime]: Field (F p) := ZMod.instField p

instance (p : ℕ) [Fact p.Prime] : Fintype (F p) := ZMod.fintype p

instance (p : ℕ) [Fact p.Prime] : Inhabited (F p) := ⟨0⟩

instance (p : ℕ) : CommRing (F p) := ZMod.commRing p

instance {p : ℕ} : DecidableEq (F p) := ZMod.decidableEq p

namespace FieldUtils
instance : NeZero p := ⟨p_prime.elim.ne_zero⟩
end FieldUtils

namespace FieldUtils
def natToField (n : ℕ) (lt : n < p) : F p :=
  match p with
  | 0 => False.elim (Nat.not_lt_zero n lt)
  | _ + 1 => ⟨ n, lt ⟩
end FieldUtils

namespace FieldUtils
def less_than_p (x : F p) : x.val < p :=
end FieldUtils

namespace FieldUtils
def mod (x : F p) (c : ℕ+) (lt : c < p) : F p :=
  FieldUtils.natToField (x.val % c) (by admit /- proof elided -/
  )
end FieldUtils

namespace FieldUtils
def floorDiv (x : F p) (c : ℕ+) : F p :=
  FieldUtils.natToField (x.val / c) (by admit /- proof elided -/
  )
end FieldUtils

namespace ByteUtils
def mod256 (x : F p) [p_large_enough: Fact (p > 512)] : F p :=
  mod x 256 (by admit /- proof elided -/
  )
end ByteUtils

namespace ByteUtils
def floorDiv256 (x : F p) : F p := floorDiv x 256
end ByteUtils

namespace ByteUtils
def splitTwoBytes (i : Fin (256 * 256)) : Fin 256 × Fin 256 :=
  let x := i.val / 256
  let y := i.val % 256
  have x_lt : x < 256 := by admit /- proof elided -/
end ByteUtils

namespace ByteUtils
def concatTwoBytes (x y : Fin 256) : Fin (256 * 256) :=
  let i := x.val * 256 + y.val
  have i_lt : i < 256 * 256 := by admit /- proof elided -/
end ByteUtils

namespace ByteUtils
def fromByte (x : Fin 256) : F p :=
  FieldUtils.natToField x.val (by admit /- proof elided -/
  )
end ByteUtils

namespace Examples.FemtoCairo.Spec
def decodeInstruction (instr : (F p)) : Option (ℕ × ℕ × ℕ × ℕ) :=
  if instr.val ≥ 256 then
    none
  else
  let bits := fieldToBits 8 instr
  let type := bits[0].val + 2 * bits[1].val
  let mode1 := bits[2].val + 2 * bits[3].val
  let mode2 := bits[4].val + 2 * bits[5].val
  let mode3 := bits[6].val + 2 * bits[7].val
  some (type, mode1, mode2, mode3)
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def memoryAccess {n : ℕ} [NeZero n] (memory : Fin n → F p) (addr : F p) : Option (F p) :=
  if h : addr.val < n then
    some (memory ⟨addr.val, h⟩)
  else
    none
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def fetchInstruction
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p))
    (pc : (F p)) : Option (RawInstruction (F p)) := do
  let type ← memoryAccess program pc
  let op1 ← memoryAccess program (pc + 1)
  let op2 ← memoryAccess program (pc + 2)
  let op3 ← memoryAccess program (pc + 3)
  some { rawInstrType := type, op1, op2, op3 }
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def dataMemoryAccess
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p))
    (addr : (F p)) (mode : ℕ) (ap : F p) (fp : F p) : Option (F p) :=
  match mode with
  | 0 => do
      let addr' ← memoryAccess memory (ap + addr)
      memoryAccess memory addr'
  | 1 => memoryAccess memory (ap + addr)
  | 2 => memoryAccess memory (fp + addr)
  | _ => addr
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def dataMemoryAddresses
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p))
    (addr : (F p)) (ap : F p) (fp : F p) : Set (F p) :=
  {fp + addr, ap + addr, addr} ∪
  match memoryAccess memory (ap + addr) with
   | some addr' => {addr'}
   | none => ∅
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def computeNextState
    (instr_type : ℕ) (v1 : (F p)) (v2 : (F p)) (v3 : (F p))
    (state : State (F p)) :
    Option (State (F p)) :=
  match instr_type with
  
  | 0 => if v1 + v2 = v3 then
            some { pc := state.pc + 4, ap := state.ap, fp := state.fp }
         else
            none
  
  | 1 => if v1 * v2 = v3 then
            some { pc := state.pc + 4, ap := state.ap, fp := state.fp }
          else
            none
  
  | 2 => if v1 = state.pc ∧ v2 = state.ap ∧ v3 = state.fp then
            some { pc := state.pc + 4, ap := state.ap, fp := state.fp }
          else
            none
  
  | 3 => some { pc := v1, ap := v2, fp := v3 }
  
  | _ => none
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def femtoCairoMachineTransition
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p))
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p))
    (state : State (F p)) : Option (State (F p)) := do
  
  let { rawInstrType, op1, op2, op3 } ← fetchInstruction program state.pc

  
  let (instr_type, mode1, mode2, mode3) ← decodeInstruction rawInstrType

  
  let v1 ← dataMemoryAccess memory op1 mode1 state.ap state.fp
  let v2 ← dataMemoryAccess memory op2 mode2 state.ap state.fp
  let v3 ← dataMemoryAccess memory op3 mode3 state.ap state.fp

  
  computeNextState instr_type v1 v2 v3 state
end Examples.FemtoCairo.Spec

namespace Examples.FemtoCairo.Spec
def femtoCairoMachineBoundedExecution
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p))
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p))
    (state : Option (State (F p))) (steps : Nat) :
    Option (State (F p)) := match steps with
  | 0 => state
  | i + 1 => do
    let reachedState ← femtoCairoMachineBoundedExecution program memory state i
    femtoCairoMachineTransition program memory reachedState
end Examples.FemtoCairo.Spec

namespace Gadgets.Keccak256.KeccakRound
def main (rc : UInt64) (state : Var KeccakState (F p)) : Circuit (F p) (Var KeccakState (F p)) := do
  let state ← Theta.circuit state
  let state ← RhoPi.circuit state
  let state ← Chi.circuit state

  
  let s0 ← Xor64.circuit ⟨state[0], const (U64.fromUInt64 rc)⟩
  return state.set 0 s0
end Gadgets.Keccak256.KeccakRound

namespace Gadgets.Keccak256.KeccakRound
def Assumptions (state : KeccakState (F p)) := state.Normalized
end Gadgets.Keccak256.KeccakRound

namespace Gadgets.Keccak256.KeccakRound
def Spec (rc : UInt64) (state : KeccakState (F p)) (out_state : KeccakState (F p)) :=
  out_state.Normalized
  ∧ out_state.value = keccakRound state.value rc
end Gadgets.Keccak256.KeccakRound

namespace Gadgets.Keccak256.KeccakRound
instance elaborated (rc : UInt64) : ElaboratedCircuit (F p) KeccakState KeccakState where
  main := main rc
  localLength _ := 1288
  output _ i0 := (Vector.mapRange 25 fun i => varFromOffset U64 (i0 + i*16 + 888) ).set 0 (varFromOffset U64 (i0 + 1280))

  localLength_eq _ _ := by admit /- proof elided -/
end Gadgets.Keccak256.KeccakRound

namespace Gadgets.Keccak256.KeccakRound
def circuit (rc : UInt64) : FormalCircuit (F p) KeccakState KeccakState := {
  elaborated rc with
  Spec := Spec rc
  Assumptions
  soundness := soundness rc,
  completeness := completeness rc,
}
end Gadgets.Keccak256.KeccakRound

namespace Tables.Fibonacci32
def fib32 : ℕ -> ℕ
  | 0 => 0
  | 1 => 1
  | (n + 2) => (fib32 n + fib32 (n + 1)) % 2^32
end Tables.Fibonacci32

namespace Tables.Fibonacci32
structure RowType (F : Type) where
  x: U32 F
  y: U32 F
end Tables.Fibonacci32

namespace Tables.Fibonacci32
instance : ProvableStruct RowType where
  components := [U32, U32]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
  combinedSize := 8 
end Tables.Fibonacci32

namespace Tables.Fibonacci32
@[reducible]
def nextRowOff : RowType (CellOffset 2 RowType) := {
  x := ⟨.next 0, .next 1, .next 2, .next 3⟩,
  y := ⟨.next 4, .next 5, .next 6, .next 7⟩
}
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def assignU32 (offs : U32 (CellOffset 2 RowType)) (x : Var U32 (F p)) : TwoRowsConstraint RowType (F p) := do
  assign offs.x0 x.x0
  assign offs.x1 x.x1
  assign offs.x2 x.x2
  assign offs.x3 x.x3
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def recursiveRelation : TwoRowsConstraint RowType (F p) := do
  let curr ← TableConstraint.getCurrRow
  let next ← TableConstraint.getNextRow

  let z ← Gadgets.Addition32.circuit { x := curr.x, y := curr.y }

  assignU32 nextRowOff.y z
  curr.y === next.x
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def boundary : SingleRowConstraint RowType (F p) := do
  let row ← TableConstraint.getCurrRow
  row.x === (const (U32.fromByte 0))
  row.y === (const (U32.fromByte 1))
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def fib32Table : List (TableOperation RowType (F p)) := [
  .boundary (.fromStart 0) boundary,
  .everyRowExceptLast recursiveRelation,
]
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def Spec {N : ℕ} (trace : TraceOfLength (F p) RowType N) : Prop :=
  trace.ForAllRowsOfTraceWithIndex fun row index =>
    (row.x.value = fib32 index) ∧
    (row.y.value = fib32 (index + 1)) ∧
    row.x.Normalized ∧ row.y.Normalized
end Tables.Fibonacci32

namespace Tables.Fibonacci32
def formalFib32Table : FormalTable (F p) RowType := {
  constraints := fib32Table,
  Spec := Spec,

  soundness := by admit /- proof elided -/
end Tables.Fibonacci32

inductive FlatOperation (F : Type) where
  | witness : (m : ℕ) → (Environment F → Vector F m) → FlatOperation F
  | assert : Expression F → FlatOperation F
  | lookup : Lookup F → FlatOperation F

namespace FlatOperation
instance [Repr F] : Repr (FlatOperation F) where
  reprPrec
  | witness m _, _ => "(Witness " ++ reprStr m ++ ")"
  | assert e, _ => "(Assert " ++ reprStr e ++ " == 0)"
  | lookup l, _ => reprStr l
end FlatOperation

namespace FlatOperation
def ConstraintsHoldFlat (eval : Environment F) : List (FlatOperation F) → Prop
  | [] => True
  | op :: ops => match op with
    | assert e => (eval e = 0) ∧ ConstraintsHoldFlat eval ops
    | lookup { table, entry } =>
      table.Contains (entry.map eval) ∧ ConstraintsHoldFlat eval ops
    | _ => ConstraintsHoldFlat eval ops
end FlatOperation

namespace FlatOperation
@[circuit_norm]
def localLength : List (FlatOperation F) → ℕ
  | [] => 0
  | witness m _ :: ops => m + localLength ops
  | assert _ :: ops | lookup _ :: ops => localLength ops
end FlatOperation

namespace FlatOperation
@[circuit_norm]
def localWitnesses (env : Environment F) : (l : List (FlatOperation F)) → Vector F (localLength l)
  | [] => #v[]
  | witness _ compute :: ops => compute env ++ localWitnesses env ops
  | assert _ :: ops | lookup _ :: ops => localWitnesses env ops
end FlatOperation

namespace FlatOperation
def induct {motive : List (FlatOperation F) → Sort*}
  (empty : motive [])
  (witness : ∀ m c ops, motive ops → motive (.witness m c :: ops))
  (assert : ∀ e ops, motive ops → motive (.assert e :: ops))
  (lookup : ∀ l ops, motive ops → motive (.lookup l :: ops))
    (ops : List (FlatOperation F)) : motive ops :=
  match ops with
  | [] => empty
  | .witness m c :: ops => witness m c ops (induct empty witness assert lookup ops)
  | .assert e :: ops => assert e ops (induct empty witness assert lookup ops)
  | .lookup l :: ops => lookup l ops (induct empty witness assert lookup ops)
end FlatOperation

@[circuit_norm]
def Environment.ExtendsVector (env : Environment F) (wit : Vector F n) (offset : ℕ) : Prop :=
  ∀ i : Fin n, env.get (offset + i.val) = wit[i.val]

structure Subcircuit (F : Type) [Field F] (offset : ℕ) where
  ops : List (FlatOperation F)

  
  
  
  Soundness : Environment F → Prop
  Completeness : Environment F → Prop
  UsesLocalWitnesses : Environment F → Prop

  
  
  localLength : ℕ

  
  imply_soundness : ∀ env,
    ConstraintsHoldFlat env ops → Soundness env

  
  implied_by_completeness : ∀ env, env.ExtendsVector (localWitnesses env ops) offset →
    Completeness env → ConstraintsHoldFlat env ops

  
  imply_usesLocalWitnesses : ∀ env, env.ExtendsVector (localWitnesses env ops) offset →
    UsesLocalWitnesses env

  
  localLength_eq : localLength = FlatOperation.localLength ops

@[reducible, circuit_norm]
def Subcircuit.witnesses (sc : Subcircuit F n) env :=
  (FlatOperation.localWitnesses env sc.ops).cast sc.localLength_eq.symm

inductive Operation (F : Type) [Field F] where
  | witness : (m : ℕ) → (compute : Environment F → Vector F m) → Operation F
  | assert : Expression F → Operation F
  | lookup : Lookup F → Operation F
  | subcircuit : {n : ℕ} → Subcircuit F n → Operation F

namespace Operation
instance [Repr F] : Repr (Operation F) where
  reprPrec op _ := match op with
    | witness m _ => "(Witness " ++ reprStr m ++ ")"
    | assert e => "(Assert " ++ reprStr e ++ " == 0)"
    | lookup l => reprStr l
    | subcircuit { ops, .. } => "(Subcircuit " ++ reprStr ops ++ ")"
end Operation

namespace Operation
@[circuit_norm]
def localLength : Operation F → ℕ
  | .witness m _ => m
  | .assert _ => 0
  | .lookup _ => 0
  | .subcircuit s => s.localLength
end Operation

namespace Operation
def localWitnesses (env : Environment F) : (op : Operation F) → Vector F op.localLength
  | .witness _ c => c env
  | .assert _ => #v[]
  | .lookup _ => #v[]
  | .subcircuit s => s.witnesses env
end Operation

@[reducible, circuit_norm]
def Operations (F : Type) [Field F] := List (Operation F)

namespace Operations
def toList : Operations F → List (Operation F) := id
end Operations

namespace Operations
def toFlat : Operations F → List (FlatOperation F)
  | [] => []
  | .witness m c :: ops => .witness m c :: toFlat ops
  | .assert e :: ops => .assert e :: toFlat ops
  | .lookup l :: ops => .lookup l :: toFlat ops
  | .subcircuit s :: ops => s.ops ++ toFlat ops
end Operations

namespace Operations
@[circuit_norm]
def localLength : Operations F → ℕ
  | [] => 0
  | .witness m _ :: ops => m + localLength ops
  | .assert _ :: ops => localLength ops
  | .lookup _ :: ops => localLength ops
  | .subcircuit s :: ops => s.localLength + localLength ops
end Operations

namespace Operations
@[circuit_norm]
def localWitnesses (env : Environment F) : (ops : Operations F) → Vector F ops.localLength
  | [] => #v[]
  | .witness _ c :: ops => c env ++ localWitnesses env ops
  | .assert _ :: ops => localWitnesses env ops
  | .lookup _ :: ops => localWitnesses env ops
  | .subcircuit s :: ops => s.witnesses env ++ localWitnesses env ops
end Operations

namespace Operations
def induct {motive : Operations F → Sort*}
  (empty : motive [])
  (witness : ∀ m c ops, motive ops → motive (.witness m c :: ops))
  (assert : ∀ e ops, motive ops → motive (.assert e :: ops))
  (lookup : ∀ l ops, motive ops → motive (.lookup l :: ops))
  (subcircuit : ∀ {n} (s : Subcircuit F n) ops, motive ops → motive (.subcircuit s :: ops))
    (ops : Operations F) : motive ops :=
  match ops with
  | [] => empty
  | .witness m c :: ops => witness m c ops (induct empty witness assert lookup subcircuit ops)
  | .assert e :: ops => assert e ops (induct empty witness assert lookup subcircuit ops)
  | .lookup l :: ops => lookup l ops (induct empty witness assert lookup subcircuit ops)
  | .subcircuit s :: ops => subcircuit s ops (induct empty witness assert lookup subcircuit ops)
end Operations

structure Condition (F : Type) [Field F] where
  witness (offset : ℕ) : (m : ℕ) → (Environment F → Vector F m) → Prop := fun _ _ => True
  assert (offset : ℕ) (_ : Expression F) : Prop := True
  lookup (offset : ℕ) (_ : Lookup F) : Prop := True
  subcircuit (offset : ℕ) {m : ℕ} (_ : Subcircuit F m) : Prop := True

@[circuit_norm]
def Condition.apply (condition : Condition F) (offset : ℕ) : Operation F → Prop
  | .witness m c => condition.witness offset m c
  | .assert e => condition.assert offset e
  | .lookup l => condition.lookup offset l
  | .subcircuit s => condition.subcircuit offset s

def Condition.implies (c c': Condition F) : Condition F where
  witness n m compute := c.witness n m compute → c'.witness n m compute
  assert offset e := c.assert offset e → c'.assert offset e
  lookup offset l := c.lookup offset l → c'.lookup offset l
  subcircuit offset _ s := c.subcircuit offset s → c'.subcircuit offset s

namespace Operations
 @[circuit_norm]
def forAll (offset : ℕ) (condition : Condition F) : Operations F → Prop
  | [] => True
  | .witness m c :: ops => condition.witness offset m c ∧ forAll (m + offset) condition ops
  | .assert e :: ops => condition.assert offset e ∧ forAll offset condition ops
  | .lookup l :: ops => condition.lookup offset l ∧ forAll offset condition ops
  | .subcircuit s :: ops => condition.subcircuit offset s ∧ forAll (s.localLength + offset) condition ops
end Operations

namespace Operations
@[circuit_norm]
def SubcircuitsConsistent (offset : ℕ) (ops : Operations F) := ops.forAll offset {
  subcircuit offset {n} _ := n = offset
}
end Operations

namespace Operations
def inductConsistent {motive : (ops : Operations F) → (n : ℕ) → ops.SubcircuitsConsistent n → Sort*}
  (empty : ∀ n, motive [] n trivial)
  (witness : ∀ n m c ops {h}, motive ops (m + n) h →
    motive (.witness m c :: ops) n (by admit /- proof elided -/
    ))
  (assert : ∀ n e ops {h}, motive ops n h →
    motive (.assert e :: ops) n (by admit /- proof elided -/
    ))
  (lookup : ∀ n l ops {h}, motive ops n h →
    motive (.lookup l :: ops) n (by admit /- proof elided -/
    ))
  (subcircuit : ∀ n (s : Subcircuit F n) ops {h}, motive ops (s.localLength + n) h →
    motive (.subcircuit s :: ops) n (by admit /- proof elided -/
    ))
    (ops : Operations F) (n : ℕ) (h : ops.SubcircuitsConsistent n) : motive ops n h :=
  motive' ops n h
where motive' : (ops : Operations F) → (n : ℕ) → (h : ops.SubcircuitsConsistent n) → motive ops n h
  | [], n, _ => empty n
  | .witness m c :: ops, n, h | .assert e :: ops, n, h | .lookup e :: ops, n, h => by admit /- proof elided -/
    | exact witness _ _ _ _ (motive' ops _ h.right)
    | exact assert _ _ _ (motive' ops _ h.right)
    | exact lookup _ _ _ (motive' ops _ h.right)
  | .subcircuit s :: ops, n', h => by admit /- proof elided -/
end Operations

def Condition.ignoreSubcircuit (condition : Condition F) : Condition F :=
  { condition with subcircuit _ _ _ := True }

def Condition.applyFlat (condition : Condition F) (offset : ℕ) : FlatOperation F → Prop
  | .witness m c => condition.witness offset m c
  | .assert e => condition.assert offset e
  | .lookup l => condition.lookup offset l

def FlatOperation.singleLocalLength : FlatOperation F → ℕ
  | .witness m _ => m
  | .assert _ => 0
  | .lookup _ => 0

def FlatOperation.forAll (offset : ℕ) (condition : Condition F) : List (FlatOperation F) → Prop
  | [] => True
  | .witness m c :: ops => condition.witness offset m c ∧ forAll (m + offset) condition ops
  | .assert e :: ops => condition.assert offset e ∧ forAll offset condition ops
  | .lookup l :: ops => condition.lookup offset l ∧ forAll offset condition ops

def Operations.forAllFlat (n : ℕ) (condition : Condition F) (ops : Operations F) : Prop :=
  forAll n { condition with subcircuit n _ s := FlatOperation.forAll n condition s.ops } ops

structure U64 (T : Type) where
  x0 : T
  x1 : T
  x2 : T
  x3 : T
  x4 : T
  x5 : T
  x6 : T
  x7 : T
deriving DecidableEq

instance : ProvableType U64 where
  size := 8
  toElements x := #v[x.x0, x.x1, x.x2, x.x3, x.x4, x.x5, x.x6, x.x7]
  fromElements v :=
    let ⟨.mk [v0, v1, v2, v3, v4, v5, v6, v7], _⟩ := v
    ⟨ v0, v1, v2, v3, v4, v5, v6, v7 ⟩

instance : NonEmptyProvableType U64 where
  nonempty := by admit /- proof elided -/

instance (T : Type) [Repr T] : Repr (U64 T) where
  reprPrec x _ := "⟨" ++ repr x.x0 ++ ", " ++ repr x.x1 ++ ", " ++ repr x.x2 ++ ", " ++ repr x.x3 ++ ", " ++ repr x.x4 ++ ", " ++ repr x.x5 ++ ", " ++ repr x.x6 ++ ", " ++ repr x.x7 ++ "⟩"

namespace U64
def toLimbs {F} (x : U64 F) : Vector F 8 := toElements x
end U64

namespace U64
def fromLimbs {F} (v : Vector F 8) : U64 F := fromElements v
end U64

namespace U64
def map {α β : Type} (x : U64 α) (f : α → β) : U64 β :=
  ⟨ f x.x0, f x.x1, f x.x2, f x.x3, f x.x4, f x.x5, f x.x6, f x.x7 ⟩
end U64

namespace U64
def vals (x : U64 (F p)) : U64 ℕ := x.map ZMod.val
end U64

namespace U64
def Normalized (x : U64 (F p)) :=
  x.x0.val < 256 ∧ x.x1.val < 256 ∧ x.x2.val < 256 ∧ x.x3.val < 256 ∧
  x.x4.val < 256 ∧ x.x5.val < 256 ∧ x.x6.val < 256 ∧ x.x7.val < 256
end U64

namespace U64
def value (x : U64 (F p)) :=
  x.x0.val + x.x1.val * 256 + x.x2.val * 256^2 + x.x3.val * 256^3 +
  x.x4.val * 256^4 + x.x5.val * 256^5 + x.x6.val * 256^6 + x.x7.val * 256^7
end U64

namespace U64
def valueNat (x : U64 ℕ) :=
  x.x0 + x.x1 * 256 + x.x2 * 256^2 + x.x3 * 256^3 +
  x.x4 * 256^4 + x.x5 * 256^5 + x.x6 * 256^6 + x.x7 * 256^7
end U64

namespace U64
def decomposeNat (x : ℕ) : U64 (F p) :=
  let x0 := x % 256
  let x1 : ℕ := (x / 256) % 256
  let x2 : ℕ := (x / 256^2) % 256
  let x3 : ℕ := (x / 256^3) % 256
  let x4 : ℕ := (x / 256^4) % 256
  let x5 : ℕ := (x / 256^5) % 256
  let x6 : ℕ := (x / 256^6) % 256
  let x7 : ℕ := (x / 256^7) % 256
  ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩
end U64

namespace U64
def decomposeNatNat (x : ℕ) : U64 ℕ :=
  let x0 := x % 256
  let x1 := (x / 256) % 256
  let x2 := (x / 256^2) % 256
  let x3 := (x / 256^3) % 256
  let x4 := (x / 256^4) % 256
  let x5 := (x / 256^5) % 256
  let x6 := (x / 256^6) % 256
  let x7 := (x / 256^7) % 256
  ⟨ x0, x1, x2, x3, x4, x5, x6, x7 ⟩
end U64

namespace U64
def decomposeNatExpr (x : ℕ) : U64 (Expression (F p)) :=
  let (⟨x0, x1, x2, x3, x4, x5, x6, x7⟩ : U64 (F p)) := decomposeNat x
  ⟨ x0, x1, x2, x3 , x4, x5, x6, x7 ⟩
end U64

namespace U64
def fromUInt64 (x : UInt64) : U64 (F p) :=
  decomposeNat x.toFin
end U64

namespace U64
def valueU64 (x : U64 (F p)) (h : x.Normalized) : UInt64 :=
  UInt64.ofNatLT x.value (value_lt_of_normalized h)
end U64

namespace U64.AssertNormalized
def main (inputs : Var U64 (F p)) : Circuit (F p) Unit  := do
  let ⟨x0, x1, x2, x3, x4, x5, x6, x7⟩ := inputs
  lookup ByteTable x0
  lookup ByteTable x1
  lookup ByteTable x2
  lookup ByteTable x3
  lookup ByteTable x4
  lookup ByteTable x5
  lookup ByteTable x6
  lookup ByteTable x7
end U64.AssertNormalized

namespace U64.AssertNormalized
def circuit : FormalAssertion (F p) U64 where
  main

  Assumptions _ := True
  Spec inputs := inputs.Normalized

  soundness := by admit /- proof elided -/
end U64.AssertNormalized

def U64.witness (compute : Environment (F p) → U64 (F p)) := do
  let x ← ProvableType.witness compute
  U64.AssertNormalized.circuit x
  return x

namespace U64
def fromByte (x : Fin 256) : U64 (F p) :=
  ⟨ x.val, 0, 0, 0, 0, 0, 0, 0 ⟩
end U64

namespace Circomlib.Sign
def main (input : Vector (Expression (F p)) 254) :=
  
  CompConstant.circuit ((p - 1) / 2) input
end Circomlib.Sign

namespace Circomlib.Sign
def circuit : FormalCircuit (F p) (fields 254) field where
  main
  localLength input := (CompConstant.circuit ((p - 1) / 2)).localLength input

  Assumptions input :=
    
    ∀ i (_ : i < 254), IsBool input[i]

  Spec input output :=
    
    
    output = if Utils.Bits.fromBits (input.map ZMod.val) > (p - 1) / 2 then 1 else 0

  soundness := by admit /- proof elided -/
end Circomlib.Sign

namespace Circomlib
instance hp135 : Fact (p > 2^135) := .mk (by admit /- proof elided -/
)
end Circomlib

namespace Circomlib.AliasCheck
def main (input : Vector (Expression (F p)) 254) := do
  
  let comp_out ← CompConstant.circuit (p - 1) input
  comp_out === 0
end Circomlib.AliasCheck

namespace Circomlib.AliasCheck
def circuit : FormalAssertion (F p) (fields 254) where
  main
  localLength _ := 127 + 1 + 135 + 1

  Assumptions input := ∀ i (_ : i < 254), input[i] = 0 ∨ input[i] = 1

  Spec bits := fromBits (bits.map ZMod.val) < p

  soundness := by admit /- proof elided -/
end Circomlib.AliasCheck

namespace Circomlib
@[reducible]
def BinSumInput (n ops : ℕ) := ProvableVector (fields n) ops
end Circomlib

namespace Circomlib
instance {n : ℕ} [hn : NeZero n] : NonEmptyProvableType (fields n) where
  nonempty := Nat.pos_of_ne_zero hn.out
end Circomlib

namespace Circomlib
def nbits (a : ℕ) : ℕ :=
  if a = 0 then 1 else Nat.log2 a + 1
end Circomlib

namespace Circomlib.BinSum
def inputLinearSum (n ops : ℕ) (inp : BinSumInput n ops (Expression (F p))) : Expression (F p) :=
  
  Fin.foldl n (fun lin k =>
    Fin.foldl ops (fun lin j => lin + inp[j][k] * (2^k.val : F p)) lin) 0
end Circomlib.BinSum

namespace Circomlib.BinSum
def main (n ops : ℕ)
    (inp : BinSumInput n ops (Expression (F p))) : Circuit (F p) (Vector (Expression (F p)) (nbits ((2^n - 1) * ops))) := do
  let nout := nbits ((2^n - 1) * ops)

  
  let lin := inputLinearSum n ops inp

  
  let out ← Num2Bits.arbitraryBitLengthCircuit nout lin

  return out
end Circomlib.BinSum

namespace Circomlib.BinSum
def circuit (n ops : ℕ) [hn : NeZero n] (hnout : 2^(nbits ((2^n - 1) * ops)) < p) :
    FormalCircuit (F p) (BinSumInput n ops) (fields (nbits ((2^n - 1) * ops))) where
  main input := main n ops input

  localLength _ := nbits ((2^n - 1) * ops)
  localLength_eq := by admit /- proof elided -/
end Circomlib.BinSum

namespace Examples.FemtoCairo
def ReadOnlyTableFromFunction
    {n : ℕ} (f : Fin n → (F p)) (h : n < p) [NeZero n] :
    Table (F p) fieldPair
  := .fromStatic {
  name := "ReadOnlyMemory"
  length := n
  row i := (i, f i)
  index := fun (i, _) => i.val
  Spec := fun (i, v) => v = f (Fin.ofNat n i.val) ∧ i.val < n
  contains_iff := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def decodeInstructionCircuit : GeneralFormalCircuit (F p) field DecodedInstruction where
  main := fun instruction => do
    let bits ← Gadgets.ToBits.toBits 8 (by admit /- proof elided -/
    ) instruction
    return {
      instrType := {
        isAdd := (1 : Expression _) - bits[0] - bits[1] + bits[0] * bits[1],
        isMul := bits[0] - bits[0] * bits[1],
        isStoreState := bits[1] - bits[0] * bits[1],
        isLoadState := bits[0] * bits[1]
      },
      mode1 := {
        isDoubleAddressing := (1 : Expression _) - bits[2] - bits[3] + bits[2] * bits[3],
        isApRelative := bits[2] - bits[2] * bits[3],
        isFpRelative := bits[3] - bits[2] * bits[3],
        isImmediate := bits[2] * bits[3]
      },
      mode2 := {
        isDoubleAddressing := (1 : Expression _) - bits[4] - bits[5] + bits[4] * bits[5],
        isApRelative := bits[4] - bits[4] * bits[5],
        isFpRelative := bits[5] - bits[4] * bits[5],
        isImmediate := bits[4] * bits[5]
      },
      mode3 := {
        isDoubleAddressing := (1 : Expression _) - bits[6] - bits[7] + bits[6] * bits[7],
        isApRelative := bits[6] - bits[6] * bits[7],
        isFpRelative := bits[7] - bits[6] * bits[7],
        isImmediate := bits[6] * bits[7]
      }
    }
  localLength _ := 8

  Assumptions
  | instruction => instruction.val < 256

  Spec
  | instruction, output =>
    match Spec.decodeInstruction instruction with
    | some (instr_type, mode1, mode2, mode3) =>
      output.instrType.val = instr_type ∧ output.instrType.isEncodedCorrectly ∧
      output.mode1.val = mode1 ∧ output.mode1.isEncodedCorrectly ∧
      output.mode2.val = mode2 ∧ output.mode2.isEncodedCorrectly ∧
      output.mode3.val = mode3 ∧ output.mode3.isEncodedCorrectly
    | none => False 

  soundness := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def fetchInstructionCircuit
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p) :
    GeneralFormalCircuit (F p) field RawInstruction where
  main := fun pc => do
    let programTable := ReadOnlyTableFromFunction program h_programSize

    let rawInstrType : Expression _ ← witness fun eval => program <| Fin.ofNat _ (eval pc).val
    let op1 ← witness fun eval => program <| Fin.ofNat _ (eval (pc + 1)).val
    let op2 ← witness fun eval => program <| Fin.ofNat _ (eval (pc + 2)).val
    let op3 ← witness fun eval => program <| Fin.ofNat _ (eval (pc + 3)).val

    lookup programTable ⟨pc, rawInstrType⟩
    lookup programTable ⟨pc + 1, op1⟩
    lookup programTable ⟨pc + 2, op2⟩
    lookup programTable ⟨pc + 3, op3⟩

    return { rawInstrType, op1, op2, op3 }

  localLength _ := 4
  Assumptions
  | pc => pc.val + 3 < programSize

  Spec
  | pc, output =>
    match Spec.fetchInstruction program pc with
      | some claimed_output => output = claimed_output
      | none => False 
  soundness := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def readFromMemoryCircuit
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p) :
    GeneralFormalCircuit (F p) MemoryReadInput field where
  main := fun { state, offset, mode } => do
    let memoryTable := ReadOnlyTableFromFunction memory h_memorySize

    
    let v1_tmp : Expression _ ← witness fun eval => memory <| Fin.ofNat _ (eval (state.ap + offset)).val
    let v1 : Expression _ ← witness fun eval => memory <| Fin.ofNat _ (eval v1_tmp).val
    let v2 : Expression _ ← witness fun eval =>  memory <| Fin.ofNat _ (eval (state.ap + offset)).val
    let v3 : Expression _ ← witness fun eval =>  memory <| Fin.ofNat _ (eval (state.fp + offset)).val
    lookup memoryTable ⟨(state.ap + offset), v1_tmp⟩
    lookup memoryTable ⟨v1_tmp, v1⟩
    lookup memoryTable ⟨(state.ap + offset), v2⟩
    lookup memoryTable ⟨(state.fp + offset), v3⟩

    
    let value <==
      mode.isDoubleAddressing * v1 +
      mode.isApRelative * v2 +
      mode.isFpRelative * v3 +
      mode.isImmediate * offset

    return value

  localLength _ := 5
  Assumptions
  | {state, mode, offset} =>
    ∀ addr ∈ Spec.dataMemoryAddresses memory offset state.ap state.fp,
      addr.val < memorySize
  Spec
  | {state, offset, mode}, output =>
    DecodedAddressingMode.isEncodedCorrectly mode →
    match Spec.dataMemoryAccess memory offset (DecodedAddressingMode.val mode) state.ap state.fp with
      | some value => output = value
      | none => False 
  soundness := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def nextStateCircuit : GeneralFormalCircuit (F p) StateTransitionInput State where
  main := fun { state, decoded, v1, v2, v3 } => do
    
    let nextState : State _ ← ProvableType.witness fun eval => {
      pc := if eval decoded.instrType.isLoadState = 1 then eval v1 else eval state.pc + 4
      ap := if eval decoded.instrType.isLoadState = 1 then eval v2 else eval state.ap
      fp := if eval decoded.instrType.isLoadState = 1 then eval v3 else eval state.fp
    }

    assertZero (decoded.instrType.isAdd * (v3 - (v1 + v2)))

    assertZero (decoded.instrType.isMul * (v3 - (v1 * v2)))

    assertZero (decoded.instrType.isStoreState * (v1 - state.pc))
    assertZero (decoded.instrType.isStoreState * (v2 - state.ap))
    assertZero (decoded.instrType.isStoreState * (v3 - state.fp))

    assertZero (decoded.instrType.isLoadState * (nextState.pc - v1))
    assertZero (decoded.instrType.isLoadState * (nextState.ap - v2))
    assertZero (decoded.instrType.isLoadState * (nextState.fp - v3))

    assertZero ((1 - decoded.instrType.isLoadState) * (nextState.pc - (state.pc + 4)))
    assertZero ((1 - decoded.instrType.isLoadState) * (nextState.ap - state.ap))
    assertZero ((1 - decoded.instrType.isLoadState) * (nextState.fp - state.fp))

    return nextState

  localLength _ := 3
  Assumptions
  | {state, decoded, v1, v2, v3} =>
    DecodedInstructionType.isEncodedCorrectly decoded.instrType ∧
    (Spec.computeNextState (DecodedInstructionType.val decoded.instrType) v1 v2 v3 state).isSome
  Spec
  | {state, decoded, v1, v2, v3}, output =>
    DecodedInstructionType.isEncodedCorrectly decoded.instrType →
    match Spec.computeNextState (DecodedInstructionType.val decoded.instrType) v1 v2 v3 state with
      | some nextState => output = nextState
      | none => False 
  soundness := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoStepElaboratedCircuit
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p) :
    ElaboratedCircuit (F p) State State where
    main := fun state => do
      
      let { rawInstrType, op1, op2, op3 } ← subcircuitWithAssertion (fetchInstructionCircuit program h_programSize) state.pc

      
      let decoded ← subcircuitWithAssertion decodeInstructionCircuit rawInstrType

      
      let v1 ← subcircuitWithAssertion (readFromMemoryCircuit memory h_memorySize) { state, offset := op1, mode := decoded.mode1 }
      let v2 ← subcircuitWithAssertion (readFromMemoryCircuit memory h_memorySize) { state, offset := op2, mode := decoded.mode2 }
      let v3 ← subcircuitWithAssertion (readFromMemoryCircuit memory h_memorySize) { state, offset := op3, mode := decoded.mode3 }

      
      nextStateCircuit { state, decoded, v1, v2, v3 }
    localLength := 30
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoCircuitSpec
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p))
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p))
    (state : State (F p)) (nextState : State (F p)) : Prop :=
  match Spec.femtoCairoMachineTransition program memory state with
    | some s => s = nextState
    | none => False 
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoAssumptions (_state : State (F p)) : Prop :=
  True
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoStepCircuitSoundness
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p)
    : GeneralFormalCircuit.Soundness (F p) (femtoCairoStepElaboratedCircuit program h_programSize memory h_memorySize) (femtoCairoCircuitSpec program memory) :=
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoStepCircuitCompleteness {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p))
  (h_programSize : programSize < p) {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p) :
    GeneralFormalCircuit.Completeness (F p) (femtoCairoStepElaboratedCircuit program h_programSize memory h_memorySize)
      femtoCairoAssumptions :=
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoStepCircuit
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p)
    : GeneralFormalCircuit (F p) State State := {
      femtoCairoStepElaboratedCircuit program h_programSize memory h_memorySize with
      Assumptions := femtoCairoAssumptions,
      Spec := femtoCairoCircuitSpec program memory,
      soundness := femtoCairoStepCircuitSoundness program h_programSize memory h_memorySize,
      completeness := femtoCairoStepCircuitCompleteness program h_programSize memory h_memorySize,
    }
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoTable
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p)
    : InductiveTable (F p) State unit where
  step state _ := do
    femtoCairoStepCircuit program h_programSize memory h_memorySize state

  Spec initial_state _ i _ state : Prop := match
      Spec.femtoCairoMachineBoundedExecution program memory (some initial_state) i with
    | some reachedState => state = reachedState
    | none => False 

  soundness := by admit /- proof elided -/
end Examples.FemtoCairo

namespace Examples.FemtoCairo
def femtoCairoFormalTable
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p)
    (output : State (F p)) := (femtoCairoTable program h_programSize memory h_memorySize).toFormal {
  pc := 0,
  ap := 0,
  fp := 0
} output
end Examples.FemtoCairo

namespace Gadgets.Xor64
structure Inputs (F : Type) where
  x: U64 F
  y: U64 F
end Gadgets.Xor64

namespace Gadgets.Xor64
instance : ProvableStruct Inputs where
  components := [U64, U64]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.Xor64

namespace Gadgets.Xor64
def main (input : Var Inputs (F p)) : Circuit (F p) (Var U64 (F p))  := do
  let ⟨x, y⟩ := input
  let z ← witness fun env =>
    let z0 := (env x.x0).val ^^^ (env y.x0).val
    let z1 := (env x.x1).val ^^^ (env y.x1).val
    let z2 := (env x.x2).val ^^^ (env y.x2).val
    let z3 := (env x.x3).val ^^^ (env y.x3).val
    let z4 := (env x.x4).val ^^^ (env y.x4).val
    let z5 := (env x.x5).val ^^^ (env y.x5).val
    let z6 := (env x.x6).val ^^^ (env y.x6).val
    let z7 := (env x.x7).val ^^^ (env y.x7).val
    U64.mk z0 z1 z2 z3 z4 z5 z6 z7

  lookup ByteXorTable (x.x0, y.x0, z.x0)
  lookup ByteXorTable (x.x1, y.x1, z.x1)
  lookup ByteXorTable (x.x2, y.x2, z.x2)
  lookup ByteXorTable (x.x3, y.x3, z.x3)
  lookup ByteXorTable (x.x4, y.x4, z.x4)
  lookup ByteXorTable (x.x5, y.x5, z.x5)
  lookup ByteXorTable (x.x6, y.x6, z.x6)
  lookup ByteXorTable (x.x7, y.x7, z.x7)
  return z
end Gadgets.Xor64

namespace Gadgets.Xor64
def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized
end Gadgets.Xor64

namespace Gadgets.Xor64
def Spec (input : Inputs (F p)) (z : U64 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = x.value ^^^ y.value ∧ z.Normalized
end Gadgets.Xor64

namespace Gadgets.Xor64
instance elaborated : ElaboratedCircuit (F p) Inputs U64 where
  main := main
  localLength _ := 8
  output _ i0 := varFromOffset U64 i0
end Gadgets.Xor64

namespace Gadgets.Xor64
def circuit : FormalCircuit (F p) Inputs U64 where
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.Xor64

namespace Gadgets.Conditional
structure Inputs (M : TypeMap) (F : Type) where
  selector : F
  ifTrue : M F
  ifFalse : M F
end Gadgets.Conditional

namespace Gadgets.Conditional
instance : ProvableStruct (Inputs M) where
  components := [field, M, M]
  toComponents := fun { selector, ifTrue, ifFalse } => .cons selector (.cons ifTrue (.cons ifFalse .nil))
  fromComponents := fun (.cons selector (.cons ifTrue (.cons ifFalse .nil))) => { selector, ifTrue, ifFalse }
end Gadgets.Conditional

namespace Gadgets.Conditional
def main [DecidableEq F] (input : Var (Inputs M) F) : Circuit F (Var M F) := do
  let { selector, ifTrue, ifFalse } := input

  
  let trueVars := toVars ifTrue
  let falseVars := toVars ifFalse

  
  let resultVars := Vector.ofFn fun i => selector * (trueVars[i] - falseVars[i]) + falseVars[i]

  return fromVars resultVars
end Gadgets.Conditional

namespace Gadgets.Conditional
def Assumptions (input : Inputs M F) : Prop :=
  IsBool input.selector
end Gadgets.Conditional

namespace Gadgets.Conditional
def Spec [DecidableEq F] (input : Inputs M F) (output : M F) : Prop :=
  output = if input.selector = 1 then input.ifTrue else input.ifFalse
end Gadgets.Conditional

namespace Gadgets.Conditional
instance elaborated [DecidableEq F] : ElaboratedCircuit F (Inputs M) M where
  main
  localLength _ := 0
end Gadgets.Conditional

namespace Gadgets.Conditional
def circuit [DecidableEq F] : FormalCircuit F (Inputs M) M where
  elaborated := elaborated
  Assumptions
  Spec
  soundness
  completeness
end Gadgets.Conditional

namespace Gadgets
def Addition8Full.circuit : FormalCircuit (F p) Addition8FullCarry.Inputs field where
  main := fun inputs => do
    let { z, .. } ← Addition8FullCarry.circuit inputs
    return z

  localLength _ := 2
  output _ i0 := var ⟨i0⟩

  Assumptions := fun { x, y, carryIn } =>
    x.val < 256 ∧ y.val < 256 ∧ IsBool carryIn

  Spec := fun { x, y, carryIn } z =>
    z.val = (x.val + y.val + carryIn.val) % 256

  
  soundness := by admit /- proof elided -/
end Gadgets

namespace Gadgets.Addition8
structure Inputs (F : Type) where
  x: F
  y: F
end Gadgets.Addition8

namespace Gadgets.Addition8
instance : ProvableStruct Inputs where
  components := [field, field]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }
end Gadgets.Addition8

namespace Gadgets.Addition8
def circuit : FormalCircuit (F p) Inputs field where
  main := fun { x, y } =>
    Addition8Full.circuit { x, y, carryIn := 0 }

  localLength _ := 2
  output _ i0 := var ⟨i0⟩

  Assumptions | { x, y } => x.val < 256 ∧ y.val < 256

  Spec | { x, y }, z => z.val = (x.val + y.val) % 256

  
  soundness := by admit /- proof elided -/
end Gadgets.Addition8

namespace Gadgets.Rotation32Bytes
def main (offset : Fin 4) (input : Var U32 (F p)) : Circuit (F p) (Var U32 (F p)) := do
  let ⟨x0, x1, x2, x3⟩ := input

  if offset = 0 then
    return ⟨ x0, x1, x2, x3 ⟩
  else if offset = 1 then
    return ⟨ x1, x2, x3, x0 ⟩
  else if offset = 2 then
    return ⟨ x2, x3, x0, x1 ⟩
  else
    return ⟨ x3, x0, x1, x2 ⟩
end Gadgets.Rotation32Bytes

namespace Gadgets.Rotation32Bytes
def Assumptions (input : U32 (F p)) := input.Normalized
end Gadgets.Rotation32Bytes

namespace Gadgets.Rotation32Bytes
def Spec (offset : Fin 4) (x : U32 (F p)) (y : U32 (F p)) :=
  y.value = rotRight32 x.value (offset.val * 8) ∧ y.Normalized
end Gadgets.Rotation32Bytes

namespace Gadgets.Rotation32Bytes
instance elaborated (off : Fin 4): ElaboratedCircuit (F p) U32 U32 where
  main := main off
  localLength _ := 0
  output input i0 :=
    let ⟨x0, x1, x2, x3⟩ := input
    match off with
    | 0 => ⟨ x0, x1, x2, x3 ⟩
    | 1 => ⟨ x1, x2, x3, x0 ⟩
    | 2 => ⟨ x2, x3, x0, x1 ⟩
    | 3 => ⟨ x3, x0, x1, x2 ⟩

  subcircuitsConsistent x i0 := by admit /- proof elided -/
end Gadgets.Rotation32Bytes

namespace Gadgets.Rotation32Bytes
def circuit (off : Fin 4) : FormalCircuit (F p) U32 U32 := {
  elaborated off with
  main := main off
  Assumptions
  Spec := Spec off
  soundness := soundness off
  completeness := completeness off
}
end Gadgets.Rotation32Bytes

instance (p : ℕ) : ToJson (F p) where
  toJson x := toJson x.val

def exprToJson [ToJson F]: Expression F → Json
  | .var v => Json.mkObj [
    ("type", Json.str "var"),
    ("index", Json.num v.index)
  ]
  | .const c => Json.mkObj [
    ("type", Json.str "const"),
    ("value", toJson c)
  ]
  | .add x y => Json.mkObj [
    ("type", Json.str "add"),
    ("lhs", exprToJson x),
    ("rhs", exprToJson y)
  ]
  | .mul x y => Json.mkObj [
    ("type", Json.str "mul"),
    ("lhs", exprToJson x),
    ("rhs", exprToJson y)
  ]

instance : ToJson (Expression F) where
  toJson := exprToJson

instance : ToJson (Lookup F) where
  toJson l := Json.mkObj [
    ("table", toJson l.table.name),
    ("entry", toJson l.entry.toArray),
  ]

instance : ToJson (FlatOperation F) where
  toJson
    | FlatOperation.witness m _ => Json.mkObj [("witness", toJson m)]
    | FlatOperation.assert e => Json.mkObj [("assert", toJson e)]
    | FlatOperation.lookup l => Json.mkObj [("lookup", toJson l)]

instance : ToJson (Operation F) where
  toJson
    | Operation.witness m _ => Json.mkObj [("witness", toJson m)]
    | Operation.assert e => Json.mkObj [("assert", toJson e)]
    | Operation.lookup l => Json.mkObj [("lookup", toJson l)]
    | Operation.subcircuit { ops, .. } => Json.mkObj [("subcircuit", toJson ops)]

instance : ToJson (Operations F) where
  toJson ops := toJson ops.toList

IO in Init.System.IO

String in Init.Prelude

Unit in Init.Prelude

IO.FS.writeFile in Init.System.IO

IO.println in Init.System.IO

List in Init.Prelude

Vector in Init.Data.Vector.Basic

Fin in Init.Prelude

Nat in Init.Prelude

Nat.twoStepInduction in Mathlib.Data.Nat.Init

ZMod in Mathlib.Data.ZMod.Defs

ZMod.val in Mathlib.Data.ZMod.Basic

Nat.reduceAdd in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

Nat.reduceMod in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

Fin.isValue in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Fin

Nat.reduceLT in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

PNat in Mathlib.Data.PNat.Notation

Vector.get in Init.Data.Vector.Basic

Vector.instGetElemNatLt in Init.Data.Vector.Basic

reduceDIte in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Core

Nat.reducePow in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

Fact in Mathlib.Logic.Basic

Vector.ofFn in Init.Data.Vector.Basic

Fin.foldl in Init.Data.Fin.Fold

Prod in Init.Prelude

Prod.fst in Init.Prelude

Prod.mk in Init.Prelude

cast in Init.Prelude

Array in Init.Prelude

Vector.foldl in Init.Data.Vector.Basic

List.foldl in Init.Prelude

Vector.cast in Init.Data.Vector.Basic

Vector.map in Init.Data.Vector.Basic

Vector.extract in Init.Data.Vector.Basic

NeZero in Init.Data.NeZero

Nat.log2 in Init.Data.Nat.Log2

Or in Init.Prelude

Or.inl in Init.Prelude

And in Init.Prelude

Bool in Init.Prelude

Nat.testBit in Init.Data.Nat.Bitwise.Basic

Prod.snd in Init.Prelude

Fin.ofNat in Init.Data.Fin.Basic

Coe in Init.Coe

DecidableEq in Init.Prelude

Nat.reduceGT in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

Inhabited in Init.Prelude

Repr in Init.Data.Repr

repr in Init.Data.Repr

Nat.Prime in Mathlib.Data.Nat.Prime.Defs

UInt32 in Init.Prelude

UInt32.ofNatLT in Init.Prelude

Field in Mathlib.Algebra.Field.Defs

Nat.bitwise in Init.Data.Nat.Bitwise.Basic

UInt64 in Init.Prelude

UInt64.ofNatLT in Init.Prelude

List.replicate in Init.Data.List.Basic

reprStr in Init.Data.Repr

Zero in Init.Prelude

One in Init.Prelude

Add in Init.Prelude

Neg in Init.Prelude

Sub in Init.Prelude

Mul in Init.Prelude

OfNat in Init.Prelude

OfNat.ofNat in Init.Prelude

HMul in Init.Prelude

HDiv in Init.Prelude

CoeFun in Init.Coe

Fin.last in Init.Data.Fin.Basic

Lean.ToJson in Lean.Data.Json.FromToJson.Basic

Lean.Json in Lean.Data.Json.Basic

Lean.Json.mkObj in Lean.Data.Json.Basic

Lean.Json.num in Lean.Data.Json.Basic

Lean.Json.str in Lean.Data.Json.Basic

id in Init.Prelude

CoeDep in Init.Coe

Vector.replicate in Init.Data.Vector.Basic

HAdd in Init.Prelude

HSub in Init.Prelude

Inv in Init.Prelude

CoeOut in Init.Coe

CoeTail in Init.Coe

CoeHead in Init.Coe

inferInstanceAs in Init.Prelude

Monad in Init.Prelude

outParam in Init.Prelude

LawfulMonad in Init.Control.Lawful.Basic

Functor in Init.Prelude

Functor.map in Init.Prelude

Seq in Init.Prelude

Seq.seq in Init.Prelude

SeqLeft in Init.Prelude

SeqLeft.seqLeft in Init.Prelude

SeqRight in Init.Prelude

SeqRight.seqRight in Init.Prelude

Vector.forM in Init.Data.Vector.Basic

Vector.toList in Init.Data.Vector.Basic

Array.mapM in Init.Data.Array.Basic

Array.toList in Init.Prelude

Vector.mapM in Init.Data.Vector.Basic

List.ofFn in Init.Data.List.OfFn

Vector.mapIdx in Init.Data.Vector.Basic

Vector.foldlM in Init.Data.Vector.Basic

Vector.finRange in Init.Data.Vector.FinRange

ForM in Init.Control.Basic

Nat.toUInt64 in Init.Data.UInt.BasicAux

UInt64.toNat in Init.Data.UInt.BasicAux

AndOp in Init.Prelude

HAnd in Init.Prelude

HAnd.hAnd in Init.Prelude

Nat.land in Init.Data.Nat.Bitwise.Basic

Fin.sum in Batteries.Data.Fin.Basic

CommSemiring in Mathlib.Algebra.Ring.Defs

Fin.castSucc in Init.Data.Fin.Basic

AddCommMonoid in Mathlib.Algebra.Group.Defs

Finset in Mathlib.Data.Finset.Defs

Finset.range in Mathlib.Data.Finset.Range

Fintype in Mathlib.Data.Fintype.Defs

instFintypeProd in Mathlib.Data.Fintype.Prod

instDecidableEqProd in Init.Core

Finset.univ in Mathlib.Data.Fintype.Defs

Finset.erase in Mathlib.Data.Finset.Erase

List.tail in Init.Data.List.Basic

List.zip in Init.Data.List.Basic

List.Sublist in Init.Data.List.Basic

List.drop in Init.Data.List.Basic

List.count in Init.Data.List.Basic

List.Sublist.cons₂ in Init.Data.List.Basic

List.take in Init.Data.List.Basic

Nat.reduceLeDiff in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

List.countP in Init.Data.List.Basic

reduceCtorEq in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Core

List.head? in Init.Data.List.Basic

Option in Init.Prelude

List.getLast? in Init.Data.List.Basic

Nat.cast in Init.Data.Cast

BEq in Init.Prelude

Fintype.card in Mathlib.Data.Fintype.Card

List.length in Init.Prelude

Vector.mk in Init.Data.Vector.Basic

HEq in Init.Prelude

List.dropLast in Init.Data.List.Basic

Vector.push in Init.Data.Vector.Basic

Vector.zero in Init.Data.Vector.Algebra

Vector.take in Init.Data.Vector.Basic

Vector.drop in Init.Data.Vector.Basic

Composition in Mathlib.Combinatorics.Enumerative.Composition

Composition.blocks in Mathlib.Combinatorics.Enumerative.Composition

Vector.attachWith in Init.Data.Vector.Attach

Vector.toArray in Init.Data.Vector.Basic

Vector.range in Init.Data.Vector.Basic

Or.inr in Init.Prelude

ZMod.instField in Mathlib.Algebra.Field.ZMod

ZMod.fintype in Mathlib.Data.ZMod.Defs

CommRing in Mathlib.Algebra.Ring.Defs

ZMod.commRing in Mathlib.Data.ZMod.Defs

ZMod.decidableEq in Mathlib.Data.ZMod.Defs

False.elim in Init.Prelude

List.finRange in Init.Data.List.FinRange

List.Pairwise in Init.Data.List.Basic

Decidable in Init.Prelude

Nat.decLe in Init.Prelude

Nat.decLt in Init.Prelude

IsTrans in Mathlib.Order.Defs.Unbundled

IsTotal in Mathlib.Order.Defs.Unbundled

IsAntisymm in Mathlib.Order.Defs.Unbundled

IsIrrefl in Mathlib.Order.Defs.Unbundled

List.insertionSort in Mathlib.Data.List.Sort

List.Sorted in Mathlib.Deprecated.Sort

List.filter in Init.Data.List.Basic

List.Perm in Init.Data.List.Basic

BitVec in Init.Prelude

UInt64.ofNat in Init.Data.UInt.BasicAux

UInt64.reduceToNat in Lean.Meta.Tactic.Simp.BuiltinSimprocs.UInt

UInt64.size in Init.Prelude

cond in Init.Prelude

UInt32.ofNat in Init.Data.UInt.BasicAux

UInt32.reduceToNat in Lean.Meta.Tactic.Simp.BuiltinSimprocs.UInt

UInt32.size in Init.Prelude

UInt32.toNat in Init.Prelude

List.Vector in Mathlib.Data.Vector.Defs

StateM in Init.Control.State

MonadLift in Init.Prelude

modify in Init.Prelude

Id in Init.Control.Id

Id.run in Init.Control.Id

Std.HashMap in Std.Data.HashMap.Basic

Std.HashMap.emptyWithCapacity in Std.Data.HashMap.Basic

Array.replicate in Init.Data.Array.Basic

List.Forall in Mathlib.Data.List.Defs

IsEmpty in Mathlib.Logic.IsEmpty

Zero.toOfNat0 in Init.Data.Zero

Zero.zero in Init.Prelude

List.cons in Init.Prelude

Preorder in Mathlib.Order.Defs.PartialOrder

NoZeroDivisors in Mathlib.Algebra.GroupWithZero.Defs

Ring in Mathlib.Algebra.Ring.Defs

MulZeroOneClass in Mathlib.Algebra.GroupWithZero.Defs

toNat in Mathlib.Computability.Reduce

List.range in Init.Data.List.Basic

Lean.throwError in Lean.Exception

Lean.Elab.Tactic.TacticM in Lean.Elab.Tactic.Basic

Lean.Elab.Tactic.withMainContext in Lean.Elab.Tactic.Basic

Lean.FVarId in Lean.Expr

Lean.Meta.inferType in Lean.Meta.Basic

Lean.Elab.Tactic.getMainGoal in Lean.Elab.Tactic.Basic

Lean.Elab.Tactic.evalTactic in Lean.Elab.Tactic.Basic

Lean.Elab.Tactic.replaceMainGoal in Lean.Elab.Tactic.Basic

Lean.Meta.whnf in Lean.Meta.Basic

Lean.mkIdent in Init.Meta.Defs

Lean.trace in Lean.Util.Trace

Lean.Expr in Lean.Expr

Lean.MonadLCtx in Lean.LocalContext

Lean.MonadLCtx.getLCtx in Lean.LocalContext

Lean.Meta.mkAppM in Lean.Meta.AppBuilder

Lean.Meta.trySynthInstance in Lean.Meta.SynthInstance

Lean.Name in Init.Prelude

Lean.getStructureInfo? in Lean.Structure

Lean.getStructureFields in Lean.Structure

Not in Init.Prelude

Lean.Elab.Tactic.elabTermWithHoles in Lean.Elab.Tactic.ElabTerm

Lean.mkAppN in Lean.Expr

Eq in Init.Prelude

Set in Mathlib.Data.Set.Defs

Fin.NatCast.instNatCast in Init.Data.Fin.Lemmas

Fin.xor in Init.Data.Fin.Basic

HXor in Init.Prelude

HXor.hXor in Init.Prelude

XorOp in Init.Prelude

XorOp.xor in Init.Prelude

Int in Init.Data.Int.Basic

Fin.reduceMod in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Fin

List.map in Init.Prelude

Nat.reduceMul in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat

Fin.reduceEq in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Fin

Fin.reduceFinMk in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Fin

UInt16 in Init.Prelude

UInt16.toNat in Init.Data.UInt.BasicAux

CharP in Mathlib.Algebra.CharP.Defs

Int.reducePow in Lean.Meta.Tactic.Simp.BuiltinSimprocs.Int
</all_available_defs>

<all_available_lemmas>
namespace Gadgets.Rotation32
theorem soundness (offset : Fin 32) : Soundness (F p) (circuit := elaborated offset) Assumptions (Spec offset)
end Gadgets.Rotation32

namespace Gadgets.Rotation32
theorem completeness (offset : Fin 32) : Completeness (F p) (elaborated offset) Assumptions
end Gadgets.Rotation32

namespace FlatOperation
lemma constraintsHold_cons : ∀ {op : FlatOperation F}, ∀ {ops : List (FlatOperation F)}, ∀ {env : Environment F},
    ConstraintsHoldFlat env (op :: ops) ↔ ConstraintsHoldFlat env [op] ∧ ConstraintsHoldFlat env ops
end FlatOperation

namespace FlatOperation
lemma constraintsHold_append : ∀ {a b: List (FlatOperation F)}, ∀ {env : Environment F},
    ConstraintsHoldFlat env (a ++ b) ↔ ConstraintsHoldFlat env a ∧ ConstraintsHoldFlat env b
end FlatOperation

theorem Circuit.constraintsHold_toFlat_iff : ∀ {ops : Operations F}, ∀ {env : Environment F},
    ConstraintsHoldFlat env ops.toFlat ↔ ConstraintsHold env ops

namespace Circuit
lemma subcircuit_localLength_eq (circuit : FormalCircuit F β α) (input : Var β F) (offset : ℕ) :
  (circuit.toSubcircuit offset input).localLength = circuit.localLength input
end Circuit

namespace Circuit
lemma assertion_localLength_eq (circuit : FormalAssertion F β) (input : Var β F) (offset : ℕ) :
  (circuit.toSubcircuit offset input).localLength = circuit.localLength input
end Circuit

namespace Circuit
lemma subcircuitWithAssertion_localLength_eq (circuit : GeneralFormalCircuit F β α) (input : Var β F) (offset : ℕ) :
  (circuit.toSubcircuit offset input).localLength = circuit.localLength input
end Circuit

namespace ElaboratedCircuit
lemma computableWitnesses_implies {circuit : ElaboratedCircuit F β α} :
    circuit.ComputableWitnesses → circuit.ComputableWitnesses'
end ElaboratedCircuit

namespace ElaboratedCircuit
theorem compose_computableWitnesses (circuit : ElaboratedCircuit F β α) (input : Var β F) (n : ℕ) :
  Environment.OnlyAccessedBelow n (eval · input) ∧ circuit.ComputableWitnesses →
    (circuit.main input).ComputableWitnesses n
end ElaboratedCircuit

theorem Circuit.subcircuit_computableWitnesses (circuit : FormalCircuit F β α) (input : Var β F) (n : ℕ) :
  Environment.OnlyAccessedBelow n (eval · input) ∧ circuit.ComputableWitnesses →
    (subcircuit circuit input).ComputableWitnesses n

@[circuit_norm]
theorem FormalCircuit.toSubcircuit_usesLocalWitnesses
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : FormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).UsesLocalWitnesses env =
    (circuit.Assumptions (eval env input_var) → circuit.Spec (eval env input_var) (eval env (circuit.output input_var n)))

@[circuit_norm]
theorem GeneralFormalCircuit.toSubcircuit_usesLocalWitnesses
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : GeneralFormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).UsesLocalWitnesses env =
    (circuit.Assumptions (eval env input_var) → circuit.Spec (eval env input_var) (eval env (circuit.output input_var n)))

@[circuit_norm]
theorem FormalAssertion.toSubcircuit_usesLocalWitnesses
    {F : Type} [Field F] {Input : TypeMap} [ProvableType Input]
    (circuit : FormalAssertion F Input) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).UsesLocalWitnesses env = True

@[circuit_norm]
theorem FormalCircuit.toSubcircuit_localLength
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : FormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) :
    (circuit.toSubcircuit n input_var).localLength = circuit.localLength input_var

@[circuit_norm]
theorem GeneralFormalCircuit.toSubcircuit_localLength
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : GeneralFormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) :
    (circuit.toSubcircuit n input_var).localLength = circuit.localLength input_var

@[circuit_norm]
theorem FormalAssertion.toSubcircuit_localLength
    {F : Type} [Field F] {Input : TypeMap} [ProvableType Input]
    (circuit : FormalAssertion F Input) (n : ℕ) (input_var : Var Input F) :
    (circuit.toSubcircuit n input_var).localLength = circuit.localLength input_var

@[circuit_norm]
theorem FormalCircuit.toSubcircuit_soundness
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : FormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Soundness env =
    (circuit.Assumptions (eval env input_var) → circuit.Spec (eval env input_var) (eval env (circuit.output input_var n)))

@[circuit_norm]
theorem GeneralFormalCircuit.toSubcircuit_soundness
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : GeneralFormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Soundness env =
    circuit.Spec (eval env input_var) (eval env (circuit.output input_var n))

@[circuit_norm]
theorem FormalAssertion.toSubcircuit_soundness
    {F : Type} [Field F] {Input : TypeMap} [ProvableType Input]
    (circuit : FormalAssertion F Input) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Soundness env =
    (circuit.Assumptions (eval env input_var) → circuit.Spec (eval env input_var))

@[circuit_norm]
theorem FormalCircuit.toSubcircuit_completeness
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : FormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Completeness env =
    circuit.Assumptions (eval env input_var)

@[circuit_norm]
theorem GeneralFormalCircuit.toSubcircuit_completeness
    {F : Type} [Field F] {Input Output : TypeMap} [ProvableType Input] [ProvableType Output]
    (circuit : GeneralFormalCircuit F Input Output) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Completeness env =
    circuit.Assumptions (eval env input_var)

@[circuit_norm]
theorem FormalAssertion.toSubcircuit_completeness
    {F : Type} [Field F] {Input : TypeMap} [ProvableType Input]
    (circuit : FormalAssertion F Input) (n : ℕ) (input_var : Var Input F) (env : Environment F) :
    (circuit.toSubcircuit n input_var).Completeness env =
    (circuit.Assumptions (eval env input_var) ∧ circuit.Spec (eval env input_var))

namespace Gadgets.BLAKE3.FinalStateUpdate
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.FinalStateUpdate

namespace TestSplitProvableStructEq
theorem test_struct_literal_eq_literal {F : Type} [Field F]
    (h : (TestInputs.mk 1 2 3 : TestInputs F) = TestInputs.mk 4 5 6) :
    (1 : F) = 4
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_struct_literal_eq_variable {F : Type} [Field F] (input : TestInputs F)
    (h : TestInputs.mk 1 2 3 = input) :
    input.x = 1
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_struct_variable_eq_literal {F : Type} [Field F] (input : TestInputs F)
    (h : input = TestInputs.mk 1 2 3) :
    input.x = 1
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_multiple_equalities {F : Type} [Field F] (input1 input2 : TestInputs F)
    (h1 : TestInputs.mk 1 2 3 = input1)
    (h2 : input2 = TestInputs.mk 4 5 6) :
    input1.x = 1 ∧ input2.y = 5
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_conjunction_with_struct_eq {F : Type} [Field F] (input : TestInputs F) (x : F)
    (h : TestInputs.mk 1 2 3 = input ∧ x = 7) :
    input.x = 1 ∧ x = 7
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_nested_conjunctions {F : Type} [Field F] (input1 input2 : TestInputs F) (x : F)
    (h : (TestInputs.mk 1 2 3 = input1 ∧ x = 7) ∧ input2 = TestInputs.mk 4 5 6) :
    input1.x = 1 ∧ input2.y = 5
end TestSplitProvableStructEq

namespace TestSplitProvableStructEq
theorem test_with_base_and_non_base {F : Type} [Field F] (input1 input2 input3 : TestInputs F) (x : F)
    (h : (TestInputs.mk 1 2 3 = input1 ∧ x = 7) ∧ input2 = input3) :
    input1.x = 1
end TestSplitProvableStructEq

namespace InductiveTable
theorem equalityConstraint.soundness {row : State F × Input F} {input_state : State F} {env : Environment F} :
  Circuit.ConstraintsHold.Soundness (windowEnv (equalityConstraint Input input_state) ⟨<+> +> row, rfl⟩ env)
    (equalityConstraint Input input_state .empty).2.circuit
    ↔ row.1 = input_state
end InductiveTable

namespace InductiveTable
omit [Field F] in
lemma traceInputs_length {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) :
    (traceInputs trace).length = N
end InductiveTable

namespace InductiveTable
lemma table_soundness_aux (table : InductiveTable F State Input) (input output : State F)
  (N : ℕ+) (trace : TraceOfLength F (ProvablePair State Input) N) (env : ℕ → ℕ → Environment F) :
  table.Spec input [] 0 rfl input →
  TableConstraintsHold (table.tableConstraints input output) trace env →
    trace.ForAllRowsWithPrevious (fun row i rest => table.Spec input (traceInputs rest) i (traceInputs_length rest) row.1)
    ∧ trace.lastRow.1 = output
end InductiveTable

namespace InductiveTable
theorem table_soundness (table : InductiveTable F State Input) (input output : State F)
  (N : ℕ+) (trace : TraceOfLength F (ProvablePair State Input) N) (env : ℕ → ℕ → Environment F) :
  table.Spec input [] 0 rfl input → TableConstraintsHold (table.tableConstraints input output) trace env →
    table.Spec input (traceInputs trace.tail) (N-1) (traceInputs_length trace.tail) output
end InductiveTable

namespace Gadgets.And.And64
omit [Fact (Nat.Prime p)] p_large_enough in
theorem soundness_to_u64 {x y z : U64 (F p)}
  (x_norm : x.Normalized) (y_norm : y.Normalized)
  (h_eq :
    z.x0.val = x.x0.val &&& y.x0.val ∧
    z.x1.val = x.x1.val &&& y.x1.val ∧
    z.x2.val = x.x2.val &&& y.x2.val ∧
    z.x3.val = x.x3.val &&& y.x3.val ∧
    z.x4.val = x.x4.val &&& y.x4.val ∧
    z.x5.val = x.x5.val &&& y.x5.val ∧
    z.x6.val = x.x6.val &&& y.x6.val ∧
    z.x7.val = x.x7.val &&& y.x7.val) : Spec { x, y } z
end Gadgets.And.And64

namespace Gadgets.And.And64
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.And.And64

namespace Gadgets.IsZero
lemma foldl_isZero_eq_one_iff {n : ℕ} {vars : Vector (Expression F) n} {vals : Vector F n}
    {env : Environment F} {i₀ : ℕ}
    (h_eval : Vector.map (Expression.eval env) vars = vals)
    (h_isZero : ∀ (i : Fin n),
      IsZeroField.circuit.Assumptions (Expression.eval (F:=F) env vars[i]) →
        IsZeroField.circuit.Spec (Expression.eval (F:=F) env vars[i])
          (Expression.eval (F:=F) env
            (IsZeroField.circuit.output vars[i]
              (i₀ + i * IsZeroField.circuit.localLength vars[i])))) :
    Expression.eval env
      (Fin.foldl n
        (fun acc i => acc * (IsZeroField.circuit.output vars[i] (i₀ + i * IsZeroField.circuit.localLength vars[i]) : Var field F))
        1) =
    if ∀ (i : ℕ) (x : i < n), vals[i] = 0 then 1 else 0
end Gadgets.IsZero

namespace Gadgets.IsZero
theorem soundness [DecidableEq (M F)] : Soundness F (elaborated (M := M)) Assumptions Spec
end Gadgets.IsZero

namespace Gadgets.IsZero
theorem completeness : Completeness F (elaborated (M := M)) Assumptions
end Gadgets.IsZero

namespace Gadgets.Rotation64
theorem soundness (offset : Fin 64) : Soundness (F p) (circuit := elaborated offset) Assumptions (Spec offset)
end Gadgets.Rotation64

namespace Gadgets.Rotation64
theorem completeness (offset : Fin 64) : Completeness (F p) (elaborated offset) Assumptions
end Gadgets.Rotation64

namespace Gadgets.BLAKE3.Compress
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
lemma ApplyRounds.circuit_assumptions_is :
  ApplyRounds.circuit.Assumptions (F := F p) = ApplyRounds.Assumptions
end Gadgets.BLAKE3.Compress

namespace Gadgets.BLAKE3.Compress
lemma ApplyRouunds.circuit_spec_is :
  ApplyRounds.circuit.Spec (F := F p) = ApplyRounds.Spec
end Gadgets.BLAKE3.Compress

namespace Gadgets.ByteDecomposition.Theorems
theorem byteDecomposition_lift {low high two_power : F p}
  (h_low : low.val < 2^8) (h_high : high.val < 2^8) (h_two_power : two_power.val ≤ 2^8) :
    (low + high * two_power).val = low.val + high.val * two_power.val
end Gadgets.ByteDecomposition.Theorems

namespace Gadgets.ByteDecomposition.Theorems
theorem byteDecomposition_lt (o : ℕ) (ho : o ≤ 8) {low high : F p} (h_low : low.val < 2^o) (h_high : high.val < 2^(8-o)) :
    (low + high * (2^o : ℕ)).val < 2^8
    ∧ (low + high * (2^o : ℕ)).val = low.val + high.val * 2^o
end Gadgets.ByteDecomposition.Theorems

namespace Gadgets.ByteDecomposition.Theorems
theorem soundness (offset : Fin 8) (x low high : F p)
  (x_lt : x.val < 2^8) (low_lt : low.val < 2^offset.val) (high_lt : high.val < 2^8)
  (h_eq : x = low + high * 2^offset.val) :
    low.val = x.val % 2^offset.val ∧ high.val = x.val / 2^offset.val
end Gadgets.ByteDecomposition.Theorems

namespace Vector
theorem toList_length_one {α : Type} (v : Vector α 1) :
    v.toList = [v[0]]
end Vector

namespace Vector
theorem toList_length_two {α : Type} (v : Vector α 2) :
    v.toList = [v[0], v[1]]
end Vector

namespace Vector
theorem toList_cons {a : α} {v : Vector α n} : (cons a v).toList = a :: v.toList
end Vector

namespace Vector
theorem cast_heq {v : Vector α n} (h : n = m) : HEq (v.cast h) v
end Vector

namespace Vector
theorem heq_cast {v : Vector α n} (h : n = m) : HEq v (v.cast h)
end Vector

namespace Vector
theorem empty_push (x : α) : #v[].push x = #v[x]
end Vector

namespace Vector
theorem cons_push (x y : α) (xs : Vector α n) : (cons x xs).push y = cons x (xs.push y)
end Vector

namespace Vector
theorem inductPush_nil {motive : {n : ℕ} → Vector α n → Sort u}
  {nil : motive #v[]}
  {push : ∀ {n : ℕ} (as : Vector α n) (a : α), motive as → motive (as.push a)} :
    inductPush nil push #v[] = nil
end Vector

namespace Vector
lemma inductPush_cons_push {motive : {n : ℕ} → Vector α n → Sort u}
  {nil : motive #v[]}
  {push' : ∀ {n : ℕ} (as : Vector α n) (a : α), motive as → motive (as.push a)}
  {n : ℕ} (xs : Vector α n) (x a : α) :
    inductPush nil push' (cons x (xs.push a)) = push' (cons x xs) a (inductPush nil push' (cons x xs))
end Vector

namespace Vector
theorem inductPush_push {motive : {n : ℕ} → Vector α n → Sort u}
  {nil : motive #v[]}
  {push : ∀ {n : ℕ} (as : Vector α n) (a : α), motive as → motive (as.push a)}
  {n : ℕ} (as : Vector α n) (a : α) :
    inductPush nil push (as.push a) = push as a (inductPush nil push as)
end Vector

namespace Vector
theorem getElemFin_finRange {n} (i : Fin n) : (finRange n)[i] = i
end Vector

namespace Vector
theorem mapFinRange_zero {create : Fin 0 → α} : mapFinRange 0 create = #v[]
end Vector

namespace Vector
theorem mapFinRange_succ {n : ℕ} {create : Fin (n + 1) → α} :
    mapFinRange (n + 1) create = (mapFinRange n (fun i => create i.castSucc)).push (create (.last n))
end Vector

namespace Vector
theorem cast_mapFinRange {n} {create : Fin n → α} (h : n = m) :
    mapFinRange n create = (mapFinRange m (fun i => create (i.cast h.symm))).cast h.symm
end Vector

namespace Vector
theorem getElemFin_mapFinRange {n} {create : Fin n → α} :
    ∀ i : Fin n, (mapFinRange n create)[i] = create i
end Vector

namespace Vector
theorem getElem_mapFinRange {n} {create : Fin n → α} :
    ∀ (i : ℕ) (hi : i < n), (mapFinRange n create)[i] = create ⟨ i, hi ⟩
end Vector

namespace Vector
theorem mapRange_zero {create : ℕ → α} : mapRange 0 create = #v[]
end Vector

namespace Vector
theorem mapRange_succ {n} {create : ℕ → α} :
    mapRange (n + 1) create = (mapRange n create).push (create n)
end Vector

namespace Vector
theorem cast_mapRange {n} {create : ℕ → α} (h : n = m) :
    mapRange n create = (mapRange m create).cast h.symm
end Vector

namespace Vector
theorem getElem_mapRange {n} {create : ℕ → α} :
    ∀ (i : ℕ) (hi : i < n), (mapRange n create)[i] = create i
end Vector

namespace Vector
theorem map_mapRange {n} {create : ℕ → α} {f : α → β} :
  Vector.map f (Vector.mapRange n create) =
    Vector.mapRange n (fun i => f (create i))
end Vector

namespace Vector
theorem mapRange_add_eq_append {n m} (create : ℕ → α) :
    mapRange (n + m) create = mapRange n create ++ mapRange m (fun i => create (n + i))
end Vector

namespace Vector
theorem getElem_fill {n} {a : α} {i : ℕ} {hi : i < n} :
    (fill n a)[i] = a
end Vector

namespace Vector
theorem cast_take_append_of_eq_length {v : Vector α n} {w : Vector α m} :
    (v ++ w |>.take n |>.cast Nat.min_add_right_self) = v
end Vector

namespace Vector
theorem cast_drop_append_of_eq_length {v : Vector α n} {w : Vector α m} :
    (v ++ w |>.drop n |>.cast (Nat.add_sub_self_left n m)) = w
end Vector

namespace Vector
theorem append_take_drop {v : Vector α (n + m)} :
    (v.take n |>.cast Nat.min_add_right_self) ++ (v.drop n |>.cast (Nat.add_sub_self_left n m)) = v
end Vector

namespace Vector
lemma map_take {α β : Type} {n : ℕ} (f : α → β) (xs : Vector α n) (i : ℕ) :
    (xs.map f).take i = (xs.take i).map f
end Vector

theorem Composition.ofProductLength_mem_length {m : ℕ+} {α : Type} {l : List α} {hl : l.length = n * m.val}
  (comp : Composition l.length) (hcomp : comp = Composition.ofProductLength m hl) :
  ∀ li ∈ l.splitWrtComposition comp, li.length = m

namespace Vector
theorem toChunks_flatten {α : Type} (m : ℕ+) (v : Vector α (n*m)) :
    (v.toChunks m).flatten = v
end Vector

namespace Vector
theorem flatten_toChunks {α : Type} (m : ℕ+) (v : Vector (Vector α m) n) :
    v.flatten.toChunks m = v
end Vector

namespace Vector
theorem toChunks_push (m : ℕ+) {α : Type} (vs : Vector α (n*m)) (v : Vector α m) :
    have h : n * m + m = (n + 1) * m
end Vector

namespace Vector
theorem mapM_singleton (a : α) {m : Type → Type} [Monad m] [LawfulMonad m] (f : α → m β) :
    #v[a].mapM f = (do pure #v[←f a])
end Vector

namespace Vector
theorem mapM_push (as : Vector α n) {m : Type → Type} [Monad m] [LawfulMonad m] (f : α → m β) (a : α) :
    (as.push a).mapM f = (do
      let bs ← as.mapM f
      let b ← f a
      pure (bs.push b))
end Vector

namespace Vector
theorem rotate_rotate {α : Type} {n : ℕ} (v : Vector α n) (off1 off2 : ℕ) :
    v.rotate (off1 + off2) = (v.rotate off1).rotate off2
end Vector

namespace Vector
theorem getElem_rotate {α : Type} {n : ℕ} {v : Vector α n} {off : ℕ} {i : ℕ} (hi : i < n) :
    (v.rotate off)[i] = v[(i + off) % n]'(Nat.mod_lt _ (Nat.pos_iff_ne_zero.mpr (Nat.ne_zero_of_lt hi)))
end Vector

namespace Vector
lemma getElem_takeShort {α : Type} {n : ℕ} (v : Vector α n) (j : ℕ) (h_j : j < n) (i : ℕ) (h_i : i < j) :
    (v.takeShort j h_j)[i] = v[i]
end Vector

namespace Vector
lemma map_takeShort {α β : Type} (f : α → β) {j n : ℕ} (v : Vector α n) (h_j : j < n) :
    Vector.map f (v.takeShort j h_j) = (v.map f).takeShort j h_j
end Vector

namespace Circomlib.MultiAND
theorem localLength_eq (n : ℕ) (input : Var (fields n) (F p)) (offset : ℕ) :
    (main input).localLength offset = n - 1
end Circomlib.MultiAND

namespace Circomlib.MultiAND
theorem Circuit.subcircuitsConsistent_bind {α β : Type} (f : Circuit (F p) α) (g : α → Circuit (F p) β) (offset : ℕ)
    (hf : Operations.SubcircuitsConsistent offset (f.operations offset))
    (hg : Operations.SubcircuitsConsistent (offset + f.localLength offset)
          ((g (f.output offset)).operations (offset + f.localLength offset))) :
    Operations.SubcircuitsConsistent offset ((f >>= g).operations offset)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
theorem subcircuitsConsistent (n : ℕ) (input : Var (fields n) (F p)) (offset : ℕ) :
    Operations.SubcircuitsConsistent offset ((main input).operations offset)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma main_usesLocalWitnesses_iff_completeness (n : ℕ) (input : Var (fields n) (F p)) (offset1 offset2 : ℕ) (env : Environment (F p)) :
    offset1 = offset2 ->
    (env.UsesLocalWitnesses offset1 ((main input).operations offset2) ↔
     env.UsesLocalWitnessesCompleteness offset1 ((main input).operations offset2))
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma eval_toArray_extract_eq {n : ℕ} (start finish : ℕ) {env : Environment (F p)}
    {v : Var (fields n) (F p)} {w : fields n (F p)}
    (h_eval : w = eval env v)
    (h_bounds : finish ≤ n) (h_start : start ≤ finish) :
    ProvableType.eval (α := fields (finish - start)) env
      ⟨v.toArray.extract start finish, by simp [Array.size_extract]; omega⟩ =
    ⟨w.toArray.extract start finish, by simp [Array.size_extract]; omega⟩
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma extract_preserves_element {p n n1 : ℕ} (input : fields n (F p)) (i : ℕ) (hi : i < n1) (h_n1_lt : n1 ≤ n) :
    let input1 : fields n1 (F p) := ⟨input.toArray.extract 0 n1, by simp [Array.size_extract, Vector.size_toArray]; exact h_n1_lt⟩
    input1[i]'hi = input[i]'(by omega)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma extract_from_offset_preserves_element {p n n1 n2 : ℕ} (input : fields n (F p))
    (i : ℕ) (hi : i < n2) (h_sum : n1 + n2 = n) :
    let input2 : fields n2 (F p) := ⟨input.toArray.extract n1 n, by simp [Array.size_extract, Vector.size_toArray]; omega⟩
    input2[i]'hi = input[n1 + i]'(by omega)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma Vector.foldl_empty' {α β : Type} (init : β) (f : β → α → β) (v : Vector α 0) :
    Vector.foldl f init v = init
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma Vector.foldl_and_split {n1 n2 n3 : ℕ} (v : Vector ℕ n3)
    (v1 : Vector ℕ n1) (v2 : Vector ℕ n2) (h_sum : n1 + n2 = n3)
    (h_split : v = h_sum ▸ (v1 ++ v2)) :
    Vector.foldl (· &&& ·) 1 v =
    Vector.foldl (· &&& ·) 1 v1 &&& Vector.foldl (· &&& ·) 1 v2
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma soundness_zero {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 0) (F p))
    (input : fields 0 (F p)) (_h_env : input = eval env input_var)
    (_h_assumptions : Assumptions 0 input)
    (_h_hold : Circuit.ConstraintsHold.Soundness env ((main input_var).operations offset)) :
    Spec 0 input (env ((main input_var).output offset))
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma soundness_one {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 1) (F p))
    (input : fields 1 (F p)) (h_env : input = eval env input_var)
    (h_assumptions : Assumptions 1 input)
    (_h_hold : Circuit.ConstraintsHold.Soundness env ((main input_var).operations offset)) :
    Spec 1 input (env ((main input_var).output offset))
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma soundness_two {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 2) (F p))
    (input : fields 2 (F p)) (h_env : input = eval env input_var)
    (h_assumptions : Assumptions 2 input)
    (h_hold : Circuit.ConstraintsHold.Soundness env ((main input_var).operations offset)) :
    Spec 2 input (env ((main input_var).output offset))
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma completeness_zero {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 0) (F p))
    (input : fields 0 (F p))
    (_h_local_witnesses : env.UsesLocalWitnessesCompleteness offset ((main input_var).operations offset))
    (_h_env : input = eval env input_var)
    (_h_assumptions : Assumptions 0 input) :
    Circuit.ConstraintsHold.Completeness env ((main input_var).operations offset)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma completeness_one {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 1) (F p))
    (input : fields 1 (F p))
    (_h_local_witnesses : env.UsesLocalWitnessesCompleteness offset ((main input_var).operations offset))
    (_h_env : input = eval env input_var)
    (_h_assumptions : Assumptions 1 input) :
    Circuit.ConstraintsHold.Completeness env ((main input_var).operations offset)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma completeness_two {p : ℕ} [Fact p.Prime]
    (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields 2) (F p))
    (input : fields 2 (F p))
    (h_local_witnesses : env.UsesLocalWitnessesCompleteness offset ((main input_var).operations offset))
    (h_env : input = eval env input_var)
    (h_assumptions : Assumptions 2 input) :
    Circuit.ConstraintsHold.Completeness env ((main input_var).operations offset)
end Circomlib.MultiAND

namespace Circomlib.MultiAND
theorem soundness {p : ℕ} [Fact p.Prime] (n : ℕ) :
    ∀ (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields n) (F p))
      (input : fields n (F p)),
    input = eval env input_var →
    Assumptions n input →
    Circuit.ConstraintsHold.Soundness env ((main input_var).operations offset) →
    Spec n input (env ((main input_var).output offset))
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma main_output_binary (n : ℕ) (offset : ℕ) (env : Environment (F p))
    (input_var : Var (fields n) (F p)) (input : fields n (F p))
    (h_eval : input = eval env input_var)
    (h_assumptions : Assumptions n input)
    (h_constraints : Circuit.ConstraintsHold env ((main input_var).operations offset)) :
    let output := env ((main input_var).output offset)
    IsBool output
end Circomlib.MultiAND

namespace Circomlib.MultiAND
lemma main_output_binary_from_completeness (n : ℕ) (offset : ℕ) (env : Environment (F p))
    (input_var : Var (fields n) (F p)) (input : fields n (F p))
    (h_eval : input = eval env input_var)
    (h_assumptions : Assumptions n input)
    (h_local_witnesses : env.UsesLocalWitnessesCompleteness offset ((main input_var).operations offset))
    (h_completeness : Circuit.ConstraintsHold.Completeness env ((main input_var).operations offset)) :
    let output := env ((main input_var).output offset)
    IsBool output
end Circomlib.MultiAND

namespace Circomlib.MultiAND
theorem completeness {p : ℕ} [Fact p.Prime] (n : ℕ) :
    ∀ (offset : ℕ) (env : Environment (F p)) (input_var : Var (fields n) (F p))
      (input : fields n (F p)),
    env.UsesLocalWitnessesCompleteness offset ((main input_var).operations offset) →
    input = eval env input_var →
    Assumptions n input →
    Circuit.ConstraintsHold.Completeness env ((main input_var).operations offset)
end Circomlib.MultiAND

namespace InductiveTable
theorem equalityConstraint.soundness {row : State F × Input F} {input_state : State F} {env : Environment F} :
  Circuit.ConstraintsHold.Soundness (windowEnv (equalityConstraint Input input_state) ⟨<+> +> row, rfl⟩ env)
    (equalityConstraint Input input_state .empty).2.circuit
    ↔ row.1 = input_state
end InductiveTable

namespace InductiveTable
omit [Field F] in
lemma traceInputs_length {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) :
    (traceInputs trace).length = N
end InductiveTable

namespace InductiveTable
lemma table_soundness_aux (table : InductiveTable F State Input) (input output : State F)
  (N : ℕ+) (trace : TraceOfLength F (ProvablePair State Input) N) (env : ℕ → ℕ → Environment F) :
  table.Spec input [] 0 rfl input →
  TableConstraintsHold (table.tableConstraints input output) trace env →
    trace.ForAllRowsWithPrevious (fun row i rest => table.Spec input (traceInputs rest) i (traceInputs_length rest) row.1)
    ∧ trace.lastRow.1 = output
end InductiveTable

namespace Tables.Fibonacci32Inductive
theorem tableStatement (output : Row (F p)) : ∀ n > 0, ∀ trace,
    (formalTable output).statement n trace → output.y.value = fib32 n
end Tables.Fibonacci32Inductive

namespace Gadgets.Rotation64.Theorems
lemma rotRight64_bytes_u64_eq (o : ℕ) (x : U64 ℕ) :
  rotRight64_bytes x.toLimbs o = (rotRight64_u64 x o).toLimbs
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_mod {o : ℕ} (ho : o < 8) {x0 x1 x2 x3 x4 x5 x6 x7 : ℕ} :
    (x0 + x1 * 256 + x2 * 256 ^ 2 + x3 * 256 ^ 3 + x4 * 256 ^ 4 + x5 * 256 ^ 5 + x6 * 256 ^ 6 + x7 * 256 ^ 7) %
      2^o = x0 % 2^o
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_div {o : ℕ} (ho : o < 8) {x0 x1 x2 x3 x4 x5 x6 x7 : ℕ} :
    (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3 + x4 * 256^4 + x5 * 256^5 + x6 * 256^6 + x7 * 256^7) / 2 ^ o
    = x0 / 2^o + x1 * 2^(8-o) + x2 * 256 * 2^(8-o) + x3 * 256^2 * 2^(8-o) + x4 * 256^3 * 2^(8-o) +
    x5 * 256^4 * 2^(8-o) + x6 * 256^5 * 2^(8-o) + x7 * 256^6 * 2^(8-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_x0_const {o : ℕ} (ho : o < 8) :
    2^(8-o) * 256^7 = 2^(64-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
theorem rotation64_bits_soundness {o : ℕ} (ho : o < 8) {x : U64 ℕ} :
    (rotRight64_u64 x o).valueNat = rotRight64 x.valueNat o
end Gadgets.Rotation64.Theorems

namespace Gadgets.Keccak256.ThetaC
lemma thetaC_loop (state : Vector ℕ 25) :
    Specs.Keccak256.thetaC state = .mapFinRange 5 fun i =>
      state[5*i.val] ^^^ state[5*i.val + 1] ^^^ state[5*i.val + 2] ^^^ state[5*i.val + 3] ^^^ state[5*i.val + 4]
end Gadgets.Keccak256.ThetaC

namespace Gadgets.Keccak256.ThetaC
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.ThetaC

namespace Gadgets
theorem allZero.soundness {offset : ℕ} {env : Environment F} {n} {xs : Vector (Expression F) n} :
    ConstraintsHold.Soundness env ((allZero xs).operations offset) → ∀ x ∈ xs, x.eval env = 0
end Gadgets

namespace Gadgets
theorem allZero.completeness {offset : ℕ} {env : Environment F} {n} {xs : Vector (Expression F) n} :
    (∀ x ∈ xs, x.eval env = 0) → ConstraintsHold.Completeness env ((allZero xs).operations offset)
end Gadgets

namespace Gadgets.Equality
@[circuit_norm ↓]
lemma elaborated_eq (α : TypeMap) [ProvableType α] : (circuit α (F:=F)).elaborated = elaborated α
end Gadgets.Equality

namespace Gadgets.Equality
@[circuit_norm]
theorem soundness (α : TypeMap) [ProvableType α] (n : ℕ) (env : Environment F) (x y : Var α F) :
    ((circuit α).toSubcircuit n (x, y)).Soundness env = (eval env x = eval env y)
end Gadgets.Equality

namespace Gadgets.Equality
@[circuit_norm]
theorem completeness (α : TypeMap) [ProvableType α] (n : ℕ) (env : Environment F) (x y : Var α F) :
    ((circuit α).toSubcircuit n (x, y)).Completeness env = (eval env x = eval env y)
end Gadgets.Equality

namespace Gadgets.Equality
@[circuit_norm]
theorem usesLocalWitnesses (α : TypeMap) [ProvableType α] (n : ℕ) (env : Environment F) (x y : Var α F) :
    ((circuit α).toSubcircuit n (x, y)).UsesLocalWitnesses env = True
end Gadgets.Equality

namespace IsBool
@[circuit_norm]
theorem zero {α : Type*} [Zero α] [One α] : IsBool (0 : α)
end IsBool

namespace IsBool
@[circuit_norm]
theorem one {α : Type*} [Zero α] [One α] : IsBool (1 : α)
end IsBool

namespace IsBool
theorem lt_two {α : Type*} [Zero α] [One α] [Preorder α] [OfNat α 2]
    {x : α} (h : IsBool x) (h0 : (0 : α) < 2) (h1 : (1 : α) < 2) : x < 2
end IsBool

namespace IsBool
theorem iff_mul_sub_one {α : Type*} [Ring α] [NoZeroDivisors α] {x : α} :
    IsBool x ↔ x * (x - 1) = 0
end IsBool

namespace IsBool
theorem nat_of_lt_two {x : ℕ} (h : x < 2) : IsBool x
end IsBool

namespace IsBool
theorem land_inherit_left (l r : ℕ) (h : IsBool l) : IsBool (l &&& r)
end IsBool

namespace IsBool
theorem val_lt_two {p : ℕ} [Fact p.Prime] {x : F p} (h : IsBool x) : x.val < 2
end IsBool

namespace IsBool
theorem and_is_bool {α : Type*} [MulZeroOneClass α] {x y : α} (hx : IsBool x) (hy : IsBool y) :
    IsBool (x * y)
end IsBool

namespace IsBool
theorem or_is_bool {α : Type*} [Ring α] {x y : α} (hx : IsBool x) (hy : IsBool y) :
    IsBool (x - x*y + y)
end IsBool

namespace IsBool
theorem not_is_bool {α : Type*} [Ring α] {x : α} (hx : IsBool x) :
    IsBool (1 + x - 2*x)
end IsBool

namespace IsBool
theorem xor_is_bool {α : Type*} [Ring α] {x y : α} (hx : IsBool x) (hy : IsBool y) :
    IsBool (x + y - 2*x*y)
end IsBool

namespace IsBool
theorem nand_is_bool {α : Type*} [Ring α] {x y : α} (hx : IsBool x) (hy : IsBool y) :
    IsBool (1 - x * y)
end IsBool

namespace IsBool
theorem nor_is_bool {α : Type*} [Ring α] {x y : α} (hx : IsBool x) (hy : IsBool y) :
    IsBool (x * y + 1 - x - y)
end IsBool

namespace IsBool
theorem land_one_of_IsBool (a : ℕ) (h : IsBool a) : a &&& 1 = a
end IsBool

namespace IsBool
theorem one_land_of_IsBool (a : ℕ) (h : IsBool a) : 1 &&& a = a
end IsBool

namespace IsBool
theorem val_of_IsBool {p : ℕ} [Fact p.Prime] {x : F p} (h : IsBool x) : IsBool x.val
end IsBool

namespace IsBool
theorem xor_eq_val_xor {a b : F p} (ha : IsBool a) (hb : IsBool b) :
    (a + b - 2*a*b).val = a.val ^^^ b.val
end IsBool

namespace IsBool
theorem and_eq_val_and {a b : F p} (ha : IsBool a) (hb : IsBool b) :
    (a * b).val = a.val &&& b.val
end IsBool

namespace IsBool
theorem or_eq_val_or {a b : F p} (ha : IsBool a) (hb : IsBool b) :
    (a - a * b + b).val = a.val ||
end IsBool

namespace IsBool
theorem nand_eq_val_nand {a b : F p} (ha : IsBool a) (hb : IsBool b) :
    (1 - a * b).val = 1 - (a.val &&& b.val)
end IsBool

namespace IsBool
theorem nor_eq_val_nor {a b : F p} (ha : IsBool a) (hb : IsBool b) :
    (a * b + 1 - a - b).val = 1 - (a.val ||| b.val)
end IsBool

namespace IsBool
theorem not_eq_val_not {a : F p} (ha : IsBool a) :
    (1 - 2*a + a).val = 1 - a.val
end IsBool

namespace Gadgets.Keccak256
omit [Fact (Nat.Prime p)] p_large_enough in
lemma KeccakState.normalized_value_ext (state : KeccakState (F p)) (rhs : Vector ℕ 25) :
  (∀ i : Fin 25, state[i.val].Normalized ∧ state[i.val].value = rhs[i.val]) →
    state.Normalized ∧ state.value = rhs
end Gadgets.Keccak256

namespace Gadgets.Keccak256
omit [Fact (Nat.Prime p)] p_large_enough in
lemma KeccakRow.normalized_value_ext (row : KeccakRow (F p)) (rhs : Vector ℕ 5) :
  (∀ i : Fin 5, row[i.val].Normalized ∧ row[i.val].value = rhs[i.val]) →
    row.Normalized ∧ row.value = rhs
end Gadgets.Keccak256

lemma rotLeft64_eq_rotRight64 (x : ℕ) (offset : Fin 64) :
    rotLeft64 x offset = rotRight64 x (-offset).val

theorem eq_of_mod_eq_and_div_eq (m : ℕ) {x y : ℕ} (mod : x % m = y % m) (div : x / m = y / m) : x = y

theorem xor_eq_add {x : ℕ} (n : ℕ) (hx : x < 2^n) (y : ℕ) : x + 2^n * y = x ^^^ 2^n * y

theorem and_mul_two_pow {x y n : ℕ} : 2 ^ n * (x &&& y) =  2 ^ n * x &&&  2 ^ n * y

theorem xor_mul_two_pow {x y n : ℕ} : 2 ^ n * (x ^^^ y) =  2 ^ n * x ^^^  2 ^ n * y

lemma and_mul_two_pow_lt {n : ℕ} {x : ℕ} (hx : x < 2^n) (y : ℕ) : x &&& 2^n * y = 0

lemma and_xor_sum (x0 x1 y0 y1 : ℕ) (hx0 : x0 < 2^8) (hy0 : y0 < 2^8) :
  (x0 ^^^ (2^8 * x1)) &&& (y0 ^^^ (2^8 * y1)) = (x0 &&& y0) ^^^ 2^8 * (x1 &&& y1)

theorem not64_eq_sub {x : ℕ} (x_lt : x < 2^64) :
    not64 x = 2^64 - 1 - x

theorem and_zero_absorb (a : ℕ) :
    0 &&& a = 0

theorem and_one_id_binary (a : ℕ) (ha : a = 0 ∨ a = 1) :
    1 &&& a = a

namespace Operations
@[circuit_norm]
theorem append_localLength {a b: Operations F} :
    (a ++ b).localLength = a.localLength + b.localLength
end Operations

namespace Operations
theorem localLength_cons {a : Operation F} {as : Operations F} :
    localLength (a :: as) = a.localLength + as.localLength
end Operations

namespace Operations
theorem localWitnesses_cons (op : Operation F) (ops : Operations F) (env : Environment F) :
  localWitnesses env (op :: ops) =
    (op.localWitnesses env ++ ops.localWitnesses env).cast (localLength_cons.symm)
end Operations

namespace Operations
@[circuit_norm]
theorem forAll_empty {condition : Condition F} {n : ℕ} : forAll n condition [] = True
end Operations

namespace Operations
@[circuit_norm]
theorem forAll_cons {condition : Condition F} {offset : ℕ} {op : Operation F} {ops : Operations F} :
  forAll offset condition (op :: ops) ↔
    condition.apply offset op ∧ forAll (op.localLength + offset) condition ops
end Operations

namespace Operations
@[circuit_norm]
theorem forAll_append {condition : Condition F} {offset : ℕ} {as bs: Operations F} :
  forAll offset condition (as ++ bs) ↔
    forAll offset condition as ∧ forAll (as.localLength + offset) condition bs
end Operations

namespace Circuit
theorem pure_operations_eq (a : α) (n : ℕ) :
  (pure a : Circuit F α).operations n = []
end Circuit

namespace Circuit
theorem bind_operations_eq (f : Circuit F α) (g : α → Circuit F β) (n : ℕ) :
  (f >>= g).operations n = f.operations n ++ (g (f.output n)).operations (n + f.localLength n)
end Circuit

namespace Circuit
theorem map_operations_eq (f : Circuit F α) (g : α → β) (n : ℕ) :
  (g <$> f).operations n = f.operations n
end Circuit

namespace Circuit
theorem pure_localLength_eq (a : α) (n : ℕ) :
  (pure a : Circuit F α).localLength n = 0
end Circuit

namespace Circuit
theorem bind_localLength_eq (f : Circuit F α) (g : α → Circuit F β) (n : ℕ) :
    (f >>= g).localLength n = f.localLength n + (g (f.output n)).localLength (n + f.localLength n)
end Circuit

namespace Circuit
theorem map_localLength_eq (f : Circuit F α) (g : α → β) (n : ℕ) :
  (g <$> f).localLength n = f.localLength n
end Circuit

namespace Circuit
theorem pure_output_eq (a : α) (n : ℕ) :
  (pure a : Circuit F α).output n = a
end Circuit

namespace Circuit
theorem bind_output_eq (f : Circuit F α) (g : α → Circuit F β) (n : ℕ) :
  (f >>= g).output n = (g (f.output n)).output (n + f.localLength n)
end Circuit

namespace Circuit
theorem map_output_eq (f : Circuit F α) (g : α → β) (n : ℕ) :
  (g <$> f).output n = g (f.output n)
end Circuit

namespace Circuit
theorem ext_iff {f g : Circuit F α} :
  (f = g) ↔ (∀ n, (f.output n = g.output n) ∧ (f.operations n = g.operations n))
end Circuit

namespace Circuit
@[ext]
theorem ext {f g : Circuit F α}
  (h_output : ∀ n, f.output n = g.output n)
  (h_operations : ∀ n, f.operations n = g.operations n) :
    f = g
end Circuit

namespace Circuit
theorem can_replace_soundness {ops : Operations F} {env} :
  ConstraintsHold env ops → ConstraintsHold.Soundness env ops
end Circuit

namespace FlatOperation
lemma localLength_cons {F} {op : FlatOperation F} {ops : List (FlatOperation F)} :
    localLength (op :: ops) = op.singleLocalLength + localLength ops
end FlatOperation

namespace FlatOperation
lemma localLength_append {F} {a b: List (FlatOperation F)} :
    localLength (a ++ b) = localLength a + localLength b
end FlatOperation

namespace FlatOperation
theorem forAll_empty {condition : Condition F} {n : ℕ} : forAll n condition [] = True
end FlatOperation

namespace FlatOperation
theorem forAll_cons {condition : Condition F} {offset : ℕ} {op : FlatOperation F} {ops : List (FlatOperation F)} :
  forAll offset condition (op :: ops) ↔
    condition.applyFlat offset op ∧ forAll (op.singleLocalLength + offset) condition ops
end FlatOperation

namespace FlatOperation
lemma forAll_append {condition : Condition F} {ops ops' : List (FlatOperation F)} (n : ℕ) :
  forAll n condition (ops ++ ops') ↔
    forAll n condition ops ∧ forAll (localLength ops + n) condition ops'
end FlatOperation

namespace FlatOperation
lemma localWitnesses_append {F} {a b: List (FlatOperation F)} {env} :
    (localWitnesses env (a ++ b)).toArray = (localWitnesses env a).toArray ++ (localWitnesses env b).toArray
end FlatOperation

namespace FlatOperation
lemma localLength_toFlat {ops : Operations F} :
    localLength ops.toFlat = ops.localLength
end FlatOperation

namespace FlatOperation
lemma localWitnesses_toFlat {ops : Operations F} {env} :
  (localWitnesses env ops.toFlat).toArray = (ops.localWitnesses env).toArray
end FlatOperation

namespace Environment
lemma env_extends_witness {F} {n : ℕ} {ops : List (FlatOperation F)} {env : Environment F} {m c} :
    env.ExtendsVector (localWitnesses env (.witness m c :: ops)) n ↔
      (env.ExtendsVector (c env) n ∧ env.ExtendsVector (localWitnesses env ops) (m + n))
end Environment

namespace Environment
theorem usesLocalWitnessesFlat_iff_extends {env : Environment F} (n : ℕ) {ops : List (FlatOperation F)}  :
    env.UsesLocalWitnessesFlat n ops ↔ env.ExtendsVector (localWitnesses env ops) n
end Environment

namespace Environment
theorem can_replace_usesLocalWitnessesCompleteness {env : Environment F} {ops : Operations F} {n : ℕ} (h : ops.SubcircuitsConsistent n) :
  env.UsesLocalWitnesses n ops → env.UsesLocalWitnessesCompleteness n ops
end Environment

namespace Environment
theorem usesLocalWitnessesCompleteness_iff_forAll (n : ℕ) {env : Environment F} {ops : Operations F} :
  env.UsesLocalWitnessesCompleteness n ops ↔ ops.forAll n {
    witness m _ c := env.ExtendsVector (c env) m,
    subcircuit _ _ s := s.UsesLocalWitnesses env
  }
end Environment

namespace Environment
theorem usesLocalWitnesses_iff_forAll (n : ℕ) {env : Environment F} {ops : Operations F} :
  env.UsesLocalWitnesses n ops ↔ ops.forAll n {
    witness n _ c := env.ExtendsVector (c env) n,
    subcircuit n _ s := FlatOperation.forAll n { witness n _ c := env.ExtendsVector (c env) n} s.ops
  }
end Environment

namespace Circuit
theorem ConstraintsHold.soundness_iff_forAll (n : ℕ) (env : Environment F) (ops : Operations F) :
  ConstraintsHold.Soundness env ops ↔ ops.forAll n {
    assert _ e := env e = 0,
    lookup _ l := l.table.Soundness (l.entry.map env),
    subcircuit _ _ s := s.Soundness env
  }
end Circuit

namespace Circuit
theorem ConstraintsHold.completeness_iff_forAll (n : ℕ) (env : Environment F) (ops : Operations F) :
  ConstraintsHold.Completeness env ops ↔ ops.forAll n {
    assert _ e := env e = 0,
    lookup _ l := l.table.Completeness (l.entry.map env),
    subcircuit _ _ s := s.Completeness env
  }
end Circuit

namespace Circuit
theorem can_replace_completeness {env} {ops : Operations F} {n : ℕ} (h : ops.SubcircuitsConsistent n) : env.UsesLocalWitnesses n ops →
    ConstraintsHold.Completeness env ops → ConstraintsHold env ops
end Circuit

namespace Circuit
@[circuit_norm]
theorem bind_forAll {f : Circuit F α} {g : α → Circuit F β} :
  ((f >>= g).operations n).forAll n prop ↔
    (f.operations n).forAll n prop ∧ (((g (f.output n)).operations (n + f.localLength n)).forAll (n + f.localLength n)) prop
end Circuit

namespace Circuit
lemma forAll_def {circuit : Circuit F α} {n : ℕ} :
  circuit.forAll n prop ↔ (circuit.operations n).forAll n prop
end Circuit

namespace Circuit
theorem bind_forAll' {f : Circuit F α} {g : α → Circuit F β} :
  (f >>= g).forAll n prop ↔
    f.forAll n prop ∧ ((g (f.output n)).forAll (n + f.localLength n) prop)
end Circuit

namespace Circuit
theorem ConstraintsHold.soundness_iff_forAll' {env : Environment F} {circuit : Circuit F α} {n : ℕ} :
  ConstraintsHold.Soundness env (circuit.operations n) ↔ circuit.forAll n {
    assert _ e := env e = 0,
    lookup _ l := l.table.Soundness (l.entry.map env),
    subcircuit _ _ s := s.Soundness env
  }
end Circuit

namespace Circuit
theorem ConstraintsHold.completeness_iff_forAll' {env : Environment F} {circuit : Circuit F α} {n : ℕ} :
  ConstraintsHold.Completeness env (circuit.operations n) ↔ circuit.forAll n {
    assert _ e := env e = 0,
    lookup _ l := l.table.Completeness (l.entry.map env),
    subcircuit _ _ s := s.Completeness env
  }
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.append_soundness {as bs : Operations F} :
  ConstraintsHold.Soundness env (as ++ bs)
  ↔ ConstraintsHold.Soundness env as ∧ ConstraintsHold.Soundness env bs
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.bind_soundness {f : Circuit F α} {g : α → Circuit F β} (n : ℕ) :
  ConstraintsHold.Soundness env ((f >>= g).operations n)
  ↔ ConstraintsHold.Soundness env (f.operations n) ∧
    ConstraintsHold.Soundness env ((g (f.output n)).operations (n + f.localLength n))
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.append_completeness {as bs : Operations F} :
  ConstraintsHold.Completeness env (as ++ bs)
  ↔ ConstraintsHold.Completeness env as ∧ ConstraintsHold.Completeness env bs
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.bind_completeness {f : Circuit F α} {g : α → Circuit F β} (n : ℕ) :
  ConstraintsHold.Completeness env ((f >>= g).operations n)
  ↔ ConstraintsHold.Completeness env (f.operations n) ∧
    ConstraintsHold.Completeness env ((g (f.output n)).operations (n + f.localLength n))
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.append_localWitnesses {as bs : Operations F} (n : ℕ) :
  env.UsesLocalWitnessesCompleteness n (as ++ bs)
  ↔ env.UsesLocalWitnessesCompleteness n as ∧ env.UsesLocalWitnessesCompleteness (as.localLength + n) bs
end Circuit

namespace Circuit
@[circuit_norm] theorem ConstraintsHold.bind_usesLocalWitnesses {f : Circuit F α} {g : α → Circuit F β} (n : ℕ) :
  env.UsesLocalWitnessesCompleteness n ((f >>= g).operations n)
  ↔ env.UsesLocalWitnessesCompleteness n (f.operations n) ∧
    env.UsesLocalWitnessesCompleteness (n + f.localLength n) ((g (f.output n)).operations (n + f.localLength n))
end Circuit

namespace FlatOperation
theorem forAll_implies {c c' : Condition F} (n : ℕ) {ops : List (FlatOperation F)} :
    (forAll n (c.implies c').ignoreSubcircuit ops) → (forAll n c ops → forAll n c' ops)
end FlatOperation

namespace Operations
lemma forAll_toFlat_iff (n : ℕ) (condition : Condition F) (ops : Operations F) :
    FlatOperation.forAll n condition ops.toFlat ↔ ops.forAllFlat n condition
end Operations

lemma Environment.usesLocalWitnesses_iff_flat {n : ℕ} {ops : Operations F} {env : Environment F} :
    env.UsesLocalWitnesses n ops ↔
    env.UsesLocalWitnessesFlat n ops.toFlat

namespace FlatOperation
lemma dynamicWitness_length {op : FlatOperation F} {init : List F} :
    (op.dynamicWitness init).length = op.singleLocalLength
end FlatOperation

namespace FlatOperation
lemma dynamicWitnesses_length {ops : List (FlatOperation F)} (init : List F) :
    (dynamicWitnesses ops init).length = init.length + localLength ops
end FlatOperation

namespace FlatOperation
lemma dynamicWitnesses_cons {op : FlatOperation F} {ops : List (FlatOperation F)} {acc : List F} :
    dynamicWitnesses (op :: ops) acc = dynamicWitnesses ops (acc ++ op.dynamicWitness acc)
end FlatOperation

namespace FlatOperation
lemma getElem?_dynamicWitnesses_of_lt {ops : List (FlatOperation F)} {acc : List F} {i : ℕ} (hi : i < acc.length) :
    (dynamicWitnesses ops acc)[i]?.getD 0 = acc[i]
end FlatOperation

namespace FlatOperation
lemma getElem?_dynamicWitnesses_cons_right {op : FlatOperation F} {ops : List (FlatOperation F)} {init : List F} {i : ℕ} (hi : i < op.singleLocalLength) :
    (dynamicWitnesses (op :: ops) init)[init.length + i]?.getD 0 = (op.dynamicWitness init)[i]'(dynamicWitness_length (F:=F) ▸ hi)
end FlatOperation

namespace FlatOperation
theorem proverEnvironment_usesLocalWitnesses {ops : List (FlatOperation F)} (init : List F) :
  (∀ (env env' : Environment F),
    forAll init.length { witness n _ c := env.AgreesBelow n env' → c env = c env' } ops) →
    (proverEnvironment ops init).UsesLocalWitnessesFlat init.length ops
end FlatOperation

theorem Circuit.proverEnvironment_usesLocalWitnesses (circuit : Circuit F α) (init : List F) :
  circuit.ComputableWitnesses init.length →
    (circuit.proverEnvironment init).UsesLocalWitnesses init.length (circuit.operations init.length)

lemma Environment.agreesBelow_of_le {F} {n m : ℕ} {env env' : Environment F} :
    env.AgreesBelow n env' → m ≤ n → env.AgreesBelow m env'

namespace FlatOperation
theorem onlyAccessedBelow_all {ops : List (FlatOperation F)} (n : ℕ) :
  forAll n { witness n _ := Environment.OnlyAccessedBelow n } ops →
    Environment.OnlyAccessedBelow (n + localLength ops) (localWitnesses · ops)
end FlatOperation

namespace Tables.Fibonacci8Table
lemma fib8_less_than_256 (n : ℕ) : fib8 n < 256
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
omit p_large_enough in
lemma boundaryFib_eq : boundaryFib (p:=p) = (do
    assign (.curr 0) 0
    assign (.curr 1) 1)
end Tables.Fibonacci8Table

namespace Tables.Fibonacci8Table
omit p_large_enough in
lemma boundary_step (first_row : Row (F p) RowType) (aux_env : Environment (F p)) :
  Circuit.ConstraintsHold.Soundness (boundaryFib.windowEnv ⟨<+> +> first_row, rfl⟩ aux_env) boundaryFib.operations
    → ZMod.val first_row.x = fib8 0 ∧ ZMod.val first_row.y = fib8 1
end Tables.Fibonacci8Table

lemma Vector.forM_toList (xs : Vector α n) {m : Type → Type} [Monad m] (body : α → m Unit) :
    xs.forM body = forM xs.toList body

lemma Vector.toList_mapM (xs : Vector α n) {m : Type → Type} [monad: Monad m] [LawfulMonad m] (body : α → m β) :
    Vector.toList <$> (xs.mapM body) = xs.toList.mapM body

lemma Vector.foldlM_toList (xs : Vector α n) {m : Type → Type} [Monad m] (body : β → α → m β) (init : β) :
    xs.foldlM body init = xs.toList.foldlM body init

namespace Circuit
lemma ConstantLength.length_eq_default {circuit : α → Circuit F β} (_ : ConstantLength circuit) [Inhabited α] (a : α) (n : ℕ) :
   (circuit a).localLength n = (circuit default).localLength 0
end Circuit

namespace Circuit
lemma forAll_flatten_abstract (circuit : Fin m → Circuit F β) (constant : ConstantLength circuit) :
  Operations.forAll n prop (List.ofFn fun i => (circuit i).operations (n + i * constant.localLength)).flatten
    ↔ ∀ (i : Fin m), (circuit i).forAll (n + i * constant.localLength) prop
end Circuit

namespace Circuit
lemma forAll_flatten (xs : Vector α m) {circuit : α → Circuit F β} (constant : ConstantLength circuit) :
  Operations.forAll n prop (List.ofFn fun (i : Fin m) => (circuit xs[i.val]).operations (n + i * constant.localLength)).flatten
    ↔ ∀ (i : Fin m), (circuit xs[i.val]).forAll (n + i * constant.localLength) prop
end Circuit

namespace Circuit
private lemma ofFn_flatten_cons {circuit : α → Circuit F β} (constant : ConstantLength circuit) (x : α) (xs : Vector α m) (n : ℕ) :
  (List.ofFn fun i => (circuit (Vector.cons x xs)[i.val]).operations (n + i * constant.localLength)).flatten
    = (circuit x).operations n ++ (List.ofFn fun i => (circuit xs[i.val]).operations (n + constant.localLength + i * constant.localLength)).flatten
end Circuit

namespace Circuit.ForM
theorem localLength_eq : (xs.forM circuit).localLength n = m * constant.localLength
end Circuit.ForM

namespace Circuit.ForM
theorem output_eq : (xs.forM circuit).output n = ()
end Circuit.ForM

namespace Circuit.ForM
theorem operations_eq :
  (xs.forM circuit).operations n =
    (List.ofFn fun (i : Fin m) => (circuit xs[i.val]).operations (n + i * constant.localLength)).flatten
end Circuit.ForM

namespace Circuit.ForM
theorem forAll_iff {prop : Condition F} :
  (xs.forM circuit).forAll n prop ↔
    ∀ (i : Fin m), (circuit xs[i.val]).forAll (n + i * constant.localLength) prop
end Circuit.ForM

namespace Circuit.MapM
theorem localLength_eq : (xs.mapM circuit).localLength n = m * constant.localLength
end Circuit.MapM

namespace Circuit.MapM
theorem output_eq : (xs.mapM circuit).output n =
    xs.mapIdx fun i x => (circuit x).output (n + i * constant.localLength)
end Circuit.MapM

namespace Circuit.MapM
lemma ext_map_toList (f g : Circuit F (Vector α n)) :
    (fun v => v.toList) <$> f = (fun v => v.toList) <$> g → f = g
end Circuit.MapM

namespace Circuit.MapM
lemma mapM_cons (xs : Vector α n) (body : α → Circuit F β) (x : α) :
  (Vector.cons x xs).mapM body = do
    let y ← body x
    let ys ← xs.mapM body
    return Vector.cons y ys
end Circuit.MapM

namespace Circuit.MapM
theorem operations_eq : (xs.mapM circuit).operations n =
    (List.ofFn fun (i : Fin m) => (circuit xs[i.val]).operations (n + i * constant.localLength)).flatten
end Circuit.MapM

namespace Circuit.MapM
theorem forAll_iff :
  (xs.mapM circuit).forAll n prop ↔
    ∀ (i : Fin m), (circuit xs[i.val]).forAll (n + i * constant.localLength) prop
end Circuit.MapM

namespace Circuit.MapM
theorem mapFinRangeM_forAll_iff {circuit : Fin m → Circuit F β} [constant : ConstantLength circuit] :
  (Vector.mapFinRangeM m circuit).forAll n prop ↔
    ∀ i : Fin m, (circuit i).forAll (n + i*constant.localLength) prop
end Circuit.MapM

namespace Circuit.FoldlM
lemma foldlM_cons (x : α) :
  (Vector.cons x xs).foldlM circuit init = (do
    let init' ← circuit init x
    xs.foldlM circuit init')
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem localLength_eq :
    (xs.foldlM circuit init).localLength n = m * constant.localLength
end Circuit.FoldlM

namespace Circuit.FoldlM
lemma finFoldl_cons_succ (x : α) :
  Fin.foldl (m + 1) (fun acc i => (circuit acc (Vector.cons x xs)[i.val]).output (n + i * constant.localLength)) init
    = Fin.foldl m (fun acc i => (circuit acc xs[i.val]).output (n + constant.localLength + i * constant.localLength)) ((circuit init x).output n)
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem output_eq :
  (xs.foldlM circuit init).output n =
    Fin.foldl m (fun acc i => (circuit acc xs[i.val]).output (n + i * constant.localLength)) init
end Circuit.FoldlM

namespace Circuit.FoldlM
lemma foldlAcc_zero [NeZero m] : foldlAcc n xs circuit init 0 = init
end Circuit.FoldlM

namespace Circuit.FoldlM
lemma foldlAcc_cons_succ (i : Fin m) (x : α) [constant : ConstantLength (prod circuit)] :
  foldlAcc n (Vector.cons x xs) circuit init i.succ =
    foldlAcc (n + (circuit init x).localLength n) xs circuit ((circuit init x).output n) i
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem operations_eq :
  (Vector.foldlM circuit init xs).operations n =
    (List.ofFn fun i => (circuit (foldlAcc n xs circuit init i) xs[i.val]).operations (n + i * constant.localLength)).flatten
end Circuit.FoldlM

namespace Circuit.FoldlM
lemma forAll_flatten_foldl :
  Operations.forAll n prop (List.ofFn fun (i : Fin m) => (circuit (foldlAcc n xs circuit init i) xs[i.val]).operations (n + i * constant.localLength)).flatten
    ↔ ∀ (i : Fin m), (circuit (foldlAcc n xs circuit init i) xs[i.val]).forAll (n + i * constant.localLength) prop
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem forAll_iff {constant : ConstantLength (prod circuit)} :
  (xs.foldlM circuit init).forAll n prop ↔
    ∀ i : Fin m, (circuit (foldlAcc n xs circuit init i) xs[i.val]).forAll (n + i * (circuit init xs[i.val]).localLength) prop
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem forAll_iff_finRange {constant : ConstantLength (prod circuit)} :
  ((Vector.finRange m).foldlM circuit init).forAll n prop ↔
    ∀ i : Fin m, (circuit (foldlAcc n (Vector.finRange m) circuit init i) i)
    |>.forAll (n + i * (circuit init i).localLength) prop
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem foldlAcc_const_succ (constant : ConstantLength (prod circuit))
  (h_const_out : ConstantOutput fun (t : β × α) => circuit t.1 t.2)
  (i : ℕ) (hi : i + 1 < m) :
  foldlAcc n xs circuit init ⟨ i + 1, hi ⟩ =
    (circuit default xs[i]).output (n + i * (circuit default default).localLength)
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem foldlAcc_const (constant : ConstantLength (prod circuit))
  (h_const_out : ConstantOutput fun (t : β × α) => circuit t.1 t.2)
  (i : ℕ) (hi : i < m) :
  foldlAcc n xs circuit init ⟨ i, hi ⟩ = match i with
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem operations_eq_const [NeZero m] (constant : ConstantLength (prod circuit))
    (h_const_out : ConstantOutput (prod circuit)) :
  (Vector.foldlM circuit init xs).operations n =
  (circuit init (xs[0]'(NeZero.pos m))).operations n ++
  (List.ofFn fun (⟨i, _⟩ : Fin (m - 1)) =>
    let k := (circuit default default).localLength
    let acc := (circuit default xs[i]).output (n + i*k)
    (circuit acc xs[i + 1]).operations (n + (i + 1)*k)).flatten
end Circuit.FoldlM

namespace Circuit.FoldlM
theorem forAll_iff_const [NeZero m] (constant : ConstantLength (prod circuit))
    (h_const_out : ConstantOutput (prod circuit)) :
  (xs.foldlM circuit init).forAll n prop ↔
  (circuit init (xs[0]'(NeZero.pos m))).forAll n prop ∧
  ∀ (i : ℕ) (hi : i + 1 < m),
    let acc := (circuit default xs[i]).output (n + i*(circuit default default).localLength);
    (circuit acc xs[i + 1]).forAll (n + (i + 1)*(circuit default default).localLength) prop
end Circuit.FoldlM

namespace Circuit
@[circuit_norm ↓]
lemma forEach.localLength_eq :
    (forEach xs body constant).localLength n = m * (body default).localLength
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma forEach.output_eq :
  (forEach xs body constant).output n = ()
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma forEach.forAll :
  Operations.forAll n prop ((forEach xs body constant).operations n) ↔
    ∀ i : Fin m, (body xs[i.val] |>.forAll (n + i*(body default).localLength) prop)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma forEach.soundness :
  ConstraintsHold.Soundness env ((forEach xs body constant).operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Soundness env (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
lemma forEach.soundness' :
  ConstraintsHold.Soundness env (forEach xs body constant |>.operations n) →
    ∀ x ∈ xs, ∃ k : ℕ, ConstraintsHold.Soundness env (body x |>.operations k)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma forEach.completeness :
  ConstraintsHold.Completeness env ((forEach xs body constant).operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Completeness env (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma forEach.usesLocalWitnesses :
  env.UsesLocalWitnessesCompleteness n ((forEach xs body constant).operations n) ↔
    ∀ i : Fin m, env.UsesLocalWitnessesCompleteness (n + i*(body default).localLength) (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.localLength_eq :
    (map xs body constant).localLength n = m * (body default).localLength
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.output_eq :
  (map xs body constant).output n =
    xs.mapIdx fun i x => (body x).output (n + i*(body default).localLength)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.forAll :
  Operations.forAll n prop (map xs body constant |>.operations n) ↔
    ∀ i : Fin m, (body xs[i.val] |>.forAll (n + i*(body default).localLength) prop)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.soundness :
  ConstraintsHold.Soundness env (map xs body constant |>.operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Soundness env (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.completeness :
  ConstraintsHold.Completeness env (map xs body constant |>.operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Completeness env (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma map.usesLocalWitnesses :
  env.UsesLocalWitnessesCompleteness n (map xs body constant |>.operations n) ↔
    ∀ i : Fin m, env.UsesLocalWitnessesCompleteness (n + i*(body default).localLength) (body xs[i.val] |>.operations (n + i*(body default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.localLength_eq :
    (mapFinRange m body constant).localLength n = m * (body 0).localLength
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.output_eq :
  (mapFinRange m body constant).output n =
    Vector.mapFinRange m fun i => (body i).output (n + i*(body 0).localLength)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.forAll :
  Operations.forAll n prop (mapFinRange m body constant |>.operations n) ↔
    ∀ i : Fin m, (body i |>.forAll (n + i*(body 0).localLength) prop)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.soundness :
  ConstraintsHold.Soundness env (mapFinRange m body constant |>.operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Soundness env (body i |>.operations (n + i*(body 0).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.completeness :
  ConstraintsHold.Completeness env (mapFinRange m body constant |>.operations n) ↔
    ∀ i : Fin m, ConstraintsHold.Completeness env (body i |>.operations (n + i*(body 0).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma mapFinRange.usesLocalWitnesses :
  env.UsesLocalWitnessesCompleteness n (mapFinRange m body constant |>.operations n) ↔
    ∀ i : Fin m, env.UsesLocalWitnessesCompleteness (n + i*(body 0).localLength) (body i |>.operations (n + i*(body 0).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.localLength_eq :
    (foldl xs init body const_out constant).localLength n = m * (body default default).localLength
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.output_eq [NeZero m] :
  (foldl xs init body const_out constant).output n =
    (body default (xs[m-1]'(Nat.pred_lt (NeZero.ne m)))).output (n + (m-1)*(body default default).localLength)
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.forAll [NeZero m] :
  Operations.forAll n prop (foldl xs init body const_out constant |>.operations n) ↔
    (body init (xs[0]'(NeZero.pos m)) |>.forAll n prop) ∧
    ∀ (i : ℕ) (hi : i + 1 < m),
      let k := (body default default).localLength;
      let acc := (body default xs[i]).output (n + i*k);
      (body acc xs[i + 1]).forAll (n + (i + 1)*k) prop
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.soundness [NeZero m] :
  ConstraintsHold.Soundness env (foldl xs init body const_out constant |>.operations n) ↔
    ConstraintsHold.Soundness env (body init (xs[0]'(NeZero.pos m)) |>.operations n) ∧
    ∀ (i : ℕ) (hi : i + 1 < m),
      let acc := (body default xs[i]).output (n + i*(body default default).localLength);
      ConstraintsHold.Soundness env (body acc xs[i + 1] |>.operations (n + (i + 1)*(body default default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.completeness [NeZero m] :
  ConstraintsHold.Completeness env (foldl xs init body const_out constant |>.operations n) ↔
    ConstraintsHold.Completeness env (body init (xs[0]'(NeZero.pos m)) |>.operations n) ∧
    ∀ (i : ℕ) (hi : i + 1 < m),
      let acc := (body default xs[i]).output (n + i*(body default default).localLength);
      ConstraintsHold.Completeness env (body acc xs[i + 1] |>.operations (n + (i + 1)*(body default default).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldl.usesLocalWitnesses [NeZero m] :
  env.UsesLocalWitnessesCompleteness n (foldl xs init body const_out constant |>.operations n) ↔
    env.UsesLocalWitnessesCompleteness n (body init (xs[0]'(NeZero.pos m)) |>.operations n) ∧
    ∀ (i : ℕ) (hi : i + 1 < m),
      let k := (body default default).localLength;
      let acc := (body default xs[i]).output (n + i*k);
      env.UsesLocalWitnessesCompleteness (n + (i + 1)*k) (body acc xs[i + 1] |>.operations (n + (i + 1)*k))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.localLength_eq :
    (foldlRange m init body constant).localLength n =
      if h : m > 0 then m * (body default ⟨0, h⟩).localLength else 0
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.output_eq :
  (foldlRange m init body constant).output n =
    Fin.foldl m (fun acc i => (body acc i).output (n + i*(body default i).localLength)) init
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.forAll :
  Operations.forAll n prop (foldlRange m init body constant |>.operations n) ↔
    ∀ i : Fin m,
      body (FoldlM.foldlAcc n (Vector.finRange m) body init i) i
      |>.forAll (n + i * (body default i).localLength) prop
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.soundness :
  ConstraintsHold.Soundness env (foldlRange m init body constant |>.operations n) ↔
    ∀ i : Fin m,
    ConstraintsHold.Soundness env (body (FoldlM.foldlAcc n (Vector.finRange m) body init i) i
    |>.operations (n + i * (body default i).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.completeness :
  ConstraintsHold.Completeness env (foldlRange m init body constant |>.operations n) ↔
    ∀ i : Fin m,
    ConstraintsHold.Completeness env (body (FoldlM.foldlAcc n (Vector.finRange m) body init i) i
    |>.operations (n + i * (body default i).localLength))
end Circuit

namespace Circuit
@[circuit_norm ↓]
lemma foldlRange.usesLocalWitnesses :
  env.UsesLocalWitnessesCompleteness n (foldlRange m init body constant |>.operations n) ↔
    ∀ i : Fin m,
      env.UsesLocalWitnessesCompleteness (n + i * (body default i).localLength) (body (FoldlM.foldlAcc n (Vector.finRange m) body init i) i
      |>.operations (n + i * (body default i).localLength))
end Circuit

namespace Gadgets.Keccak256.ThetaD
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.ThetaD

namespace Gadgets.IsZeroField
theorem soundness : Soundness F elaborated Assumptions (Spec (F:=F))
end Gadgets.IsZeroField

namespace Gadgets.IsZeroField
theorem completeness : Completeness F elaborated Assumptions
end Gadgets.IsZeroField

namespace Gadgets.Addition32Full
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Addition32Full

namespace Gadgets.Keccak256.AbsorbBlock
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.AbsorbBlock

namespace Gadgets.Keccak256.RhoPi
lemma rhoPi_loop (state : Vector ℕ 25) :
    Specs.Keccak256.rhoPi state = rhoPiConstants.map fun (i, s) => rotLeft64 state[i.val] s
end Gadgets.Keccak256.RhoPi

namespace Gadgets.Keccak256.RhoPi
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.RhoPi

namespace LookupCircuit
theorem proverEnvironment_usesLocalWitnesses (circuit : LookupCircuit F α β) (input : α F) :
    (circuit.proverEnvironment input).UsesLocalWitnesses 0 ((circuit.main (const input)).operations 0)
end LookupCircuit

namespace Utils.StateTransition
lemma finset_ssubset_univ_of_not_mem {α : Type*} [Fintype α] (s : Finset α) (x : α)
    (h : x ∉ s) :
    s ⊂ Finset.univ
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_decrease {α : Type*} [Fintype α] [DecidableEq α] (f g : α → ℕ) (a : α)
    (h_a_decrease : g a < f a)
    (h_others_le : ∀ x, g x ≤ f x) :
    ∑ x : α, g x < ∑ x : α, f x
end Utils.StateTransition

namespace Utils.StateTransition
lemma path_has_transition {S : Type*} [DecidableEq S] (path : List S)
    (h_len : path.length ≥ 2) :
    ∃ (t : Transition S), t ∈ path.zip path.tail
end Utils.StateTransition

namespace Utils.StateTransition
lemma containsPath_has_positive_transition (R : Run S) (path : List S)
    (h_contains : R.containsPath path) (t : Transition S)
    (h_in : t ∈ path.zip path.tail) :
    R t > 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma zip_drop_sublist (l : List S) (n : ℕ) :
    ((l.drop n).zip (l.drop (n + 1))).Sublist (l.zip l.tail)
end Utils.StateTransition

namespace Utils.StateTransition
lemma containsPath_drop (R : Run S) (path : List S) (n : ℕ)
    (h_contains : R.containsPath path) :
    R.containsPath (path.drop n)
end Utils.StateTransition

namespace Utils.StateTransition
lemma zip_take_sublist (l1 l2 : List S) (n m : ℕ) :
    ((l1.take n).zip (l2.take m)).Sublist (l1.zip l2)
end Utils.StateTransition

namespace Utils.StateTransition
lemma tail_take {α : Type*} (l : List α) (n : ℕ) :
    (l.take n).tail = (l.tail).take (n - 1)
end Utils.StateTransition

namespace Utils.StateTransition
lemma containsPath_take (R : Run S) (path : List S) (n : ℕ)
    (h_contains : R.containsPath path) :
    R.containsPath (path.take n)
end Utils.StateTransition

namespace Utils.StateTransition
lemma drop_take_length_ge_two {α : Type*} (path : List α) (n m : Fin path.length)
    (h_n_lt_m : n < m) :
    ((path.drop n.val).take (m.val - n.val + 1)).length ≥ 2
end Utils.StateTransition

namespace Utils.StateTransition
lemma getLast_drop_take {α : Type*} (path : List α) (n k : ℕ)
    (h_n_lt : n < path.length)
    (h_bound : n + k ≤ path.length)
    (h_k_pos : k > 0) :
    ((path.drop n).take k).getLast? = path[n + k - 1]?
end Utils.StateTransition

namespace Utils.StateTransition
lemma drop_take_cycle_same_endpoints (path : List S) (x : S) (n m : Fin path.length)
    (h_n_lt_m : n < m)
    (h_x_at_n : path[n] = x)
    (h_x_at_m : path[m] = x) :
    ((path.drop n.val).take (m.val - n.val + 1)).head? =
    ((path.drop n.val).take (m.val - n.val + 1)).getLast?
end Utils.StateTransition

namespace Utils.StateTransition
lemma containsPath_drop_take (R : Run S) (path : List S) (n m : ℕ)
    (h_contains : R.containsPath path) :
    R.containsPath ((path.drop n).take m)
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_containsPath_nodup (R : Run S) (path : List S)
    (h_acyclic : R.isAcyclic)
    (h_contains : R.containsPath path) :
    path.Nodup
end Utils.StateTransition

namespace Utils.StateTransition
lemma countTransitionInPath_append_singleton (path : List S) (x y : S)
    (h_nonempty : path ≠ [])
    (h_last : path.getLast? = some x)
    (h_not_in : (x, y) ∉ path.zip path.tail) :
    countTransitionInPath (x, y) (path ++ [y]) = 1
end Utils.StateTransition

namespace Utils.StateTransition
lemma countTransitionInPath_append_singleton_other (path : List S) (x y : S) (t : Transition S)
    (h_nonempty : path ≠ [])
    (h_last : path.getLast? = some x)
    (h_ne : t ≠ (x, y)) :
    countTransitionInPath t (path ++ [y]) = countTransitionInPath t path
end Utils.StateTransition

namespace Utils.StateTransition
lemma mem_of_mem_zip_fst {α β : Type*} (l1 : List α) (l2 : List β) (a : α) (b : β) :
    (a, b) ∈ l1.zip l2 → a ∈ l1
end Utils.StateTransition

namespace Utils.StateTransition
lemma head_pair_not_in_tail_zip {α : Type*} [DecidableEq α] (h : α) (t : List α)
    (h_not_in : h ∉ t) :
    ∀ h2, (h, h2) ∉ t.zip t.tail
end Utils.StateTransition

namespace Utils.StateTransition
lemma nodup_zip_tail [DecidableEq S] (path : List S) (h_nodup : path.Nodup) :
    (path.zip path.tail).Nodup
end Utils.StateTransition

namespace Utils.StateTransition
lemma nodup_transition_count_le_one (path : List S) (h_nodup : path.Nodup)
    (t : Transition S) :
    countTransitionInPath t path ≤ 1
end Utils.StateTransition

namespace Utils.StateTransition
lemma countAsFirst_cons (hd : S) (tl : List S) (x : S) :
    countAsFirst (hd :: tl) x = (if hd = x ∧ tl ≠ [] then 1 else 0) + countAsFirst tl x
end Utils.StateTransition

namespace Utils.StateTransition
lemma countAsSecond_cons (hd : S) (tl : List S) (x : S) :
    countAsSecond (hd :: tl) x = (if tl.head? = some x then 1 else 0) + countAsSecond tl x
end Utils.StateTransition

namespace Utils.StateTransition
lemma countAsFirst_add_last_eq_countAsSecond_add_head (xs : List S) (x : S) :
    countAsFirst xs x + (if xs.getLast? = some x then 1 else 0) =
    countAsSecond xs x + (if xs.head? = some x then 1 else 0)
end Utils.StateTransition

namespace Utils.StateTransition
lemma cycle_balanced_at_node (cycle : List S) (x : S)
    (h_cycle : cycle.head? = cycle.getLast?) :
    (cycle.zip cycle.tail).countP (fun p => p.1 = x) =
    (cycle.zip cycle.tail).countP (fun p => p.2 = x)
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_count_pairs_fst (xs : List (S × S)) (a : S) :
    ∑ b : S, List.count (a, b) xs = List.countP (fun p => p.1 = a) xs
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_count_pairs_snd (xs : List (S × S)) (b : S) :
    ∑ a : S, List.count (a, b) xs = List.countP (fun p => p.2 = b) xs
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_countTransitionInPath_fst (cycle : List S) (x : S) :
    ∑ y : S, (countTransitionInPath (x, y) cycle : ℤ) = (countAsFirst cycle x : ℤ)
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_countTransitionInPath_snd (cycle : List S) (x : S) :
    ∑ y : S, (countTransitionInPath (y, x) cycle : ℤ) = (countAsSecond cycle x : ℤ)
end Utils.StateTransition

namespace Utils.StateTransition
lemma netFlow_sub (R R' : Run S) (x : S)
    (h_valid : ∀ t, R' t ≤ R t) :
    Run.netFlow (fun t => R t - R' t) x = R.netFlow x - R'.netFlow x
end Utils.StateTransition

namespace Utils.StateTransition
lemma cycle_netFlow_zero (cycle : List S) (x : S)
    (h_cycle : cycle.head? = cycle.getLast?) :
    Run.netFlow (fun t => countTransitionInPath t cycle) x = 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma netFlow_removeCycle_eq (R : Run S) (cycle : List S) (x : S)
    (h_contains : R.containsPath cycle)
    (h_cycle : cycle.head? = cycle.getLast?) :
    (R.removeCycle cycle).netFlow x = R.netFlow x
end Utils.StateTransition

namespace Utils.StateTransition
lemma size_removeCycle_lt (R : Run S) (cycle : List S)
    (h_len : cycle.length ≥ 2)
    (h_contains : R.containsPath cycle)
    (_h_cycle : cycle.head? = cycle.getLast?) :
    (R.removeCycle cycle).size < R.size
end Utils.StateTransition

namespace Utils.StateTransition
lemma removeCycle_le (R : Run S) (cycle : List S) (t : Transition S) :
    (R.removeCycle cycle) t ≤ R t
end Utils.StateTransition

namespace Utils.StateTransition
lemma exists_smaller_run_with_same_netFlow (R : Run S) (h_cycle : R.hasCycle) :
    ∃ (R' : Run S), (∀ x, R'.netFlow x = R.netFlow x) ∧ R'.size < R.size ∧ (∀ t, R' t ≤ R t)
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_no_self_loop (R : Run S) (s : S) (h_acyclic : R.isAcyclic) (h_edge : R (s, s) > 0) : False
end Utils.StateTransition

namespace Utils.StateTransition
lemma two_cycle_contains_both_edges (R : Run S) (a b : S)
    (h_ne : a ≠ b)
    (h_edge1 : R (a, b) > 0) (h_edge2 : R (b, a) > 0) :
    ∀ t : Transition S, countTransitionInPath t [a, b, a] ≤ R t
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_no_two_cycle (R : Run S) (a b : S)
    (h_acyclic : R.isAcyclic) (h_ne : a ≠ b)
    (h_edge1 : R (a, b) > 0) (h_edge2 : R (b, a) > 0) : False
end Utils.StateTransition

namespace Utils.StateTransition
lemma getLast_mem {α : Type*} (l : List α) (x : α) (h_last : l.getLast? = some x) :
    x ∈ l
end Utils.StateTransition

namespace Utils.StateTransition
lemma last_not_in_zip_tail {α : Type*} [DecidableEq α] (l : List α) (x : α)
    (h_nodup : l.Nodup)
    (h_last : l.getLast? = some x) :
    ∀ y : α, (x, y) ∉ l.zip l.tail
end Utils.StateTransition

namespace Utils.StateTransition
lemma drop_of_lt_length_nonempty {α : Type*} (path : List α) (i : ℕ)
    (h_i_lt : i < path.length) :
    path.drop i ≠ []
end Utils.StateTransition

namespace Utils.StateTransition
lemma cycle_from_suffix_contains (R : Run S) (suffix : List S) (current y : S)
    (h_suffix_nodup : suffix.Nodup)
    (h_contains_suffix : R.containsPath suffix)
    (h_suffix_nonempty : suffix ≠ [])
    (h_suffix_last : suffix.getLast? = some current)
    (h_edge : R (current, y) > 0) :
    ∀ t : Transition S, countTransitionInPath t (suffix ++ [y]) ≤ R t
end Utils.StateTransition

namespace Utils.StateTransition
lemma path_with_back_edge_creates_cycle (R : Run S) (path : List S) (current y : S)
    (h_acyclic : R.isAcyclic)
    (h_end : path.getLast? = some current)
    (h_contains : R.containsPath path)
    (h_y_in_path : y ∈ path)
    (h_edge : R (current, y) > 0) :
    R.hasCycle
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_edge_not_in_path (R : Run S) (path : List S) (current y : S)
    (h_acyclic : R.isAcyclic)
    (h_end : path.getLast? = some current)
    (h_contains : R.containsPath path)
    (h_edge : R (current, y) > 0)
    (h_y_in_path : y ∈ path) :
    False
end Utils.StateTransition

namespace Utils.StateTransition
lemma not_mem_implies_transition_not_in_zip_tail {α : Type*} (path : List α) (x y : α)
    (h_y_not_in : y ∉ path) :
    (x, y) ∉ path.zip path.tail
end Utils.StateTransition

namespace Utils.StateTransition
lemma containsPath_append_singleton (R : Run S) (path : List S) (x y : S)
    (h_nonempty : path ≠ [])
    (h_last : path.getLast? = some x)
    (h_contains : R.containsPath path)
    (h_y_not_in_path : y ∉ path)
    (h_edge : R (x, y) > 0) :
    R.containsPath (path ++ [y])
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_has_leaf_aux (R : Run S) (root current : S)
    (path : List S)
    (h_acyclic : R.isAcyclic)
    (h_start : path.head? = some root)
    (h_end : path.getLast? = some current)
    (h_nonempty : path ≠ [])
    (h_contains : R.containsPath path)
    (h_has_out : ∃ y, y ∉ path ∧ R (current, y) > 0) :
    ∃ leaf, R.isLeaf root leaf
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_has_leaf (R : Run S) (root : S)
    (h_acyclic : R.isAcyclic)
    (h_has_out : ∃ y, R (root, y) > 0) :
    ∃ leaf, R.isLeaf root leaf
end Utils.StateTransition

namespace Utils.StateTransition
lemma single_le_sum_of_nonneg {α : Type*} [Fintype α] (f : α → ℤ) (a : α)
    (h_nonneg : ∀ x, f x ≥ 0) :
    f a ≤ ∑ x : α, f x
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_pos_of_pos_element {α : Type*} [Fintype α] (f : α → ℤ) (a : α)
    (h_pos : f a > 0)
    (h_nonneg : ∀ x, f x ≥ 0) :
    ∑ x : α, f x > 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_nat_cast_pos {α : Type*} [Fintype α] (f : α → ℕ) (a : α)
    (h_pos : f a > 0) :
    ∑ x : α, (f x : ℤ) > 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma leaf_has_negative_netFlow (R : Run S) (root leaf : S)
    (h_leaf : R.isLeaf root leaf)
    (h_in : ∃ y, R (y, leaf) > 0) :
    R.netFlow leaf < 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_zero_of_all_zero {α : Type*} [Fintype α] (f : α → ℕ) (h : ∀ x, f x = 0) :
    ∑ x : α, (f x : ℤ) = 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_nat_cast_nonneg {α : Type*} [Fintype α] (f : α → ℕ) :
    0 ≤ ∑ x : α, (f x : ℤ)
end Utils.StateTransition

namespace Utils.StateTransition
lemma sum_nat_cast_zero_of_not_pos {α : Type*} [Fintype α] (f : α → ℕ)
    (h : ∀ x, ¬(f x > 0)) :
    ∑ x : α, (f x : ℤ) = 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma positive_netFlow_has_outgoing_edge (R : Run S) (s : S)
    (h_pos : R.netFlow s > 0) :
    ∃ y, R (s, y) > 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma last_has_incoming_transition {α : Type*} (l : List α) (x : α)
    (h_len : l.length ≥ 2)
    (h_last : l.getLast? = some x) :
    ∃ y, (y, x) ∈ l.zip l.tail
end Utils.StateTransition

namespace Utils.StateTransition
lemma path_distinct_head_last_length_ge_two {α : Type*} (path : List α) (x y : α)
    (h_nonempty : path ≠ [])
    (h_head : path.head? = some x)
    (h_last : path.getLast? = some y)
    (h_ne : x ≠ y) :
    path.length ≥ 2
end Utils.StateTransition

namespace Utils.StateTransition
lemma reachable_leaf_has_incoming_edge (R : Run S) (root leaf : S)
    (h_leaf : R.isLeaf root leaf)
    (h_ne : root ≠ leaf) :
    ∃ y, R (y, leaf) > 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma unique_negative_netFlow (R : Run S) (s d x : S)
    (h_source : R.netFlow s = 1)
    (h_others : ∀ y, y ≠ s → y ≠ d → R.netFlow y = 0)
    (h_x_neg : R.netFlow x < 0) :
    x = d
end Utils.StateTransition

namespace Utils.StateTransition
lemma leaf_has_incoming_and_negative_netFlow (R : Run S) (root leaf : S)
    (h_leaf : R.isLeaf root leaf)
    (h_root_out : ∃ y, R (root, y) > 0) :
    R.netFlow leaf < 0
end Utils.StateTransition

namespace Utils.StateTransition
lemma acyclic_run_has_path_from_source_to_sink (R : Run S) (s d : S)
    (h_acyclic : R.isAcyclic)
    (h_source : R.netFlow s = 1)
    (h_others : ∀ x, x ≠ s → x ≠ d → R.netFlow x = 0) :
    ∃ (path : List S), path.head? = some s ∧ path.getLast? = some d ∧
      path ≠ [] ∧ R.containsPath path ∧ path.Nodup
end Utils.StateTransition

namespace Utils.StateTransition
theorem exists_path_from_source_to_sink
    (R : Run S) (s d : S)
    (h_source : R.netFlow s = 1)
    (h_others : ∀ x, x ≠ s → x ≠ d → R.netFlow x = 0) :
    ∃ (path : List S), path.head? = some s ∧ path.getLast? = some d ∧
      path ≠ [] ∧ R.containsPath path ∧ path.Nodup
end Utils.StateTransition

namespace Gadgets.Not
omit p_large_enough in
lemma eval_not {env} {x_var : Var U64 (F p)} :
    eval env (not64_bytewise x_var) = not64_bytewise_value (eval env x_var)
end Gadgets.Not

namespace Gadgets.Not
theorem not_zify (n : ℕ) {x : ℕ} (hx : x < n) : ((n - 1 - x : ℕ) : ℤ) = ↑n - 1 - ↑x
end Gadgets.Not

namespace Gadgets.Not
theorem not_lt (n : ℕ) {x : ℕ} (hx : x < n) : n - 1 - (x : ℤ) < n
end Gadgets.Not

namespace Gadgets.Not
theorem not_bytewise_value_spec {x : U64 (F p)} (x_lt : x.Normalized) :
    (not64_bytewise_value x).value = not64 x.value
    ∧ (not64_bytewise_value x).Normalized
end Gadgets.Not

namespace Trace
lemma tail_len : (trace : Trace F S) → trace.tail.len = trace.len - 1
  | <+>  => rfl
end Trace

namespace Trace
lemma toList_length : (trace : Trace F S) → trace.toList.length = trace.len
  | <+> => rfl
end Trace

@[table_norm, table_assignment_norm]
theorem bind_def {β : Type} (f : TableConstraint W S F α) (g : α → TableConstraint W S F β) :
  (f >>= g) = fun ctx =>
    let (a, ctx')

@[table_norm, table_assignment_norm]
theorem TableConstraint.assignVar_circuit : ∀ ctx (off : CellOffset W S) (v : Variable F),
  (assignVar off v ctx).snd.circuit = ctx.circuit

namespace Gadgets.ByteDecomposition
theorem soundness (offset : Fin 8) : Soundness (F p) (circuit := elaborated offset) Assumptions (Spec offset)
end Gadgets.ByteDecomposition

namespace Gadgets.ByteDecomposition
theorem completeness (offset : Fin 8) : Completeness (F p) (elaborated offset) Assumptions
end Gadgets.ByteDecomposition

namespace Gadgets.Keccak256.ThetaXor
lemma thetaXor_loop (state : Vector ℕ 25) (d : Vector ℕ 5) :
    Specs.Keccak256.thetaXor state d = .mapFinRange 25 fun i => state[i.val] ^^^ d[i.val / 5]
end Gadgets.Keccak256.ThetaXor

namespace Gadgets.Keccak256.ThetaXor
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.ThetaXor

namespace Circomlib.MultiMux1
lemma Vector.mapRange_one {α : Type} (f : ℕ → α) :
  Vector.mapRange 1 f = #v[f 0]
end Circomlib.MultiMux1

namespace Circomlib.MultiMux1
lemma Vector.getElem_flatten_singleton {α : Type} {n : ℕ} (v : Vector (Vector α 1) n) (i : ℕ) (hi : i < n) :
    v.flatten[i] = (v[i])[0]
end Circomlib.MultiMux1

namespace Circomlib.MultiMux1
lemma Vector.getElem_map_singleton_flatten {α β : Type} {n : ℕ} (v : Vector α n) (f : α → β) (i : ℕ) (hi : i < n) :
    (v.map (fun x => #v[f x])).flatten[i] = f (v[i])
end Circomlib.MultiMux1

namespace Gadgets.Or32
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Or32

lemma trivial_lem_comm :
    ∀ (x y : Nat), x + y = y + x

namespace TraceOfLength
theorem lastRow_of_forAllWithIndex {N : ℕ+} {prop : Row F S → ℕ → Prop}
  (trace : TraceOfLength F S N) (h : ForAllRowsOfTraceWithIndex trace prop) :
    prop trace.lastRow (N - 1)
end TraceOfLength

namespace TraceOfLength
theorem lastRow_of_forAllWithPrevious {N : ℕ+} {prop : Row F S → (i : ℕ) → TraceOfLength F S i → Prop}
  (trace : TraceOfLength F S N) (h : ForAllRowsWithPrevious trace prop) :
    prop trace.lastRow (N - 1) trace.tail
end TraceOfLength

namespace CellAssignment
lemma pushRow_offset (assignment : CellAssignment W S) (row : Fin W) :
  (assignment.pushRow row).offset = assignment.offset + size S
end CellAssignment

namespace CellAssignment
theorem assignmentFromCircuit_offset (as : CellAssignment W S) (ops : Operations F) :
    (assignmentFromCircuit as ops).offset = as.offset + ops.localLength
end CellAssignment

namespace CellAssignment
theorem assignmentFromCircuit_vars (as : CellAssignment W S) (ops : Operations F) :
    (assignmentFromCircuit as ops).vars = (as.vars ++ (.mapRange ops.localLength fun i => .aux (as.aux_length + i) : Vector (Cell W S) _)
      ).cast (assignmentFromCircuit_offset ..).symm
end CellAssignment

namespace Fin
lemma foldl_sum_val_bound {p : ℕ} [Fact p.Prime] {ops : ℕ} (f : Fin ops → ZMod p) (M : ℕ)
    (h_bound : ∀ j : Fin ops, (f j).val ≤ M) (h_no_overflow : ops * M < p) :
    (Fin.foldl ops (fun sum j => sum + f j) 0).val ≤ ops * M
end Fin

namespace Fin
lemma foldl_factor_const {α : Type*} [CommSemiring α] {n : ℕ} (f : Fin n → α) (c : α) (init : α) :
    Fin.foldl n (fun acc i => acc + f i * c) init =
    init + c * Fin.foldl n (fun acc i => acc + f i) 0
end Fin

namespace Fin
lemma foldl_eq_sum_range {α : Type*} [AddCommMonoid α] : ∀ (n' : ℕ) (f' : Fin n' → α),
    Fin.foldl n' (fun acc i => acc + f' i) 0 = ∑ i ∈ Finset.range n', if h : i < n' then f' ⟨i, h⟩ else 0
end Fin

namespace Fin
lemma foldl_to_sum {α : Type*} [AddCommMonoid α] : ∀ (n' : ℕ) (f' : Fin n' → α),
    Fin.foldl n' (fun acc i => acc + f' i) 0 = ∑ i : Fin n', f' i
end Fin

namespace Fin
lemma sum_interchange {α : Type*} [CommSemiring α] {n ops : ℕ} (f : Fin ops → Fin n → α) (g : ℕ → α) :
    Fin.foldl n (fun acc k => acc + g k.val *
      Fin.foldl ops (fun acc' j => acc' + f j k) 0) 0 =
    Fin.foldl ops (fun acc j => acc +
      Fin.foldl n (fun acc' k => acc' + f j k * g k.val) 0) 0
end Fin

namespace Examples.FemtoCairo.Types
@[ext]
lemma State.ext {F : Type} {s1 s2 : State F} (h1 : s1.pc = s2.pc) (h2 : s1.ap = s2.ap) (h3 : s1.fp = s2.fp) : s1 = s2
end Examples.FemtoCairo.Types

@[circuit_norm]
lemma FormalCircuit.concat_assumptions {F Input Mid Output} [Field F] [ProvableType Input] [ProvableType Mid] [ProvableType Output]
    (c1 : FormalCircuit F Input Mid) (c2 : FormalCircuit F Mid Output) p0 p1 :
    (c1.concat c2 p0 p1).Assumptions = c1.Assumptions

@[circuit_norm]
lemma FormalCircuit.weakenSpec_assumptions {F Input Output} [Field F] [ProvableType Input] [ProvableType Output]
    (c : FormalCircuit F Input Output) (WeakerSpec : Input F → Output F → Prop) h_spec_implication :
    (c.weakenSpec WeakerSpec h_spec_implication).Assumptions = c.Assumptions

namespace Gadgets.Or.Or8
private theorem or_times_two_sub_xor {x y : ℕ} (hx : x < 256) (hy : y < 256) :
    2 * (x ||| y) = x + y + (x ^^^ y)
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
private theorem or_times_two_sub_xor' {x y : ℕ} (hx : x < 256) (hy : y < 256) :
    2 * (x ||| y) - x - y = (x ^^^ y)
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
private theorem two_or_ge_add {x y : ℕ} (hx : x < 256) (hy : y < 256) : 2 * (x ||| y) ≥ x + y
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
lemma val_two : (2 : F p).val = 2
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
lemma two_non_zero : (2 : F p) ≠ 0
end Gadgets.Or.Or8

namespace Gadgets.Or.Or8
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Or.Or8

namespace Gadgets.Rotation32Bits
theorem soundness (offset : Fin 8) : Soundness (F p) (elaborated offset) Assumptions (Spec offset)
end Gadgets.Rotation32Bits

namespace Gadgets.Rotation32Bits
theorem completeness (offset : Fin 8) : Completeness (F p) (elaborated offset) Assumptions
end Gadgets.Rotation32Bits

namespace Circomlib.Num2Bits
omit [Fact (p > 2)] in
lemma lc_eq {i0} {env} {n : ℕ} :
  (Expression.eval env <| Prod.fst <|
    Fin.foldl n (fun (lc1, e2) i => (lc1 + (var (F:=F p) ⟨ i0 + ↑i ⟩) * e2, e2 + e2)) (0, 1))
    = fieldFromBits (Vector.mapRange n fun i => env.get (i0 + i))
end Circomlib.Num2Bits

namespace Circomlib.Bits2Num
omit [Fact (p > 2)] in
lemma lc_eq {env} {n : ℕ} {v : Vector (Expression (F p)) n} :
  (Expression.eval env <| Prod.fst <|
    Fin.foldl n (fun ((lc1, e2) : Expression (F p) × Expression (F p)) i =>
      (lc1 + v[↑i] * e2, e2 + e2)) (0, 1))
    = fieldFromBits (Vector.mapFinRange n fun i => v[↑i].eval env)
end Circomlib.Bits2Num

namespace InductiveTable
theorem equalityConstraint.soundness {row : State F × Input F} {input_state : State F} {env : Environment F} :
  Circuit.ConstraintsHold.Soundness (windowEnv (equalityConstraint Input input_state) ⟨<+> +> row, rfl⟩ env)
    (equalityConstraint Input input_state .empty).2.circuit
    ↔ row.1 = input_state
end InductiveTable

namespace InductiveTable
omit [Field F] in
lemma traceInputs_length {N : ℕ} (trace : TraceOfLength F (ProvablePair State Input) N) :
    (traceInputs trace).length = N
end InductiveTable

namespace InductiveTable
lemma table_soundness_aux (table : InductiveTable F State Input) (input output : State F)
  (N : ℕ+) (trace : TraceOfLength F (ProvablePair State Input) N) (env : ℕ → ℕ → Environment F) :
  table.Spec input [] 0 rfl input →
  TableConstraintsHold (table.tableConstraints input output) trace env →
    trace.ForAllRowsWithPrevious (fun row i rest => table.Spec input (traceInputs rest) i (traceInputs_length rest) row.1)
    ∧ trace.lastRow.1 = output
end InductiveTable

namespace Gadgets.BLAKE3.G
theorem soundness (a b c d : Fin 16) : Soundness (F p) (elaborated a b c d) Assumptions (Spec a b c d)
end Gadgets.BLAKE3.G

namespace Gadgets.BLAKE3.G
theorem completeness (a b c d : Fin 16) : Completeness (F p) (elaborated a b c d) Assumptions
end Gadgets.BLAKE3.G

namespace Utils.Bits
lemma toBits_fromBits_aux {n : ℕ} (bits : Vector ℕ n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    (fromBits bits) < 2^n ∧ toBits n (fromBits bits) = bits
end Utils.Bits

namespace Utils.Bits
theorem toBits_fromBits {n : ℕ} (bits : Vector ℕ n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    toBits n (fromBits bits) = bits
end Utils.Bits

namespace Utils.Bits
theorem fromBits_lt {n : ℕ} (bits : Vector ℕ n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    (fromBits bits) < 2^n
end Utils.Bits

namespace Utils.Bits
theorem toBits_injective (n : ℕ) {x y : ℕ} : x < 2^n → y < 2^n →
    toBits n x = toBits n y → x = y
end Utils.Bits

namespace Utils.Bits
theorem fromBits_toBits {n : ℕ} {x : ℕ} (hx : x < 2^n) :
    fromBits (toBits n x) = x
end Utils.Bits

namespace Utils.Bits
theorem fieldFromBits_eval {n : ℕ} {eval : Environment (F p)} (bits : Vector (Expression (F p)) n) :
    eval (fieldFromBitsExpr bits) = fieldFromBits (bits.map eval)
end Utils.Bits

namespace Utils.Bits
theorem fieldToBits_bits {n : ℕ} {x : F p} :
    ∀ i (_ : i < n), (fieldToBits n x)[i] = 0 ∨ (fieldToBits n x)[i] = 1
end Utils.Bits

namespace Utils.Bits
lemma fieldToBits_fieldFromBits_aux {n : ℕ} (hn : 2^n < p) (bits : Vector (F p) n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    (fieldFromBits bits).val < 2^n ∧ fieldToBits n (fieldFromBits bits) = bits
end Utils.Bits

namespace Utils.Bits
theorem fieldFromBits_lt {n : ℕ} (bits : Vector (F p) n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    (fieldFromBits bits).val < 2^n
end Utils.Bits

namespace Utils.Bits
theorem fieldToBits_fieldFromBits {n : ℕ} (hn : 2^n < p) (bits : Vector (F p) n)
  (h_bits : ∀ (i : ℕ) (hi : i < n), bits[i] = 0 ∨ bits[i] = 1) :
    fieldToBits n (fieldFromBits bits) = bits
end Utils.Bits

namespace Utils.Bits
theorem fieldToBits_injective (n : ℕ) {x y : F p} : x.val < 2^n → y.val < 2^n →
    fieldToBits n x = fieldToBits n y → x = y
end Utils.Bits

namespace Utils.Bits
lemma val_natCast_toBits {n} {x : ℕ} :
    Vector.map (ZMod.val ∘ Nat.cast (R:=F p)) (toBits n x) = toBits n x
end Utils.Bits

namespace Utils.Bits
theorem fieldFromBits_fieldToBits {n : ℕ} {x : F p} (hx : x.val < 2^n) :
    fieldFromBits (fieldToBits n x) = x
end Utils.Bits

namespace Utils.Bits
lemma fieldFromBits_succ (n : ℕ) (bits : Vector (F p) (n + 1)) :
    fieldFromBits bits = fieldFromBits bits.pop + bits[n] * (2^n : F p)
end Utils.Bits

namespace Utils.Bits
lemma fieldFromBits_as_sum {n : ℕ} (bits : Vector (F p) n) :
    fieldFromBits bits =
    Fin.foldl n (fun acc k => acc + bits[k.val] * (2^k.val : F p)) 0
end Utils.Bits

namespace Gadgets.Rotation64.Theorems
lemma rotRight64_bytes_u64_eq (o : ℕ) (x : U64 ℕ) :
  rotRight64_bytes x.toLimbs o = (rotRight64_u64 x o).toLimbs
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_mod {o : ℕ} (ho : o < 8) {x0 x1 x2 x3 x4 x5 x6 x7 : ℕ} :
    (x0 + x1 * 256 + x2 * 256 ^ 2 + x3 * 256 ^ 3 + x4 * 256 ^ 4 + x5 * 256 ^ 5 + x6 * 256 ^ 6 + x7 * 256 ^ 7) %
      2^o = x0 % 2^o
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_div {o : ℕ} (ho : o < 8) {x0 x1 x2 x3 x4 x5 x6 x7 : ℕ} :
    (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3 + x4 * 256^4 + x5 * 256^5 + x6 * 256^6 + x7 * 256^7) / 2 ^ o
    = x0 / 2^o + x1 * 2^(8-o) + x2 * 256 * 2^(8-o) + x3 * 256^2 * 2^(8-o) + x4 * 256^3 * 2^(8-o) +
    x5 * 256^4 * 2^(8-o) + x6 * 256^5 * 2^(8-o) + x7 * 256^6 * 2^(8-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
lemma h_x0_const {o : ℕ} (ho : o < 8) :
    2^(8-o) * 256^7 = 2^(64-o)
end Gadgets.Rotation64.Theorems

namespace Gadgets.Rotation64.Theorems
theorem rotation64_bits_soundness {o : ℕ} (ho : o < 8) {x : U64 ℕ} :
    (rotRight64_u64 x o).valueNat = rotRight64 x.valueNat o
end Gadgets.Rotation64.Theorems

namespace Gadgets.BLAKE3.Permute
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.Permute

@[circuit_norm]
lemma eval_mul (env : Environment F) (a b : Expression F) :
    Expression.eval env (Expression.mul a b) = (Expression.eval env a) * (Expression.eval env b)

lemma eval_foldl (env : Environment F) (n : ℕ)
    (f : Expression F → Fin n → Expression F) (init : Expression F)
    (hf : ∀ (e : Expression F) (i : Fin n),
      Expression.eval env (f e i) = Expression.eval env (f (Expression.const (Expression.eval env e)) i)) :
    Expression.eval env (Fin.foldl n f init) =
    Fin.foldl n (fun (acc : F) (i : Fin n) => Expression.eval env (f (Expression.const acc) i)) (Expression.eval env init)

namespace ProvableType
@[circuit_norm]
theorem fromElements_eval_toElements {env : Environment F} (x : α (Expression F)) :
  fromElements (Vector.map (Expression.eval env) (toElements x)) = eval env x
end ProvableType

namespace TestDecomposeProvableStruct
theorem test_decompose_simple {F : Type} [Field F] (input : TestInputs F) :
    input.x + input.y + input.z = input.z + input.y + input.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_nested {F : Type} [Field F] (input : NestedInputs F) :
    input.first.x + input.second.y = input.second.y + input.first.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_multiple {F : Type} [Field F] (a : TestInputs F) (b : TestInputs F) :
    a.x + b.y = b.y + a.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_mixed_auto {F : Type} [Field F] (a : TestInputs F) (b : NestedInputs F) :
    a.x + b.first.y = b.first.y + a.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_from_hypothesis {F : Type} [Field F] (input : TestInputs F)
    (h : input.x = 5) : input.y + input.z = input.z + input.y
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_multiple_hypotheses {F : Type} [Field F] (a : TestInputs F) (b : TestInputs F)
    (h1 : a.x = b.y) (h2 : b.z = 10) : a.x = a.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_decompose_nested_hypothesis {F : Type} [Field F] (input : NestedInputs F)
    (h : input.first.x = 7) : input.second.y = input.second.y
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_no_decompose_without_projection {F : Type} [Field F] (input : TestInputs F) :
    input = input
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_selective_decompose {F : Type} [Field F] (a : TestInputs F) (b : TestInputs F) (c : TestInputs F) :
    a.x + b.y = b.y + a.x
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_no_projection_struct {F : Type} [Field F] (input : TestInputs F)
    (h : input = ⟨5, 3, 1⟩) :
    input = ⟨5, 3, 1⟩
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_multiple_no_projection {F : Type} [Field F]
    (a : TestInputs F) (b : TestInputs F) (c : TestInputs F)
    (ha : a = ⟨1, 2, 3⟩) (hb : b = ⟨4, 5, 6⟩) (hc : c = ⟨7, 8, 9⟩) :
    a = ⟨1, 2, 3⟩ ∧ b = ⟨4, 5, 6⟩ ∧ c = ⟨7, 8, 9⟩
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_whole_struct_equality {F : Type} [Field F]
    (x : TestInputs F) (y : TestInputs F) (h : x = y) :
    x = y
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_struct_in_function {F : Type} [Field F]
    (input : TestInputs F) (h : processStruct input = 10) :
    processStruct input = 10
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_struct_comparison {F : Type} [Field F]
    (a : TestInputs F) (b : TestInputs F) :
    a = b ↔ a = b
end TestDecomposeProvableStruct

namespace TestDecomposeProvableStruct
theorem test_equality_not_triggar {F : Type} [Field F]
    (a : TestInputs F) (_ : a = {x := 1, y := 2, z := 4}) :
    a = a
end TestDecomposeProvableStruct

namespace Gadgets.Rotation32.Theorems
lemma rotRight32_bytes_u32_eq (o : ℕ) (x : U32 ℕ) :
  rotRight32_bytes x.toLimbs o = (rotRight32_u32 x o).toLimbs
end Gadgets.Rotation32.Theorems

namespace Gadgets.Rotation32.Theorems
lemma h_mod32 {o : ℕ} (ho : o < 8) {x0 x1 x2 x3 : ℕ} :
    (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3) % 2^o = x0 % 2^o
end Gadgets.Rotation32.Theorems

namespace Gadgets.Rotation32.Theorems
lemma h_div32 {o : ℕ} (ho : o < 8) {x0 x1 x2 x3: ℕ} :
    (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3) / 2^o
    = x0 / 2^o + x1 * 2^(8-o) + x2 * 256 * 2^(8-o) + x3 * 256^2 * 2^(8-o)
end Gadgets.Rotation32.Theorems

namespace Gadgets.Rotation32.Theorems
lemma h_x0_const32 {o : ℕ} (ho : o < 8) :
    2^(8-o) * 256^3 = 2^(32-o)
end Gadgets.Rotation32.Theorems

namespace Gadgets.Rotation32.Theorems
theorem rotation32_bits_soundness {o : ℕ} (ho : o < 8) {x : U32 ℕ} :
    (rotRight32_u32 x o).valueNat = rotRight32 x.valueNat o
end Gadgets.Rotation32.Theorems

theorem ProvableType.witnessAny.localWitnesses (n : ℕ) (env : Environment F) :
    env.UsesLocalWitnessesCompleteness n (ProvableType.witnessAny α |>.operations n) ↔ True

theorem MemoryAccessList.addressTimestampSort_sorted (accesses : MemoryAccessList) :
    (MemoryAccessList.addressTimestampSort accesses).Sorted address_timestamp_ordering

theorem MemoryAccessList.addressTimestampSort_perm (accesses : MemoryAccessList) :
    (MemoryAccessList.addressTimestampSort accesses).Perm accesses

theorem MemoryAccessList.addressStrictTimestampSorted_of_AddressTimestampSorted_noTimestampDup
    (accesses : MemoryAccessList) (h_sorted : accesses.isAddressTimestampSorted)
    (h_no_timestamp_dup : accesses.Notimestampdup) :
    accesses.isAddressStrictTimestampSorted

theorem MemoryAccessList.noTimestampDup_perm (l1 l2 : MemoryAccessList)
    (h_l1_nodup : l1.Notimestampdup) (h_perm : l1.Perm l2) :
    l2.Notimestampdup

theorem MemoryAccessList.noTimestampDup_of_cons (head : MemoryAccess) (tail : MemoryAccessList)
    (h : Notimestampdup (head :: tail)) :
    Notimestampdup tail

theorem MemoryAccessList.isAddressTimestampSorted_of_cons (head : MemoryAccess) (tail : MemoryAccessList)
    (h : isAddressTimestampSorted (head :: tail)) :
    isAddressTimestampSorted tail

theorem MemoryAccessList.noTimestampDup_of_TimestampSorted
    (accesses : MemoryAccessList) (h_sorted : accesses.isTimestampSorted) :
    accesses.Notimestampdup

theorem MemoryAccessList.filterAddress_sorted (accesses : MemoryAccessList)
    (h : accesses.isTimestampSorted) (addr : ℕ) :
    (MemoryAccessList.filterAddress accesses addr).isTimestampSorted

theorem MemoryAccessList.filterAddress_cons (head : MemoryAccess) (tail : MemoryAccessList) (addr : ℕ) :
    MemoryAccessList.filterAddress (head :: tail) addr =
    match head with

theorem MemoryAccessList.isConsistentSingleAddress_iff (accesses : MemoryAccessList) (addr : ℕ) (h_sorted : accesses.isTimestampSorted)
    (h_eq : accesses.all (fun (_t, addr', _readValue, _writeValue) => addr' = addr)) :
    MemoryAccessList.isConsistentOnline accesses h_sorted ↔
    MemoryAccessList.isConsistentSingleAddress accesses h_sorted

theorem MemoryAccessList.lastWriteValue_filter (accesses : MemoryAccessList)
    (h_sorted : accesses.isTimestampSorted) (addr : ℕ) (h_sorted' : ((MemoryAccessList.filterAddress accesses addr).isTimestampSorted))  :
    MemoryAccessList.lastWriteValue accesses h_sorted addr =
    MemoryAccessList.lastWriteValue (MemoryAccessList.filterAddress accesses addr) h_sorted' addr

theorem MemoryAccessList.isConsistentOnline_filter_of_consistentOnline (accesses : MemoryAccessList) (h_sorted : accesses.isTimestampSorted)
    (h_consistent : MemoryAccessList.isConsistentOnline accesses h_sorted) (addr : ℕ) :
    MemoryAccessList.isConsistentOnline (MemoryAccessList.filterAddress accesses addr) (MemoryAccessList.filterAddress_sorted accesses h_sorted addr)

theorem MemoryAccessList.isTimestampSorted_cons (head : MemoryAccess) (tail : MemoryAccessList) :
    isTimestampSorted (head :: tail) → isTimestampSorted tail

theorem MemoryAccessList.isConsistentSingleAddress_cons (head : MemoryAccess) (tail : MemoryAccessList)
    (h_sorted : isTimestampSorted (head :: tail)) (h_sorted' : tail.isTimestampSorted)
    (h : isConsistentSingleAddress (head :: tail) h_sorted) :
    isConsistentSingleAddress tail h_sorted'

theorem MemoryAccessList.isConsistentSingleAddress_filterAddress_of_cons (head : MemoryAccess) (tail : MemoryAccessList)
    (addr : ℕ)
    (h_sorted : isTimestampSorted (head :: tail))
    (h : isConsistentSingleAddress (filterAddress (head :: tail) addr)
         (filterAddress_sorted (head :: tail) h_sorted addr)) :
    isConsistentSingleAddress (filterAddress tail addr)
      (filterAddress_sorted tail (isTimestampSorted_cons head tail h_sorted) addr)

theorem MemoryAccessList.isConsistentSingleAddress_cons_forall (head : MemoryAccess) (tail : MemoryAccessList)
    (h_sorted : isTimestampSorted (head :: tail))
    : (∀ addr : ℕ, (filterAddress (head :: tail) addr).isConsistentSingleAddress (MemoryAccessList.filterAddress_sorted (head :: tail) h_sorted addr)) →
    (∀ addr : ℕ, isConsistentSingleAddress (filterAddress tail addr) (MemoryAccessList.filterAddress_sorted tail (by simp_all only [isTimestampSorted,
      List.sorted_cons]) addr))

theorem MemoryAccessList.isConsistent_iff_all_single_address (accesses : MemoryAccessList) (h_sorted : accesses.isTimestampSorted) :
    MemoryAccessList.isConsistentOnline accesses h_sorted ↔
    (∀ addr : ℕ, MemoryAccessList.isConsistentSingleAddress (MemoryAccessList.filterAddress accesses addr) (MemoryAccessList.filterAddress_sorted accesses h_sorted addr))

theorem MemoryAccessList.filterAddress_sorted_from_addressTimestampSorted
    (accesses : MemoryAccessList)
    (h_sorted : accesses.isAddressTimestampSorted)
    (h_nodup : accesses.Notimestampdup)
    (addr : ℕ) :
    (accesses.filterAddress addr).isTimestampSorted

theorem MemoryAccessList.isConsistentSingleAddress_filterAddress_forall_of_cons
    (head : MemoryAccess) (tail : MemoryAccessList)
    (h_sorted : isAddressTimestampSorted (head :: tail))
    (h_nodup : Notimestampdup (head :: tail))
    (h : ∀ addr, isConsistentSingleAddress (filterAddress (head :: tail) addr)
         (filterAddress_sorted_from_addressTimestampSorted (head :: tail) h_sorted h_nodup addr)) :
    ∀ addr, isConsistentSingleAddress (filterAddress tail addr)
      (filterAddress_sorted_from_addressTimestampSorted tail
        (isAddressTimestampSorted_of_cons head tail h_sorted)
        (noTimestampDup_of_cons head tail h_nodup) addr)

theorem MemoryAccessList.filterAddress_empty_when_address_changes
    (head : MemoryAccess) (second : MemoryAccess) (tail : MemoryAccessList)
    (h_sorted : isAddressTimestampSorted (head :: second :: tail))
    (h_addr_ne : head.2.1 ≠ second.2.1) :
    filterAddress (second :: tail) head.2.1 = []

theorem MemoryAccessList.isConsistentOffline_of_cons
    (head : MemoryAccess) (tail : MemoryAccessList)
    (h_sorted : isAddressTimestampSorted (head :: tail))
    (h_offline : isConsistentOffline (head :: tail) h_sorted) :
    isConsistentOffline tail (isAddressTimestampSorted_of_cons head tail h_sorted)

theorem MemoryAccessList.isConsistentOffline_implies_single_address
    (accesses : MemoryAccessList)
    (h_sorted : accesses.isAddressTimestampSorted)
    (h_nodup : accesses.Notimestampdup)
    (h_offline : accesses.isConsistentOffline h_sorted)
    (addr : ℕ) :
    (accesses.filterAddress addr).isConsistentSingleAddress
      (filterAddress_sorted_from_addressTimestampSorted accesses h_sorted h_nodup addr)

theorem MemoryAccessList.isConsistentOffline_iff_all_single_addresses (accesses : MemoryAccessList) (h_sorted : accesses.isAddressTimestampSorted) (h_nodup : accesses.Notimestampdup) :
    MemoryAccessList.isConsistentOffline accesses h_sorted ↔
    ∀ addr, MemoryAccessList.isConsistentSingleAddress (MemoryAccessList.filterAddress accesses addr) (filterAddress_sorted_from_addressTimestampSorted accesses h_sorted h_nodup addr)

theorem MemoryAccessList.addressTimestampSort_noTimestampDup
    (accesses : MemoryAccessList)
    (h_nodup : accesses.Notimestampdup) :
    accesses.addressTimestampSort.Notimestampdup

theorem MemoryAccessList.filterAddress_addressTimestampSort_eq
    (accesses : MemoryAccessList)
    (h_sorted : accesses.isTimestampSorted)
    (h_nodup : accesses.Notimestampdup)
    (addr : ℕ) :
    (accesses.filterAddress addr).isConsistentSingleAddress
      (filterAddress_sorted accesses h_sorted addr) ↔
    (accesses.addressTimestampSort.filterAddress addr).isConsistentSingleAddress
      (filterAddress_sorted_from_addressTimestampSorted accesses.addressTimestampSort
        (addressTimestampSort_sorted accesses)
        (addressTimestampSort_noTimestampDup accesses h_nodup) addr)

theorem MemoryAccessList.isConsistentOnline_iff_sorted_isConsistentOffline
    (accesses : MemoryAccessList)
    (h_sorted : accesses.isTimestampSorted)
    (h_nodup : accesses.Notimestampdup) :
    MemoryAccessList.isConsistentOnline accesses h_sorted ↔
    MemoryAccessList.isConsistentOffline (MemoryAccessList.addressTimestampSort accesses) (MemoryAccessList.addressTimestampSort_sorted accesses)

lemma MemoryAccessList.eq_of_perm_of_sorted {l1 l2 l3 : MemoryAccessList} (h_l1_sorted: l1.isTimestampSorted)
    (h_l2_sorted : l2.isAddressTimestampSorted) (h_l3_sorted : l3.isAddressTimestampSorted)
    (h_perm1 : l1.Perm l2) (h_perm2 : l1.Perm l3) : l2 = l3

theorem MemoryAccessList.isConsistentOnline_iff_isConsistentOffline
    (accesses : MemoryAccessList)
    (h_sorted : accesses.isTimestampSorted)
    (h_nodup : accesses.Notimestampdup) :
    MemoryAccessList.isConsistentOnline accesses h_sorted ↔
    ∃ permuted : AddressSortedMemoryAccessList,
      permuted.val.Perm accesses ∧
      MemoryAccessList.isConsistentOffline permuted.val permuted.property

namespace Circuit
@[circuit_norm]
theorem bind_def {α β} (f : Circuit F α) (g : α → Circuit F β) :
  f >>= g = fun n =>
    let (a, ops)
end Circuit

namespace Circuit
@[circuit_norm]
theorem pure_def {α} (a : α) : (pure a : Circuit F α) = fun _ => (a, [])
end Circuit

namespace Circuit
@[circuit_norm]
theorem map_def {α β} (f : α → β) (circuit : Circuit F α) :
  f <$> circuit = fun n => let (a, ops)
end Circuit

namespace Circuit
@[circuit_norm]
theorem bind_normalize {α β} (f : Circuit F α) (g : α → Circuit F β) : f.bind g = f >>= g
end Circuit

namespace Gadgets.And.And8
theorem and_times_two_add_xor {x y : ℕ} (hx : x < 256) (hy : y < 256) : 2 * (x &&& y) + (x ^^^ y) = x + y
end Gadgets.And.And8

namespace Gadgets.And.And8
theorem xor_le_add {x y : ℕ} (hx : x < 256) (hy : y < 256) : x ^^^ y ≤ x + y
end Gadgets.And.And8

namespace Gadgets.And.And8
theorem two_and_le_add {x y : ℕ} (hx : x < 256) (hy : y < 256) : 2 * (x &&& y) ≤ x + y
end Gadgets.And.And8

namespace Gadgets.And.And8
lemma val_two : (2 : F p).val = 2
end Gadgets.And.And8

namespace Gadgets.And.And8
lemma two_non_zero : (2 : F p) ≠ 0
end Gadgets.And.And8

namespace Gadgets.And.And8
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.And.And8

namespace Gadgets.Keccak256.Chi
lemma chi_loop (state : Vector ℕ 25) :
    Specs.Keccak256.chi state = .mapFinRange 25 fun i => state[i] ^^^ ((not64 state[i + 5]) &&& state[i + 10])
end Gadgets.Keccak256.Chi

namespace Gadgets.Keccak256.Chi
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.Chi

namespace TestSimplifyProvableStructEval
lemma test_eval_eq_struct_literal {F : Type} [Field F] (env : Environment F)
    (x_var y_var z_var : Var field F)
    (h : eval env (TestInputs.mk x_var y_var z_var) = TestInputs.mk 1 2 3) :
    Expression.eval env x_var = 1 ∧ Expression.eval env y_var = 2 ∧ Expression.eval env z_var = 3
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_struct_literal_eq_eval {F : Type} [Field F] (env : Environment F)
    (x_var y_var z_var : Var field F)
    (h : TestInputs.mk 1 2 3 = eval env (TestInputs.mk x_var y_var z_var)) :
    1 = Expression.eval env x_var ∧ 2 = Expression.eval env y_var ∧ 3 = Expression.eval env z_var
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_eval_eq_struct_variable {F : Type} [Field F] (env : Environment F) (input : TestInputs F)
    (x_var y_var z_var : Var field F)
    (h : eval env (TestInputs.mk x_var y_var z_var) = input) :
    TestInputs.mk (Expression.eval env x_var) (Expression.eval env y_var) (Expression.eval env z_var) = input
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_eval_in_conjunction {F : Type} [Field F] (env : Environment F) (x : F)
    (x_var y_var z_var : Var field F)
    (h : eval env (TestInputs.mk x_var y_var z_var) = TestInputs.mk 1 2 3 ∧ x = 7) :
    Expression.eval env x_var = 1 ∧ Expression.eval env y_var = 2 ∧ Expression.eval env z_var = 3 ∧ x = 7
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_nested_conjunctions_with_eval {F : Type} [Field F] (env : Environment F) (x : F)
    (x_var y_var z_var a_var b_var : Var field F)
    (h : (eval env (TestInputs.mk x_var y_var z_var) = TestInputs.mk 1 2 3 ∧ x = 7) ∧
         eval env (SimpleStruct.mk a_var b_var) = SimpleStruct.mk 8 9) :
    Expression.eval env x_var = 1 ∧ Expression.eval env a_var = 8
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_multiple_eval_expressions {F : Type} [Field F] (env1 env2 : Environment F)
    (x1_var y1_var z1_var : Var field F) (a2_var b2_var : Var field F)
    (h1 : eval env1 (TestInputs.mk x1_var y1_var z1_var) = TestInputs.mk 1 2 3)
    (h2 : eval env2 (SimpleStruct.mk a2_var b2_var) = SimpleStruct.mk 4 5) :
    Expression.eval env1 x1_var = 1 ∧ Expression.eval env2 b2_var = 5
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_complex_eval {F : Type} [Field F] (env : Environment F)
    (a_var b_var c_var d_var e_var : Var field F)
    (h : eval env (TestInputs.mk (a_var + b_var) (c_var * d_var) e_var) = TestInputs.mk 5 6 7) :
    Expression.eval env (a_var + b_var) = 5 ∧
    Expression.eval env (c_var * d_var) = 6 ∧
    Expression.eval env e_var = 7
end TestSimplifyProvableStructEval

namespace TestSimplifyProvableStructEval
theorem test_conjunction_with_base_and_non_base {F : Type} [Field F] (env : Environment F) (x : F)
    (x_var y_var z_var : Var field F) (s1 s2 : Var SimpleStruct F)
    (h : (eval env (TestInputs.mk x_var y_var z_var) = TestInputs.mk 1 2 3 ∧ x = 7) ∧
         eval env s1 = eval env s2) :
    Expression.eval env x_var = 1
end TestSimplifyProvableStructEval

namespace Gadgets.Addition32.Theorems
lemma lift_val1 {x y b : (F p)} (x_byte : x.val < 256) (y_byte : y.val < 256) (b_bool : IsBool b) :
    (x + y + b).val = (x.val + y.val + b.val)
end Gadgets.Addition32.Theorems

namespace Gadgets.Addition32.Theorems
lemma lift_val2 {x b : (F p)} (x_byte : x.val < 256) (b_bool : IsBool b) :
    (b * 256 + x).val = (b.val * 256 + x.val)
end Gadgets.Addition32.Theorems

namespace Gadgets.Addition32.Theorems
omit p_large_enough in
lemma zify_bool {b : (F p)} (b_bool : IsBool b) : (↑(b.val) : ℤ) = 0 ∨ (↑(b.val) : ℤ) = 1
end Gadgets.Addition32.Theorems

namespace Gadgets.Addition32.Theorems
theorem add32_soundness {x0 x1 x2 x3 y0 y1 y2 y3 carry_in c0 c1 c2 c3 z0 z1 z2 z3 : F p}
    (x0_byte : x0.val < 256) (x1_byte : x1.val < 256) (x2_byte : x2.val < 256) (x3_byte : x3.val < 256)
    (y0_byte : y0.val < 256) (y1_byte : y1.val < 256) (y2_byte : y2.val < 256) (y3_byte : y3.val < 256)
    (z0_byte : z0.val < 256) (z1_byte : z1.val < 256) (z2_byte : z2.val < 256) (z3_byte : z3.val < 256)
    (carry_in_bool : IsBool carry_in) (c0_bool : IsBool c0)
    (c1_bool : IsBool c1) (c2_bool : IsBool c2) (c3_bool : IsBool c3)
    (h0 : x0 + y0 + carry_in = c0 * 256 + z0)
    (h1 : x1 + y1 + c0 = c1 * 256 + z1)
    (h2 : x2 + y2 + c1 = c2 * 256 + z2)
    (h3 : x3 + y3 + c2 = c3 * 256 + z3) :
    ZMod.val z0 + ZMod.val z1 * 256 + ZMod.val z2 * 256 ^ 2 + ZMod.val z3 * 256 ^ 3 =
      (ZMod.val x0 + ZMod.val x1 * 256 + ZMod.val x2 * 256 ^ 2 + ZMod.val x3 * 256 ^ 3 +
            (ZMod.val y0 + ZMod.val y1 * 256 + ZMod.val y2 * 256 ^ 2 + ZMod.val y3 * 256 ^ 3) +
          ZMod.val carry_in) %
        2 ^ 32 ∧
    ZMod.val c3 =
      (ZMod.val x0 + ZMod.val x1 * 256 + ZMod.val x2 * 256 ^ 2 + ZMod.val x3 * 256 ^ 3 +
            (ZMod.val y0 + ZMod.val y1 * 256 + ZMod.val y2 * 256 ^ 2 + ZMod.val y3 * 256 ^ 3) +
          ZMod.val carry_in) /
        2 ^ 32
end Gadgets.Addition32.Theorems

namespace Tables.BLAKE3.ProcessBlocksInductive
private lemma ZMod_val_64 :
    ZMod.val (n:=p) 64 = 64
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
private lemma step_process_block (env : Environment (F p))
    (acc_var : Var ProcessBlocksState (F p)) (x_var : Var BlockInput (F p))
    (acc : ProcessBlocksState (F p)) (x : BlockInput (F p))
    (h_eval : eval env acc_var = acc ∧ eval env x_var = x)
    (h_x : x.block_exists = 1)
    (h_holds : Circuit.ConstraintsHold.Soundness env ((step acc_var x_var).operations (size ProcessBlocksState + size BlockInput)))
    (acc_normalized : acc.Normalized)
    (x_normalized : x.Normalized)
    (blocks_compressed_not_many : acc.toChunkState.blocks_compressed < 2^32 - 1) :
    (eval env ((step acc_var x_var).output (size ProcessBlocksState + size BlockInput))).toChunkState =
      processBlockWords acc.toChunkState (x.block_data.map (·.value)) ∧
    (eval env ((step acc_var x_var).output (size ProcessBlocksState + size BlockInput))).Normalized
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
lemma soundness : InductiveTable.Soundness (F p) ProcessBlocksState BlockInput Spec step
end Tables.BLAKE3.ProcessBlocksInductive

namespace Tables.BLAKE3.ProcessBlocksInductive
lemma completeness : InductiveTable.Completeness (F p) ProcessBlocksState BlockInput InputAssumptions InitialStateAssumptions Spec step
end Tables.BLAKE3.ProcessBlocksInductive

namespace Gadgets.BLAKE3.FinalizeChunk
omit p_large_enough in
lemma bytesToWords_normalized (env : Environment (F p)) (bytes_var : Var (ProvableVector field 64) (F p))
    (h_bytes : ∀ i : Fin 64, (eval env bytes_var)[i].val < 256) :
    ∀ i : Fin 16, (eval env (α := ProvableVector U32 16) (bytesToWords bytes_var))[i].Normalized
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
omit [Fact (Nat.Prime p)] p_large_enough in
lemma block_len_normalized (buffer_len : F p) (h : buffer_len.val ≤ 64) :
    (U32.mk buffer_len 0 0 0 : U32 (F p)).Normalized
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
private lemma ZMod_val_chunkEnd :
    ZMod.val (n:=p) ↑chunkEnd = 2
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
omit p_large_enough in
private lemma eval_bytesToWords (env : Environment (F p))
    (input_var_buffer_data : Vector (Expression (F p)) 64) :
    eval env (α := ProvableVector U32 16) (bytesToWords input_var_buffer_data) =
      bytesToWords (eval (α:=ProvableVector field 64) env input_var_buffer_data)
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.BLAKE3.FinalizeChunk
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.FinalizeChunk

namespace Gadgets.Keccak256.Theta
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.Theta

namespace ProvableType
@[circuit_norm]
theorem fromElements_eval_toElements {env : Environment F} (x : α (Expression F)) :
  fromElements (Vector.map (Expression.eval env) (toElements x)) = eval env x
end ProvableType

namespace ProvableStruct
theorem fromElements_toElements {F} : (cs : List WithProvableType) → (xs : ProvableTypeList F cs) →
    componentsFromElements cs (componentsToElements cs xs) = xs
end ProvableStruct

namespace ProvableStruct
theorem toElements_fromElements {F} : (cs : List WithProvableType) → (xs : Vector F (combinedSize' cs)) →
    componentsToElements cs (componentsFromElements cs xs) = xs
end ProvableStruct

namespace ProvableStruct
@[circuit_norm ↓ high]
theorem eval_eq_eval {α : TypeMap} [ProvableStruct α] : ∀ (env : Environment F) (x : Var α F),
    ProvableType.eval env x = ProvableStruct.eval env x
end ProvableStruct

namespace ProvableStruct
@[circuit_norm ↓ high]
theorem varFromOffset_eq_varFromOffset {α : TypeMap} [ProvableStruct α] (offset : ℕ) :
    ProvableType.varFromOffset (F:=F) α offset = ProvableStruct.varFromOffset α offset
end ProvableStruct

namespace ProvableType
@[circuit_norm ↓ high]
theorem eval_field {F : Type} [Field F] (env : Environment F) (x : Var field F) :
  ProvableType.eval env x = Expression.eval env x
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem varFromOffset_field {F} (offset : ℕ) :
  varFromOffset (F:=F) field offset = var ⟨offset⟩
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem eval_fields {F : Type} [Field F] (env : Environment F) (x : Var (fields n) F) :
  ProvableType.eval env x = x.map (Expression.eval env)
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem varFromOffset_fields {F} (offset : ℕ) :
  varFromOffset (F:=F) (fields n) offset = .mapRange n fun i => var ⟨offset + i⟩
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem eval_fieldPair {F : Type} [Field F] (env : Environment F) (t : Var fieldPair F) :
  ProvableType.eval env t = (match t with | (x, y) => (Expression.eval env x, Expression.eval env y))
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem eval_fieldTriple {F : Type} [Field F] (env : Environment F) (t : Var fieldTriple F) :
  ProvableType.eval env t = (match t with
    | (x, y, z) => (Expression.eval env x, Expression.eval env y, Expression.eval env z))
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem varFromOffset_fieldPair {F} (offset : ℕ) :
  varFromOffset (F:=F) fieldPair offset = (var ⟨offset⟩, var ⟨offset + 1⟩)
end ProvableType

namespace ProvableType
@[circuit_norm ↓]
theorem varFromOffset_fieldTriple {F} (offset : ℕ) :
  varFromOffset (F:=F) fieldTriple offset = (var ⟨offset⟩, var ⟨offset + 1⟩, var ⟨offset + 2⟩)
end ProvableType

namespace ProvableType
lemma toElements_comp_fromElements {F} :
    toElements ∘ @fromElements α _ F = id
end ProvableType

namespace ProvableType
lemma fromElements_comp_toElements {F} :
    fromElements ∘ @toElements α _ F = id
end ProvableType

namespace ProvableType
lemma fromElements_eq_iff {M : TypeMap} [ProvableType M] {F : Type} {A : Vector F (size M)} {B : M F} :
    fromElements A = B ↔ A = toElements B
end ProvableType

namespace ProvableType
lemma fromElements_eq_iff' {M : TypeMap} [ProvableType M] {F : Type} {B : Vector F (size M)} {A : M F} :
    A = fromElements B ↔ toElements A = B
end ProvableType

namespace ProvableType
@[circuit_norm]
theorem eval_const {F : Type} [Field F] {α : TypeMap} [ProvableType α] {env : Environment F} {x : α F} :
    eval env (const x) = x
end ProvableType

namespace ProvableType
theorem eval_varFromOffset {α : TypeMap} [ProvableType α] (env : Environment F) (offset : ℕ) :
    eval env (varFromOffset α offset) = fromElements (.mapRange (size α) fun i => env.get (offset + i))
end ProvableType

namespace ProvableType
theorem ext_iff {F : Type} {α : TypeMap} [ProvableType α] (x y : α F) :
    x = y ↔ ∀ i (hi : i < size α), (toElements x)[i] = (toElements y)[i]
end ProvableType

namespace ProvableType
theorem eval_fromElements {F : Type} [Field F] {α : TypeMap} [ProvableType α] (env : Environment F)
  (xs : Vector (Expression F) (size α)) :
    eval env (fromElements (F:=Expression F) xs) = fromElements (xs.map env)
end ProvableType

namespace ProvableType
theorem eval_fromVars {F : Type} [Field F] {α : TypeMap} [ProvableType α] (env : Environment F)
  (xs : Vector (Expression F) (size α)) :
    eval env (fromVars xs) = fromElements (xs.map env)
end ProvableType

namespace ProvableType
theorem getElem_eval_toElements {F : Type} [Field F] {α : TypeMap} [ProvableType α]
  {env : Environment F} (x : α (Expression F)) (i : ℕ) (hi : i < size α) :
    Expression.eval env (toElements x)[i] = (toElements (eval env x))[i]
end ProvableType

namespace ProvableType
theorem getElem_eval_toVars {F : Type} [Field F] {α : TypeMap} [ProvableType α]
  {env : Environment F} (x : Var α F) (i : ℕ) (hi : i < size α) :
    Expression.eval env (toVars x)[i] = (toElements (eval env x))[i]
end ProvableType

namespace ProvableType
theorem getElem_eval_fields {F : Type} [Field F] {n : ℕ} {env : Environment F}
  (x : Var (fields n) F) (i : ℕ) (hi : i < n) :
    Expression.eval env x[i] = (eval env x)[i]
end ProvableType

theorem eval_vector (env : Environment F)
  (x : Var (ProvableVector α n) F) :
    eval env x = x.map (eval env)

theorem getElem_eval_vector (env : Environment F) (x : Var (ProvableVector α n) F) (i : ℕ) (h : i < n) :
    (eval env x[i]) = (eval env x)[i]

lemma eval_vector_eq_get {M : TypeMap} [NonEmptyProvableType M] {n : ℕ} (env : Environment F)
    (vars : Vector (Var M F) n)
    (vals : Vector (M F) n)
    (h : (eval env vars : ProvableVector _ _ _) = (vals : ProvableVector _ _ _))
    (i : ℕ) (h_i : i < n) :
    eval env vars[i] = vals[i]

lemma eval_vector_take {M : TypeMap} [NonEmptyProvableType M] {n : ℕ} (env : Environment F)
    (vars : Var (ProvableVector M n) F) (i : ℕ) :
    (eval env (vars.take i) : ProvableVector _ _ _) = (eval env vars).take i

lemma eval_vector_takeShort {M : TypeMap} [NonEmptyProvableType M] {n : ℕ} (env : Environment F)
    (vars : Var (ProvableVector M n) F) (i : ℕ) (h_i : i < n) :
    (eval env (vars.takeShort i h_i) : ProvableVector _ _ _) = (eval env vars).takeShort i h_i

theorem varFromOffset_vector {F : Type} [Field F] {α : TypeMap} [NonEmptyProvableType α] (offset : ℕ) :
    varFromOffset (F:=F) (ProvableVector α n) offset
    = .mapRange n fun i => varFromOffset α (offset + (size α)*i)

@[circuit_norm ↓ high]
theorem eval_pair {α β: TypeMap} [ProvableType α] [ProvableType β] (env : Environment F)
  (a : Var α F) (b : Var β F) :
    eval (α:=ProvablePair α β) env (a, b) = (eval env a, eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_left_expr {β : TypeMap} [ProvableType β] (env : Environment F)
  (a : Expression F) (b : Var β F) :
    eval (α:=ProvablePair field β) env (a, b) = (Expression.eval env a, eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_right_expr {α : TypeMap} [ProvableType α] (env : Environment F)
  (a : Var α F) (b : Expression F) :
    eval (α:=ProvablePair α field) env (a, b) = (eval env a, Expression.eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_both_expr (env : Environment F)
  (a b : Expression F) :
    eval (α:=ProvablePair field field) env (a, b) = (Expression.eval env a, Expression.eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_left_vector_expr {n : ℕ} {β : TypeMap} [ProvableType β] (env : Environment F)
  (a : Vector (Expression F) n) (b : Var β F) :
    eval (α:=ProvablePair (fields n) β) env (a, b) = (eval env a, eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_right_vector_expr {n : ℕ} {α : TypeMap} [ProvableType α] (env : Environment F)
  (a : Var α F) (b : Vector (Expression F) n) :
    eval (α:=ProvablePair α (fields n)) env (a, b) = (eval env a, eval env b)

@[circuit_norm ↓ high]
theorem eval_pair_both_vector_expr {n m : ℕ} (env : Environment F)
  (a : Vector (Expression F) n) (b : Vector (Expression F) m) :
    eval (α:=ProvablePair (fields n) (fields m)) env (a, b) = (eval env a, eval env b)

omit [Field F] in
@[circuit_norm ↓ high]
theorem varFromOffset_pair {α β: TypeMap} [ProvableType α] [ProvableType β] (offset : ℕ) :
    varFromOffset (F:=F) (ProvablePair α β) offset
    = (varFromOffset α offset, varFromOffset β (offset + size α))

namespace Gadgets.BLAKE3.ApplyRounds
lemma blake3_value_eq_map_value {p : ℕ} (msg : Vector (U32 (F p)) 16) :
  BLAKE3State.value msg = Vector.map U32.value msg
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
lemma applyRounds_eq_applySevenRounds
    (chaining_value : Vector ℕ 8)
    (block_words : Vector ℕ 16)
    (counter : ℕ)
    (block_len : ℕ)
    (flags : ℕ) :
    applyRounds chaining_value block_words counter block_len flags =
    applySevenRounds
      (#v[
        chaining_value[0], chaining_value[1], chaining_value[2], chaining_value[3],
        chaining_value[4], chaining_value[5], chaining_value[6], chaining_value[7],
        iv[0].toNat, iv[1].toNat, iv[2].toNat, iv[3].toNat,
        counter % 2^32, counter / 2^32, block_len, flags
      ])
      block_words
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
lemma eval_decomposeNatExpr_small (env : Environment (F p)) (x : ℕ) :
    x < 256^4 ->
    (eval env (U32.decomposeNatExpr x)).value = x
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
lemma initial_state_and_messages_are_normalized
    (env : Environment (F p))
    (input_var : Var Inputs (F p))
    (block_words : BLAKE3State (F p))
    (chaining_value counter_high counter_low block_len flags)
    (h_input : eval env input_var = { chaining_value, block_words, counter_high, counter_low, block_len, flags })
    (h_normalized : Assumptions { chaining_value, block_words, counter_high, counter_low, block_len, flags }) :
    (eval env (initializeStateVector input_var)).Normalized ∧ ∀ (i : Fin 16), block_words[i].Normalized
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.ApplyRounds
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.ApplyRounds

namespace Gadgets.BLAKE3.Round
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.BLAKE3.Round

namespace Gadgets.Rotation64Bits
theorem soundness (offset : Fin 8) : Soundness (F p) (elaborated offset) Assumptions (Spec offset)
end Gadgets.Rotation64Bits

namespace Gadgets.Rotation64Bits
theorem completeness (offset : Fin 8) : Completeness (F p) (elaborated offset) Assumptions
end Gadgets.Rotation64Bits

namespace Utils.Rotation
lemma rotRight64_def (x : ℕ) (off : ℕ) (hx : x < 2^64) :
    rotRight64 x off = x >>> (off % 64) ||
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight64_off_mod_64 (x : ℕ) (off1 off2 : ℕ) (h : off1 = off2 % 64) :
    rotRight64 x off1 = rotRight64 x off2
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight64_fin (x : ℕ) (offset : Fin 64) :
    rotRight64 x offset.val = x % (2^offset.val) * (2^(64-offset.val)) + x / (2^offset.val)
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight64_testBit_of_lt (x r i : ℕ) (h : x < 2^64) (hi : i < 64 - r % 64) :
    (rotRight64 x r).testBit i = x.testBit (r % 64 + i)
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight64_testBit_of_ge (x r i : ℕ) (h : x < 2^64) (hi : i ≥ 64 - r % 64) :
    (rotRight64 x r).testBit i = (decide (i < 64) && x.testBit (i - (64 - r % 64)))
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight64_testBit (x r i : ℕ) (h : x < 2^64) :
    (rotRight64 x r).testBit i =
    if i < 64 - r % 64 then
      x.testBit (r % 64 + i)
    else (decide (i < 64) && x.testBit (i - (64 - r % 64)))
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight64_toBits (x r : ℕ) (h : x < 2^64):
    toBits 64 (rotRight64 x r) = (toBits 64 x).rotate r
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight64_lt (x r : ℕ) (h : x < 2^64) :
    rotRight64 x r < 2^64
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight64_composition (x n m : ℕ) (h : x < 2^64) :
    rotRight64 (rotRight64 x n) m = rotRight64 x (n + m)
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_def (x : ℕ) (off : ℕ) (hx : x < 2^32) :
    rotRight32 x off = x >>> (off % 32) ||
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_off_mod_32 (x : ℕ) (off1 off2 : ℕ) (h : off1 = off2 % 32) :
    rotRight32 x off1 = rotRight32 x off2
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_fin (x : ℕ) (offset : Fin 32) :
    rotRight32 x offset.val = x % (2^offset.val) * (2^(32-offset.val)) + x / (2^offset.val)
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_testBit_of_lt (x r i : ℕ) (h : x < 2^32) (hi : i < 32 - r % 32) :
    (rotRight32 x r).testBit i = x.testBit (r % 32 + i)
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_testBit_of_ge (x r i : ℕ) (h : x < 2^32) (hi : i ≥ 32 - r % 32) :
    (rotRight32 x r).testBit i = (decide (i < 32) && x.testBit (i - (32 - r % 32)))
end Utils.Rotation

namespace Utils.Rotation
lemma rotRight32_testBit (x r i : ℕ) (h : x < 2^32) :
    (rotRight32 x r).testBit i =
    if i < 32 - r % 32 then
      x.testBit (r % 32 + i)
    else (decide (i < 32) && x.testBit (i - (32 - r % 32)))
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight32_toBits (x r : ℕ) (h : x < 2^32):
    toBits 32 (rotRight32 x r) = (toBits 32 x).rotate r
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight32_lt (x r : ℕ) (h : x < 2^32) :
    rotRight32 x r < 2^32
end Utils.Rotation

namespace Utils.Rotation
theorem rotRight32_composition (x n m : ℕ) (h : x < 2^32) :
    rotRight32 (rotRight32 x n) m = rotRight32 x (n + m)
end Utils.Rotation

namespace Utils.Rotation
lemma two_power_val {p : ℕ} {offset : Fin 8} [p_large_enough: Fact (p > 2^16 + 2^8)] :
    ((2 ^ (8 - offset.val) % 256 : ℕ) : F p).val = 2 ^ (8 - offset.val) % 256
end Utils.Rotation

namespace Utils.Rotation
lemma mul_mod256_off {offset : ℕ} (ho : offset < 8) (x i : ℕ) (h : i > 0):
    (x * 256^i) % 2^offset = 0
end Utils.Rotation

namespace Utils.Rotation
lemma Nat.pow_minus_one_mul {x y : ℕ} (hy : y > 0) : x ^ y = x * x ^ (y - 1)
end Utils.Rotation

namespace Utils.Rotation
lemma divides_256_two_power {offset : ℕ} (ho : offset < 8) {x i : ℕ} (h : i > 0):
    (2^offset) ∣ x * (256 ^ i)
end Utils.Rotation

namespace Utils.Rotation
lemma div_256_two_power {offset : ℕ} (ho : offset < 8) {i : ℕ} (h : i > 0):
    256^i / 2^offset = 256^(i-1) * 2^(8-offset)
end Utils.Rotation

namespace Utils.Rotation
lemma mul_div_256_off {offset : ℕ} (ho : offset < 8) {x : ℕ} (i : ℕ) (h : i > 0):
    (x * 256^i) / 2^offset = x * 256^(i-1) * 2^(8-offset)
end Utils.Rotation

namespace Utils.Rotation
lemma two_off_eq_mod (offset : Fin 8) (h : offset.val ≠ 0):
    (2 ^ (8-offset.val) % 256) = 2 ^ (8-offset.val)
end Utils.Rotation

namespace Utils.Rotation
lemma shifted_decomposition_eq {offset : ℕ} (ho : offset < 8) {x1 x2 : ℕ} :
    (x1 / 2 ^ offset + x2 % 2 ^ offset * 2 ^ (8-offset)) * 256 =
    (2^offset * (x1 / 2^offset) + (x2 % 2^offset) * 256) * 2^(8-offset)
end Utils.Rotation

namespace Utils.Rotation
lemma shifted_decomposition_eq' {offset : ℕ} (ho : offset < 8) {x1 x2 i : ℕ} (hi : i > 0) :
    (x1 / 2 ^ offset + x2 % 2 ^ offset * 2 ^ (8-offset)) * 256^i =
    (2^offset * (x1 / 2^offset) + (x2 % 2^offset) * 256) * 2^(8-offset) * 256^(i-1)
end Utils.Rotation

namespace Utils.Rotation
lemma shifted_decomposition_eq'' {offset : ℕ} (ho : offset < 8) {x1 x2 i : ℕ} (hi : i > 0) :
    (x1 / 2 ^ offset + x2 % 2 ^ offset * 2 ^ (8-offset)) * 256^i =
    (2^offset * (x1 / 2^offset) * 2^(8-offset) * 256^(i-1) +
    (x2 % 2^offset) * 2^(8-offset) * 256^i)
end Utils.Rotation

namespace Utils.Rotation
lemma soundness_simp {offset : ℕ} {x y : ℕ} :
    x % 2 ^ offset * 2 ^ (8-offset) * y + 2 ^ offset * (x / 2 ^ offset) * 2 ^ (8-offset) * y =
    x * y * 2^ (8-offset)
end Utils.Rotation

namespace Utils.Rotation
lemma soundness_simp' {offset : ℕ} {x : ℕ} :
    x % 2 ^ offset * 2 ^ (8-offset) + 2 ^ offset * (x / 2 ^ offset) * 2 ^ (8-offset) =
    x * 2^ (8-offset)
end Utils.Rotation

namespace Utils.Rotation
lemma two_power_byte {p : ℕ} {offset : Fin 8} :
    ZMod.val ((2 ^ (8 - offset.val) % 256 : ℕ) : F p) < 256
end Utils.Rotation

namespace Gadgets.Xor32
omit [Fact (Nat.Prime p)] p_large_enough in
theorem soundness_to_u32 {x y z : U32 (F p)}
  (x_norm : x.Normalized) (y_norm : y.Normalized)
  (h_eq :
    z.x0.val = x.x0.val ^^^ y.x0.val ∧
    z.x1.val = x.x1.val ^^^ y.x1.val ∧
    z.x2.val = x.x2.val ^^^ y.x2.val ∧
    z.x3.val = x.x3.val ^^^ y.x3.val) : Spec { x, y } z
end Gadgets.Xor32

namespace Gadgets.Xor32
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Xor32

namespace Tables.KeccakInductive
lemma initialState_value : (initialState (p:=p)).value = .fill 25 0
end Tables.KeccakInductive

namespace Tables.KeccakInductive
lemma initialState_normalized : (initialState (p:=p)).Normalized
end Tables.KeccakInductive

namespace Tables.KeccakInductive
theorem tableStatement (output : KeccakState (F p)) : ∀ n > 0, ∀ trace, ∃ blocks, blocks.length = n - 1 ∧
  (formalTable output).statement n trace →
    output.Normalized ∧ output.value = absorbBlocks blocks
end Tables.KeccakInductive

namespace Gadgets.Addition32
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Addition32

namespace Gadgets.Keccak256.Permutation
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Keccak256.Permutation

namespace U32
@[ext]
lemma ext {x y : U32 (F p)}
    (h0 : x.x0 = y.x0)
    (h1 : x.x1 = y.x1)
    (h2 : x.x2 = y.x2)
    (h3 : x.x3 = y.x3)
    : x = y
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_lt_of_normalized {x : U32 (F p)} (hx : x.Normalized) : x.value < 2^32
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_horner (x : U32 (F p)) : x.value =
    x.x0.val + 2^8 * (x.x1.val + 2^8 * (x.x2.val + 2^8 * x.x3.val))
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_xor_horner {x : U32 (F p)} (hx : x.Normalized) : x.value =
    x.x0.val ^^^ 2^8 * (x.x1.val ^^^ 2^8 * (x.x2.val ^^^ 2^8 * x.x3.val))
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
lemma vals_valueNat (x : U32 (F p)) : x.vals.valueNat = x.value
end U32

namespace U32
lemma value_of_decomposedNat_of_small (x : ℕ) :
    x < 256^4 ->
    (decomposeNat (p:=p) x).value = x
end U32

namespace U32
lemma fromUInt32_normalized (x : UInt32) : (fromUInt32 (p:=p) x).Normalized
end U32

namespace U32
theorem value_fromUInt32 (x : UInt32) : value (fromUInt32 (p:=p) x) = x.toNat
end U32

namespace U32
lemma fromByte_value {x : Fin 256} : (fromByte x).value (p:=p) = x
end U32

namespace U32
lemma fromByte_normalized {x : Fin 256} : (fromByte x).Normalized (p:=p)
end U32

namespace U32
omit p_large_enough in
lemma value_injective_on_normalized (x y : U32 (F p))
    (hx : x.Normalized) (hy : y.Normalized) :
    x.value = y.value → x = y
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
@[circuit_norm]
lemma value_of_literal (a b c d : F p) :
  (U32.mk a b c d).value =
    a.val + b.val * 256 + c.val * 256^2 + d.val * 256^3
end U32

namespace U32
omit [Fact (Nat.Prime p)] p_large_enough in
@[circuit_norm]
lemma value_of_literal' (a b c d : field (F p)) :
  (U32.mk a b c d).value =
    a.val + b.val * 256 + c.val * 256^2 + d.val * 256^3
end U32

namespace U32
omit p_large_enough in
@[circuit_norm]
lemma eval_of_literal (env : Environment (F p)) (a b c d : Var field (F p)) :
    eval env (U32.mk a b c d) =
    U32.mk (eval env a) (eval env b) (eval env c) (eval env d)
end U32

namespace U32
omit p_large_enough in
omit [Fact (Nat.Prime p)] in
@[circuit_norm]
lemma normalized_componentwise (a b c d : F p):
    (U32.mk a b c d).Normalized ↔
    (a.val < 256 ∧ b.val < 256 ∧ c.val < 256 ∧ d.val < 256)
end U32

namespace U32
omit p_large_enough in
omit [Fact (Nat.Prime p)] in
@[circuit_norm]
lemma normalized_componentwise' (a b c d : field (F p)):
    (U32.mk a b c d).Normalized ↔
    (a.val < 256 ∧ b.val < 256 ∧ c.val < 256 ∧ d.val < 256)
end U32

namespace U32
omit p_large_enough in
@[circuit_norm]
lemma value_zero :
    (0 : U32 (F p)) = U32.mk 0 0 0 0
end U32

namespace U32
omit p_large_enough in
@[circuit_norm]
lemma value_zero_iff_zero {x : U32 (F p)} (hx : x.Normalized) :
    x.value = 0 ↔ x = U32.mk 0 0 0 0
end U32

namespace U32.ByteVector
theorem fromLimbs_toLimbs {F} (x : U32 F) :
    U32.fromLimbs x.toLimbs = x
end U32.ByteVector

namespace U32.ByteVector
theorem toLimbs_fromLimbs {F} (v : Vector F 4) :
    (U32.fromLimbs v).toLimbs = v
end U32.ByteVector

namespace U32.ByteVector
theorem ext_iff {F} {x y : U32 F} :
    x = y ↔ ∀ i (_ : i < 4), x.toLimbs[i] = y.toLimbs[i]
end U32.ByteVector

namespace U32.ByteVector
omit [Fact (Nat.Prime p)] p_large_enough in
theorem normalized_iff {x : U32 (F p)} :
    x.Normalized ↔ ∀ i (_ : i < 4), x.toLimbs[i].val < 256
end U32.ByteVector

namespace U32.ByteVector
lemma toLimbs_map {α β : Type} (x : U32 α) (f : α → β) :
  toLimbs (map x f) = (toLimbs x).map f
end U32.ByteVector

namespace U32.ByteVector
lemma getElem_eval_toLimbs {F} [Field F] {env : Environment F} {x : U32 (Expression F)} {i : ℕ} (hi : i < 4) :
    Expression.eval env x.toLimbs[i] = (eval env x).toLimbs[i]
end U32.ByteVector

namespace U32.ByteVector
lemma eval_fromLimbs {F} [Field F] {env : Environment F} {v : Vector (Expression F) 4} :
    eval env (U32.fromLimbs v) = .fromLimbs (v.map env)
end U32.ByteVector

namespace U32.ByteVector
private lemma reorganize_value (a b c d : ℕ) :
  a + 256 * (b + 256 * (c + 256 * d)) =
  2^8 * (2^8 * (2^8 * d + c) + b) + a
end U32.ByteVector

namespace U32.ByteVector
private lemma reorganize_value' (a b c d : ℕ) :
  a + b * 256 + c * 256 ^ 2 + d * 256 ^ 3 =
  2^8 * (2^8 * (2^8 * d + c) + b) + a
end U32.ByteVector

namespace U32.ByteVector
omit [Fact (Nat.Prime p)] p_large_enough in
lemma bitwise_componentwise (f : Bool → Bool → Bool)
    {x y : U32 (F p)} (x_norm : x.Normalized) (y_norm : y.Normalized) :
    f false false = false →
    Nat.bitwise f x.value y.value =
      Nat.bitwise f x.x0.val y.x0.val + 256 *
        (Nat.bitwise f x.x1.val y.x1.val + 256 *
          (Nat.bitwise f x.x2.val y.x2.val + 256 * Nat.bitwise f x.x3.val y.x3.val))
end U32.ByteVector

namespace U32.ByteVector
omit [Fact (Nat.Prime p)] p_large_enough in
lemma or_componentwise {x y : U32 (F p)} (x_norm : x.Normalized) (y_norm : y.Normalized) :
    x.value ||
end U32.ByteVector

namespace Gadgets.Rotation64Bytes
theorem soundness (off : Fin 8) : Soundness (F p) (elaborated off) Assumptions (Spec off)
end Gadgets.Rotation64Bytes

namespace Gadgets.Rotation64Bytes
theorem completeness (off : Fin 8) : Completeness (F p) (elaborated off) Assumptions
end Gadgets.Rotation64Bytes

namespace FieldUtils
theorem p_ne_zero : p ≠ 0
end FieldUtils

namespace FieldUtils
theorem ext {x y : F p} (h : x.val = y.val) : x = y
end FieldUtils

namespace FieldUtils
theorem ext_iff {x y : F p} : x = y ↔ x.val = y.val
end FieldUtils

namespace FieldUtils
theorem val_lt_p {p : ℕ} (x : ℕ) : (x < p) → (x : F p).val = x
end FieldUtils

namespace FieldUtils
lemma val_eq_256 [p_large_enough: Fact (p > 512)] : (256 : F p).val = 256
end FieldUtils

namespace FieldUtils
theorem natToField_zero : natToField 0 (p_prime.elim.pos) = 0
end FieldUtils

namespace FieldUtils
theorem natToField_eq {n : ℕ} {lt : n < p} (x : F p) (hx : x = natToField n lt) : x.val = n
end FieldUtils

namespace FieldUtils
theorem natToField_of_val_eq_iff {x : F p} {lt : x.val < p} : natToField (x.val) lt = x
end FieldUtils

namespace FieldUtils
theorem natToField_eq_natCast {n : ℕ} (lt : n < p) : ↑n = FieldUtils.natToField n lt
end FieldUtils

namespace FieldUtils
theorem val_of_natToField_eq {n : ℕ} (lt : n < p) : (natToField n lt).val = n
end FieldUtils

namespace FieldUtils
theorem mod_lt {x : F p} {c : ℕ+} {lt : c < p} : (mod x c lt).val < c
end FieldUtils

namespace FieldUtils
theorem floorDiv_lt {x : F p} {c : ℕ+} {d : ℕ} (h : x.val < c * d) : (floorDiv x c).val < d
end FieldUtils

namespace FieldUtils
lemma val_mul_floorDiv_self {x : F p} {c : ℕ+} (lt : c < p) : (c * (floorDiv x c)).val = c * (x.val / c)
end FieldUtils

namespace FieldUtils
theorem mod_add_floorDiv {x : F p} {c : ℕ+} (lt : c < p) :
    mod x c lt + c * (floorDiv x c) = x
end FieldUtils

namespace FieldUtils
theorem mul_val_of_dvd {x c : F p} :
    c.val ∣ (c * x).val → (c * x).val = c.val * x.val
end FieldUtils

namespace FieldUtils
theorem mul_nat_val_of_dvd {x : F p} (c : ℕ) (c_lt : c < p) {z : ℕ} :
    (c * x).val = c * z → (c * x).val = c * x.val
end FieldUtils

namespace FieldUtils
theorem mul_nat_val_eq_iff {x : F p} (c : ℕ) (c_pos : c > 0) (c_lt : c < p) {z : ℕ} :
    (c * x).val = c * z ↔ (x.val = z ∧ c * x.val < p)
end FieldUtils

namespace FieldUtils
omit p_prime in
lemma two_lt : 2 < p
end FieldUtils

namespace FieldUtils
lemma two_val : (2 : F p).val = 2
end FieldUtils

namespace FieldUtils
omit p_prime in
lemma two_pow_lt (n : ℕ) (hn : n ≤ 8) : 2^n < p
end FieldUtils

namespace FieldUtils
lemma two_pow_val (n : ℕ) (hn : n ≤ 8) : (2^n : F p).val = 2^n
end FieldUtils

namespace ByteUtils
theorem mod256_lt [Fact (p > 512)] (x : F p) : (mod256 x).val < 256
end ByteUtils

namespace ByteUtils
theorem floorDiv256_bool [Fact (p > 512)] {x : F p} (h : x.val < 512) :
  floorDiv256 x = 0 ∨ floorDiv256 x = 1
end ByteUtils

namespace ByteUtils
theorem mod_add_div256 [Fact (p > 512)] (x : F p) : x = mod256 x + 256 * (floorDiv256 x)
end ByteUtils

namespace ByteUtils
theorem splitTwoBytes_concatTwoBytes (x y : Fin 256) : splitTwoBytes (concatTwoBytes x y) = (x, y)
end ByteUtils

namespace ByteUtils
theorem byte_sum_do_not_wrap (x y : F p) [Fact (p > 512)]:
    x.val < 256 -> y.val < 256 -> (x + y).val = x.val + y.val
end ByteUtils

namespace ByteUtils
theorem byte_sum_le_bound (x y : F p) [Fact (p > 512)]:
    x.val < 256 -> y.val < 256 -> (x + y).val < 511
end ByteUtils

namespace ByteUtils
theorem byte_sum_and_bit_do_not_wrap (x y b : F p) [Fact (p > 512)]:
    x.val < 256 -> y.val < 256 -> b.val < 2 -> (b + x + y).val = b.val + x.val + y.val
end ByteUtils

namespace ByteUtils
theorem byte_sum_and_bit_do_not_wrap' (x y b : F p) [Fact (p > 512)]:
    x.val < 256 -> y.val < 256 -> b.val < 2 -> (x + y + b).val = x.val + y.val + b.val
end ByteUtils

namespace ByteUtils
theorem byte_sum_and_bit_lt_512 (x y b : F p) [Fact (p > 512)]:
    x.val < 256 -> y.val < 256 -> b.val < 2 -> (x + y + b).val < 512
end ByteUtils

namespace ByteUtils
theorem byte_plus_256_do_not_wrap (x : F p) [Fact (p > 512)]:
    x.val < 256 -> (x + 256).val = x.val + 256
end ByteUtils

namespace ByteUtils
lemma fromByte_lt (x : Fin 256) : (fromByte (p:=p) x).val < 256
end ByteUtils

namespace ByteUtils
lemma fromByte_eq (x : F p) (x_lt : x.val < 256) : fromByte ⟨ x.val, x_lt ⟩ = x
end ByteUtils

namespace Gadgets.Keccak256.KeccakRound
theorem soundness (rc : UInt64) : Soundness (F p) (elaborated rc) Assumptions (Spec rc)
end Gadgets.Keccak256.KeccakRound

namespace Gadgets.Keccak256.KeccakRound
theorem completeness (rc : UInt64) : Completeness (F p) (elaborated rc) Assumptions
end Gadgets.Keccak256.KeccakRound

namespace Tables.Fibonacci32
lemma fib_assignment : (recursiveRelation (p:=p)).finalAssignment.vars =
   #v[.input ⟨0, 0⟩, .input ⟨0, 1⟩, .input ⟨0, 2⟩, .input ⟨0, 3⟩, .input ⟨0, 4⟩, .input ⟨0, 5⟩, .input ⟨0, 6⟩,
      .input ⟨0, 7⟩, .input ⟨1, 0⟩, .input ⟨1, 1⟩, .input ⟨1, 2⟩, .input ⟨1, 3⟩, .input ⟨1, 4⟩, .input ⟨1, 5⟩,
      .input ⟨1, 6⟩, .input ⟨1, 7⟩, .input ⟨1, 4⟩, .aux 1, .input ⟨1, 5⟩, .aux 3, .input ⟨1, 6⟩, .aux 5,
      .input ⟨1, 7⟩, .aux 7]
end Tables.Fibonacci32

namespace Tables.Fibonacci32
lemma fib_vars (curr next : Row (F p) RowType) (aux_env : Environment (F p)) :
    let env := recursiveRelation.windowEnv ⟨<+> +> curr +> next, rfl⟩ aux_env;
    eval env (varFromOffset U32 0) = curr.x ∧
    eval env (varFromOffset U32 4) = curr.y ∧
    eval env (varFromOffset U32 8) = next.x ∧
    eval env (U32.mk (var ⟨16⟩) (var ⟨18⟩) (var ⟨20⟩) (var ⟨22⟩)) = next.y
end Tables.Fibonacci32

namespace Tables.Fibonacci32
lemma fib_constraints (curr next : Row (F p) RowType) (aux_env : Environment (F p))
  : recursiveRelation.ConstraintsHoldOnWindow ⟨<+> +> curr +> next, rfl⟩ aux_env →
  curr.y = next.x ∧
  (curr.x.Normalized → curr.y.Normalized → next.y.value = (curr.x.value + curr.y.value) % 2^32 ∧ next.y.Normalized)
end Tables.Fibonacci32

namespace Tables.Fibonacci32
lemma boundary_constraints (first_row : Row (F p) RowType) (aux_env : Environment (F p)) :
  Circuit.ConstraintsHold.Soundness (windowEnv boundary ⟨<+> +> first_row, rfl⟩ aux_env) boundary.operations →
  first_row.x.value = fib32 0 ∧ first_row.y.value = fib32 1 ∧ first_row.x.Normalized ∧ first_row.y.Normalized
end Tables.Fibonacci32

namespace Gadgets.Addition8.Theorems
theorem soundness_zero_carry (x y out carry_in : F p):
    x.val < 256 -> y.val < 256 -> out.val < 256  -> carry_in.val < 2 ->
    (carry_in + x + y - out = 0 -> (out.val = (carry_in.val + x.val + y.val) % 256
    ∧ (carry_in.val + x.val + y.val) / 256 = 0))
end Gadgets.Addition8.Theorems

namespace Gadgets.Addition8.Theorems
theorem soundness_one_carry (x y out carry_in : F p):
    x.val < 256 -> y.val < 256 -> out.val < 256 -> carry_in.val < 2 ->
    carry_in + x + y - out - 256 = 0 -> (out.val = (carry_in.val + x.val + y.val) % 256
    ∧ (carry_in.val + x.val + y.val) / 256 = 1)
end Gadgets.Addition8.Theorems

namespace Gadgets.Addition8.Theorems
theorem soundness (x y out carry_in carry_out : F p):
    x.val < 256 -> y.val < 256 ->
    out.val < 256 ->
    IsBool carry_in ->
    IsBool carry_out ->
    (x + y + carry_in + -out + -(carry_out * 256) = 0) ->
    (out.val = (x.val + y.val + carry_in.val) % 256
    ∧ carry_out.val = (x.val + y.val + carry_in.val) / 256)
end Gadgets.Addition8.Theorems

namespace Gadgets.Addition8.Theorems
theorem completeness_add [p_neq_zero : NeZero p] (x y carry_in : F p) :
    x.val < 256 ->
    y.val < 256 ->
    carry_in.val < 2 ->
    let carry_out := FieldUtils.floorDiv (x + y + carry_in) 256
    let z := ByteUtils.mod256 (x + y + carry_in)
    x + y + carry_in + -z + -(carry_out * 256) = 0
end Gadgets.Addition8.Theorems

namespace Gadgets.Addition8.Theorems
theorem completeness_bool [p_neq_zero : NeZero p] (x y carry_in : F p) :
    x.val < 256 ->
    y.val < 256 ->
    carry_in.val < 2 ->
    let carry_out := FieldUtils.floorDiv (x + y + carry_in) 256
    IsBool carry_out
end Gadgets.Addition8.Theorems

namespace U64
@[ext]
lemma ext {x y : U64 (F p)}
    (h0 : x.x0 = y.x0)
    (h1 : x.x1 = y.x1)
    (h2 : x.x2 = y.x2)
    (h3 : x.x3 = y.x3)
    (h4 : x.x4 = y.x4)
    (h5 : x.x5 = y.x5)
    (h6 : x.x6 = y.x6)
    (h7 : x.x7 = y.x7)
    : x = y
end U64

namespace U64
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_lt_of_normalized {x : U64 (F p)} (hx : x.Normalized) : x.value < 2^64
end U64

namespace U64
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_horner (x : U64 (F p)) : x.value =
    x.x0.val + 2^8 * (x.x1.val + 2^8 * (x.x2.val + 2^8 * (x.x3.val +
      2^8 * (x.x4.val + 2^8 * (x.x5.val + 2^8 * (x.x6.val + 2^8 * x.x7.val))))))
end U64

namespace U64
omit [Fact (Nat.Prime p)] p_large_enough in
theorem value_xor_horner {x : U64 (F p)} (hx : x.Normalized) : x.value =
    x.x0.val ^^^ 2^8 * (x.x1.val ^^^ 2^8 * (x.x2.val ^^^ 2^8 * (x.x3.val ^^^
      2^8 * (x.x4.val ^^^ 2^8 * (x.x5.val ^^^ 2^8 * (x.x6.val ^^^ 2^8 * x.x7.val))))))
end U64

namespace U64
omit [Fact (Nat.Prime p)] p_large_enough in
lemma vals_valueNat (x : U64 (F p)) : x.vals.valueNat = x.value
end U64

namespace U64
lemma fromUInt64_normalized (x : UInt64) : (fromUInt64 (p:=p) x).Normalized
end U64

namespace U64
theorem value_fromUInt64 (x : UInt64) : value (fromUInt64 (p:=p) x) = x.toNat
end U64

namespace U64
lemma fromByte_value {x : Fin 256} : (fromByte x).value (p:=p) = x
end U64

namespace U64
lemma fromByte_normalized {x : Fin 256} : (fromByte x).Normalized (p:=p)
end U64

namespace U64.ByteVector
theorem fromLimbs_toLimbs {F} (x : U64 F) :
    U64.fromLimbs x.toLimbs = x
end U64.ByteVector

namespace U64.ByteVector
theorem toLimbs_fromLimbs {F} (v : Vector F 8) :
    (U64.fromLimbs v).toLimbs = v
end U64.ByteVector

namespace U64.ByteVector
theorem ext_iff {F} {x y : U64 F} :
    x = y ↔ ∀ i (_ : i < 8), x.toLimbs[i] = y.toLimbs[i]
end U64.ByteVector

namespace U64.ByteVector
omit [Fact (Nat.Prime p)] p_large_enough in
theorem normalized_iff {x : U64 (F p)} :
    x.Normalized ↔ ∀ i (_ : i < 8), x.toLimbs[i].val < 256
end U64.ByteVector

namespace U64.ByteVector
lemma toLimbs_map {α β : Type} (x : U64 α) (f : α → β) :
  toLimbs (map x f) = (toLimbs x).map f
end U64.ByteVector

namespace U64.ByteVector
lemma getElem_eval_toLimbs {F} [Field F] {env : Environment F} {x : U64 (Expression F)} {i : ℕ} (hi : i < 8) :
    Expression.eval env x.toLimbs[i] = (eval env x).toLimbs[i]
end U64.ByteVector

namespace U64.ByteVector
lemma eval_fromLimbs {F} [Field F] {env : Environment F} {v : Vector (Expression F) 8} :
    eval env (U64.fromLimbs v) = .fromLimbs (v.map env)
end U64.ByteVector

theorem funext_heq {α α' β : Type} (h : α = α') {f : α → β} {g : α' → β} :
    (∀ x : α, f x = g (cast h x)) → HEq f g

theorem cast_apply {α β β' : Type} (h : β = β') (f : α → β) (x : α) :
    (cast (h ▸ rfl) f : α → β') x = cast (h ▸ rfl) (f x)

theorem cast_apply' {α α' β : Type} (h : α = α') (f : α → β) (x : α') :
    (cast (h ▸ rfl) f : α' → β) x = f (cast (h ▸ rfl) x : α)

theorem cast_fst {α α' β : Type} (h : α = α') (p : α × β) :
    (cast h p.1 : α') = (cast (h ▸ rfl) p : α' × β).1

theorem cast_snd {α β β' : Type} (h : β = β') (p : α × β) :
    (cast h p.2 : β') = (cast (h ▸ rfl) p : α × β').2

theorem fst_cast {α β β' : Type} (h : β = β') (p : α × β) :
    (cast (h ▸ rfl) p : α × β').1 = p.1

theorem snd_cast {α α' β : Type} (h : α = α') (p : α × β) :
    (cast (h ▸ rfl) p : α' × β).2 = p.2

theorem Fin.foldl_const_succ (n : ℕ) (f : Fin (n + 1) → α) (init : α) :
    Fin.foldl (n + 1) (fun _ i => f i) init = f (.last n)

theorem Fin.foldl_const_zero (f : Fin 0 → α) (init : α) :
    Fin.foldl 0 (fun _ i => f i) init = init

theorem Fin.foldl_const (n : ℕ) (f : Fin n → α) (init : α) :
  Fin.foldl n (fun _ i => f i) init = match n with

lemma Fin.foldl_eq_foldl_finRange (n : ℕ) (f : α → Fin n → α) (init : α) :
    Fin.foldl n f init = (List.finRange n).foldl f init

namespace BinaryOps
theorem List.foldl_and_IsBool (l : List ℕ) :
    IsBool (List.foldl (· &&& ·) 1 l : ℕ)
end BinaryOps

namespace BinaryOps
theorem List.and_foldl_eq_foldl (a : ℕ) (orig : ℕ) (l : List ℕ) :
    a &&& List.foldl (· &&& ·) orig l = List.foldl (· &&& ·) (a &&& orig) l
end BinaryOps

namespace Circomlib
omit [Fact (p > 2)] in
lemma sum_bound_of_binary_inputs {n ops : ℕ}
  (hnout : 2^(nbits ((2^n - 1) * ops)) < p) (inputs : BinSumInput n ops (F p))
  (h_binary : ∀ j k (hj : j < ops) (hk : k < n), IsBool inputs[j][k]) :
    (Fin.foldl ops (fun sum j => sum + fieldFromBits inputs[j]) 0).val < 2^(nbits ((2^n - 1) * ops))
end Circomlib

namespace Circomlib.BinSum
omit [Fact (p > 2)] in
lemma inputLinearSum_eval_eq_sum {n ops : ℕ} [hn : NeZero n]
  (env : Environment (F p))
  (input : Var (BinSumInput n ops) (F p))
  (input_val : BinSumInput n ops (F p))
  (h_eval : eval env input = input_val) :
    Expression.eval env (inputLinearSum n ops input) =
    Fin.foldl ops (fun acc j => acc + fieldFromBits input_val[j.val]) 0
end Circomlib.BinSum

namespace Examples.FemtoCairo
theorem femtoCairoTableStatement
    {programSize : ℕ} [NeZero programSize] (program : Fin programSize → (F p)) (h_programSize : programSize < p)
    {memorySize : ℕ} [NeZero memorySize] (memory : Fin memorySize → (F p)) (h_memorySize : memorySize < p)
  (state : State (F p)) : ∀ n > 0, ∀ trace,
  (femtoCairoFormalTable program h_programSize memory h_memorySize state).statement n trace →
    match
      Spec.femtoCairoMachineBoundedExecution program memory (some {pc:=0, ap:=0, fp:=0}) (n - 1) with
end Examples.FemtoCairo

namespace Gadgets.Xor64
omit [Fact (Nat.Prime p)] p_large_enough in
theorem soundness_to_u64 {x y z : U64 (F p)}
  (x_norm : x.Normalized) (y_norm : y.Normalized)
  (h_eq :
    z.x0.val = x.x0.val ^^^ y.x0.val ∧
    z.x1.val = x.x1.val ^^^ y.x1.val ∧
    z.x2.val = x.x2.val ^^^ y.x2.val ∧
    z.x3.val = x.x3.val ^^^ y.x3.val ∧
    z.x4.val = x.x4.val ^^^ y.x4.val ∧
    z.x5.val = x.x5.val ^^^ y.x5.val ∧
    z.x6.val = x.x6.val ^^^ y.x6.val ∧
    z.x7.val = x.x7.val ^^^ y.x7.val) : Spec { x, y } z
end Gadgets.Xor64

namespace Gadgets.Xor64
theorem soundness : Soundness (F p) elaborated Assumptions Spec
end Gadgets.Xor64

namespace Gadgets.Conditional
theorem soundness [DecidableEq F] : Soundness F (elaborated (F:=F) (M:=M)) Assumptions Spec
end Gadgets.Conditional

namespace Gadgets.Conditional
theorem completeness [DecidableEq F] : Completeness F (elaborated (F:=F) (M:=M)) Assumptions
end Gadgets.Conditional

namespace Gadgets.Rotation32Bytes
theorem soundness (off : Fin 4) : Soundness (F p) (elaborated off) Assumptions (Spec off)
end Gadgets.Rotation32Bytes

namespace Gadgets.Rotation32Bytes
theorem completeness (off : Fin 4) : Completeness (F p) (elaborated off) Assumptions
end Gadgets.Rotation32Bytes

theorem FormalCircuit.original_soundness (circuit : FormalCircuit F β α) :
    ∀ (offset : ℕ) env (b_var : Var β F) (b : β F), eval env b_var = b → circuit.Assumptions b →
    -- if the constraints hold (original definition)
    ConstraintsHold env (circuit.main b_var |>.operations offset) →
    -- the spec holds
    let a := eval env (circuit.output b_var offset)
    circuit.Spec b a

theorem FormalCircuit.original_completeness (circuit : FormalCircuit F β α) :
    ∀ (offset : ℕ) env (b_var : Var β F) (b : β F), eval env b_var = b → circuit.Assumptions b →
    -- if the environment uses default witness generators (original definition)
    env.UsesLocalWitnesses offset (circuit.main b_var |>.operations offset) →
    -- the constraints hold (original definition)
    ConstraintsHold env (circuit.main b_var |>.operations offset)

theorem FormalAssertion.original_soundness (circuit : FormalAssertion F β) :
    ∀ (offset : ℕ) env (b_var : Var β F) (b : β F), eval env b_var = b → circuit.Assumptions b →
    -- if the constraints hold (original definition)
    ConstraintsHold env (circuit.main b_var |>.operations offset) →
    -- the spec holds
    circuit.Spec b

theorem FormalAssertion.original_completeness (circuit : FormalAssertion F β) :
    ∀ (offset : ℕ) env (b_var : Var β F) (b : β F), eval env b_var = b → circuit.Assumptions b →
    -- if the environment uses default witness generators (original definition)
    env.UsesLocalWitnesses offset (circuit.main b_var |>.operations offset) →
    -- the spec implies that the constraints hold (original definition)
    circuit.Spec b → ConstraintsHold env (circuit.main b_var |>.operations offset)

Array.size_extract in Init.Data.Array.Lemmas

Vector.size_toArray in Init.Data.Vector.Basic

NeZero.pos in Mathlib.Algebra.Order.Monoid.Canonical.Defs

Nat.pred_lt in Init.Data.Nat.Basic

NeZero.ne in Init.Data.NeZero

Nat.min_add_right_self in Init.Data.Nat.Lemmas

Nat.add_sub_self_left in Init.Data.Nat.Basic

Nat.mod_lt in Init.Prelude

Nat.ne_zero_of_lt in Init.Data.Nat.Basic

Nat.pos_iff_ne_zero in Init.Data.Nat.Basic

List.sorted_cons in Mathlib.Deprecated.Sort

Nat.le_succ_of_le in Init.Prelude

Nat.succ_pos in Init.Prelude
</all_available_lemmas>

<local_ctx>
import Mathlib.Algebra.Field.Basic

import Mathlib.Data.ZMod.Basic

import Clean.Utils.Primes

import Clean.Utils.Vector

import Clean.Circuit.Expression

import Clean.Circuit.Provable

import Clean.Circuit.Basic

import Clean.Utils.Field

import Clean.Types.U64

import Clean.Gadgets.Xor.ByteXorTable

section

variable {p : ℕ} [Fact p.Prime] [p_large_enough: Fact (p > 512)]

namespace Gadgets.Xor64

open Gadgets.Xor

structure Inputs (F : Type) where
  x: U64 F
  y: U64 F

instance : ProvableStruct Inputs where
  components := [U64, U64]
  toComponents := fun { x, y } => .cons x (.cons y .nil)
  fromComponents := fun (.cons x (.cons y .nil)) => { x, y }

def main (input : Var Inputs (F p)) : Circuit (F p) (Var U64 (F p))  := do
  let ⟨x, y⟩ := input
  let z ← witness fun env =>
    let z0 := (env x.x0).val ^^^ (env y.x0).val
    let z1 := (env x.x1).val ^^^ (env y.x1).val
    let z2 := (env x.x2).val ^^^ (env y.x2).val
    let z3 := (env x.x3).val ^^^ (env y.x3).val
    let z4 := (env x.x4).val ^^^ (env y.x4).val
    let z5 := (env x.x5).val ^^^ (env y.x5).val
    let z6 := (env x.x6).val ^^^ (env y.x6).val
    let z7 := (env x.x7).val ^^^ (env y.x7).val
    U64.mk z0 z1 z2 z3 z4 z5 z6 z7

  lookup ByteXorTable (x.x0, y.x0, z.x0)
  lookup ByteXorTable (x.x1, y.x1, z.x1)
  lookup ByteXorTable (x.x2, y.x2, z.x2)
  lookup ByteXorTable (x.x3, y.x3, z.x3)
  lookup ByteXorTable (x.x4, y.x4, z.x4)
  lookup ByteXorTable (x.x5, y.x5, z.x5)
  lookup ByteXorTable (x.x6, y.x6, z.x6)
  lookup ByteXorTable (x.x7, y.x7, z.x7)
  return z

def Assumptions (input : Inputs (F p)) :=
  let ⟨x, y⟩ := input
  x.Normalized ∧ y.Normalized

def Spec (input : Inputs (F p)) (z : U64 (F p)) :=
  let ⟨x, y⟩ := input
  z.value = x.value ^^^ y.value ∧ z.Normalized

instance elaborated : ElaboratedCircuit (F p) Inputs U64 where
  main := main
  localLength _ := 8
  output _ i0 := varFromOffset U64 i0

omit [Fact (Nat.Prime p)] p_large_enough in
theorem soundness_to_u64 {x y z : U64 (F p)}
  (x_norm : x.Normalized) (y_norm : y.Normalized)
  (h_eq :
    z.x0.val = x.x0.val ^^^ y.x0.val ∧
    z.x1.val = x.x1.val ^^^ y.x1.val ∧
    z.x2.val = x.x2.val ^^^ y.x2.val ∧
    z.x3.val = x.x3.val ^^^ y.x3.val ∧
    z.x4.val = x.x4.val ^^^ y.x4.val ∧
    z.x5.val = x.x5.val ^^^ y.x5.val ∧
    z.x6.val = x.x6.val ^^^ y.x6.val ∧
    z.x7.val = x.x7.val ^^^ y.x7.val) : Spec { x, y } z

theorem soundness : Soundness (F p) elaborated Assumptions Spec

lemma xor_val {x y : F p} (hx : x.val < 256) (hy : y.val < 256) :
    (x.val ^^^ y.val : F p).val = x.val ^^^ y.val
</local_ctx>

<target_theorem>
theorem completeness : Completeness (F p) elaborated Assumptions :=
</target_theorem>

