From 58b894ee6440ec722a217cd773bf2f10e5569f16 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Mon, 22 Jun 2026 15:44:39 -0500 Subject: [PATCH 1/8] perf: Merge the expensive portions of divRem proofs --- SP1Clean.lean | 1 + .../Chips/DivRemChip/Soundness/Div.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Divu.lean | 273 +------------ .../Chips/DivRemChip/Soundness/Divuw.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Divw.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Reader.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Rem.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Remu.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Remuw.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Remw.lean | 270 +------------ .../Chips/DivRemChip/Soundness/Tail.lean | 367 ++++++++++++++++++ docs/snapshots/compile-profile.md | 263 +++++++------ 12 files changed, 552 insertions(+), 2512 deletions(-) create mode 100644 SP1Clean/Proofs/Chips/DivRemChip/Soundness/Tail.lean diff --git a/SP1Clean.lean b/SP1Clean.lean index ac6c3f05..f19c2a48 100644 --- a/SP1Clean.lean +++ b/SP1Clean.lean @@ -32,6 +32,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Remu import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Remuw import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Remw import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Reader +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail import SP1Clean.Proofs.Chips.DivRemChip.Populate import SP1Clean.Proofs.Chips.DivRemChip.Populate.Abs import SP1Clean.Proofs.Chips.DivRemChip.Populate.Bounds diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Div.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Div.lean index 14b64057..602744ee 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Div.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Div.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `div` conjunct soundness (split for parallel compilation) @@ -28,7 +29,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `div` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `op_b_val`/`op_c_val` are now the raw register reads (adapter projections); `hbU`/`hcU` are their `isU64`, -- used by the shared `?_tail` `msb` range-checks. The `?_spec` arithmetic aliases the witnessed **operand** -- columns `b`/`c` (i₀+16/i₀+20) *locally* under these names and shadows `hbU`/`hcU` with the operand `isU64`. @@ -52,7 +54,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr -- alias the witnessed operand columns `b`/`c` under the old names (local to `?_spec`, so the shared -- `?_tail` is unaffected); the body's arithmetic uses these and the operand `isU64` shadows below. @@ -757,270 +758,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe interval_cases i <;> simp only [circuit_norm, Nat.add_zero] exacts [ha0, ha1, ha2, ha3] rw [hbeq, hceq] at hdiv_id; rw [haqc]; exact hdiv_id - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundDiv diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divu.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divu.lean index 9aaf8892..829de352 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divu.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divu.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `divu` conjunct soundness (split for parallel compilation) @@ -27,7 +28,11 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `divu` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + -- The `Operations.Requirements` tail is `Spec`-independent; it is supplied once by + -- `requirements_holds` via `soundness_of_specObligation`. `spec_proof_start` reproduces the stock + -- `circuit_proof_start` setup so the spec proof below is unchanged. + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +56,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr set input_op_b_val : Word (ZMod p) := Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + i }) with hbdef @@ -488,270 +492,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe interval_cases i <;> simp only [circuit_norm, Nat.add_zero] exacts [ha0, ha1, ha2, ha3] rw [hbeq, hceq] at hpair; rw [haqc]; exact hpair.1 - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundDivu diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divuw.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divuw.lean index e859d8d1..3da6225f 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divuw.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divuw.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `divuw` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `divuw` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr set input_op_b_val : Word (ZMod p) := Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + i }) with hbdef @@ -422,270 +423,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [toBitVec64_extractLsb hcU h_assumptions.2 hc0op hc1op, toBitVec64_extractLsb hbU h_assumptions.1 hb0op hb1op]] at hpair exact hpair.1 - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundDivuw diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divw.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divw.lean index 0bad08d3..f70f536e 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divw.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Divw.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `divw` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `divw` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr -- alias the witnessed operand columns `b`/`c` (i₀+16..19, i₀+20..23) under the old names (local to -- `?_spec`). `input_op_b_val`/`input_op_c_val` now mean the *operands*; the read is the adapter value. @@ -742,270 +743,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [toBitVec64_extractLsb hcU h_assumptions.2 hc0op hc1op, toBitVec64_extractLsb hbU h_assumptions.1 hb0op hb1op]] at hdivw_id exact hdivw_id - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundDivw diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Reader.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Reader.lean index f995b526..6791ae69 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Reader.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Reader.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — reader sub-`Spec` + `is_real`-binary (split for parallel compilation) @@ -40,7 +41,8 @@ set_option linter.unusedVariables false in set_option linter.unusedSimpArgs false in /-- Soundness of the reader sub-`Spec` + `is_real`-binary conjuncts. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `op_b_val`/`op_c_val` are now the raw register reads `input_adapter_op_b/c_memory_prev_value`; the `msb` -- range checks bound the reads directly (`hbU`/`hcU` are their `isU64`). The arithmetic operand `c` is the -- separate witnessed column at `i₀+20..23` — used only for the `is_c_0` split. @@ -64,7 +66,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · -- reader sub-`Spec` (`h_rtype` guarantee, its `circuit.Spec` projection unfolded) ∧ `is_real`-binary -- (the gate `E355`, per `Defs.lean`). obtain ⟨h_oir, -⟩ := h_input @@ -73,271 +74,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have hr := h_rtype h_bin simp only [Readers.RTypeReader.circuit] at hr exact ⟨hr, h_bin⟩ - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundReader diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Rem.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Rem.lean index 462eb2da..5f753d52 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Rem.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Rem.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `rem` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `rem` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr set input_op_b_val : Word (ZMod p) := Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + i }) with hbdef @@ -757,270 +758,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ] exacts [hb0, hb1, hb2, hb3] rw [hbeq, hceq] at hrem_id; rw [haqc]; exact hrem_id - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundRem diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remu.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remu.lean index 303b379e..7ebb6124 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remu.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remu.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `remu` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `remu` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr set input_op_b_val : Word (ZMod p) := Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + i }) with hbdef @@ -442,270 +443,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ] exacts [hb0, hb1, hb2, hb3] rw [hbeq, hceq] at hpair; rw [haqc]; exact hpair.2 - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundRemu diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remuw.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remuw.lean index bf28a961..6adf871a 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remuw.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remuw.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `remuw` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `remuw` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr set input_op_b_val : Word (ZMod p) := Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + i }) with hbdef @@ -422,270 +423,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [toBitVec64_extractLsb hcU h_assumptions.2 hc0op hc1op, toBitVec64_extractLsb hbU h_assumptions.1 hb0op hb1op]] at hpair exact hpair.2 - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundRemuw diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remw.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remw.lean index f13a5fc3..a209825b 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remw.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Remw.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly +import SP1Clean.Proofs.Chips.DivRemChip.Soundness.Tail /-! # `DivRemChip` — `remw` conjunct soundness (split for parallel compilation) @@ -27,7 +28,8 @@ set_option maxHeartbeats 128000000 in set_option linter.unusedSimpArgs false in /-- Soundness of the `remw` conjunct. -/ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by - circuit_proof_start + apply soundness_of_specObligation + spec_proof_start -- `Assumptions` lives in `Defs` (an enclosing namespace here, not a current-namespace member as in -- `Formal`), so `circuit_proof_start`'s `dsimp only [Assumptions]` doesn't fire; project via -- `.1`/`.2` (whnf-unfolds the `def`) rather than `rcases`/`obtain`. @@ -51,7 +53,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, e357, e359, e367, eopa0⟩ := h_own - refine ⟨?_spec, ?_tail⟩ · intro hr -- alias the witnessed operand columns `b`/`c` (i₀+16..19, i₀+20..23) under the old names (local to -- `?_spec`). `input_op_b_val`/`input_op_c_val` now mean the *operands*; the read is the adapter value. @@ -743,270 +744,5 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [toBitVec64_extractLsb hcU h_assumptions.2 hc0op hc1op, toBitVec64_extractLsb hbU h_assumptions.1 hb0op hb1op]] at hremw_id exact hremw_id - · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input - set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef - -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord - -- gadget tests *this* for the divide-by-zero split. - set cop : Word (ZMod p) := - Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) - with hcop - have hbin : input_is_real = 0 ∨ input_is_real = 1 := by - have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h - simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 - have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 - have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 - have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 - have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 - have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) - have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := - group_binary4 bdw brw bduw bruw (by omega) - have hsel5 := group_binary4 bdu bru bd br (by omega) - have hsel6 := group_binary2 bdw brw (by omega) - have hsel7 := group_binary2 bduw bruw (by omega) - have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) - + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by - linear_combination -e367 - have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ - have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - have hquotmsb := hms6.1 - have hremmsb := hms5.1 - have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) - from by linear_combination e48] - exact isU16_of_byteRowSpec (hb_q0 hrneg') - · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) - from by linear_combination e49] - exact isU16_of_byteRowSpec (hb_q1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 - hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 - hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) - have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] - exact isU16_of_byteRowSpec (hb_r0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) - = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] - exact isU16_of_byteRowSpec (hb_r1 hrneg') - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 - hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) - · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 - hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) - have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), - isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] - have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), - isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] - have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] - = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] - have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] - have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] - = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] - -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. - have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t - have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by - rcases hbin with h | h - · left; rw [h] at h13t; linear_combination h13t - · rcases he2 with h2 | h2 - · right; rw [h, h2] at h13t; linear_combination h13t - · left; rw [h, h2] at h13t; linear_combination h13t - have hirnw_imp : env.get (B + 4) = 1 → - input_is_real = 1 ∧ - env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by - intro hir - rcases hbin with h | h - · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - · rcases he2 with h2 | h2 - · exact ⟨h, h2⟩ - · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) - -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). - have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) - (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by - intro hr - have hrneg' : -input_is_real = -1 := by rw [hr] - have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr - have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 - simp only [circuit_norm] at h299 h300 h301 h302 - rw [iszeroword_result_proj] at h299 h300 h301 h302 - simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, - ZMod.val_one]; norm_num - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp - · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [hsem] at h299 h300 h301 h302 - apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) - from by linear_combination h299] - exact isU16_of_byteRowSpec (hb_absc0 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) - from by linear_combination h300] - exact isU16_of_byteRowSpec (hb_absc1 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) - from by linear_combination h301] - exact isU16_of_byteRowSpec (hb_absc2 hrneg') - · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) - from by linear_combination h302] - exact isU16_of_byteRowSpec (hb_absc3 hrneg') - -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the - -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, - -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified - -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the - -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. - have hcU_op : Word.isU64 cop := by - obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU - have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] - = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] - have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] - = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] - simp only [circuit_norm] at e21 e23 e35 e47 - refine operand_isU64 - (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) - (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc0]; linear_combination -e21 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc1]; linear_combination -e23 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc2]; linear_combination e35 - · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] - rw [← hcc3]; linear_combination e47 - · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, - -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). - have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, - hirnw⟩).1 - simp only [circuit_norm] at hcmsb - have hE10 := group_binary4 bd br bdw brw (by omega) - have h19 := e19; simp only [circuit_norm] at h19 - rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> - simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> - first - | exact Or.inl (by linear_combination -h19) - | exact Or.inr (by linear_combination -h19) - refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, - ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ - case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] - case eqb => exact Or.inl rfl - case eqc => exact Or.inl rfl - case eqb2 => exact Or.inl rfl - case eqc2 => exact Or.inl rfl - case isc0 => exact Or.inl rfl - case cpu => exact Or.inr hbin - case rtype => exact Or.inr hbin - case mulLo => - exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, - Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ - case mulHi => - refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, - fun h => (zero_ne_one h).elim, Or.inl rfl, - group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ - rcases group_binary4 bd br bdu bru (by omega) with h | h - · exact Or.inl (by linear_combination h) - · exact Or.inr (by linear_combination h) - case addc => - refine Or.inr ⟨fun hace => ?_, ?_⟩ - · have h286 := e286; simp only [circuit_norm] at h286 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 - exact zero_ne_one (h286.symm.trans hace) - · exact h - exact ⟨hcU_op, habscU hr⟩ - · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case addr => - refine Or.inr ⟨fun hrae => ?_, ?_⟩ - · have h288 := e288; simp only [circuit_norm] at h288 - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 - exact zero_ne_one (h288.symm.trans hrae) - · exact h - exact ⟨hrcU hr, habsrU hr⟩ - · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h - case lt => - -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. - have h305 := e305; simp only [circuit_norm] at h305 - rw [iszeroword_result_proj] at h305 - simp only [Vector.getElem_mapRange, circuit_norm] at h305 - rw [h_oir] at h305 - refine Or.inr ⟨fun hrcm => ?_, ?_⟩ - · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. - have hr : input_is_real = 1 := by - rcases hbin with h | h - · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) - · exact h - exact ⟨habsrU hr, hmaxU hr⟩ - · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. - rcases hbin with h | h - · left; rw [h] at h305; linear_combination -h305 - · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h - by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 - ∧ cop[3] = 0 - · left - rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - · right - rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem - simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, - Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem - rw [h, hsem] at h305; linear_combination -h305 - case msb0 => - -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 - case msb1 => - refine Or.inr ⟨fun _ => ?_, hirnw⟩ - rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 - case msb2 => - refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ - obtain ⟨hr, -⟩ := hirnw_imp hirnwg - have hrneg' : -input_is_real = -1 := by rw [hr] - exact isU16_of_byteRowSpec (hb_r3 hrneg') - case msb3 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 - case msb4 => - refine Or.inr ⟨fun _ => ?_, he2⟩ - rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 - case msb5 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ - case msb6 => - exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ end SP1Clean.DivRemChip.SoundRemw diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Tail.lean b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Tail.lean new file mode 100644 index 00000000..aa66b044 --- /dev/null +++ b/SP1Clean/Proofs/Chips/DivRemChip/Soundness/Tail.lean @@ -0,0 +1,367 @@ +import Lean +import SP1Clean.Proofs.Chips.DivRemChip.Defs +import SP1Clean.Proofs.Chips.DivRemChip.Soundness +import SP1Clean.Proofs.Chips.DivRemChip.Assembly + +/-! # `DivRemChip` — the shared `Operations.Requirements` tail (extracted once) + +The `?_tail` half of every `DivRemChip` soundness conjunct (the `Operations.Requirements` +obligation) is **byte-identical** across all nine `Soundness/.lean` files: it is +`Spec`-independent, depending only on `main`/`Assumptions`/`h_holds`. It was being re-elaborated +nine times at 128M heartbeats. Here it is proved **once**, as the `Requirements` half of a +trivial-spec (`fun _ _ _ => True`) `GeneralFormalCircuit.Soundness`, so `circuit_proof_start` sets +up the identical context. Each conjunct now discharges its `?_tail` by projecting +`(requirements_holds …).2`. -/ + +namespace SP1Clean.DivRemChip + +open Circuit +open Extracted (DivRemCols) +open SP1Clean.Channels (stateChannel byteChannel memoryChannel programChannel) + +variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 24 < p)] + +local instance : Fact (2 ^ 17 < p) := ⟨by have := Fact.out (p := 2 ^ 24 < p); omega⟩ + +set_option maxHeartbeats 128000000 in +set_option linter.unusedSimpArgs false in +set_option linter.unusedVariables false in +/-- The shared `Operations.Requirements` tail of the `DivRemChip` soundness conjuncts, proved once +(the `Spec`-independent second component). `circuit_proof_start` collapses the trivial `True` spec +via `true_and ∈ circuit_norm`, leaving exactly the `Requirements` goal the per-conjunct tail proves. -/ +lemma requirements_holds : + GeneralFormalCircuit.Soundness (ZMod p) main Assumptions + (fun _ _ _ => True : Inputs (ZMod p) → DivRemCols (ZMod p) → ProverData (ZMod p) → Prop) := by + circuit_proof_start + have hbU := h_assumptions.1 + have hcU := h_assumptions.2 + obtain ⟨h_mul_lo, h_mul_hi, + h_ctq0, h_ctq1, h_ctq2, h_ctq3, h_ctq4, h_ctq5, h_ctq6, h_ctq7, + h_eqb, h_eqc, h_eqb2, h_eqc2, h_isc0, h_addc, h_addr, h_lt, + h_msb0, h_msb1, h_msb2, h_msb3, h_msb4, h_msb5, h_msb6, h_cpu, h_rtype, h_own, + hb_e123, hb_e127, hb_e131, hb_e135, hb_e139, hb_e143, hb_e147, hb_e151, + hb_absc0, hb_absc1, hb_absc2, hb_absc3, hb_absr0, hb_absr1, hb_absr2, hb_absr3, + hb_q0, hb_q1, hb_q2, hb_q3, hb_r0, hb_r1, hb_r2, hb_r3, + hb_ctq0, hb_ctq1, hb_ctq2, hb_ctq3, hb_ctq4, hb_ctq5, hb_ctq6, hb_ctq7, + hb_e2r1, hb_e2q1⟩ := h_holds + simp only [ownAsserts] at h_own + obtain ⟨e13, e15, e17, e19, e20, e21, e22, e23, e29, e35, e41, e47, e48, e49, e51, e54, e57, e59, + e61, e64, e67, e69, e70, e71, e73, e76, e79, e81, e83, e86, e89, e91, e96, e99, e103, e105, e107, + e109, e111, e113, e115, e117, e119, e154, e157, e160, e163, e167, e171, e175, e179, e184, e189, + e194, e199, e204, e209, e214, e219, e225, e228, e230, e232, e234, e236, e238, e240, e242, e244, + e247, e250, e253, e256, e259, e262, e265, e268, e270, e272, e274, e276, e278, e280, e282, e284, + e286, e288, e299, e300, e301, e302, e305, e307, e309, e311, e313, e315, e317, e319, e321, e323, + e325, e327, e329, e331, e333, e335, e337, e339, e341, e343, e345, e347, e349, e351, e353, e355, + e357, e359, e367, eopa0⟩ := h_own + · obtain ⟨h_oir, -, -, -, -, -, ⟨h_ob, -, -⟩, -, ⟨h_oc, -, -⟩⟩ := h_input + set B := i₀ + 8 + 4 + 4 + 4 + 4 + 45 + 45 with hBdef + -- the arithmetic operand `c` (witnessed column at `i₀+20..23`), distinct from the read; the IsZeroWord + -- gadget tests *this* for the divide-by-zero split. + set cop : Word (ZMod p) := + Vector.map (Expression.eval env) (Vector.mapRange 4 fun i => var { index := i₀ + 8 + 4 + 4 + 4 + i }) + with hcop + have hbin : input_is_real = 0 ∨ input_is_real = 1 := by + have h := e355; simp only [circuit_norm] at h; rw [h_oir] at h; exact bool_of_mul_pred h + simp only [circuit_norm] at e325 e327 e329 e331 e333 e335 e337 e339 e367 + have bd := bool_of_mul_pred e325; have bdu := bool_of_mul_pred e327 + have br := bool_of_mul_pred e329; have bru := bool_of_mul_pred e331 + have bdw := bool_of_mul_pred e333; have brw := bool_of_mul_pred e335 + have bduw := bool_of_mul_pred e337; have bruw := bool_of_mul_pred e339 + have hvs := flags_val_sum bd bdu br bru bdw brw bduw bruw (by linear_combination -e367) + have he2 : env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 ∨ + env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 1 := + group_binary4 bdw brw bduw bruw (by omega) + have hsel5 := group_binary4 bdu bru bd br (by omega) + have hsel6 := group_binary2 bdw brw (by omega) + have hsel7 := group_binary2 bduw bruw (by omega) + have hsum567 : env.get (i₀ + 1) + env.get (i₀ + 3) + env.get i₀ + env.get (i₀ + 2) + + (env.get (i₀ + 4) + env.get (i₀ + 5)) + (env.get (i₀ + 6) + env.get (i₀ + 7)) = 1 := by + linear_combination -e367 + have hms6 := h_msb6 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ + have hms5 := h_msb5 ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ + have hquotmsb := hms6.1 + have hremmsb := hms5.1 + have hqcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) + (Vector.mapRange 4 fun i => var { index := i₀ + 8 + i }) : Word (ZMod p)) := by + intro hr + have hrneg' : -input_is_real = -1 := by rw [hr] + simp only [circuit_norm] at e48 e49 e51 e54 e59 e61 e64 e69 + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + · rw [show env.get (i₀ + 8) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4) + from by linear_combination e48] + exact isU16_of_byteRowSpec (hb_q0 hrneg') + · rw [show env.get (i₀ + 8 + 1) = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+4+1) + from by linear_combination e49] + exact isU16_of_byteRowSpec (hb_q1 hrneg') + · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e59 e54 e51 + hquotmsb (isU16_of_byteRowSpec (hb_q2 hrneg')) + · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e69 e64 e61 + hquotmsb (isU16_of_byteRowSpec (hb_q3 hrneg')) + have hrcU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) + (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4 + i }) : Word (ZMod p)) := by + intro hr + have hrneg' : -input_is_real = -1 := by rw [hr] + simp only [circuit_norm] at e70 e71 e73 e76 e81 e83 e86 e91 + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + · rw [show env.get (B + 7+8+8+11+11+11+4+4) + = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1) from by linear_combination e70] + exact isU16_of_byteRowSpec (hb_r0 hrneg') + · rw [show env.get (B + 7+8+8+11+11+11+4+4+1) + = env.get (B + 7+8+8+11+11+11+4+4+4+4+4+4+3+2+4+1+1+1) from by linear_combination e71] + exact isU16_of_byteRowSpec (hb_r1 hrneg') + · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e81 e76 e73 + hremmsb (isU16_of_byteRowSpec (hb_r2 hrneg')) + · exact comp_limb_isU16 hsel5 hsel6 hsel7 hsum567 e91 e86 e83 + hremmsb (isU16_of_byteRowSpec (hb_r3 hrneg')) + have habscU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) + (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11 + i }) : Word (ZMod p)) := by + intro hr + have hrneg' : -input_is_real = -1 := by rw [hr] + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + exacts [isU16_of_byteRowSpec (hb_absc0 hrneg'), isU16_of_byteRowSpec (hb_absc1 hrneg'), + isU16_of_byteRowSpec (hb_absc2 hrneg'), isU16_of_byteRowSpec (hb_absc3 hrneg')] + have habsrU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) + (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4 + i }) : Word (ZMod p)) := by + intro hr + have hrneg' : -input_is_real = -1 := by rw [hr] + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + exacts [isU16_of_byteRowSpec (hb_absr0 hrneg'), isU16_of_byteRowSpec (hb_absr1 hrneg'), + isU16_of_byteRowSpec (hb_absr2 hrneg'), isU16_of_byteRowSpec (hb_absr3 hrneg')] + have hbb1 : Expression.eval env input_var_adapter_op_b_memory_prev_value[1] + = input_adapter_op_b_memory_prev_value[1] := by rw [← h_ob]; simp [Vector.getElem_map] + have hcc1 : Expression.eval env input_var_adapter_op_c_memory_prev_value[1] + = input_adapter_op_c_memory_prev_value[1] := by rw [← h_oc]; simp [Vector.getElem_map] + have hbb3 : Expression.eval env input_var_adapter_op_b_memory_prev_value[3] + = input_adapter_op_b_memory_prev_value[3] := by rw [← h_ob]; simp [Vector.getElem_map] + have hcc3 : Expression.eval env input_var_adapter_op_c_memory_prev_value[3] + = input_adapter_op_c_memory_prev_value[3] := by rw [← h_oc]; simp [Vector.getElem_map] + -- `is_real_not_word` (E13 = `is_real * (1 - e2)`): binary, and `= 1 → is_real = 1 ∧ e2 = 0`. + have h13t := e13; simp only [circuit_norm] at h13t; rw [h_oir] at h13t + have hirnw : env.get (B + 4) = 0 ∨ env.get (B + 4) = 1 := by + rcases hbin with h | h + · left; rw [h] at h13t; linear_combination h13t + · rcases he2 with h2 | h2 + · right; rw [h, h2] at h13t; linear_combination h13t + · left; rw [h, h2] at h13t; linear_combination h13t + have hirnw_imp : env.get (B + 4) = 1 → + input_is_real = 1 ∧ + env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7) = 0 := by + intro hir + rcases hbin with h | h + · exfalso; rw [h, hir] at h13t; exact one_ne_zero (by linear_combination h13t) + · rcases he2 with h2 | h2 + · exact ⟨h, h2⟩ + · exfalso; rw [h, h2, hir] at h13t; exact one_ne_zero (by linear_combination h13t) + -- `max_abs_c_or_1` is `isU64`: `is_c_0 = 1 → #v[1,0,0,0]` (c = 0 branch), else `= abs_c` (E299-302). + have hmaxU : input_is_real = 1 → Word.isU64 (Vector.map (Expression.eval env) + (Vector.mapRange 4 fun i => var { index := B + 7+8+8+11+11+11+4+4+4 + i }) : Word (ZMod p)) := by + intro hr + have hrneg' : -input_is_real = -1 := by rw [hr] + have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr hr)) hr + have h299 := e299; have h300 := e300; have h301 := e301; have h302 := e302 + simp only [circuit_norm] at h299 h300 h301 h302 + rw [iszeroword_result_proj] at h299 h300 h301 h302 + simp only [Vector.getElem_mapRange, circuit_norm] at h299 h300 h301 h302 + by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 + ∧ cop[3] = 0 + · rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem + simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, + Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem + rw [hsem] at h299 h300 h301 h302 + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + · haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ + rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = 1 from by linear_combination h299, + ZMod.val_one]; norm_num + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = 0 from by linear_combination h300]; simp + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = 0 from by linear_combination h301]; simp + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = 0 from by linear_combination h302]; simp + · rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem + simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, + Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem + rw [hsem] at h299 h300 h301 h302 + apply Word.isU64_of_cases <;> simp only [circuit_norm, Nat.add_zero] + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4) = env.get (B + 7+8+8+11+11+11) + from by linear_combination h299] + exact isU16_of_byteRowSpec (hb_absc0 hrneg') + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 1) = env.get (B + 7+8+8+11+11+11 + 1) + from by linear_combination h300] + exact isU16_of_byteRowSpec (hb_absc1 hrneg') + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 2) = env.get (B + 7+8+8+11+11+11 + 2) + from by linear_combination h301] + exact isU16_of_byteRowSpec (hb_absc2 hrneg') + · rw [show env.get (B + 7+8+8+11+11+11+4+4+4 + 3) = env.get (B + 7+8+8+11+11+11 + 3) + from by linear_combination h302] + exact isU16_of_byteRowSpec (hb_absc3 hrneg') + -- `isU64` of the operand `c` (= `cop`), distinct from `isU64` of the read (`hcU`), feeding the + -- `MulOperation`/`AddOperation` Assumptions (`mulLo`/`mulHi`/`addc`). Low limbs equal the read (E21/E23, + -- bounded by `hcU`); high limbs are `read·(1-E2) + c_neg·E2·65535` (E35/E47) — closed by the verified + -- `operand_isU64` helper (`Extract.lean`) given `he2` + `c_neg` binary (E19: `c_neg = c_msb·E10`, the + -- `U16MSB` gadget binary, `E10` from the mutually-exclusive flags). TODO: discharge via `operand_isU64`. + have hcU_op : Word.isU64 cop := by + obtain ⟨hcr0, hcr1, hcr2, hcr3⟩ := Word.lt_cases_of_isU64 hcU + have hcc0 : Expression.eval env input_var_adapter_op_c_memory_prev_value[0] + = input_adapter_op_c_memory_prev_value[0] := by rw [← h_oc]; simp [Vector.getElem_map] + have hcc2 : Expression.eval env input_var_adapter_op_c_memory_prev_value[2] + = input_adapter_op_c_memory_prev_value[2] := by rw [← h_oc]; simp [Vector.getElem_map] + simp only [circuit_norm] at e21 e23 e35 e47 + refine operand_isU64 + (e2 := env.get (i₀ + 4) + env.get (i₀ + 5) + env.get (i₀ + 6) + env.get (i₀ + 7)) + (s := env.get (B + 6)) ?_ ?_ ?_ ?_ hcr0 hcr1 hcr2 hcr3 he2 ?_ + · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] + rw [← hcc0]; linear_combination -e21 + · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] + rw [← hcc1]; linear_combination -e23 + · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] + rw [← hcc2]; linear_combination e35 + · simp only [hcop, Vector.getElem_map, Vector.getElem_mapRange, circuit_norm] + rw [← hcc3]; linear_combination e47 + · -- `c_neg = c_msb.msb * E10` (E19) binary: `c_msb` binary from the `U16MSB` gadget (@ `irnw`, + -- via `hirnw` — unconditional), `E10 = is_div+is_rem+is_divw+is_remw ∈ {0,1}` (disjoint flags). + have hcmsb := (h_msb1 ⟨fun _ => by rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2, + hirnw⟩).1 + simp only [circuit_norm] at hcmsb + have hE10 := group_binary4 bd br bdw brw (by omega) + have h19 := e19; simp only [circuit_norm] at h19 + rcases hcmsb with hm | hm <;> rcases hE10 with hE | hE <;> + simp only [hm, hE, mul_zero, zero_mul, mul_one, one_mul, sub_zero] at h19 <;> + first + | exact Or.inl (by linear_combination -h19) + | exact Or.inr (by linear_combination -h19) + refine ⟨?mulLo, ?mulHi, ?eqb, ?eqc, ?eqb2, ?eqc2, ?isc0, ?addc, ?addr, ?lt, + ?msb0, ?msb1, ?msb2, ?msb3, ?msb4, ?msb5, ?msb6, ?cpu, ?rtype, ?own⟩ + case own => simp only [circuit_norm, assertZeros, forAllNoOffset_map_assert] + case eqb => exact Or.inl rfl + case eqc => exact Or.inl rfl + case eqb2 => exact Or.inl rfl + case eqc2 => exact Or.inl rfl + case isc0 => exact Or.inl rfl + case cpu => exact Or.inr hbin + case rtype => exact Or.inr hbin + case mulLo => + exact Or.inr ⟨fun hr => ⟨hqcU hr, hcU_op⟩, hbin, fun h => (zero_ne_one h).elim, hbin, + Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl, by rcases hbin with h | h <;> simp [h]⟩ + case mulHi => + refine Or.inr ⟨fun hr => ⟨hqcU (hirnw_imp hr).1, hcU_op⟩, hirnw, + fun h => (zero_ne_one h).elim, Or.inl rfl, + group_binary2 bd br (by omega), group_binary2 bdu bru (by omega), Or.inl rfl, Or.inl rfl, ?_⟩ + rcases group_binary4 bd br bdu bru (by omega) with h | h + · exact Or.inl (by linear_combination h) + · exact Or.inr (by linear_combination h) + case addc => + refine Or.inr ⟨fun hace => ?_, ?_⟩ + · have h286 := e286; simp only [circuit_norm] at h286 + have hr : input_is_real = 1 := by + rcases hbin with h | h + · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h286 + exact zero_ne_one (h286.symm.trans hace) + · exact h + exact ⟨hcU_op, habscU hr⟩ + · have h := e357; simp only [circuit_norm] at h; exact bool_of_mul_pred h + case addr => + refine Or.inr ⟨fun hrae => ?_, ?_⟩ + · have h288 := e288; simp only [circuit_norm] at h288 + have hr : input_is_real = 1 := by + rcases hbin with h | h + · exfalso; rw [h_oir, h, mul_zero, neg_zero, add_zero] at h288 + exact zero_ne_one (h288.symm.trans hrae) + · exact h + exact ⟨hrcU hr, habsrU hr⟩ + · have h := e359; simp only [circuit_norm] at h; exact bool_of_mul_pred h + case lt => + -- E305: `remainder_check_multiplicity = (1 - is_c_0.result) * is_real`. + have h305 := e305; simp only [circuit_norm] at h305 + rw [iszeroword_result_proj] at h305 + simp only [Vector.getElem_mapRange, circuit_norm] at h305 + rw [h_oir] at h305 + refine Or.inr ⟨fun hrcm => ?_, ?_⟩ + · -- rcm = 1 → is_real = 1, then `abs_remainder`/`max_abs_c_or_1` are `isU64`. + have hr : input_is_real = 1 := by + rcases hbin with h | h + · exfalso; rw [h] at h305; exact one_ne_zero (by linear_combination -h305 - hrcm) + · exact h + exact ⟨habsrU hr, hmaxU hr⟩ + · -- rcm binary: `(1 - is_c_0) * is_real` with `is_real`/`is_c_0` binary. + rcases hbin with h | h + · left; rw [h] at h305; linear_combination -h305 + · have hsem := IsZeroWordOperation.result_semantic (h_isc0 (Or.inr h)) h + by_cases hcz : cop[0] = 0 ∧ cop[1] = 0 ∧ cop[2] = 0 + ∧ cop[3] = 0 + · left + rw [if_pos hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem + simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, + Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem + rw [h, hsem] at h305; linear_combination -h305 + · right + rw [if_neg hcz] at hsem; dsimp only at hsem; rw [field_fromElements_one] at hsem + simp only [Vector.getElem_cast, Vector.getElem_take, Vector.getElem_drop, + Vector.getElem_mapRange, Nat.reduceAdd, circuit_norm] at hsem + rw [h, hsem] at h305; linear_combination -h305 + case msb0 => + -- the U16MSB gadget tests the raw read `bpv[3]`, bounded directly by `isU64 (read)`. + refine Or.inr ⟨fun _ => ?_, hirnw⟩ + rw [hbb3]; exact (Word.lt_cases_of_isU64 hbU).2.2.2 + case msb1 => + refine Or.inr ⟨fun _ => ?_, hirnw⟩ + rw [hcc3]; exact (Word.lt_cases_of_isU64 hcU).2.2.2 + case msb2 => + refine Or.inr ⟨fun hirnwg => ?_, hirnw⟩ + obtain ⟨hr, -⟩ := hirnw_imp hirnwg + have hrneg' : -input_is_real = -1 := by rw [hr] + exact isU16_of_byteRowSpec (hb_r3 hrneg') + case msb3 => + refine Or.inr ⟨fun _ => ?_, he2⟩ + rw [hbb1]; exact (Word.lt_cases_of_isU64 hbU).2.1 + case msb4 => + refine Or.inr ⟨fun _ => ?_, he2⟩ + rw [hcc1]; exact (Word.lt_cases_of_isU64 hcU).2.1 + case msb5 => + exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2r1 (by linear_combination -he2g)), he2⟩ + case msb6 => + exact Or.inr ⟨fun he2g => isU16_of_byteRowSpec (hb_e2q1 (by linear_combination -he2g)), he2⟩ + +/-- The `Spec`-only obligation: the soundness goal *minus* the shared `Operations.Requirements` tail. +Each conjunct proves this (its own `Spec`); the tail is supplied once by `requirements_holds`. -/ +def SpecObligation (Spec : Inputs (ZMod p) → DivRemCols (ZMod p) → ProverData (ZMod p) → Prop) : Prop := + ∀ (i₀ : ℕ) (env : Environment (ZMod p)) (input_var : Var Inputs (ZMod p)) (input : Inputs (ZMod p)), + eval env input_var = input → Assumptions input env.data → + ConstraintsHold.Soundness env (main input_var |>.operations i₀) → + Spec input (eval env (ElaboratedCircuit.output main input_var i₀)) env.data + +/-- Reassemble a full `GeneralFormalCircuit.Soundness` from the per-conjunct `SpecObligation` and the +shared `requirements_holds` tail — the binders are still **raw** here (no `circuit_proof_start`), so +`requirements_holds` applies by-term without the decomposed-context mismatch. -/ +lemma soundness_of_specObligation + {Spec : Inputs (ZMod p) → DivRemCols (ZMod p) → ProverData (ZMod p) → Prop} + (h : SpecObligation (p := p) Spec) : + GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by + intro i₀ env input_var input h_input h_assumptions h_holds + exact ⟨h i₀ env input_var input h_input h_assumptions h_holds, + (requirements_holds i₀ env input_var input h_input h_assumptions h_holds).2⟩ + +open Lean Elab Tactic + +/-- `circuit_proof_start` for a `SpecObligation` goal: mirrors the stock tactic's setup verbatim +(unfold `SpecObligation`, intro the seven binders, then the identical `circuit_norm`/`dsimp`/ +`provable_struct_simp` normalization) so each conjunct's existing spec proof runs unchanged. Must be +an `elab` using `mkIdent` (not a `macro`) so the introduced binders and the use-site `Spec`/`main`/ +`elaborated` identifiers resolve in the conjunct's namespace, exactly as `circuit_proof_start` does. +Used after `apply soundness_of_specObligation`. -/ +elab "spec_proof_start" : tactic => do + evalTactic (← `(tactic| unfold SpecObligation)) + for name in [`i₀, `env, `input_var, `input, `h_input, `h_assumptions, `h_holds] do + evalTactic (← `(tactic| intro $(mkIdent name):ident)) + try evalTactic (← `(tactic| simp only [circuit_norm] at $(mkIdent `input_var):ident)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm] at $(mkIdent `input):ident)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm] at $(mkIdent `h_input):ident)) catch _ => pure () + evalTactic (← `(tactic| try dsimp only [$(mkIdent `Assumptions):ident] at *)) + evalTactic (← `(tactic| try dsimp only [$(mkIdent `Spec):ident] at *)) + evalTactic (← `(tactic| try dsimp only [$(mkIdent `elaborated):ident] at *)) + evalTactic (← `(tactic| try dsimp only [$(mkIdent `main):ident] at *)) + try evalTactic (← `(tactic| dsimp only [ElaboratedCircuit.withData, ElaboratedCircuit.output])) catch _ => pure () + try evalTactic (← `(tactic| provable_struct_simp)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm] at $(mkIdent `h_input):ident)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm] at $(mkIdent `h_assumptions):ident)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm, $(mkIdent `h_input):ident] at $(mkIdent `h_holds):ident)) catch _ => pure () + try evalTactic (← `(tactic| simp only [circuit_norm, $(mkIdent `h_input):ident])) catch _ => pure () + +end SP1Clean.DivRemChip diff --git a/docs/snapshots/compile-profile.md b/docs/snapshots/compile-profile.md index 43eaca2a..87785c95 100644 --- a/docs/snapshots/compile-profile.md +++ b/docs/snapshots/compile-profile.md @@ -3,34 +3,24 @@ # Compile-time profile — SP1Clean Per-module wall-clock elaboration profile, with per-tactic attribution for the worst offenders and -the common threads behind them. Measured **2026-06-10** (Lean v4.28.0, warm olean cache, branch -`full-clean-dsl-implementation`), **after** the get_elem fast-path / linter / `localLength_eq` -optimization batch landed that same day. The pre-fix baseline, per-module delta table, probe -evidence, and drafted upstream reports live in `docs/snapshots/profile-baseline-2026-06-10/` -(`findings.md` is the diagnosis writeup). - -**Headline: the 2026-06-10 batch cut isolated elaboration of the swept set in half — 3422s → 1689s -(−50.7%), no regression beyond ±2% noise.** The three fixes, by measured leverage: - -1. **`v[i]` index-bound `decide` fast path** (`Foundations/GetElemFastPath.lean`, one `macro_rules` - line): every `v[i]` elaboration was paying ~11 `rw/dsimp … at *` context traversals inside Std's - slice `get_elem_tactic_extensible` rule — ~0.34s per `[i]` under a fat proof context, ~0.8s per - `have`. This single fix produced most of the −50%: medium chip Formal files −70%, generated - `Extracted/` files −87…93% (their `#v[…][k]` projection chains were this, not "monolithic term - elaboration"), `BranchChip.Formal` −77%, `OwnAsserts` −92%. -2. **`set_option linter.all false` on the 76 auto-generated modules** (~1.6s/file linter - interpretation tax; emitted by the `update_extracted.py` templates). -3. **`localLength_eq := by intros; simp +arith […]`** on the two ALU readers — the silent - `by intros; rfl` field default whnf-unfolds all of `main` (334k heartbeats ≈ 15.5s each; the simp - route is 109× cheaper). Both readers now fit the **default** heartbeat/recdepth budgets. +the common threads behind them. Measured **2026-06-19** (Lean v4.28.0, 24-core host, warm olean +cache, branch `dtumad/perf-improvement` at the `main`-merge), **after** the pillar refactor +(2026-06-18) and the DivRemChip soundness-tail dedup (`f0e6f82`). This supersedes the 2026-06-10 +snapshot, whose absolute seconds were pre-refactor and contention-inflated; the 2026-06-10 *diagnosis* +(the get_elem fast path, the linter tax, `localLength_eq`, and the "kill-list of myths" below) still +holds and is retained. The pre-fix baseline + drafted upstream reports live in +`docs/snapshots/profile-baseline-2026-06-10/`. ## How to re-run ```sh -scripts/profile_compile.sh # whole library (warm-builds first, then sweeps) -SKIP_BUILD=1 scripts/profile_compile.sh # cache already warm -SKIP_BUILD=1 scripts/profile_compile.sh Operations/ # scope to one subdir -EXCLUDE_RE='Chips/DivRemChip/Soundness/' scripts/profile_compile.sh Chips/ # skip the conjuncts +# whole library; the 9 DivRem soundness conjuncts are 18–33 min EACH in isolation — exclude them and +# take their cost from a `lake build` log instead. The regex keeps Tail.lean (the shared tail, worth +# measuring) but drops the 9 heavy conjuncts. +SKIP_BUILD=1 EXCLUDE_RE='Soundness/(Div|Divu|Divuw|Divw|Reader|Rem|Remu|Remuw|Remw)\.lean$' \ + scripts/profile_compile.sh +scripts/profile_compile.sh # warm-builds first, then sweeps (no SKIP_BUILD) +SKIP_BUILD=1 scripts/profile_compile.sh Operations/ # cache warm, scope to one subdir ``` Outputs land in `build/profile/`: @@ -45,103 +35,158 @@ After one warm `lake build`, each module is re-elaborated in isolation with `lake env lean -Dprofiler=true -Dprofiler.threshold=50 `. Every dependency loads from its cached `.olean`, so the timing isolates **that one file's** elaboration. Caveats: -1. **Import tax is a sweep artifact** (~1s/file, shared in a real parallel build). It dominates the - cheap files; the ranking of expensive files is accurate. +1. **Import tax is a sweep artifact** (~0.5s/file, shared in a real parallel build): 197s across the + 382 modules here. It dominates the cheap files; the ranking of expensive files is accurate. 2. **Profiler sums over-count** (nested/cumulative) — per-category numbers are dominance signals, - not an additive budget. -3. **`lake env lean` exits 0 even on a stack overflow** — the script records exit codes; this run - had **0 nonzero exits** across all 260 modules. -4. **The nine `Chips/DivRemChip/Soundness/*.lean` conjuncts are excluded** (each is 18–33 min to - elaborate in isolation); their costs are taken from `lake build` logs instead — see below. - -## Headline numbers (post-fix sweep) - -- **260 modules, ~1689s total** (sequential, isolated; excludes the DivRem conjuncts). - Mean 6.5s, **median 2.7s**, p95 28.3s, **max 118s** (was max 462s). -- 156/260 modules elaborate in <3s (≈ import floor). -- Generated modules (`Extracted/`, `Operations/*/Extracted`, witness vectors): **245.6s across 77 - files — was 1178.8s.** - -### Time by subsystem (vs pre-fix baseline) - -| Subsystem | Time | was | Files | -| --- | ---: | ---: | ---: | -| `Chips/` (excl. DivRem conjuncts) | 1041s | 1598s | 95 | -| `Operations/` | 285s | 553s | 62 | -| `Extracted/` | 146s | 1013s | 50 | -| `Foundations/` | 91s | 90s | 16 | -| `WitnessTests/` | 85s | 88s | 27 | -| `Readers/` | 40s | 81s | 10 | - -### DivRem soundness conjuncts (from `lake build` logs, ≤5-way-parallel — contention-inflated) - -Rem 1997s, Div 1849s, Remu 1706s, Divu 1651s, Divw 1535s, Divuw 1494s, Remw 1256s, Remuw 1090s, -Reader 720s — **~3.7 CPU-hours, the library's dominant remaining cost.** (Pre-fix sum of the same -seven measured files was ~15% higher.) Probe-measured: `circuit_proof_start` is only ~90s of each; -the rest is per-conjunct omega/bitvec/carry proof bodies at 128M maxHeartbeats. - -## Top offenders (post-fix) + not an additive budget (e.g. a file's summed "simp took" can exceed its own total time). +3. **`lake env lean` exits 0 even on a stack overflow** — the script records exit codes; this run had + **0 nonzero exits** across all 382 modules. +4. **The nine `Chips/DivRemChip/Soundness/{Div,Divu,Divuw,Divw,Reader,Rem,Remu,Remuw,Remw}.lean` + conjuncts are excluded** (18–33 min each in isolation); their costs come from the warm `lake build` + log instead — see below. `Tail.lean` is **kept** in the sweep. + +## Headline numbers (2026-06-19 sweep) + +- **382 modules, 1780.5s total** (sequential, isolated; excludes the 9 DivRem conjuncts). + Mean 4.66s, **median 2.02s**, p90 5.47s, p95 15.83s, **max 147.6s**. +- **296/382 modules (77%) elaborate in <3s** (≈ import floor). +- Generated/auto-gen modules (`Extracted/` + witness/trace vectors): **229.8s across 87 files.** + +### Time by subsystem + +| Subsystem | Time | Files | +| --- | ---: | ---: | +| `Proofs/` (excl. DivRem conjuncts) | 1131.4s | 138 | +| `Native/` | 210.8s | 66 | +| `Extracted/` | 186.7s | 77 | +| `Faithful/` | 127.4s | 50 | +| `Soundness/` | 50.1s | 27 | +| `Model/` | 46.7s | 11 | +| `FormalModel/` | 13.9s | 6 | +| `Math/` | 12.1s | 6 | + +### DivRem soundness conjuncts (from the warm `lake build` log, 9-way parallel — contention-inflated) + +Divw 357s, Remw 357s, Rem 354s, Div 348s, Divuw 334s, Remuw 334s, Divu 324s, Remu 322s, Reader 75s +— plus the shared **`Tail.lean` 147.6s (isolated, built once)**. **The `f0e6f82` tail dedup roughly +halved each conjunct**: the 2026-06-10 snapshot logged the same files at 720–1997s each (at ≤5-way +parallel, i.e. *less* contention), because each then re-elaborated the byte-identical 265-line +`Operations.Requirements` tail at 128M heartbeats. They now delegate to `requirements_holds` in +`Soundness/Tail.lean`, which is paid **once**. The conjuncts remain the library's dominant cost, but +the per-file body is now the chip-specific spec proof, not the tail. + +## Top offenders (2026-06-19) | # | Time | Module | Note | | ---: | ---: | --- | --- | -| 1 | 118s | `Chips.DivRemChip.Defs` | instance-field simps + **kernel type-checking** (~40s) — next target | -| 2 | 81s | `Chips.ShiftRightChip.Soundness.Sra` | conjunct proof body | -| 3 | 73s | `Chips.ShiftRightChip.Soundness.Sraw` | conjunct proof body | -| 4 | 73s | `Chips.ShiftRightChip.Soundness.Srl` | conjunct proof body | -| 5 | 69s | `Chips.ShiftRightChip.Soundness.Srlw` | conjunct proof body | -| 6 | 45s | `Chips.ShiftRightChip.Core` | arithmetic lemma farm | -| 7 | 45s | `Chips.ShiftLeftChip.Soundness.Sll` | conjunct proof body | -| 8 | 41s | `Chips.ShiftLeftChip.Soundness.Sllw` | conjunct proof body | -| 9 | 41s | `Operations.MulOperation.RawSpec` | | -| 10 | 40s | `Operations.MulOperation.Formal` | was 121s (−67%) | -| 11 | 37s | `Chips.ShiftRightChip.Dispatch` | | -| 12 | 31s | `Extracted.DivRemChip` | **was 462s (−93%)** | -| 13 | 29s | `Chips.ShiftLeftChip.Core` | | -| 14 | 28s | `Chips.ShiftRightChip.Defs` | was 60s | -| 15 | 27s | `Operations.LtOperationUnsigned.RawSpec` | | -| 16 | 27s | `Foundations.SailWrap` | 232 mathlib-style simp calls — not Clean-related | -| 17 | 25s | `Chips.DivRemChip.Formal` | | -| 18 | 23s | `Foundations.Register` | mathlib-style — not Clean-related | -| 19 | 23s | `WitnessTests.MulOperationWitnessVectors` | `native_decide` anchor data | -| 20 | 21s | `Chips.BranchChip.Formal` | was 95s (−77%) | - -## What the 2026-06-10 diagnosis established (kill-list of myths) +| 1 | 147.6s | `Chips.DivRemChip.Soundness.Tail` | **the shared tail, now paid once** (was inline ×9). `instantiate metavars` 79.7s + the two big `obtain`s ~25s + `rewriteSeq` 14.6s — term-intrinsic | +| 2 | 118.0s | `Chips.DivRemChip.Completeness.Driver` | the DivRem completeness driver (sub-op col folding) | +| 3 | 61.5s | `Chips.ShiftRightChip.Soundness.Sra` | conjunct proof body | +| 4 | 56.0s | `Chips.ShiftRightChip.Soundness.Sraw` | conjunct proof body | +| 5 | 54.9s | `Chips.ShiftRightChip.Soundness.Srl` | conjunct proof body | +| 6 | 52.6s | `Chips.ShiftRightChip.Soundness.Srlw` | conjunct proof body | +| 7 | 43.3s | `Chips.DivRemChip.Defs` | instance-field simps + kernel type-checking. **Was reported "118s" in 2026-06-10 — that was contention; isolated it is ~43s** | +| 8 | 35.6s | `Chips.ShiftRightChip.Formal` | | +| 9 | 34.6s | `Chips.ShiftLeftChip.Soundness.Sll` | conjunct proof body | +| 10 | 32.7s | `Chips.ShiftLeftChip.Soundness.Sllw` | conjunct proof body | +| 11 | 29.9s | `Operations.MulOperation.Formal` | | +| 12 | 29.6s | `Operations.MulOperation.RawSpec` | | +| 13 | 23.4s | `Extracted.DivRemChip` | generated (was 462s pre-2026-06-10) | +| 14 | 22.9s | `Chips.ShiftLeftChip.Formal` | | +| 15 | 21.9s | `Chips.ShiftRightChip.Defs` | | +| 16 | 20.6s | `Chips.ShiftRightChip.Core` | arithmetic lemma farm | +| 17 | 19.7s | `Operations.LtOperationUnsigned.RawSpec` | | +| 18 | 17.1s | `TraceGenTests.DivRemChipTraceVectors` | `native_decide` anchor data | +| 19 | 17.0s | `Extracted.WitnessVectors.MulOperation` | `native_decide` anchor data | +| 20 | 16.0s | `Chips.ShiftLeftChip.Core` | | +| — | 14.7s | `Model.SailWrap` | 232 mathlib-style simp calls — not Clean-related | +| — | 13.3s | `Model.Register` | 94 `@[simp]` register lemmas, proved by `fin_cases <;> trivial` — not Clean-related | + +### Project-wide hot tactic categories (summed across all 382 logs, >50ms entries) + +`simp` 412.4s (789 calls) · type-checking 204.2s · import 197.0s (sweep artifact) · `rewriteSeq` +170.1s · `share common exprs` 88.2s · `nlinarith` interpretation 55.4s · `instantiate metavars` 52.1s. +**`simp` is the single largest category, but ~48% of it is concentrated in the top-10 heavy files** +(SailWrap, DivRem, Shifts) and the rest is spread thin (~0.4s/file) — there is no pathological broad +pattern like the pre-2026-06-10 `v[i]` tax. `instantiate metavars` (79.7s in Tail alone) and +`nlinarith` interpretation are likewise concentrated in the DivRem/Shift/Mul heavies. **The remaining +cost is term-intrinsic in a handful of files, not a broad lever.** + +## What the 2026-06-10 diagnosis established (kill-list of myths — still valid) Full evidence: `profile-baseline-2026-06-10/findings.md`. In brief: - **`circuit_proof_start` / bind-chain normalization is NOT the bottleneck** — 6k–32k heartbeats on - medium chips (~10% of file cost), 320k (~90s) even on DivRem's `main`, the repo's biggest. A - once-per-chip "normalized ops" lemma (main_ops_eq) would save only ~13 min across DivRem's nine - files — parked (`scratch/design-main-ops-eq-divrem.md` at the measurement commit). -- **The dominant medium-chip cost was `v[i]` elaboration**, fixed by the decide fast path. + medium chips, ~320k (~90s) even on DivRem's `main`, the repo's biggest. The once-per-chip + `main_ops_eq` lemma would save ~4% — parked. +- **The dominant medium-chip cost was `v[i]` elaboration**, fixed by the `decide` fast path + (`Math/GetElemFastPath.lean`, one `macro_rules` line — registered above Std's slice rule). This + single fix halved isolated elaboration of the swept set in the 2026-06-10 batch. - **`ElaboratedCircuit` field defaults can silently cost seconds**: `localLength_eq`'s `rfl` default - whnf-unfolds `main`; the `channelsLawful` default outright fails on channel-heavy mains. Watch for - this on every new chip with a non-trivial `main` (hand-write the simp-route `localLength_eq`). -- The old threads C/E ("linter tax" / "Extracted monolithic terms") are **resolved** — E was mostly - the `v[i]` tax in disguise. + whnf-unfolds `main` (~15.5s on a 17-op main); `channelsLawful`'s default fails on channel-heavy + mains. Hand-write the simp-route `localLength_eq` on every new chip with a non-trivial `main`. +- The "linter tax" / "Extracted monolithic terms" threads are **resolved** (the latter was the `v[i]` + tax in disguise; the former is fixed by `set_option linter.all false` on the 76 generated modules). + +## Perf-lever investigation (2026-06-19) — broad levers are exhausted + +The 2026-06-19 sweep was mined for a new *broad* lever in the style of the `get_elem` fast path +(an attribute/macro/instance tweak that helps project-wide). **None was found** — the evidence: + +- **No shared hot tactic.** `simp`/`rewriteSeq`/`obtain`/`instantiate metavars` are all either + concentrated in the DivRem/Shift/Mul heavies or spread thin with no pathological pattern (above). +- **No broad instance-resolution cost.** Typeclass inference is negligible project-wide (only + "typeclass inference of `Nonempty`" appears, 26× small); `instantiate metavars` is concentrated, not + broad. There are no manual instance-priority declarations to tune, and none are warranted. +- **`localLength_eq` is already harvested.** All heavy chips (DivRem/Mul/Branch/Shifts/Loads/Stores) + and both ALU readers carry an explicit simp-route `localLength_eq`; the chips that still default it + (Add/Sub/Lt/Bitwise/Jal/Jalr/UType…) have small mains (2–5 ops) where the `rfl` default is cheap + (all elaborate in 2–5s). No new candidate. +- **The 94 `Model/Register.lean` register `@[simp]` lemmas impose zero global tax.** They are + discriminated by the `reg_idx_to_Register` head symbol and consumed by only 2 files + (`SailWrap`, `SailMemory`); the other 380 modules never enter them into a rewrite attempt. Measured + directly: a register-free `simp` costs **205 heartbeats with the lemmas in the default set and 205 + with them removed** (`attribute [-simp]`) — identical. Moving them to a scoped set would yield no + project-wide gain and would require editing a foundational `Model/` module (≈ full rebuild) to + validate. **Not pursued.** (Register.lean's own 13.3s is its 62 `fin_cases <;> trivial` lemma + proofs — a single-file cost, not a project lever.) +- **maxHeartbeats ceilings are loose but right-sizing is alarm-hygiene, not speed.** Lowering a + ceiling does not make a proof compile faster — it only tightens the regression alarm. The 9 DivRem + conjuncts + `Tail.lean` sit at 128M HB; measuring their true consumption needs the multi-hour + rebuild, for alarm-only value. Deferred to the mechanical `#count_heartbeats` sweep (target #4). + +**Conclusion:** the three shipped optimizations (get_elem fast path, linter-off on generated, +`localLength_eq` on big-main readers) captured the broad wins. Further compile-time reduction requires +**per-proof restructuring of the concentrated heavies** (Tail/Driver/Shift conjuncts) — a larger, +term-by-term effort, not an easy attribute/priority lever. ## Where to optimize next (by leverage) -1. **DivRem conjunct proof bodies** (~3.7 CPU-h): the omega/carry work in heavy contexts. Known - pattern from the files' own comments: extract minimal-context pure-ℕ lemmas so omega certificates - shrink. Large effort, largest prize. *Open.* -2. **`Chips.DivRemChip.Defs` (118s)**: instance-field `simp` proofs 20s+ each plus ~40s kernel - type-checking — investigate proof-term size of the field proofs (`share common exprs` lines). - *Open.* -3. **ShiftRight/Left conjunct bodies** (~70–81s each) — same family as (1). *Open.* -4. **maxHeartbeats ratcheting**: post-fix, ceilings everywhere are loose (e.g. 16M-HB files now run - in seconds). Mechanical sweep with `#count_heartbeats`, restores regression-alarm value. *Open.* -5. **Upstream filings** (`profile-baseline-2026-06-10/upstream-notes.md`): lean4/Std slice-rule - `at *` report; Clean `localLength_eq`/`channelsLawful` default reports. *Drafted, not filed.* -6. `Foundations.SailWrap` / `Foundations.Register` (~50s combined): mathlib-style simp-heavy proofs, - independent of Clean. *Open, low priority.* +1. **`Chips.DivRemChip.Soundness.Tail` (147.6s) + the conjunct bodies (~322–357s contended each).** + The cost is `instantiate metavars` + the Mul product-glue `simpa`s establishing + `r_k = product[2k] + product[2k+1]·256` against the 16-element witnessed product vectors — term + intrinsic (swapping the heavy `circuit_norm` simp set for the minimal one built green with *zero* + speedup). Reducing it needs restructuring how the Mul product columns are reduced. Large effort, + largest prize. *Open.* +2. **`Chips.DivRemChip.Completeness.Driver` (118s)** — the sub-op col-folding driver; same heavy-term + family. *Open.* +3. **ShiftRight/Left conjunct bodies (~33–61s each)** — same family as (1); the shared + `cpuA/msb1A/msb2A/msb3A/aluA` block (byte-identical 236 lines across Srl/Sra/Srlw/Sraw) is a + tail-dedup candidate mirroring `f0e6f82`, a smaller prize than DivRem's. *Open.* +4. **maxHeartbeats ratcheting** — mechanical `#count_heartbeats` sweep to tighten the now-loose + ceilings; restores regression-alarm value (not a speedup). *Open.* +5. **Upstream filings** (`profile-baseline-2026-06-10/upstream-notes.md`): lean4/Std slice-rule `at *` + report; Clean `localLength_eq`/`channelsLawful` default reports. *Drafted, not filed.* +6. **`Model.SailWrap` / `Model.Register` (~28s combined)** — mathlib-style simp/`fin_cases` proofs, + independent of Clean; Register's 62 `fin_cases <;> trivial` could try `decide`, but editing + `Model/` triggers a full rebuild. *Open, low priority.* ## Verification of this run -- Coverage: 260 modules swept, 0 nonzero exits; DivRem conjuncts excluded by `EXCLUDE_RE` (costs - recorded from build logs). -- Post-fix gate: `lake build SP1Clean` green (3630 jobs), only the five pre-existing `sorry` - warnings (ShiftLeft/ShiftRight/Mul/DivRem `Formal`, `SP1GatedVm`), 0 new warnings, 0 `info:` notes. -- Axiom audit: representative headline theorems (StoreByte soundness/completeness + bridge, both ALU - readers, Jalr, DivRem soundness, `correct_add_native`) all match the axiom ledger — no `sorryAx`, - no new axioms from the decide-based proofs. +- Coverage: 382 modules swept, **0 nonzero exits**; the 9 DivRem conjuncts excluded by `EXCLUDE_RE` + (costs from the warm build log), `Tail.lean` included. +- Build gate: `lake build SP1Clean` green (**3676 jobs**), the **single** remaining `sorry` + (`Soundness/SP1GatedVm.lean:220`), 0 errors, 0 new warnings, 0 `info:` notes. (The 2026-06-10 + ShiftLeft/ShiftRight/Mul/DivRem `Formal` sorries have since been closed.) +- Axiom audit: `DivRemChip.SoundDiv.soundness` and `DivRemChip.requirements_holds` (the dedup'd tail) + both `[propext, Classical.choice, Quot.sound]` — no `sorryAx`. From b5aa92c5609ce7af36a9d9c792ef0c43abdb92e1 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Mon, 22 Jun 2026 20:02:23 -0500 Subject: [PATCH 2/8] clean up the doc changes a bit more --- docs/agents/proof-patterns.md | 48 ++++++ docs/snapshots/compile-profile.md | 248 ++++++++++++------------------ 2 files changed, 148 insertions(+), 148 deletions(-) diff --git a/docs/agents/proof-patterns.md b/docs/agents/proof-patterns.md index 6333b50d..fa2699e2 100644 --- a/docs/agents/proof-patterns.md +++ b/docs/agents/proof-patterns.md @@ -519,6 +519,54 @@ first ``` See `Proofs/Chips/AddChip.lean` and `Native/Readers/RegisterAccessCols.lean` for the worked examples. +## Compile-time / performance landmines + +The compile-time profile lives in `docs/snapshots/compile-profile.md` (regenerate with +`scripts/profile_compile.sh`). These are the durable, still-true lessons behind it — apply them when +adding a chip or chasing a slow file. The broad attribute/macro wins have already been harvested +(below); remaining cost is term-intrinsic in the DivRem/Shift/Mul heavies, so new slowness almost +always means a *local* regression against one of these. + +- **The `v[i]` index-bound tax — already fixed by the `decide` fast path.** Every `v[i]` elaborates + an `i < n` bound side-goal; in Lean 4.28's Std the slice-support `get_elem_tactic_extensible` rule + does ~11 full-context traversals (`rw … at *`, `dsimp … at *`, a slice `simp`) per index — ~0.34s + for `1 < 4` inside a hypothesis-heavy soundness proof, paid in every `have` type. `Math/GetElemFastPath.lean` + registers one `macro_rules | get_elem_tactic_extensible => decide` line *above* Std's (so it's tried + first among the extensible rules, still after `done`/`assumption`): literal bounds close by kernel + `Nat.decLt` in ~26 heartbeats, non-literals fall through. This single line halved the swept set when + it landed; **don't regress it** (e.g. by importing a module that re-shadows the rule below Std's). If + a `have`-dense proof suddenly slows, suspect the fast path isn't in scope. + +- **`circuit_proof_start` / bind-chain normalization is NOT the bottleneck — don't chase it.** + Measured at 6k–32k heartbeats on medium chips, ~320k (~90s) even on DivRem's `main` (the repo's + biggest). The once-per-chip `main_ops_eq` lemma would save ~4% — not worth it. Slow soundness files + are slow in their *proof body* (the arithmetic / product-glue `simpa`s), not in the start tactic. + +- **`ElaboratedCircuit` `localLength_eq`'s `rfl` default whnf-unfolds `main` — seconds on a big main.** + On a 17-op `main` the default `rfl` costs ~15s; `channelsLawful`'s default fails outright on + channel-heavy mains. Hand-write the simp-route `localLength_eq` (and the `channelsWith*_eq` / + `circuit_localLength` rfl-lemmas) on every chip with a non-trivial `main` so the defaults stay cheap + — see the "ElaboratedCircuit field obligations" section above for the full recipe. Chips with small + mains (2–5 ops) can keep the `rfl` default; it's cheap there. + +- **`set_option linter.all false` on the generated `Extracted/` modules removes the linter tax.** The + ~76 auto-gen modules carry it in their headers; keep it when regenerating (the linter passes over the + monolithic generated terms were a measurable chunk of their cost). + +- **Shared-tail dedup for many-conjunct soundness proofs (the DivRem pattern).** When N conjunct files + each prove `GeneralFormalCircuit.Soundness` over the *same* `main`, they each re-elaborate the + byte-identical post-instruction "requirements tail" (readers + `is_real` + channel obligations) at a + high `maxHeartbeats` — N× the same work. Extract it once: a `requirements_holds` lemma proving the + tail with **raw** (un-`circuit_proof_start`'d) binders, a `SpecObligation Spec` wrapper, and a + `soundness_of_specObligation` that reassembles a full `Soundness` from a per-conjunct `SpecObligation` + plus the shared tail. Each conjunct then proves only its chip-specific `Spec` via a `spec_proof_start` + elab tactic (mirrors `circuit_proof_start`'s setup but unfolds `SpecObligation`, so it does *less* + work). See `Proofs/Chips/DivRemChip/Soundness/Tail.lean`. **Landmine:** `requirements_holds` must be + applied *by-term* on raw binders — after `circuit_proof_start` the decomposed context (destroyed + `input_var` binders, consumed `h_holds`) no longer matches, which is exactly why the tail lemma takes + raw binders and the `SpecObligation` indirection exists. The ShiftRight/ShiftLeft conjuncts share an + analogous `cpuA/msb*/aluA` block that is a candidate for the same treatment. + ## "emitted = projection": `Lookups_eq_emitted` (proving a trace projection IS the emission) Goal: prove a hand-written trace-level projection (`Soundness/*Consistency.lean`'s `stateLookups`, diff --git a/docs/snapshots/compile-profile.md b/docs/snapshots/compile-profile.md index 87785c95..c476828b 100644 --- a/docs/snapshots/compile-profile.md +++ b/docs/snapshots/compile-profile.md @@ -2,25 +2,27 @@ # Compile-time profile — SP1Clean -Per-module wall-clock elaboration profile, with per-tactic attribution for the worst offenders and -the common threads behind them. Measured **2026-06-19** (Lean v4.28.0, 24-core host, warm olean -cache, branch `dtumad/perf-improvement` at the `main`-merge), **after** the pillar refactor -(2026-06-18) and the DivRemChip soundness-tail dedup (`f0e6f82`). This supersedes the 2026-06-10 -snapshot, whose absolute seconds were pre-refactor and contention-inflated; the 2026-06-10 *diagnosis* -(the get_elem fast path, the linter tax, `localLength_eq`, and the "kill-list of myths" below) still -holds and is retained. The pre-fix baseline + drafted upstream reports live in -`docs/snapshots/profile-baseline-2026-06-10/`. +Per-module wall-clock elaboration profile, with per-tactic attribution for the worst offenders. +Measured **2026-06-22** (Lean v4.28.0, 24-core host, warm olean cache, branch +`dtumad/div-rem-perf` / PR #103). This run measures **all** modules in isolation, including the nine +DivRem soundness conjuncts (previously excluded and quoted from a contention-inflated parallel build +log — they are now clean isolated numbers). + +The durable proof-engineering lessons that came out of profiling (the `v[i]` fast path, +`circuit_proof_start` is not the bottleneck, the `localLength_eq` `rfl` cost, the shared-tail dedup +pattern) live in `docs/agents/proof-patterns.md` § "Compile-time / performance landmines". An older +pre-refactor baseline is archived under `docs/snapshots/profile-baseline-2026-06-10/`. ## How to re-run ```sh -# whole library; the 9 DivRem soundness conjuncts are 18–33 min EACH in isolation — exclude them and -# take their cost from a `lake build` log instead. The regex keeps Tail.lean (the shared tail, worth -# measuring) but drops the 9 heavy conjuncts. +scripts/profile_compile.sh # warm-builds first, then sweeps every module +SKIP_BUILD=1 scripts/profile_compile.sh # cache already warm — skip the warm build +SKIP_BUILD=1 scripts/profile_compile.sh Operations/ # scope to one subdir +# The nine DivRem soundness conjuncts are 277–307s EACH in isolation (~40 min for the nine). To skip +# them for a quick non-conjunct pass and take their cost from a build log instead: SKIP_BUILD=1 EXCLUDE_RE='Soundness/(Div|Divu|Divuw|Divw|Reader|Rem|Remu|Remuw|Remw)\.lean$' \ scripts/profile_compile.sh -scripts/profile_compile.sh # warm-builds first, then sweeps (no SKIP_BUILD) -SKIP_BUILD=1 scripts/profile_compile.sh Operations/ # cache warm, scope to one subdir ``` Outputs land in `build/profile/`: @@ -35,158 +37,108 @@ After one warm `lake build`, each module is re-elaborated in isolation with `lake env lean -Dprofiler=true -Dprofiler.threshold=50 `. Every dependency loads from its cached `.olean`, so the timing isolates **that one file's** elaboration. Caveats: -1. **Import tax is a sweep artifact** (~0.5s/file, shared in a real parallel build): 197s across the - 382 modules here. It dominates the cheap files; the ranking of expensive files is accurate. +1. **Import tax is a sweep artifact** (~0.8s/file, shared in a real parallel build): 314.6s across the + 391 modules here. It dominates the cheap files; the ranking of expensive files is accurate. 2. **Profiler sums over-count** (nested/cumulative) — per-category numbers are dominance signals, - not an additive budget (e.g. a file's summed "simp took" can exceed its own total time). + not an additive budget (a file's summed "simp took" can exceed its own total time). 3. **`lake env lean` exits 0 even on a stack overflow** — the script records exit codes; this run had - **0 nonzero exits** across all 382 modules. -4. **The nine `Chips/DivRemChip/Soundness/{Div,Divu,Divuw,Divw,Reader,Rem,Remu,Remuw,Remw}.lean` - conjuncts are excluded** (18–33 min each in isolation); their costs come from the warm `lake build` - log instead — see below. `Tail.lean` is **kept** in the sweep. + **0 nonzero exits** across all 391 modules. +4. **The nine DivRem soundness conjuncts are included** and measured in isolation this run (each + 277–307s; the slowest single files in the library). They are *not* run in parallel here, so these + are clean per-file costs rather than contention-inflated build-log wall times. -## Headline numbers (2026-06-19 sweep) +## Headline numbers -- **382 modules, 1780.5s total** (sequential, isolated; excludes the 9 DivRem conjuncts). - Mean 4.66s, **median 2.02s**, p90 5.47s, p95 15.83s, **max 147.6s**. -- **296/382 modules (77%) elaborate in <3s** (≈ import floor). -- Generated/auto-gen modules (`Extracted/` + witness/trace vectors): **229.8s across 87 files.** +- **391 modules, 3964.6s total** (sequential, isolated). Mean 10.14s, **median 1.84s**, p90 5.98s, + p95 25.31s, **max 306.65s**. +- **307/391 modules (79%) elaborate in <3s** (≈ the import floor). +- The eight DivRem div/rem soundness conjuncts alone are **2334.6s (59% of the total)**. ### Time by subsystem | Subsystem | Time | Files | | --- | ---: | ---: | -| `Proofs/` (excl. DivRem conjuncts) | 1131.4s | 138 | -| `Native/` | 210.8s | 66 | -| `Extracted/` | 186.7s | 77 | -| `Faithful/` | 127.4s | 50 | -| `Soundness/` | 50.1s | 27 | -| `Model/` | 46.7s | 11 | -| `FormalModel/` | 13.9s | 6 | -| `Math/` | 12.1s | 6 | - -### DivRem soundness conjuncts (from the warm `lake build` log, 9-way parallel — contention-inflated) - -Divw 357s, Remw 357s, Rem 354s, Div 348s, Divuw 334s, Remuw 334s, Divu 324s, Remu 322s, Reader 75s -— plus the shared **`Tail.lean` 147.6s (isolated, built once)**. **The `f0e6f82` tail dedup roughly -halved each conjunct**: the 2026-06-10 snapshot logged the same files at 720–1997s each (at ≤5-way -parallel, i.e. *less* contention), because each then re-elaborated the byte-identical 265-line -`Operations.Requirements` tail at 128M heartbeats. They now delegate to `requirements_holds` in -`Soundness/Tail.lean`, which is paid **once**. The conjuncts remain the library's dominant cost, but -the per-file body is now the chip-specific spec proof, not the tail. - -## Top offenders (2026-06-19) +| `Proofs/` (of which: 8 DivRem conjuncts 2334.6s, Reader 68.3s, Tail 123.6s) | 3376.4s | 147 | +| `Proofs/` excluding those ten DivRem files | 849.9s | 137 | +| `Native/` | 190.6s | 66 | +| `Extracted/` | 165.9s | 77 | +| `Faithful/` | 114.9s | 50 | +| `Soundness/` | 44.9s | 27 | +| `Model/` | 42.6s | 11 | +| `FormalModel/` | 12.6s | 6 | +| `Math/` | 11.1s | 6 | + +### DivRem soundness conjuncts (isolated) + +Divw 306.7s · Remw 305.4s · Div 298.5s · Rem 297.8s · Divuw 286.5s · Remuw 284.8s · Divu 278.3s · +Remu 276.8s — mean **291.8s** across the eight — plus **Reader 68.3s** and the shared +**`Tail.lean` 123.6s** (proved once). Each conjunct now proves only its chip-specific `Spec`; the +~265-line `Operations.Requirements` tail is delegated to `requirements_holds` in `Soundness/Tail.lean` +and paid **once** instead of inline in all nine files. + +## Top offenders | # | Time | Module | Note | | ---: | ---: | --- | --- | -| 1 | 147.6s | `Chips.DivRemChip.Soundness.Tail` | **the shared tail, now paid once** (was inline ×9). `instantiate metavars` 79.7s + the two big `obtain`s ~25s + `rewriteSeq` 14.6s — term-intrinsic | -| 2 | 118.0s | `Chips.DivRemChip.Completeness.Driver` | the DivRem completeness driver (sub-op col folding) | -| 3 | 61.5s | `Chips.ShiftRightChip.Soundness.Sra` | conjunct proof body | -| 4 | 56.0s | `Chips.ShiftRightChip.Soundness.Sraw` | conjunct proof body | -| 5 | 54.9s | `Chips.ShiftRightChip.Soundness.Srl` | conjunct proof body | -| 6 | 52.6s | `Chips.ShiftRightChip.Soundness.Srlw` | conjunct proof body | -| 7 | 43.3s | `Chips.DivRemChip.Defs` | instance-field simps + kernel type-checking. **Was reported "118s" in 2026-06-10 — that was contention; isolated it is ~43s** | -| 8 | 35.6s | `Chips.ShiftRightChip.Formal` | | -| 9 | 34.6s | `Chips.ShiftLeftChip.Soundness.Sll` | conjunct proof body | -| 10 | 32.7s | `Chips.ShiftLeftChip.Soundness.Sllw` | conjunct proof body | -| 11 | 29.9s | `Operations.MulOperation.Formal` | | -| 12 | 29.6s | `Operations.MulOperation.RawSpec` | | -| 13 | 23.4s | `Extracted.DivRemChip` | generated (was 462s pre-2026-06-10) | -| 14 | 22.9s | `Chips.ShiftLeftChip.Formal` | | -| 15 | 21.9s | `Chips.ShiftRightChip.Defs` | | -| 16 | 20.6s | `Chips.ShiftRightChip.Core` | arithmetic lemma farm | -| 17 | 19.7s | `Operations.LtOperationUnsigned.RawSpec` | | -| 18 | 17.1s | `TraceGenTests.DivRemChipTraceVectors` | `native_decide` anchor data | -| 19 | 17.0s | `Extracted.WitnessVectors.MulOperation` | `native_decide` anchor data | -| 20 | 16.0s | `Chips.ShiftLeftChip.Core` | | -| — | 14.7s | `Model.SailWrap` | 232 mathlib-style simp calls — not Clean-related | -| — | 13.3s | `Model.Register` | 94 `@[simp]` register lemmas, proved by `fin_cases <;> trivial` — not Clean-related | - -### Project-wide hot tactic categories (summed across all 382 logs, >50ms entries) - -`simp` 412.4s (789 calls) · type-checking 204.2s · import 197.0s (sweep artifact) · `rewriteSeq` -170.1s · `share common exprs` 88.2s · `nlinarith` interpretation 55.4s · `instantiate metavars` 52.1s. -**`simp` is the single largest category, but ~48% of it is concentrated in the top-10 heavy files** -(SailWrap, DivRem, Shifts) and the rest is spread thin (~0.4s/file) — there is no pathological broad -pattern like the pre-2026-06-10 `v[i]` tax. `instantiate metavars` (79.7s in Tail alone) and -`nlinarith` interpretation are likewise concentrated in the DivRem/Shift/Mul heavies. **The remaining -cost is term-intrinsic in a handful of files, not a broad lever.** - -## What the 2026-06-10 diagnosis established (kill-list of myths — still valid) - -Full evidence: `profile-baseline-2026-06-10/findings.md`. In brief: - -- **`circuit_proof_start` / bind-chain normalization is NOT the bottleneck** — 6k–32k heartbeats on - medium chips, ~320k (~90s) even on DivRem's `main`, the repo's biggest. The once-per-chip - `main_ops_eq` lemma would save ~4% — parked. -- **The dominant medium-chip cost was `v[i]` elaboration**, fixed by the `decide` fast path - (`Math/GetElemFastPath.lean`, one `macro_rules` line — registered above Std's slice rule). This - single fix halved isolated elaboration of the swept set in the 2026-06-10 batch. -- **`ElaboratedCircuit` field defaults can silently cost seconds**: `localLength_eq`'s `rfl` default - whnf-unfolds `main` (~15.5s on a 17-op main); `channelsLawful`'s default fails on channel-heavy - mains. Hand-write the simp-route `localLength_eq` on every new chip with a non-trivial `main`. -- The "linter tax" / "Extracted monolithic terms" threads are **resolved** (the latter was the `v[i]` - tax in disguise; the former is fixed by `set_option linter.all false` on the 76 generated modules). - -## Perf-lever investigation (2026-06-19) — broad levers are exhausted - -The 2026-06-19 sweep was mined for a new *broad* lever in the style of the `get_elem` fast path -(an attribute/macro/instance tweak that helps project-wide). **None was found** — the evidence: - -- **No shared hot tactic.** `simp`/`rewriteSeq`/`obtain`/`instantiate metavars` are all either - concentrated in the DivRem/Shift/Mul heavies or spread thin with no pathological pattern (above). -- **No broad instance-resolution cost.** Typeclass inference is negligible project-wide (only - "typeclass inference of `Nonempty`" appears, 26× small); `instantiate metavars` is concentrated, not - broad. There are no manual instance-priority declarations to tune, and none are warranted. -- **`localLength_eq` is already harvested.** All heavy chips (DivRem/Mul/Branch/Shifts/Loads/Stores) - and both ALU readers carry an explicit simp-route `localLength_eq`; the chips that still default it - (Add/Sub/Lt/Bitwise/Jal/Jalr/UType…) have small mains (2–5 ops) where the `rfl` default is cheap - (all elaborate in 2–5s). No new candidate. -- **The 94 `Model/Register.lean` register `@[simp]` lemmas impose zero global tax.** They are - discriminated by the `reg_idx_to_Register` head symbol and consumed by only 2 files - (`SailWrap`, `SailMemory`); the other 380 modules never enter them into a rewrite attempt. Measured - directly: a register-free `simp` costs **205 heartbeats with the lemmas in the default set and 205 - with them removed** (`attribute [-simp]`) — identical. Moving them to a scoped set would yield no - project-wide gain and would require editing a foundational `Model/` module (≈ full rebuild) to - validate. **Not pursued.** (Register.lean's own 13.3s is its 62 `fin_cases <;> trivial` lemma - proofs — a single-file cost, not a project lever.) -- **maxHeartbeats ceilings are loose but right-sizing is alarm-hygiene, not speed.** Lowering a - ceiling does not make a proof compile faster — it only tightens the regression alarm. The 9 DivRem - conjuncts + `Tail.lean` sit at 128M HB; measuring their true consumption needs the multi-hour - rebuild, for alarm-only value. Deferred to the mechanical `#count_heartbeats` sweep (target #4). - -**Conclusion:** the three shipped optimizations (get_elem fast path, linter-off on generated, -`localLength_eq` on big-main readers) captured the broad wins. Further compile-time reduction requires -**per-proof restructuring of the concentrated heavies** (Tail/Driver/Shift conjuncts) — a larger, -term-by-term effort, not an easy attribute/priority lever. +| 1 | 306.7s | `Proofs.Chips.DivRemChip.Soundness.Divw` | conjunct `Spec` proof; Mul product-glue `simpa`s + `instantiate metavars` | +| 2 | 305.4s | `Proofs.Chips.DivRemChip.Soundness.Remw` | " | +| 3 | 298.5s | `Proofs.Chips.DivRemChip.Soundness.Div` | " | +| 4 | 297.8s | `Proofs.Chips.DivRemChip.Soundness.Rem` | " | +| 5 | 286.5s | `Proofs.Chips.DivRemChip.Soundness.Divuw` | " | +| 6 | 284.8s | `Proofs.Chips.DivRemChip.Soundness.Remuw` | " | +| 7 | 278.3s | `Proofs.Chips.DivRemChip.Soundness.Divu` | " | +| 8 | 276.8s | `Proofs.Chips.DivRemChip.Soundness.Remu` | " | +| 9 | 123.6s | `Proofs.Chips.DivRemChip.Soundness.Tail` | **the shared tail, now paid once** (`requirements_holds`). `instantiate metavars` 61.2s + the two big `obtain`s + `rewriteSeq` — term-intrinsic | +| 10 | 100.2s | `Proofs.Chips.DivRemChip.Completeness.Driver` | the DivRem completeness driver (sub-op col folding) | +| 11 | 68.3s | `Proofs.Chips.DivRemChip.Soundness.Reader` | the reader conjunct | +| 12 | 50.7s | `Proofs.Chips.ShiftRightChip.Soundness.Sra` | conjunct proof body | +| 13 | 46.9s | `Proofs.Chips.ShiftRightChip.Soundness.Sraw` | " | +| 14 | 46.5s | `Proofs.Chips.ShiftRightChip.Soundness.Srl` | " | +| 15 | 44.5s | `Proofs.Chips.ShiftRightChip.Soundness.Srlw` | " | +| 16 | 39.0s | `Proofs.Chips.DivRemChip.Defs` | instance-field simps + kernel type-checking | +| 17 | 29.0s | `Proofs.Chips.ShiftLeftChip.Soundness.Sll` | conjunct proof body | +| 18 | 28.3s | `Proofs.Chips.ShiftRightChip.Formal` | | +| 19 | 27.1s | `Proofs.Chips.ShiftLeftChip.Soundness.Sllw` | conjunct proof body | +| 20 | 26.6s | `Native.Operations.MulOperation.RawSpec` | | +| 21 | 25.3s | `Proofs.Operations.MulOperation.Formal` | | +| 22 | 19.8s | `Extracted.DivRemChip` | generated | +| — | 13.9s | `Model.SailWrap` | mathlib-style simp/`grind` — not Clean-related | +| — | 12.0s | `Model.Register` | 62 `fin_cases <;> trivial` register lemmas — not Clean-related | + +### Project-wide hot tactic categories (summed across all 391 logs, >50ms entries) + +`simpa` 1349.5s (36 calls) · `instantiate metavars` 718.1s · `simp` 374.2s (821 calls) · `import` +314.6s (sweep artifact) · `obtain` 275.0s · `rewriteSeq` 170.4s · type-checking 132.1s · `nlinarith` +interpretation 64.7s. + +The dominant category, `simpa`, is **99.97% concentrated in the eight div/rem conjuncts** (1349.1s of +1349.5s) — the Mul product-glue `simpa`s establishing `r_k = product[2k] + product[2k+1]·256` against +the 16-element witnessed product vectors. `instantiate metavars` (70% in DivRem soundness), `obtain` +(88%), and `rewriteSeq` (88%) are likewise concentrated there; `nlinarith` interpretation sits almost +entirely in the two Shift `Core` lemma farms. `simp` is the only broadly-spread category (821 calls, +~0.4s/file). **No broad attribute/macro/instance lever remains** — the remaining cost is term-intrinsic +in the DivRem/Shift/Mul heavies, not a project-wide pattern. ## Where to optimize next (by leverage) -1. **`Chips.DivRemChip.Soundness.Tail` (147.6s) + the conjunct bodies (~322–357s contended each).** - The cost is `instantiate metavars` + the Mul product-glue `simpa`s establishing - `r_k = product[2k] + product[2k+1]·256` against the 16-element witnessed product vectors — term - intrinsic (swapping the heavy `circuit_norm` simp set for the minimal one built green with *zero* - speedup). Reducing it needs restructuring how the Mul product columns are reduced. Large effort, - largest prize. *Open.* -2. **`Chips.DivRemChip.Completeness.Driver` (118s)** — the sub-op col-folding driver; same heavy-term - family. *Open.* -3. **ShiftRight/Left conjunct bodies (~33–61s each)** — same family as (1); the shared - `cpuA/msb1A/msb2A/msb3A/aluA` block (byte-identical 236 lines across Srl/Sra/Srlw/Sraw) is a - tail-dedup candidate mirroring `f0e6f82`, a smaller prize than DivRem's. *Open.* +1. **The eight DivRem soundness conjuncts (277–307s each, 2334.6s combined) + `Tail.lean` (123.6s).** + The cost is the Mul product-glue `simpa`s + `instantiate metavars` — term-intrinsic (swapping the + heavy `circuit_norm` simp set for the minimal one builds green with *zero* speedup). Reducing it + needs restructuring how the Mul product columns are reduced. Largest effort, largest prize. *Open.* +2. **`Proofs.Chips.DivRemChip.Completeness.Driver` (100.2s)** — the sub-op col-folding driver; same + heavy-term family. *Open.* +3. **ShiftRight/Left conjunct bodies (~27–51s each)** — same family; the shared + `cpuA/msb1A/msb2A/msb3A/aluA` block (byte-identical across Srl/Sra/Srlw/Sraw) is a tail-dedup + candidate mirroring this PR's DivRem dedup, a smaller prize. *Open.* 4. **maxHeartbeats ratcheting** — mechanical `#count_heartbeats` sweep to tighten the now-loose - ceilings; restores regression-alarm value (not a speedup). *Open.* -5. **Upstream filings** (`profile-baseline-2026-06-10/upstream-notes.md`): lean4/Std slice-rule `at *` - report; Clean `localLength_eq`/`channelsLawful` default reports. *Drafted, not filed.* -6. **`Model.SailWrap` / `Model.Register` (~28s combined)** — mathlib-style simp/`fin_cases` proofs, - independent of Clean; Register's 62 `fin_cases <;> trivial` could try `decide`, but editing - `Model/` triggers a full rebuild. *Open, low priority.* + ceilings on the heavies; restores regression-alarm value (not a speedup). *Open.* +5. **`Model.SailWrap` / `Model.Register` (~26s combined)** — mathlib-style simp/`fin_cases` proofs, + independent of Clean; editing `Model/` triggers a full rebuild, so low priority. *Open.* ## Verification of this run -- Coverage: 382 modules swept, **0 nonzero exits**; the 9 DivRem conjuncts excluded by `EXCLUDE_RE` - (costs from the warm build log), `Tail.lean` included. +- Coverage: 391 modules swept, **0 nonzero exits** (no silent stack overflow). - Build gate: `lake build SP1Clean` green (**3676 jobs**), the **single** remaining `sorry` - (`Soundness/SP1GatedVm.lean:220`), 0 errors, 0 new warnings, 0 `info:` notes. (The 2026-06-10 - ShiftLeft/ShiftRight/Mul/DivRem `Formal` sorries have since been closed.) -- Axiom audit: `DivRemChip.SoundDiv.soundness` and `DivRemChip.requirements_holds` (the dedup'd tail) - both `[propext, Classical.choice, Quot.sound]` — no `sorryAx`. + (`Soundness/SP1GatedVm.lean:220`), 0 errors, 0 new warnings, 0 `info:` notes. +- Axiom audit: `DivRemChip.requirements_holds` (the dedup'd tail) is + `[propext, Classical.choice, Quot.sound]` — no `sorryAx`. From f84c28e90060bbb8c1d8a67c488da53df68421c8 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Wed, 24 Jun 2026 11:59:06 -0500 Subject: [PATCH 3/8] chore: Cleanup and linter pass --- .github/workflows/lean_action_ci.yml | 15 ++ AGENTS.md | 58 +++++- SP1Clean.lean | 1 + SP1Clean/Faithful/ALUTypeReader.lean | 9 +- SP1Clean/Faithful/AddrAddOperation.lean | 13 +- SP1Clean/Faithful/AddressOperation.lean | 20 +-- SP1Clean/Faithful/Addw.lean | 13 +- SP1Clean/Faithful/AddwChip.lean | 2 - SP1Clean/Faithful/AluX0.lean | 6 +- SP1Clean/Faithful/BranchChip.lean | 13 +- SP1Clean/Faithful/CPUState.lean | 13 +- SP1Clean/Faithful/ChipTactics.lean | 7 +- SP1Clean/Faithful/ITypeReader.lean | 6 +- SP1Clean/Faithful/ITypeReaderImmutable.lean | 6 +- SP1Clean/Faithful/IsEqualWordOperation.lean | 8 +- SP1Clean/Faithful/IsZeroOperation.lean | 8 +- SP1Clean/Faithful/IsZeroWordOperation.lean | 8 +- SP1Clean/Faithful/JTypeReader.lean | 7 +- SP1Clean/Faithful/JalChip.lean | 11 +- SP1Clean/Faithful/JalrChip.lean | 7 +- SP1Clean/Faithful/LoadByte.lean | 5 +- SP1Clean/Faithful/LoadDouble.lean | 6 +- SP1Clean/Faithful/LoadHalf.lean | 5 +- SP1Clean/Faithful/LoadWord.lean | 5 +- SP1Clean/Faithful/LoadX0.lean | 5 +- SP1Clean/Faithful/LtOperationSigned.lean | 13 +- SP1Clean/Faithful/LtOperationUnsigned.lean | 13 +- SP1Clean/Faithful/MulChip.lean | 1 - SP1Clean/Faithful/MulOperation.lean | 6 +- SP1Clean/Faithful/RTypeReader.lean | 10 +- SP1Clean/Faithful/ShiftLeftChip.lean | 2 - SP1Clean/Faithful/ShiftRightChip.lean | 2 - SP1Clean/Faithful/StoreByte.lean | 5 +- SP1Clean/Faithful/StoreDouble.lean | 5 +- SP1Clean/Faithful/StoreHalf.lean | 5 +- SP1Clean/Faithful/StoreWord.lean | 5 +- SP1Clean/Faithful/SubChip.lean | 1 - SP1Clean/Faithful/Subw.lean | 13 +- SP1Clean/Faithful/SubwChip.lean | 1 - SP1Clean/Faithful/U16CompareOperation.lean | 13 +- SP1Clean/Faithful/U16MSBOperation.lean | 13 +- SP1Clean/FormalModel/Contracts/Chips.lean | 40 ++++- SP1Clean/Math/Bitwise.lean | 3 +- SP1Clean/Math/Gate.lean | 75 ++++++++ SP1Clean/Math/Misc.lean | 4 +- SP1Clean/Math/MulCarryChain.lean | 7 +- SP1Clean/Math/Word.lean | 115 +++++++++++- SP1Clean/Model/InteractionBus.lean | 8 +- SP1Clean/Model/InteractionProjection.lean | 5 +- SP1Clean/Model/InteractionRecovery.lean | 8 +- SP1Clean/Model/Register.lean | 8 +- SP1Clean/Model/SP1Constraint.lean | 3 +- SP1Clean/Model/SailMemory.lean | 4 +- SP1Clean/Model/SailWrap.lean | 1 - SP1Clean/Native/Chips/JalrChip/Defs.lean | 7 - .../Native/Operations/AddressOperation.lean | 3 +- .../Operations/AddwOperation/RawSpec.lean | 10 +- .../Operations/BitwiseU16Operation.lean | 9 +- .../IsEqualWordOperation/RawSpec.lean | 6 +- .../LtOperationUnsigned/RawSpec.lean | 12 +- .../Operations/MulOperation/RawSpec.lean | 36 ++-- .../Operations/SubwOperation/RawSpec.lean | 10 +- .../Operations/U16toU8OperationUnsafe.lean | 3 +- SP1Clean/Native/Readers/ALUTypeReader.lean | 5 +- .../Readers/ALUTypeReaderImmutable.lean | 30 ++-- SP1Clean/Native/Readers/CPUState.lean | 12 +- .../Native/Readers/ITypeReaderImmutable.lean | 16 +- .../Native/Readers/RegisterAccessCols.lean | 2 - SP1Clean/Proofs/Chips/AddChip/Formal.lean | 25 ++- SP1Clean/Proofs/Chips/AddiChip/Formal.lean | 19 +- SP1Clean/Proofs/Chips/AddwChip/Bridge.lean | 75 +++++++- SP1Clean/Proofs/Chips/AddwChip/Formal.lean | 19 +- SP1Clean/Proofs/Chips/AluX0Chip/Formal.lean | 18 +- SP1Clean/Proofs/Chips/BitwiseChip/Bridge.lean | 167 ++++++++++++++++-- SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean | 55 +++--- SP1Clean/Proofs/Chips/BranchChip/Bridge.lean | 41 +++-- .../Proofs/Chips/BranchChip/Decision.lean | 67 +------ SP1Clean/Proofs/Chips/BranchChip/Formal.lean | 33 ++-- SP1Clean/Proofs/Chips/ByteChip.lean | 3 +- SP1Clean/Proofs/Chips/JalChip/Bridge.lean | 79 +++++++-- SP1Clean/Proofs/Chips/JalChip/Formal.lean | 48 +++-- SP1Clean/Proofs/Chips/JalrChip/Bridge.lean | 37 ++-- SP1Clean/Proofs/Chips/JalrChip/Formal.lean | 52 +++++- .../Proofs/Chips/LoadByteChip/Bridge.lean | 2 +- .../Proofs/Chips/LoadByteChip/Formal.lean | 79 +++------ .../Proofs/Chips/LoadDoubleChip/Bridge.lean | 26 +-- .../Proofs/Chips/LoadDoubleChip/Formal.lean | 44 ++--- .../Proofs/Chips/LoadHalfChip/Bridge.lean | 27 ++- .../Proofs/Chips/LoadHalfChip/Formal.lean | 67 +++---- .../Proofs/Chips/LoadWordChip/Bridge.lean | 16 +- .../Proofs/Chips/LoadWordChip/Formal.lean | 65 +++---- SP1Clean/Proofs/Chips/LoadX0Chip/Bridge.lean | 48 ++--- SP1Clean/Proofs/Chips/LoadX0Chip/Formal.lean | 52 +++--- SP1Clean/Proofs/Chips/LtChip/Bridge.lean | 125 +++++++++++-- SP1Clean/Proofs/Chips/LtChip/Formal.lean | 5 +- SP1Clean/Proofs/Chips/MemoryProvider.lean | 7 +- SP1Clean/Proofs/Chips/MulChip/Formal.lean | 12 +- SP1Clean/Proofs/Chips/ProgramChip.lean | 7 +- SP1Clean/Proofs/Chips/ShiftLeftChip/Core.lean | 116 +++++------- .../Proofs/Chips/ShiftLeftChip/Formal.lean | 10 +- .../Proofs/Chips/ShiftRightChip/Core.lean | 17 +- .../Proofs/Chips/ShiftRightChip/Flags.lean | 3 - .../Proofs/Chips/ShiftRightChip/Formal.lean | 6 +- .../Proofs/Chips/ShiftRightChip/Math.lean | 131 ++++++++------ .../Chips/ShiftRightChip/Soundness/Sra.lean | 12 +- .../Chips/ShiftRightChip/Soundness/Sraw.lean | 12 +- .../Chips/ShiftRightChip/Soundness/Srl.lean | 12 +- .../Chips/ShiftRightChip/Soundness/Srlw.lean | 12 +- .../Proofs/Chips/StoreByteChip/Bridge.lean | 6 +- .../Proofs/Chips/StoreByteChip/Formal.lean | 120 +++++-------- .../Proofs/Chips/StoreDoubleChip/Bridge.lean | 19 +- .../Proofs/Chips/StoreDoubleChip/Formal.lean | 22 +-- .../Proofs/Chips/StoreHalfChip/Bridge.lean | 6 +- .../Proofs/Chips/StoreHalfChip/Formal.lean | 97 ++++------ .../Proofs/Chips/StoreWordChip/Bridge.lean | 10 +- .../Proofs/Chips/StoreWordChip/Formal.lean | 79 +++------ SP1Clean/Proofs/Chips/SubChip/Formal.lean | 19 +- SP1Clean/Proofs/Chips/UTypeChip/Bridge.lean | 53 ++++-- SP1Clean/Proofs/Chips/UTypeChip/Formal.lean | 25 +-- .../Operations/AddOperation/Formal.lean | 68 ++----- .../Operations/AddwOperation/Formal.lean | 30 ++-- .../Operations/BitwiseOperation/Formal.lean | 70 ++------ .../IsEqualWordOperation/Formal.lean | 25 +-- .../Operations/IsZeroOperation/Formal.lean | 15 +- .../IsZeroWordOperation/Formal.lean | 53 +++--- .../Operations/LtOperationSigned/Formal.lean | 56 +++--- .../LtOperationUnsigned/Formal.lean | 53 +++--- .../Operations/MulOperation/Formal.lean | 2 +- .../Operations/SubOperation/Formal.lean | 67 ++----- .../Operations/SubwOperation/Formal.lean | 30 ++-- .../U16CompareOperation/Formal.lean | 1 - .../Operations/U16MSBOperation/Formal.lean | 4 +- SP1Clean/Soundness/ByteConsistency.lean | 10 +- SP1Clean/Soundness/ChipRegistry.lean | 2 +- SP1Clean/Soundness/Coverage.lean | 11 +- SP1Clean/Soundness/Decode.lean | 25 +-- SP1Clean/Soundness/GatedVm/BalanceMod.lean | 15 +- SP1Clean/Soundness/GatedVm/Chain.lean | 11 +- SP1Clean/Soundness/GatedVm/Formal.lean | 4 +- SP1Clean/Soundness/GatedVm/SailDispatch.lean | 5 +- SP1Clean/Soundness/GatedVm/StateBridge.lean | 14 +- SP1Clean/Soundness/InstructionTrace.lean | 2 +- SP1Clean/Soundness/MemoryConsistency.lean | 52 +++--- SP1Clean/Soundness/MemoryIsU64.lean | 20 +-- SP1Clean/Soundness/ProgramConsistency.lean | 8 +- SP1Clean/Soundness/ProgramProviderSpike.lean | 18 +- SP1Clean/Soundness/SP1GatedVm.lean | 8 +- SP1Clean/Soundness/StateConsistency.lean | 12 +- SP1Clean/Soundness/TargetVm.lean | 1 - SP1Clean/Soundness/ValueBound.lean | 4 +- docs/agents/README.md | 3 +- docs/agents/cleanup-backlog.md | 134 ++++++++++++++ docs/agents/proof-patterns.md | 89 +++++++++- docs/release-audit.md | 11 +- docs/snapshots/axiom-census.txt | 86 ++------- docs/snapshots/axiom-ledger.md | 14 +- lakefile.toml | 20 +++ scripts/axiom_probe.lean | 13 ++ scripts/check_no_skipkerneltc.sh | 29 +++ scripts/nolints.json | 20 +++ scripts/run_audit.sh | 8 + scripts/sp1Lint.lean | 128 ++++++++++++++ 162 files changed, 2202 insertions(+), 1963 deletions(-) create mode 100644 SP1Clean/Math/Gate.lean create mode 100644 docs/agents/cleanup-backlog.md create mode 100755 scripts/check_no_skipkerneltc.sh create mode 100644 scripts/nolints.json create mode 100644 scripts/sp1Lint.lean diff --git a/.github/workflows/lean_action_ci.yml b/.github/workflows/lean_action_ci.yml index 3dd73c49..5cd23da0 100644 --- a/.github/workflows/lean_action_ci.yml +++ b/.github/workflows/lean_action_ci.yml @@ -8,6 +8,15 @@ on: workflow_dispatch: jobs: + guards: + # Lightweight source-policy gates — run on a standard runner in parallel with the + # heavy build so a forbidden pattern blocks the PR in seconds without a Lean build. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: No skipKernelTC in source + run: scripts/check_no_skipkerneltc.sh + build: runs-on: [ @@ -28,4 +37,10 @@ jobs: test: "false" lint: "false" use-github-cache: "true" + # Environment linters: the `lake lint` pass (our `sp1Lint` lintDriver — a curated subset of the + # Batteries `#lint` linters over the hand-written `SP1Clean.*` declarations; see AGENTS.md § Linters). + # Runs in the build job, after the build, so the oleans it imports are present. Explicit `lake lint` + # (rather than lean-action's `lint:` flag) keeps the driver invocation unambiguous. + - name: Environment linters (lake lint) + run: lake lint diff --git a/AGENTS.md b/AGENTS.md index fe3d2d1d..20c3c688 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,8 @@ The SP1 Rust source (the extraction/spec oracle, read-only reference) lives in a A `run_in_background` build can outlive its shell — check with `ps -ef | grep -E "lake|lean" | grep -v lsp` before spawning another. The lean LSP server (`uvx lean-lsp-mcp`) also keeps several GB warm. - **Toolchain (pinned, do not bump):** `lean-toolchain` = `leanprover/lean4:v4.28.0`; mathlib `v4.28.0`; - Clean from `github.com/Verified-zkEVM/clean @ main`. Sail comes from two `github.com/succinctlabs/*` deps + Clean pinned to a specific commit on `github.com/Verified-zkEVM/clean` (the PR #398 head — re-pin to + `@ main` once it merges; see `lakefile.toml` + roadmap W9). Sail comes from two `github.com/succinctlabs/*` deps pinned to the `dtumad/clean-native` branch — `LeanRV64D` (`sail-riscv-lean`, the generated RV64 model) and `RISCV` (`riscv-lean`, the lightweight ISA fns) — which transitively pull the `rems-project/lean-sail @ v4` runtime; each carries a 4.28 `lean-toolchain`. All deps are fetched by `lake build`; nothing is a local @@ -135,11 +136,17 @@ builds just that layer. Isolation is **by convention** — Lake does not forbid imports within one package; the auto-gen guard is the `Extracted/` "do not hand-edit" headers + the sole writer `update_extracted.py`. -**Restructure status (2026-06-16).** Landed & green: the `Math`/`Model` split, `Extracted/` auto-gen +**Restructure status (updated 2026-06-23).** Landed & green: the `Math`/`Model` split, `Extracted/` auto-gen consolidation (`Circuit/` + `WitnessVectors/`), the `FormalModel/Contracts/` audit surface (all `Spec`s + the ALU chips' `Assumptions`/`ProverAssumptions`), the `Native/`+`Proofs/` five-pillar re-bucket of `Chips`/`Operations`/`Readers`/`WitnessTests`, and all six per-pillar layer libraries — build green -(3633 jobs), audit clean (314 probes, `sorryAx` confined to the known 4-sorry debt). Planned (approved +(3676 jobs), audit clean (366 probes, `sorryAx` confined to the known debt). **DivRem/ShiftLeft/ShiftRight/Mul +completeness are all closed** (2026-06-12 / 06-18), leaving **one** remaining `sorry` — `sp1_witness_decode` +(the capstone W1b/W1c decode seam). A large proof-cleanup campaign (2026-06-22 / 06-23) golfed ~109 +hand-written files (−591 lines) while preserving axiom-cleanliness, plus substrate-hoist refactors +(`Word.isU64_four`, Faithful `val_16`/`bool_iff` dedup → `ChipTactics`). Upstream `main` was merged in +2026-06-23 (#100 hard-gates `skipKernelTC` and removes overrides; #101 fleshes out the immediate-type Sail +bridges; #102 makes the jalr/jal/branch specs explicit about divisibility / LSB-clearing). Planned (approved plan, not yet done): lifting the remaining chips' `Assumptions` onto the audit surface (helper-dependent chips Jal/Jalr/Branch/DivRem/ShiftLeft/ShiftRight + split-`Spec` Lt/Bitwise keep theirs in their proof files). The guest-program execution model was surfaced in `FormalModel/Trace/GuestProgram.lean` (Phase 5); @@ -178,10 +185,39 @@ These are the keepers from sp1-lean's "faithful sub-circuit composition" discipl - `circuit_proof_start` (from `Clean.Utils.Tactics`) is the **first** tactic in soundness/completeness proofs; any `haveI`/`set_option` must come after it, or it errors "can only be used on Soundness/Completeness". -- Imports MUST precede the module doc-comment (the package `-D linter.flexible` flag is rejected on a - zero-imports header). +- Imports MUST precede the module doc-comment (a `-D linter.*` flag can't be validated against a header that + opens with a doc-comment before its imports — the linter's registration module isn't in scope yet; same + "Step 0" reason the package `[leanOptions]` carries no Mathlib linter flags, see `lakefile.toml`). +- **Linters — two kinds.** *Syntactic* linters run during `lake build` (option-gated); *environment* + linters run as a separate `lake lint` pass over the built environment. + - **Syntactic.** The `SP1Proofs` lake library enables `-D linter.style.lambdaSyntax`/`dollarSyntax` (via its + `moreLeanArgs`); these apply during the normal `lake build SP1Clean` (224 hand-written `Proofs/`/`Faithful/`/ + `Soundness/` modules) with **zero** violations. Package-level Mathlib linter flags do **not** work (the + Clean-only `Math/`/`Model/` files lack the registration) — scope any new linter to a lake lib, not the + package. The auto-gen `Extracted/`+`*Vectors` files carry per-file `set_option linter.all false`. + - **Environment (`lake lint`).** Run `lake lint` (after a build — it imports the oleans) for the Batteries + `#lint` checks. The driver is `scripts/sp1Lint.lean` (package `lintDriver = "sp1Lint"`), a thin wrapper over + `getChecks`/`lintCore`. We use a **custom driver, not the stock `runLinter` exe**, because `runLinter` + scopes by namespace *root* (`getDeclsInPackage module.getRoot`) — it would lint all `SP1Clean.*` incl. + `SP1Clean.Extracted.*`, and the per-file `set_option linter.all false` headers do **nothing** against + environment linters (those run post-import; only `nolints.json`/`@[nolint]` suppress them). `sp1Lint` + instead filters decls by full module path (drops `Extracted/`+`*Vectors`) and runs a **curated** set of 11 + low-noise linters. Residue lives in `scripts/nolints.json` (21 stable entries — 3 `defLemma` + obligation-bundle defs + 18 `simpComm`/`simpNF` Math/Model/Sail simp lemmas); `lake exe sp1Lint --update` + regenerates it; CI runs `lake lint` in the build job. Deliberately **dropped**: `docBlame`/`tacticDocs` + (doc-coverage noise) and `unusedArguments` (flags only the uniform field-generic / `ProverData` signature + args — all structural, and a fresh false-positive per new chip). + - Next candidates + the non-negotiable suppressions (`unusedSectionVars`/`unusedSimpArgs`, the auto-gen + `linter.all false`) are catalogued in `docs/agents/cleanup-backlog.md`. - Heavy `toBitVec64` rw chains are whnf-expensive — `set_option maxHeartbeats 2000000 in` (carry lemmas need up to `16000000`). +- **Never `set_option (debug.)skipKernelTC`.** It bypasses the kernel's type-check re-run — the trust anchor + for an axiom-clean proof — so it is **CI-gated** (`scripts/check_no_skipkerneltc.sh`, run by the audit and a + standalone CI `guards` job; any hit in `SP1Clean/**/*.lean` fails the build). If a goal blocks on a kernel + deep-recursion / `2^64`-unfold error, the fix is to factor the expensive compute into an **abstract-`BitVec` + helper** proved once over variables (the `srl_toNat`/`sra_toNat` pattern), then apply it symbolically — never + silence the kernel. See `docs/agents/proof-patterns.md` §"Bit-shift chip soundness" (the `2^64` bullet) for + the worked fix. - `mul_eq_zero` won't fire on `ZMod p` (a `Nat.rec` Mul-instance quirk) — derive booleanness via `inv_mul_cancel₀` / a `bool_of_mul_pred`-style lemma instead. - `Word` is an `abbrev` for `Vector` — `w.toBitVec64` dot-notation fails; write `Word.toBitVec64 w`. @@ -213,11 +249,21 @@ after installing or toggling. ## docs/ +- `docs/README.md` — index + "what to read first". - `docs/architecture.md` — the four-artifact chain, layout, design verdict, what's deferred. +- `docs/roadmap.md` — the W-graph dependency chart, open work, debt status. +- `docs/bus-model.md` — the cross-chip interaction-bus model (channels, consistency). +- `docs/release-audit.md` — the honest-claim / trust-boundary report (axiom census + sorry inventory; + regenerate with `scripts/run_audit.sh`). - `docs/agents/lean-sail-notes.md` — the 4.28 environment: toolchain pins, why public Clean `main`, the local Sail setup + `lake update` trap, the Clean-main ↔ Batteries import collision and its fix. -- `docs/agents/proof-patterns.md` — the witnessed-`FormalCircuit` soundness/completeness recipe + concrete landmines. +- `docs/agents/proof-patterns.md` — the witnessed-`FormalCircuit` soundness/completeness recipe + concrete + landmines + the **Golf & cleanup discipline** section (how to golf/clean proofs safely). - `docs/agents/porting-recipe.md` — step-by-step checklist to port a new chip from the Add/Bitwise template. +- `docs/agents/extraction.md` — the constraint-extraction pipeline (compiler → Python → Lean DSL). +- `docs/agents/mul-operation-learnings.md` — Mul-specific soundness/completeness pitfalls. +- `docs/agents/cleanup-backlog.md` — the prioritized follow-up cleanup/refactor backlog + the golfing/cleanup + skills catalog. - `docs/snapshots/compile-profile.md` — per-module wall-clock compile profile + worst offenders + common threads (point-in-time snapshot); re-run with `scripts/profile_compile.sh`. - `docs/snapshots/axiom-ledger.md` — machine-checked `#print axioms` inventory per theorem (point-in-time diff --git a/SP1Clean.lean b/SP1Clean.lean index f19c2a48..35ae46ca 100644 --- a/SP1Clean.lean +++ b/SP1Clean.lean @@ -218,6 +218,7 @@ import SP1Clean.Faithful.U16toU8OperationSafe import SP1Clean.Faithful.U16toU8OperationUnsafe import SP1Clean.Faithful.UTypeChip import SP1Clean.Math.Bitwise +import SP1Clean.Math.Gate import SP1Clean.Model.ByteTable import SP1Clean.Model.Channels import SP1Clean.Model.ChipAir diff --git a/SP1Clean/Faithful/ALUTypeReader.lean b/SP1Clean/Faithful/ALUTypeReader.lean index b90e0a77..f4d729db 100644 --- a/SP1Clean/Faithful/ALUTypeReader.lean +++ b/SP1Clean/Faithful/ALUTypeReader.lean @@ -32,11 +32,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16' [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in /-- **Faithfulness anchor (ALUTypeReader fragment).** Under `is_real = is_trusted = 1`, SP1's generated `ALUTypeReader` constraint list holds iff the combined spec holds. -/ @@ -69,9 +64,9 @@ theorem alutypereader_constraints_faithful Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_memory, Interaction.toProp_send_program, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, - ByteOpcode.constrain_U8Range, val_16', ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, + ByteOpcode.constrain_U8Range, val_16_zmod_p, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, sub_self, mul_zero, sub_zero, zero_mul, Nat.ofNat_pos, true_and, and_true, - show (2 : ℕ) ^ 8 = 256 from by norm_num, show (2 : ℕ) ^ 16 = 65536 from by norm_num] + show (2 : ℕ) ^ 8 = 256 by norm_num, show (2 : ℕ) ^ 16 = 65536 by norm_num] tauto open SP1Clean.Channels (byteChannel memoryChannel MemoryMsg programChannel ProgramMsg) diff --git a/SP1Clean/Faithful/AddrAddOperation.lean b/SP1Clean/Faithful/AddrAddOperation.lean index 13b86af3..1b816298 100644 --- a/SP1Clean/Faithful/AddrAddOperation.lean +++ b/SP1Clean/Faithful/AddrAddOperation.lean @@ -7,6 +7,7 @@ import SP1Clean.Model.InteractionProjection import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.AddrAddOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (AddrAdd) @@ -23,18 +24,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - /-- **Faithfulness anchor.** SP1's `AddrAddOperation` constraint list holds iff the native gadget's `RawSpec` holds. -/ theorem addrAdd_constraints_faithful (a b : Word (ZMod p)) diff --git a/SP1Clean/Faithful/AddressOperation.lean b/SP1Clean/Faithful/AddressOperation.lean index 899f0618..a45e75f3 100644 --- a/SP1Clean/Faithful/AddressOperation.lean +++ b/SP1Clean/Faithful/AddressOperation.lean @@ -3,6 +3,7 @@ import Mathlib.Data.ZMod.Basic import SP1Clean.Native.Operations.AddressOperation import SP1Clean.Model.SP1Constraint import SP1Clean.Extracted.AddressOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (Address) @@ -19,23 +20,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -/-- `(13 : ZMod p).val = 13` under `Fact (2^17 < p)`. -/ -private lemma val_13 [NeZero p] : (13 : ZMod p).val = 13 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (13 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - set_option maxHeartbeats 4000000 in /-- **Faithfulness anchor.** SP1's `AddressOperation` constraint lists (`asserts` + `interactions`) hold iff the native gadget's `RawSpec` holds. -/ @@ -52,7 +36,7 @@ theorem address_constraints_faithful (b cc : Word (ZMod p)) List.Forall, List.cons_append, List.nil_append, Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ, Interaction.toProp_send_byte, ByteOpcode.ofNat_six, - ByteOpcode.constrain_Range, val_16, val_13, one_ne_zero, ne_eq, not_false_eq_true, true_implies, + ByteOpcode.constrain_Range, val_16, val_13_zmod_p, one_ne_zero, ne_eq, not_false_eq_true, true_implies, SP1Clean.AddressOperation.RawSpec, SP1Clean.AddrAddOperation.RawSpec, one_mul, add_zero, sub_zero, sub_self, mul_zero, true_and, and_assoc, bool_iff, show (2 : ℕ) ^ 16 = 65536 from by norm_num] diff --git a/SP1Clean/Faithful/Addw.lean b/SP1Clean/Faithful/Addw.lean index bab9b5cb..adbc5a6c 100644 --- a/SP1Clean/Faithful/Addw.lean +++ b/SP1Clean/Faithful/Addw.lean @@ -8,6 +8,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.AddwOperation import SP1Clean.Faithful.U16MSBOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — `AddwOperation` constraints ↔ native raw spec @@ -25,18 +26,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - set_option linter.unusedSimpArgs false in /-- **Faithfulness anchor.** SP1's composed `AddwOperation` constraint list holds iff the native gadget's combined raw spec (`U16MSBOperation.RawSpec` on the sign bit ∧ `AddwOperation.RawSpec` on diff --git a/SP1Clean/Faithful/AddwChip.lean b/SP1Clean/Faithful/AddwChip.lean index 2bcfd17f..ecebda0e 100644 --- a/SP1Clean/Faithful/AddwChip.lean +++ b/SP1Clean/Faithful/AddwChip.lean @@ -236,7 +236,6 @@ theorem addwcols_memory_interactions_faithful_syntactic h_pl_b, h_pv_b0, h_pv_b1, h_pv_b2, h_pv_b3, h_pl_c, h_pv_c0, h_pv_c1, h_pv_c2, h_pv_c3, sub_eq_add_neg] - set_option maxHeartbeats 4000000 in set_option linter.unusedSimpArgs false in /-- **Chip-level faithfulness anchor — Byte-bus interactions, SYNTACTIC (multi-fragment `Perm` + WITNESSED).** @@ -307,7 +306,6 @@ theorem addwcols_byte_interactions_faithful_syntactic -- swap the first two blocks (the `ALUTypeReader 6` tail is shared). exact (List.perm_append_comm (l₁ := [_, _]) (l₂ := [_, _, _])).append_right [_, _, _, _, _, _] - set_option maxHeartbeats 4000000 in /-- **Chip-level faithfulness anchor — COMBINED, SYNTACTIC.** The full faithfulness statement for `AddwChip`: the interactions the row emits on its four buses — `State`, `Byte`, `Memory`, `Program` — taken together diff --git a/SP1Clean/Faithful/AluX0.lean b/SP1Clean/Faithful/AluX0.lean index b155cde6..48389ffa 100644 --- a/SP1Clean/Faithful/AluX0.lean +++ b/SP1Clean/Faithful/AluX0.lean @@ -22,10 +22,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16'' [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - private lemma val_29'' [NeZero p] : (29 : ZMod p).val = 29 := by have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega exact ZMod.val_natCast_of_lt (show (29 : ℕ) < p by omega) @@ -76,7 +72,7 @@ theorem alux0cols_constraints_faithful (cols : Extracted.AluX0Cols (ZMod p)) Interaction.toProp_send_memory, Interaction.toProp_send_program, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.ofNat_four, ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, ByteOpcode.constrain_LTU, - val_16'', val_29'', ZMod.val_zero, ZMod.val_one, one_ne_zero, ne_eq, not_false_eq_true, + val_16_zmod_p, val_29'', ZMod.val_zero, ZMod.val_one, one_ne_zero, ne_eq, not_false_eq_true, true_implies, sub_self, mul_zero, sub_zero, zero_mul, one_mul, Nat.ofNat_pos, true_and, and_true, false_or, true_iff, show (1 : ℕ) < 256 from by norm_num, show (29 : ℕ) < 256 from by norm_num, show (2 : ℕ) ^ 8 = 256 from by norm_num, show (2 : ℕ) ^ 16 = 65536 from by norm_num] diff --git a/SP1Clean/Faithful/BranchChip.lean b/SP1Clean/Faithful/BranchChip.lean index 29cf8d5e..344d1aeb 100644 --- a/SP1Clean/Faithful/BranchChip.lean +++ b/SP1Clean/Faithful/BranchChip.lean @@ -3,6 +3,7 @@ import Mathlib.Data.ZMod.Basic import SP1Clean.Proofs.Chips.BranchChip.Formal import SP1Clean.Model.SP1Constraint import SP1Clean.Extracted.BranchChip +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor for BRANCH @@ -25,16 +26,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(14 : ZMod p).val = 14` under `Fact (2^17 < p)` (for the `Range(next_pc[0]/4, 14)` send). -/ -private lemma val_14 [NeZero p] : (14 : ZMod p).val = 14 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (14 : ℕ) < p by omega) - -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)` (for the `next_pc[1]`/`next_pc[2]` u16 sends). -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - /-- The chip-local structural meaning of BRANCH's `asserts` inline tail (everything past the composed `CPUState`/`ITypeReaderImmutable`/`LtOperationSigned` sub-lists): the six opcode-flag booleans, the flag sum bound (`E16 = Σ is_b*` boolean), the `is_branching` boolean, the `is_real`-gated decision constraint @@ -97,7 +88,7 @@ theorem branch_interactions_faithful (cols : BranchColumns (ZMod p)) : -- `Range(next_pc[0]/4, 14)`, u16 `next_pc[1]`, u16 `next_pc[2]`, each gated by the flag sum `E16`. simp only [Extracted.BranchColumns.interactions, List.forall_append, List.Forall, Interaction.toProp_send_byte, ByteOpcode.ofNat_six, ByteOpcode.constrain_Range, - val_14, val_16] at h + val_14_zmod_p, val_16] at h obtain ⟨-, h0, h1, h2⟩ := h simp only [branchInteractSpec] refine ⟨?_, ?_, ?_⟩ diff --git a/SP1Clean/Faithful/CPUState.lean b/SP1Clean/Faithful/CPUState.lean index 30180dd2..418e3fee 100644 --- a/SP1Clean/Faithful/CPUState.lean +++ b/SP1Clean/Faithful/CPUState.lean @@ -24,11 +24,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(13 : ZMod p).val = 13` under `Fact (2^17 < p)`. -/ -private lemma val_13 [NeZero p] : (13 : ZMod p).val = 13 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (13 : ℕ) < p by omega) - /-- **Faithfulness anchor (CPUState fragment).** Under `is_real = 1`, SP1's generated `CPUState` constraint list holds iff the two clock-range bounds hold — i.e. iff `Readers.CPUState.Spec` holds. The `.state` `receive`/`send` interactions contribute `True` (their meaning is the trace-level bus); @@ -42,9 +37,9 @@ theorem cpustate_constraints_faithful simp only [Extracted.CPUState.asserts, Extracted.CPUState.interactions, List.Forall, Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_state, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, - ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_13, ZMod.val_zero, + ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_13_zmod_p, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, sub_self, mul_zero, - Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 from by norm_num] + Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 by norm_num] omit [Fact (2 ^ 17 < p)] in /-- **CPUState fragment — assertion half.** `CPUState` emits only the (vacuous at `is_real = 1`) @@ -65,9 +60,9 @@ theorem cpustate_interactions_faithful simp only [Extracted.CPUState.interactions, List.Forall, Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_state, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, - ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_13, ZMod.val_zero, + ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_13_zmod_p, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, - Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 from by norm_num] + Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 by norm_num] open SP1Clean.Channels (stateChannel byteChannel StateMsg) diff --git a/SP1Clean/Faithful/ChipTactics.lean b/SP1Clean/Faithful/ChipTactics.lean index 62a148b7..7e39ca25 100644 --- a/SP1Clean/Faithful/ChipTactics.lean +++ b/SP1Clean/Faithful/ChipTactics.lean @@ -22,10 +22,9 @@ namespace SP1Clean.Faithful variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) +/-- `(16 : ZMod p).val = 16` — the Faithful-local spelling of `SP1Clean.val_16_zmod_p` +(kept as the established name used across the per-chip anchors' `simp only` lists). -/ +lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := val_16_zmod_p omit [Fact (2 ^ 17 < p)] in /-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via the `mul_eq_zero`-free diff --git a/SP1Clean/Faithful/ITypeReader.lean b/SP1Clean/Faithful/ITypeReader.lean index 8ee8e2db..f0b11093 100644 --- a/SP1Clean/Faithful/ITypeReader.lean +++ b/SP1Clean/Faithful/ITypeReader.lean @@ -2,6 +2,7 @@ import Mathlib.Tactic import Mathlib.Data.ZMod.Basic import SP1Clean.Model.SP1Constraint import SP1Clean.Extracted.ITypeReader +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — SP1's `ITypeReader` constraint fragment ↔ the native reader spec @@ -30,11 +31,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 2000000 in /-- **Faithfulness anchor (ITypeReader fragment).** Under `is_real = is_trusted = 1`, SP1's generated `ITypeReader` constraint list holds iff the four `op_a_0` zeroing equations and the two register diff --git a/SP1Clean/Faithful/ITypeReaderImmutable.lean b/SP1Clean/Faithful/ITypeReaderImmutable.lean index 4b0558c0..5a433fa4 100644 --- a/SP1Clean/Faithful/ITypeReaderImmutable.lean +++ b/SP1Clean/Faithful/ITypeReaderImmutable.lean @@ -2,6 +2,7 @@ import Mathlib.Tactic import Mathlib.Data.ZMod.Basic import SP1Clean.Model.SP1Constraint import SP1Clean.Extracted.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — SP1's `ITypeReaderImmutable` constraint fragment ↔ the native reader spec @@ -26,11 +27,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 2000000 in /-- **Faithfulness anchor (ITypeReaderImmutable fragment).** Under `is_real = is_trusted = 1`, SP1's generated `ITypeReaderImmutable` constraint list holds iff the four `op_a_0` *read*-zeroing equations and diff --git a/SP1Clean/Faithful/IsEqualWordOperation.lean b/SP1Clean/Faithful/IsEqualWordOperation.lean index 222daa5b..8873e328 100644 --- a/SP1Clean/Faithful/IsEqualWordOperation.lean +++ b/SP1Clean/Faithful/IsEqualWordOperation.lean @@ -8,6 +8,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.IsEqualWordOperation import SP1Clean.Faithful.IsZeroWordOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (IsEqualWord) @@ -24,13 +25,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - omit [Fact (2 ^ 17 < p)] in /-- **Faithfulness anchor.** SP1's `IsEqualWordOperation` constraint list holds iff the native gadget's `RawSpec` holds. -/ diff --git a/SP1Clean/Faithful/IsZeroOperation.lean b/SP1Clean/Faithful/IsZeroOperation.lean index 02ab0286..f2ddd07b 100644 --- a/SP1Clean/Faithful/IsZeroOperation.lean +++ b/SP1Clean/Faithful/IsZeroOperation.lean @@ -7,6 +7,7 @@ import SP1Clean.Model.InteractionProjection import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.IsZeroOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (IsZero) @@ -24,13 +25,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - omit [Fact (2 ^ 17 < p)] in /-- **Faithfulness anchor — assertion half.** SP1's `IsZeroOperation` `asserts` list holds iff the native gadget's `AssertSpec` holds. (No range bounds here, so `NeZero p` follows from primality diff --git a/SP1Clean/Faithful/IsZeroWordOperation.lean b/SP1Clean/Faithful/IsZeroWordOperation.lean index e27dd6cc..0302993c 100644 --- a/SP1Clean/Faithful/IsZeroWordOperation.lean +++ b/SP1Clean/Faithful/IsZeroWordOperation.lean @@ -8,6 +8,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.IsZeroWordOperation import SP1Clean.Faithful.IsZeroOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (IsZeroWord) @@ -26,13 +27,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - omit [Fact (2 ^ 17 < p)] in /-- **Faithfulness anchor.** SP1's `IsZeroWordOperation` constraint list holds iff the native gadget's `RawSpec` holds. (No range bounds, so `NeZero p` follows from primality.) -/ diff --git a/SP1Clean/Faithful/JTypeReader.lean b/SP1Clean/Faithful/JTypeReader.lean index a6c076ec..8eb8b7af 100644 --- a/SP1Clean/Faithful/JTypeReader.lean +++ b/SP1Clean/Faithful/JTypeReader.lean @@ -31,11 +31,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16' [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - omit [Fact (2 ^ 17 < p)] in /-- **JTypeReader fragment — assertion half.** Under `is_real = 1`, SP1's generated `JTypeReader` `asserts` list holds iff the four `op_a_0` zeroing equations (`rd = x0 ⟹ write 0`); the two binary @@ -67,7 +62,7 @@ theorem jtypereader_interactions_faithful Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_memory, Interaction.toProp_send_program, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, - ByteOpcode.constrain_U8Range, val_16', ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, + ByteOpcode.constrain_U8Range, val_16_zmod_p, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 from by norm_num, show (2 : ℕ) ^ 16 = 65536 from by norm_num] diff --git a/SP1Clean/Faithful/JalChip.lean b/SP1Clean/Faithful/JalChip.lean index 86d20b4b..8137681a 100644 --- a/SP1Clean/Faithful/JalChip.lean +++ b/SP1Clean/Faithful/JalChip.lean @@ -33,15 +33,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(13 : ZMod p).val = 13` under `Fact (2^17 < p)`. -/ -private lemma val_13 [NeZero p] : (13 : ZMod p).val = 13 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (13 : ℕ) < p by omega) - -/-- `(14 : ZMod p).val = 14` under `Fact (2^17 < p)`. -/ -private lemma val_14 [NeZero p] : (14 : ZMod p).val = 14 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (14 : ℕ) < p by omega) set_option maxHeartbeats 2000000 in /-- **Chip-level faithfulness anchor — assertion half.** Under `is_real = 1 ∧ op_a_0 = 0`, SP1's generated @@ -101,7 +92,7 @@ theorem jalcols_interactions_faithful (cols : Extracted.JalColumns (ZMod p)) simp only [List.Forall, Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_memory, Interaction.toProp_send_program, Interaction.toProp_send_state, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, - ByteOpcode.constrain_U8Range, val_13, val_14, val_16, ZMod.val_zero, one_ne_zero, ne_eq, + ByteOpcode.constrain_U8Range, val_13_zmod_p, val_14_zmod_p, val_16, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 from by norm_num] tauto diff --git a/SP1Clean/Faithful/JalrChip.lean b/SP1Clean/Faithful/JalrChip.lean index 77c9d888..e00971d4 100644 --- a/SP1Clean/Faithful/JalrChip.lean +++ b/SP1Clean/Faithful/JalrChip.lean @@ -35,11 +35,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(14 : ZMod p).val = 14` under `Fact (2^17 < p)`. -/ -private lemma val_14 [NeZero p] : (14 : ZMod p).val = 14 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (14 : ℕ) < p by omega) - set_option maxHeartbeats 2000000 in /-- **Chip-level faithfulness anchor — assertion half.** Under `is_real = 1 ∧ op_a_0 = 0`, SP1's generated JALR `asserts` list holds iff: the two `AddOperation` assertion specs (carry-bools) for the jump target @@ -107,7 +102,7 @@ theorem jalrcols_interactions_faithful (cols : Extracted.JalrColumns (ZMod p)) simp only [Extracted.ITypeReader.interactions, List.Forall, Interaction.toProp_send_byte, Interaction.toProp_receive, Interaction.toProp_send_memory, Interaction.toProp_send_program, ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, - ByteOpcode.constrain_U8Range, val_14, val_16, ZMod.val_zero, one_ne_zero, ne_eq, + ByteOpcode.constrain_U8Range, val_14_zmod_p, val_16, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, Nat.ofNat_pos, true_and, and_true, show (2 : ℕ) ^ 8 = 256 from by norm_num] tauto diff --git a/SP1Clean/Faithful/LoadByte.lean b/SP1Clean/Faithful/LoadByte.lean index c4a5cd59..6ea40045 100644 --- a/SP1Clean/Faithful/LoadByte.lean +++ b/SP1Clean/Faithful/LoadByte.lean @@ -6,6 +6,7 @@ import SP1Clean.Extracted.LoadByteChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReader +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `LoadByte` chip constraint list ↔ combined spec -/ @@ -17,10 +18,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option linter.unusedSimpArgs false in set_option maxHeartbeats 4000000 in theorem loadbytecols_constraints_faithful (cols : Extracted.LoadByteColumns (ZMod p)) diff --git a/SP1Clean/Faithful/LoadDouble.lean b/SP1Clean/Faithful/LoadDouble.lean index 6538844d..7989ec19 100644 --- a/SP1Clean/Faithful/LoadDouble.lean +++ b/SP1Clean/Faithful/LoadDouble.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.LoadDoubleChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReader +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `LoadDouble` chip constraint list ↔ combined spec @@ -28,11 +29,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in /-- **Chip-level faithfulness anchor.** Under `is_real = 1`, SP1's generated `LoadDouble` chip constraint list holds iff: the `AddressOperation` raw spec on the `rs1` register read and the diff --git a/SP1Clean/Faithful/LoadHalf.lean b/SP1Clean/Faithful/LoadHalf.lean index 0faa98cf..961c3e21 100644 --- a/SP1Clean/Faithful/LoadHalf.lean +++ b/SP1Clean/Faithful/LoadHalf.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.LoadHalfChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReader +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `LoadHalf` chip constraint list ↔ combined spec -/ @@ -16,10 +17,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option linter.unusedSimpArgs false in set_option maxHeartbeats 4000000 in theorem loadhalfcols_constraints_faithful (cols : Extracted.LoadHalfColumns (ZMod p)) diff --git a/SP1Clean/Faithful/LoadWord.lean b/SP1Clean/Faithful/LoadWord.lean index 0d7b5bec..0fa95ea1 100644 --- a/SP1Clean/Faithful/LoadWord.lean +++ b/SP1Clean/Faithful/LoadWord.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.LoadWordChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReader +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `LoadWord` chip constraint list ↔ combined spec -/ @@ -16,10 +17,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in theorem loadwordcols_constraints_faithful (cols : Extracted.LoadWordColumns (ZMod p)) (h_real : cols.is_lw + cols.is_lwu = 1) : diff --git a/SP1Clean/Faithful/LoadX0.lean b/SP1Clean/Faithful/LoadX0.lean index 085bbc7b..569e2d76 100644 --- a/SP1Clean/Faithful/LoadX0.lean +++ b/SP1Clean/Faithful/LoadX0.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.LoadX0Chip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `LoadX0` chip constraint list ↔ combined spec @@ -26,10 +27,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 8000000 in /-- **Chip-level faithfulness anchor.** Under the one-hot selector hypothesis `is_lb + … + is_ld = 1`, SP1's generated `LoadX0` chip constraint list holds iff: the `AddressOperation` raw spec (with the three diff --git a/SP1Clean/Faithful/LtOperationSigned.lean b/SP1Clean/Faithful/LtOperationSigned.lean index 9dbad5a0..25231cc1 100644 --- a/SP1Clean/Faithful/LtOperationSigned.lean +++ b/SP1Clean/Faithful/LtOperationSigned.lean @@ -4,6 +4,7 @@ import SP1Clean.Native.Operations.LtOperationSigned.RawSpec import SP1Clean.Model.SP1Constraint import SP1Clean.Extracted.LtOperationSigned import SP1Clean.Faithful.LtOperationUnsigned +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (LtSigned) @@ -22,18 +23,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - set_option maxHeartbeats 8000000 in /-- **Faithfulness anchor.** SP1's `LtOperationSigned` constraint list (at `is_real = 1`, `is_signed` free) holds iff the native gadget's `RawSpec` holds. -/ diff --git a/SP1Clean/Faithful/LtOperationUnsigned.lean b/SP1Clean/Faithful/LtOperationUnsigned.lean index 4bcde0bd..0fab4e79 100644 --- a/SP1Clean/Faithful/LtOperationUnsigned.lean +++ b/SP1Clean/Faithful/LtOperationUnsigned.lean @@ -8,6 +8,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Faithful.U16CompareOperation import SP1Clean.Extracted.LtOperationUnsigned +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (LtUnsigned) @@ -25,18 +26,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - set_option maxHeartbeats 4000000 in /-- **Faithfulness anchor.** SP1's `LtOperationUnsigned` constraint list holds iff the native gadget's `RawSpec` holds. -/ diff --git a/SP1Clean/Faithful/MulChip.lean b/SP1Clean/Faithful/MulChip.lean index 49ad7ce5..da3f55b2 100644 --- a/SP1Clean/Faithful/MulChip.lean +++ b/SP1Clean/Faithful/MulChip.lean @@ -32,7 +32,6 @@ theorem mul_asserts_faithful (cols : Extracted.MulCols (ZMod p)) : intro h haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 24 < p); omega⟩ simp only [Extracted.MulCols.asserts, Extracted.forall_append_single, List.Forall] at h - dsimp only [SP1Clean.MulChip.AssertSpec] exact h.2 omit [Fact (2 ^ 24 < p)] in diff --git a/SP1Clean/Faithful/MulOperation.lean b/SP1Clean/Faithful/MulOperation.lean index 676dd266..9b1d0087 100644 --- a/SP1Clean/Faithful/MulOperation.lean +++ b/SP1Clean/Faithful/MulOperation.lean @@ -26,10 +26,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16' [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - /-- The high decomposition byte of `b` (`E7 = U16toU8OperationSafe.value b ...[7]`), the value the `MSB` send pins `b_msb` against. -/ private def hiByteB (b : Word (ZMod p)) (cols : Extracted.MulOperation (ZMod p)) (is_real : ZMod p) : @@ -92,7 +88,7 @@ theorem mulOp_interactions_faithful (a b c : Word (ZMod p)) haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ simp only [Extracted.MulOperation.interactions, List.forall_append, List.Forall, Interaction.toProp_send_byte, ByteOpcode.ofNat_three, ByteOpcode.ofNat_five, ByteOpcode.ofNat_six, - ByteOpcode.constrain_U8Range, ByteOpcode.constrain_MSB, ByteOpcode.constrain_Range, val_16', + ByteOpcode.constrain_U8Range, ByteOpcode.constrain_MSB, ByteOpcode.constrain_Range, val_16_zmod_p, one_ne_zero, ne_eq, not_false_eq_true, true_implies, MulOpInteractSpec, hiByteB, hiByteC, and_assoc] diff --git a/SP1Clean/Faithful/RTypeReader.lean b/SP1Clean/Faithful/RTypeReader.lean index 9d879136..f8c48b56 100644 --- a/SP1Clean/Faithful/RTypeReader.lean +++ b/SP1Clean/Faithful/RTypeReader.lean @@ -6,6 +6,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Native.Readers.RTypeReader import SP1Clean.Extracted.RTypeReader +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — SP1's `RTypeReader` constraint fragment ↔ the native reader spec @@ -33,11 +34,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 2000000 in /-- **Faithfulness anchor (RTypeReader fragment).** Under `is_real = is_trusted = 1`, SP1's generated `RTypeReader` constraint list holds iff the four `op_a_0` zeroing equations and the three operands' @@ -68,7 +64,7 @@ theorem rtypereader_constraints_faithful ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_16, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, sub_self, mul_zero, sub_zero, Nat.ofNat_pos, true_and, and_true, - show (2 : ℕ) ^ 8 = 256 from by norm_num, show (2 : ℕ) ^ 16 = 65536 from by norm_num] + show (2 : ℕ) ^ 8 = 256 by norm_num, show (2 : ℕ) ^ 16 = 65536 by norm_num] -- The simp leaves a right-associated flat conjunction; the RHS groups the byte facts per operand. -- Only associativity differs. tauto @@ -113,7 +109,7 @@ theorem rtypereader_interactions_faithful ByteOpcode.ofNat_six, ByteOpcode.ofNat_three, ByteOpcode.constrain_Range, ByteOpcode.constrain_U8Range, val_16, ZMod.val_zero, one_ne_zero, ne_eq, not_false_eq_true, true_implies, Nat.ofNat_pos, true_and, and_true, - show (2 : ℕ) ^ 8 = 256 from by norm_num, show (2 : ℕ) ^ 16 = 65536 from by norm_num] + show (2 : ℕ) ^ 8 = 256 by norm_num, show (2 : ℕ) ^ 16 = 65536 by norm_num] tauto open SP1Clean.Channels (byteChannel memoryChannel MemoryMsg programChannel ProgramMsg) diff --git a/SP1Clean/Faithful/ShiftLeftChip.lean b/SP1Clean/Faithful/ShiftLeftChip.lean index 86c5f826..c3eb21c7 100644 --- a/SP1Clean/Faithful/ShiftLeftChip.lean +++ b/SP1Clean/Faithful/ShiftLeftChip.lean @@ -28,7 +28,6 @@ theorem shiftLeft_asserts_faithful (cols : Extracted.ShiftLeftCols (ZMod p)) : intro h haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ simp only [Extracted.ShiftLeftCols.asserts, Extracted.forall_append_single, List.Forall] at h - dsimp only [SP1Clean.ShiftLeftChip.AssertSpec] exact h.2 omit [Fact (2 ^ 17 < p)] in @@ -43,7 +42,6 @@ theorem shiftLeft_interactions_faithful (cols : Extracted.ShiftLeftCols (ZMod p) List.Forall, Interaction.toProp_send_byte, show (ByteOpcode.ofNat 6 : ByteOpcode) = ByteOpcode.Range from rfl, ByteOpcode.constrain, zero_add] at h - dsimp only [SP1Clean.ShiftLeftChip.InteractSpec] exact h.2 end SP1Clean.Faithful diff --git a/SP1Clean/Faithful/ShiftRightChip.lean b/SP1Clean/Faithful/ShiftRightChip.lean index 52914989..33ff33c8 100644 --- a/SP1Clean/Faithful/ShiftRightChip.lean +++ b/SP1Clean/Faithful/ShiftRightChip.lean @@ -34,7 +34,6 @@ theorem shiftRight_asserts_faithful (cols : Extracted.ShiftRightCols (ZMod p)) : haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ simp only [Extracted.ShiftRightCols.asserts, Extracted.forall_append_single, List.Forall, Nat.cast_one] at h - dsimp only [SP1Clean.ShiftRightChip.AssertSpec] exact h.2 omit [Fact (2 ^ 17 < p)] in @@ -49,7 +48,6 @@ theorem shiftRight_interactions_faithful (cols : Extracted.ShiftRightCols (ZMod List.Forall, Interaction.toProp_send_byte, show (ByteOpcode.ofNat 6 : ByteOpcode) = ByteOpcode.Range from rfl, ByteOpcode.constrain, zero_add] at h - dsimp only [SP1Clean.ShiftRightChip.InteractSpec] exact h.2 end SP1Clean.Faithful diff --git a/SP1Clean/Faithful/StoreByte.lean b/SP1Clean/Faithful/StoreByte.lean index f2388fbb..a0d0e9a7 100644 --- a/SP1Clean/Faithful/StoreByte.lean +++ b/SP1Clean/Faithful/StoreByte.lean @@ -6,6 +6,7 @@ import SP1Clean.Extracted.StoreByteChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `StoreByte` chip constraint list ↔ combined spec -/ @@ -17,10 +18,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option linter.unusedSimpArgs false in set_option maxHeartbeats 4000000 in theorem storebytecols_constraints_faithful (cols : Extracted.StoreByteColumns (ZMod p)) diff --git a/SP1Clean/Faithful/StoreDouble.lean b/SP1Clean/Faithful/StoreDouble.lean index 3362fe3e..0ac18cda 100644 --- a/SP1Clean/Faithful/StoreDouble.lean +++ b/SP1Clean/Faithful/StoreDouble.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.StoreDoubleChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `StoreDouble` chip constraint list ↔ combined spec @@ -28,10 +29,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in /-- **Chip-level faithfulness anchor.** Under `is_real = 1`, SP1's generated `StoreDouble` chip constraint list holds iff: the `AddressOperation` raw spec on the `rs1` register read and the immediate diff --git a/SP1Clean/Faithful/StoreHalf.lean b/SP1Clean/Faithful/StoreHalf.lean index 923e3e22..29b8a391 100644 --- a/SP1Clean/Faithful/StoreHalf.lean +++ b/SP1Clean/Faithful/StoreHalf.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.StoreHalfChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `StoreHalf` chip constraint list ↔ combined spec -/ @@ -16,10 +17,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in theorem storehalfcols_constraints_faithful (cols : Extracted.StoreHalfColumns (ZMod p)) (h_real : cols.is_real = 1) : diff --git a/SP1Clean/Faithful/StoreWord.lean b/SP1Clean/Faithful/StoreWord.lean index 95d79fb0..0cc4af23 100644 --- a/SP1Clean/Faithful/StoreWord.lean +++ b/SP1Clean/Faithful/StoreWord.lean @@ -5,6 +5,7 @@ import SP1Clean.Extracted.StoreWordChip import SP1Clean.Faithful.AddressOperation import SP1Clean.Faithful.CPUState import SP1Clean.Faithful.ITypeReaderImmutable +import SP1Clean.Faithful.ChipTactics /-! # Chip-level faithfulness anchor — SP1's whole `StoreWord` chip constraint list ↔ combined spec -/ @@ -16,10 +17,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - set_option maxHeartbeats 4000000 in theorem storewordcols_constraints_faithful (cols : Extracted.StoreWordColumns (ZMod p)) (h_real : cols.is_real = 1) : diff --git a/SP1Clean/Faithful/SubChip.lean b/SP1Clean/Faithful/SubChip.lean index 37cd0cdf..6e297e4f 100644 --- a/SP1Clean/Faithful/SubChip.lean +++ b/SP1Clean/Faithful/SubChip.lean @@ -364,5 +364,4 @@ theorem subcols_interactions_faithful_syntactic rw [hS, hM, hP] exact ((hB.append_left _).append_right _).append_right _ - end SP1Clean.Faithful diff --git a/SP1Clean/Faithful/Subw.lean b/SP1Clean/Faithful/Subw.lean index ae2ed81c..c8eb58e6 100644 --- a/SP1Clean/Faithful/Subw.lean +++ b/SP1Clean/Faithful/Subw.lean @@ -8,6 +8,7 @@ import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.SubwOperation import SP1Clean.Faithful.U16MSBOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — `SubwOperation` constraints ↔ native combined raw spec @@ -26,18 +27,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - set_option linter.unusedSimpArgs false in /-- **Faithfulness anchor.** SP1's composed `SubwOperation` constraint list holds iff the native gadget's combined raw spec (`U16MSBOperation.RawSpec` on the sign bit ∧ `SubwOperation.RawSpec` on diff --git a/SP1Clean/Faithful/SubwChip.lean b/SP1Clean/Faithful/SubwChip.lean index 7d274b3f..93510835 100644 --- a/SP1Clean/Faithful/SubwChip.lean +++ b/SP1Clean/Faithful/SubwChip.lean @@ -370,5 +370,4 @@ theorem subwcols_interactions_faithful_syntactic rw [hS, hM, hP] exact ((hB.append_left _).append_right _).append_right _ - end SP1Clean.Faithful diff --git a/SP1Clean/Faithful/U16CompareOperation.lean b/SP1Clean/Faithful/U16CompareOperation.lean index b2803104..52729da2 100644 --- a/SP1Clean/Faithful/U16CompareOperation.lean +++ b/SP1Clean/Faithful/U16CompareOperation.lean @@ -7,6 +7,7 @@ import SP1Clean.Model.InteractionProjection import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.U16CompareOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor to the SP1 (Rust-extraction) constraints (U16Compare) @@ -24,18 +25,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - /-- **Faithfulness anchor.** SP1's `U16CompareOperation` constraint list holds iff the native gadget's `RawSpec` holds. -/ theorem u16compare_constraints_faithful (a b : ZMod p) diff --git a/SP1Clean/Faithful/U16MSBOperation.lean b/SP1Clean/Faithful/U16MSBOperation.lean index 76d6bc9f..ced152ee 100644 --- a/SP1Clean/Faithful/U16MSBOperation.lean +++ b/SP1Clean/Faithful/U16MSBOperation.lean @@ -7,6 +7,7 @@ import SP1Clean.Model.InteractionProjection import SP1Clean.Model.InteractionRecovery import SP1Clean.Faithful.ExtractedInteractionModel import SP1Clean.Extracted.U16MSBOperation +import SP1Clean.Faithful.ChipTactics /-! # Faithfulness anchor — `U16MSBOperation` constraints ↔ native `RawSpec` @@ -23,18 +24,6 @@ open scoped SP1Clean.ConstraintCoe variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] -/-- `(16 : ZMod p).val = 16` under `Fact (2^17 < p)`. -/ -private lemma val_16 [NeZero p] : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) - -omit [Fact (2 ^ 17 < p)] in -/-- Carry-bool bridge: `x*(x-1)=0 ↔ x∈{0,1}` (field; `→` via `bool_of_mul_pred`). -/ -private lemma bool_iff {x : ZMod p} : x * (x - 1) = 0 ↔ (x = 0 ∨ x = 1) := by - rw [sub_eq_add_neg] - exact ⟨SP1Clean.bool_of_mul_pred, - fun h => by rcases h with h | h <;> rw [h] <;> ring⟩ - -- All four `1 ≠ 0 →` discharge lemmas (`one_ne_zero`/`ne_eq`/`not_false_eq_true`/`true_implies`) -- are genuinely needed — dropping either pair leaves unsolved goals — but on this short -- (single-send) constraint list the `unusedSimpArgs` linter false-positives on two of them, so diff --git a/SP1Clean/FormalModel/Contracts/Chips.lean b/SP1Clean/FormalModel/Contracts/Chips.lean index b1b4a7af..61544dd2 100644 --- a/SP1Clean/FormalModel/Contracts/Chips.lean +++ b/SP1Clean/FormalModel/Contracts/Chips.lean @@ -374,9 +374,7 @@ private lemma high64_mul (b' c' : BitVec 129) (b'' c'' : BitVec 128) /-- `RV64.mul rs2 rs1 = rs1 * rs2` (commuted into the gadget's `b * c` form). -/ lemma rv64_mul_eq (x y : BitVec 64) : RV64.mul x y = y * x := by - simp only [RV64.mul] - apply BitVec.eq_of_toNat_eq - rw [BitVec.toNat_mul, BitVec.toNat_mul, Nat.mul_comm] + rw [RV64.mul, BitVec.mul_comm] /-- `RV64.mulh`'s high-64-bit signed×signed product equals the gadget's `>>>64 |>.setWidth 64` form. -/ lemma rv64_mulh_eq (x y : BitVec 64) : @@ -535,7 +533,10 @@ def pcWord (cols : JalColumns (ZMod p)) : Word (ZMod p) := `is_real`-gated jump/link semantics. On a real row: the jump target `add_operation.value = pc + op_b_imm` (`op_b_imm` is the sign-extended 21-bit immediate — the actual `BitVec 21` ↔ word relation is a received decode fact, supplied at the Sail bridge), and — when `rd ≠ x0` (`op_a_0 = 0`) — the link-address write -`op_a_operation.value = pc + 4`. Vacuous on padding. -/ +`op_a_operation.value = pc + 4`. The final conjunct records that the jump target's low limb +(`add_operation.value[0]`) is divisible by 4 — i.e. the target is 4-byte aligned — forced by the +in-circuit alignment `Range` byte-lookup (`value[0] · 4⁻¹ < 2^14`); the Sail bridge lifts it to the whole +word (so alignment is no longer an assumed bridge precondition). Vacuous on padding. -/ def Spec (input : Inputs (ZMod p)) (cols : JalColumns (ZMod p)) (_ : ProverData (ZMod p)) : Prop := Readers.JTypeReader.Spec { cols := cols.adapter, is_real := input.is_real, is_trusted := input.is_real, @@ -550,7 +551,8 @@ def Spec (input : Inputs (ZMod p)) (cols : JalColumns (ZMod p)) (_ : ProverData = Word.toBitVec64 (pcWord cols) + Word.toBitVec64 cols.adapter.op_b_imm) ∧ (input.is_real = 1 → cols.adapter.op_a_0 = 0 → Word.toBitVec64 cols.op_a_operation.value - = Word.toBitVec64 (pcWord cols) + Word.toBitVec64 (#v[4, 0, 0, 0] : Word (ZMod p))) + = Word.toBitVec64 (pcWord cols) + Word.toBitVec64 (#v[4, 0, 0, 0] : Word (ZMod p))) ∧ + (input.is_real = 1 → (cols.add_operation.value[0]).val % 4 = 0) end SP1Clean.JalChip @@ -635,12 +637,25 @@ operand of the link `AddOperation` (`pc + 4 = link`). -/ def pcWord (cols : JalrColumns (ZMod p)) : Word (ZMod p) := #v[cols.state.pc[0], cols.state.pc[1], cols.state.pc[2], 0] +/-- The committed, **LSB-cleared** next-pc word the chip feeds `CPUState` — the jump target +`add_operation.value` with its low bit removed (`value[0] - lsb`), faithful to RISC-V JALR's +`(rs1 + imm) & ~1` and the Sail `BitVec.update target 0 0#1`. Named by the `Spec`'s LSB-clearing +conjunct and the Sail bridge. -/ +def nextPcWord (cols : JalrColumns (ZMod p)) : Word (ZMod p) := + #v[cols.add_operation.value[0] - cols.lsb, cols.add_operation.value[1], + cols.add_operation.value[2], 0] + /-- Semantic contract for the JALR row, composed from the I-type reader sub-`Spec` plus the `is_real`-gated jump/link semantics. On a real row: the jump target `add_operation.value = rs1 + op_c_imm` (`op_c_imm` is the sign-extended 12-bit immediate — the `BitVec 12` ↔ word relation is a received decode fact, supplied at the Sail bridge), the `lsb` witness is binary, and — when `rd ≠ x0` (`op_a_0 = 0`) — the link-address write `op_a_operation.value = pc + 4`. The committed next_pc is the LSB-cleared -`nextPcWord`. Vacuous on padding. -/ +`nextPcWord`. The final conjunct records that this cleared low limb (`add_operation.value[0] - lsb`) +is divisible by 4 — i.e. the jump target is 4-byte aligned — forced by the in-circuit alignment +`Range` byte-lookup (`(value[0] - lsb) · 4⁻¹ < 2^14`); the Sail bridge lifts it to the whole word. +The final conjunct exposes the **LSB-clearing relation** itself — the committed `nextPcWord` is the +jump target with bit 0 cleared (`~~~1#64 &&&`, the Sail-free form of `BitVec.update _ 0 0#1`) — so the +Sail bridge *derives* it from this `Spec` rather than assuming it. Vacuous on padding. -/ def Spec (input : Inputs (ZMod p)) (cols : JalrColumns (ZMod p)) (_ : ProverData (ZMod p)) : Prop := Readers.ITypeReader.Spec { cols := cols.adapter, is_real := input.is_real, is_trusted := input.is_real, @@ -656,7 +671,10 @@ def Spec (input : Inputs (ZMod p)) (cols : JalrColumns (ZMod p)) (_ : ProverData = Word.toBitVec64 (rs1Word cols) + Word.toBitVec64 cols.adapter.op_c_imm) ∧ (input.is_real = 1 → cols.adapter.op_a_0 = 0 → Word.toBitVec64 cols.op_a_operation.value - = Word.toBitVec64 (pcWord cols) + Word.toBitVec64 (#v[4, 0, 0, 0] : Word (ZMod p))) + = Word.toBitVec64 (pcWord cols) + Word.toBitVec64 (#v[4, 0, 0, 0] : Word (ZMod p))) ∧ + (input.is_real = 1 → (cols.add_operation.value[0] - cols.lsb).val % 4 = 0) ∧ + (input.is_real = 1 → + Word.toBitVec64 (nextPcWord cols) = ~~~(1#64) &&& Word.toBitVec64 cols.add_operation.value) end SP1Clean.JalrChip @@ -716,7 +734,10 @@ flag-dispatched decision relating `is_branching` to the RISC-V condition on rs1/ skeletal `LtOperationSigned`). On a real row: - if `is_branching = 1`, `next_pc = pc + op_c_imm` (the sign-extended offset is a received decode fact, supplied at the Sail bridge); if `is_branching = 0`, `next_pc = pc + 4`; -- and `is_branching` is taken iff the opcode's condition holds (`BEQ ↔ rs1 = rs2`, `BLT ↔ rs1 <ₛ rs2`, …). +- and `is_branching` is taken iff the opcode's condition holds (`BEQ ↔ rs1 = rs2`, `BLT ↔ rs1 <ₛ rs2`, …); +- and the branch target's low limb (`next_pc[0]`) is divisible by 4 — i.e. the target is 4-byte aligned — + forced by the in-circuit alignment `Range` byte-lookup (`next_pc[0] · 4⁻¹ < 2^14`); the Sail bridge lifts + it to the whole word (so alignment is no longer an assumed bridge precondition). Vacuous on padding. -/ def Spec (input : Inputs (ZMod p)) (cols : BranchColumns (ZMod p)) (_ : ProverData (ZMod p)) : Prop := Readers.ITypeReaderImmutable.Spec @@ -748,6 +769,7 @@ def Spec (input : Inputs (ZMod p)) (cols : BranchColumns (ZMod p)) (_ : ProverDa (Word.toBitVec64 (rs1Word cols)).ult (Word.toBitVec64 (rs2Word cols)) = true)) ∧ (cols.is_bgeu = 1 → (cols.is_branching = 1 ↔ - (Word.toBitVec64 (rs1Word cols)).ult (Word.toBitVec64 (rs2Word cols)) = false))) + (Word.toBitVec64 (rs1Word cols)).ult (Word.toBitVec64 (rs2Word cols)) = false))) ∧ + (input.is_real = 1 → (cols.next_pc[0]).val % 4 = 0) end SP1Clean.BranchChip diff --git a/SP1Clean/Math/Bitwise.lean b/SP1Clean/Math/Bitwise.lean index 36f1bbb6..fef904d3 100644 --- a/SP1Clean/Math/Bitwise.lean +++ b/SP1Clean/Math/Bitwise.lean @@ -26,8 +26,7 @@ variable {p : ℕ} exact ZMod.val_natCast_of_lt (show (256 : ℕ) < p by omega) lemma val_256_ne_zero [NeZero p] [Fact (2 ^ 17 < p)] : (256 : ZMod p) ≠ 0 := by - have h : (256 : ZMod p).val = 256 := val_256_zmod_p - intro hz; rw [hz] at h; simp at h + simp [← ZMod.val_eq_zero, val_256_zmod_p] /-! ## The byte operation (AND=0, OR=1, XOR=2) -/ diff --git a/SP1Clean/Math/Gate.lean b/SP1Clean/Math/Gate.lean new file mode 100644 index 00000000..9bcf6a1b --- /dev/null +++ b/SP1Clean/Math/Gate.lean @@ -0,0 +1,75 @@ +import Mathlib.Data.ZMod.Basic +import Mathlib.Tactic + +/-! # Binary-field gates and BitVec comparisons + +Field-generic helper lemmas for **binary (boolean) field elements** (`x = 0 ∨ x = 1`) and the two +`BitVec` order comparisons (`slt`/`ult`). These recur in the chip decision/selector proofs (Branch's +signed/unsigned branch decision, Lt's flag selectors); collected here so any chip can reuse them rather +than re-proving a private copy. Pure `ZMod p` / `BitVec` facts — no SP1-specific or circuit dependency. -/ + +namespace SP1Clean + +variable {p : ℕ} + +/-- A boolean field element has `val ≤ 1`. -/ +lemma bool_val_le [Fact p.Prime] {x : ZMod p} (h : x = 0 ∨ x = 1) : x.val ≤ 1 := by + haveI : Fact (1 < p) := ⟨(Fact.out : p.Prime).one_lt⟩ + rcases h with h | h <;> simp [h, ZMod.val_one] + +/-- Distinct field elements have distinct `val`s. -/ +lemma val_ne [NeZero p] {x y : ZMod p} (h : x ≠ y) : x.val ≠ y.val := fun hv => + h (by rw [← ZMod.natCast_zmod_val x, ← ZMod.natCast_zmod_val y, hv]) + +/-- A field element equal to `if P then 1 else 0` is `1` iff `P` holds. -/ +lemma eq_one_iff_of_ite [Fact p.Prime] {x : ZMod p} {P : Prop} [Decidable P] + (h : x = if P then 1 else 0) : x = 1 ↔ P := by + by_cases hP : P + · simp [h, hP] + · simp only [h, if_neg hP] + exact ⟨fun he => absurd he zero_ne_one, fun hp => absurd hp hP⟩ + +/-- A field element equal to `1 - (if P then 1 else 0)` is `1` iff `P` fails. -/ +lemma eq_one_iff_of_one_sub_ite [Fact p.Prime] {x : ZMod p} {P : Prop} [Decidable P] + (h : x = 1 - (if P then 1 else 0)) : x = 1 ↔ ¬ P := by + by_cases hP : P + · simp only [h, if_pos hP, sub_self] + exact ⟨fun he => absurd he zero_ne_one, fun hp => absurd hP hp⟩ + · simp [h, hP] + +/-- A binary `x` with `x = 1 ↔ P` is `if P then 1 else 0` (completeness direction of +`eq_one_iff_of_ite`). -/ +lemma bool_eq_ite_of_iff {x : ZMod p} (hx : x = 0 ∨ x = 1) {P : Prop} [Decidable P] + (h : x = 1 ↔ P) : x = if P then 1 else 0 := by + by_cases hP : P + · rw [if_pos hP]; exact h.mpr hP + · rw [if_neg hP]; rcases hx with h0 | h1 + · exact h0 + · exact absurd (h.mp h1) hP + +/-- A binary `x` with `x = 1 ↔ y = 0` (for binary `y`) is `1 - y`. -/ +lemma bool_eq_one_sub [Fact p.Prime] {x y : ZMod p} (hx : x = 0 ∨ x = 1) (hy : y = 0 ∨ y = 1) + (h : x = 1 ↔ y = 0) : x = 1 - y := by + rcases hy with hy | hy + · rw [hy, sub_zero]; exact h.mpr hy + · rw [hy]; rcases hx with hx | hx + · rw [hx]; simp + · have := h.mp hx; rw [hy] at this; exact absurd this one_ne_zero + +/-- `BitVec.slt` as a signed-`toInt` comparison. -/ +lemma slt_true_iff {w : ℕ} (x y : BitVec w) : x.slt y = true ↔ x.toInt < y.toInt := by + simp [BitVec.slt] + +/-- `BitVec.slt` false as the negated signed comparison. -/ +lemma slt_false_iff {w : ℕ} (x y : BitVec w) : x.slt y = false ↔ ¬ x.toInt < y.toInt := by + simp [BitVec.slt] + +/-- `BitVec.ult` as an unsigned-`toNat` comparison. -/ +lemma ult_true_iff {w : ℕ} (x y : BitVec w) : x.ult y = true ↔ x.toNat < y.toNat := by + simp [BitVec.ult] + +/-- `BitVec.ult` false as the negated unsigned comparison. -/ +lemma ult_false_iff {w : ℕ} (x y : BitVec w) : x.ult y = false ↔ ¬ x.toNat < y.toNat := by + simp [BitVec.ult] + +end SP1Clean diff --git a/SP1Clean/Math/Misc.lean b/SP1Clean/Math/Misc.lean index b3e45676..bb0bb6cc 100644 --- a/SP1Clean/Math/Misc.lean +++ b/SP1Clean/Math/Misc.lean @@ -60,8 +60,8 @@ end Int · split_ifs <;> simp [h] instance Fin.noZeroDivisors_of_prime (p : ℕ) - [hp : Fact (Nat.Prime (p + 1))] : NoZeroDivisors (Fin (p + 1)) := by - refine IsDomain.to_noZeroDivisors (ZMod (p + 1)) + [hp : Fact (Nat.Prime (p + 1))] : NoZeroDivisors (Fin (p + 1)) := + IsDomain.to_noZeroDivisors (ZMod (p + 1)) @[simp] theorem Std.ExtDHashMap.insert_insert [BEq α] [Hashable α] [LawfulBEq α] {m : Std.ExtDHashMap α β} (a : α) (b b' : β a) : diff --git a/SP1Clean/Math/MulCarryChain.lean b/SP1Clean/Math/MulCarryChain.lean index cf2c2daf..39abd46a 100644 --- a/SP1Clean/Math/MulCarryChain.lean +++ b/SP1Clean/Math/MulCarryChain.lean @@ -86,9 +86,9 @@ variable {p : ℕ} theorem gate_zero (cp : ℕ → ℕ) : ((product cp 0 : ℕ) : ZMod p) = ((cp 0 : ℕ) : ZMod p) - ((carry cp 0 : ℕ) : ZMod p) * 256 := by - have h := recurrence_zero cp have hc : (((product cp 0 : ℕ) : ZMod p) + 256 * ((carry cp 0 : ℕ) : ZMod p)) - = ((cp 0 : ℕ) : ZMod p) := by exact_mod_cast congrArg (Nat.cast : ℕ → ZMod p) h + = ((cp 0 : ℕ) : ZMod p) := by + exact_mod_cast congrArg (Nat.cast : ℕ → ZMod p) (recurrence_zero cp) linear_combination hc /-- Step carry-chain gate over `ZMod p`. -/ @@ -96,10 +96,9 @@ theorem gate_succ (cp : ℕ → ℕ) (i : ℕ) : ((product cp (i + 1) : ℕ) : ZMod p) = ((cp (i + 1) : ℕ) : ZMod p) + ((carry cp i : ℕ) : ZMod p) - ((carry cp (i + 1) : ℕ) : ZMod p) * 256 := by - have h := recurrence_succ cp i have hc : (((product cp (i + 1) : ℕ) : ZMod p) + 256 * ((carry cp (i + 1) : ℕ) : ZMod p)) = ((cp (i + 1) : ℕ) : ZMod p) + ((carry cp i : ℕ) : ZMod p) := by - exact_mod_cast congrArg (Nat.cast : ℕ → ZMod p) h + exact_mod_cast congrArg (Nat.cast : ℕ → ZMod p) (recurrence_succ cp i) linear_combination hc /-- The carry cast into `ZMod p` round-trips through `.val` (no wrap), giving the `< 65536` range diff --git a/SP1Clean/Math/Word.lean b/SP1Clean/Math/Word.lean index 19c09dbe..c2add9d1 100644 --- a/SP1Clean/Math/Word.lean +++ b/SP1Clean/Math/Word.lean @@ -58,6 +58,19 @@ lemma toBitVec64_toNat [NeZero p] {w : Word (ZMod p)} (hw : w.isU64) : have := lt_cases_of_isU64 hw omega +/-- The 64-bit value's residue mod 4 is the low limb's residue mod 4 (the higher limbs all carry a +factor `2^16`, hence `4 ∣`). No `isU64` hypothesis needed: `2^64` is itself a multiple of 4, so the +`BitVec.ofNat` wrap-around drops out. Used by the JALR/Jal/Branch alignment bridges to lift the +in-circuit low-limb divisibility to the committed `next_pc` word. -/ +lemma toBitVec64_toNat_mod_four [NeZero p] (w : Word (ZMod p)) : + w.toBitVec64.toNat % 4 = w[0].val % 4 := by + simp only [toBitVec64, BitVec.toNat_ofNat, toNat, + show (2 : ℕ) ^ 16 = 65536 from by norm_num, + show (2 : ℕ) ^ 32 = 4294967296 from by norm_num, + show (2 : ℕ) ^ 48 = 281474976710656 from by norm_num] + rw [Nat.mod_mod_of_dvd _ (show (4 : ℕ) ∣ 2 ^ 64 from by norm_num)] + omega + /-- The 128-bit zero-extension of a (64-bit) word's value. Used for the `MULH*` high-half product specs, where the schoolbook 16-byte product is the full 128-bit product of the (sign/zero-extended) operands. -/ @@ -70,8 +83,34 @@ lemma toBitVec128_toNat [NeZero p] {w : Word (ZMod p)} (hw : w.isU64) : have := lt_cases_of_isU64 hw rw [Nat.mod_eq_of_lt (by omega)] +/-- The constant word `#v[4,0,0,0]` (the "+4" PC increment used by Jal/Jalr/Branch) is a valid u64. -/ +lemma isU64_four [NeZero p] [Fact (2 ^ 17 < p)] : + isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := by + have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + refine isU64_of_cases ?_ ?_ ?_ ?_ <;> + simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, + List.getElem_cons_succ, show (4 : ZMod p) = ((4 : ℕ) : ZMod p) from by norm_cast, + ZMod.val_natCast_of_lt h4lt, ZMod.val_zero] <;> norm_num + end Word +/-- Clearing bit 0 (the Sail `BitVec.update _ 0 0#1`, which unfolds to `~~~(1#64) &&& ·`) of +`BitVec.ofNat 64 (M + b)` — with `b ≤ 1` and `M` even — yields `BitVec.ofNat 64 M`. Stated in the +Sail-free `~~~(1#64) &&&` form so `Math/` stays Sail-independent; the JALR bridge bridges this to +`BitVec.update` (a one-line `simp [Sail.BitVec.update, Sail.BitVec.updateSubrange']`). Used by the JALR +LSB-clearing `Spec` conjunct (`toNat add_value = toNat next_pc_word + lsb`, `lsb ∈ {0,1}`). -/ +lemma ofNat64_clear_lsb_and {M b : ℕ} (hb : b ≤ 1) (hM : M % 2 = 0) : + BitVec.ofNat 64 M = ~~~(1#64) &&& BitVec.ofNat 64 (M + b) := by + apply BitVec.eq_of_getLsbD_eq + intro i hi + simp only [BitVec.getLsbD_and, BitVec.getLsbD_not, BitVec.getLsbD_ofNat, hi, decide_true, + Bool.true_and] + rcases Nat.eq_zero_or_pos i with h0 | hpos + · subst h0; simp [Nat.testBit_zero, hM] + · obtain ⟨j, rfl⟩ := Nat.exists_eq_succ_of_ne_zero (by omega : i ≠ 0) + have hdiv : (M + b) / 2 = M / 2 := by omega + simp [Nat.testBit_succ, hdiv] + /-! ## Field constants (`ZMod p`, `Fact (2 ^ 17 < p)`) -/ @[simp] lemma val_65536_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : @@ -80,19 +119,91 @@ end Word exact ZMod.val_natCast_of_lt (show (65536 : ℕ) < p by omega) lemma val_65536_ne_zero [NeZero p] [Fact (2 ^ 17 < p)] : (65536 : ZMod p) ≠ 0 := by - have h : (65536 : ZMod p).val = 65536 := val_65536_zmod_p - intro hz; rw [hz] at h; simp at h + simp [← ZMod.val_eq_zero, val_65536_zmod_p] @[simp] lemma val_2_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : (2 : ZMod p).val = 2 := by have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega exact ZMod.val_natCast_of_lt (show (2 : ℕ) < p by omega) +@[simp] lemma val_4_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (4 : ZMod p).val = 4 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (4 : ℕ) < p by omega) + +lemma val_4_ne_zero [NeZero p] [Fact (2 ^ 17 < p)] : (4 : ZMod p) ≠ 0 := by + simp [← ZMod.val_eq_zero, val_4_zmod_p] + +@[simp] lemma val_8_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (8 : ZMod p).val = 8 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (8 : ℕ) < p by omega) + +@[simp] lemma val_13_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (13 : ZMod p).val = 13 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (13 : ℕ) < p by omega) + +@[simp] lemma val_14_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (14 : ZMod p).val = 14 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (14 : ℕ) < p by omega) + +@[simp] lemma val_16_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (16 : ZMod p).val = 16 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) + +@[simp] lemma val_32_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (32 : ZMod p).val = 32 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (32 : ℕ) < p by omega) + +@[simp] lemma val_64_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (64 : ZMod p).val = 64 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (64 : ℕ) < p by omega) + +@[simp] lemma val_32768_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : + (32768 : ZMod p).val = 32768 := by + have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + exact ZMod.val_natCast_of_lt (show (32768 : ℕ) < p by omega) + +/-- The `÷4` alignment check `(x · 4⁻¹).val < 2^14` pins `x = (x · 4⁻¹) · 4` exactly in `ℕ`: since +`(x · 4⁻¹).val · 4 < 2^16 < p` there is no wrap, so `x.val = (x · 4⁻¹).val · 4`. The shared core of the +two corollaries below (divisibility + the `< 2^16` bound). -/ +lemma val_eq_mul_inv_four_mul_four [Fact p.Prime] [Fact (2 ^ 17 < p)] {x : ZMod p} + (h : (x * (4 : ZMod p)⁻¹).val < 2 ^ 14) : x.val = (x * (4 : ZMod p)⁻¹).val * 4 := by + haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ + have hp : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega + rw [show (2 : ℕ) ^ 14 = 16384 from by norm_num] at h + have h41 : (4 : ZMod p)⁻¹ * 4 = 1 := inv_mul_cancel₀ val_4_ne_zero + conv_lhs => rw [show x = (x * (4 : ZMod p)⁻¹) * 4 by rw [mul_assoc, h41, mul_one]] + rw [ZMod.val_mul, val_4_zmod_p, Nat.mod_eq_of_lt (by omega)] + +/-- The reverse of `U16toU8OperationSafe.high_byte_lt` for the `÷4` alignment check: if `x · 4⁻¹` is a +14-bit value (the in-circuit `Range` byte-lookup), then `x` is divisible by 4. -/ +lemma val_mod_four_of_mul_inv_four_lt [Fact p.Prime] [Fact (2 ^ 17 < p)] {x : ZMod p} + (h : (x * (4 : ZMod p)⁻¹).val < 2 ^ 14) : x.val % 4 = 0 := by + rw [val_eq_mul_inv_four_mul_four h]; omega + +/-- Companion bound to `val_mod_four_of_mul_inv_four_lt`: the same `÷4` range check also gives +`x.val < 2^16` (`x.val = (x · 4⁻¹).val · 4 < 2^14 · 4`). Used by the JALR LSB-clearing soundness to lift +the field subtraction `value[0] - lsb` to `ℕ`. -/ +lemma val_lt_65536_of_mul_inv_four_lt [Fact p.Prime] [Fact (2 ^ 17 < p)] {x : ZMod p} + (h : (x * (4 : ZMod p)⁻¹).val < 2 ^ 14) : x.val < 2 ^ 16 := by + have he := val_eq_mul_inv_four_mul_four h + rw [show (2 : ℕ) ^ 14 = 16384 from by norm_num] at h + rw [he, show (2 : ℕ) ^ 16 = 65536 from by norm_num]; omega + @[simp] lemma val_65535_zmod_p [NeZero p] [Fact (2 ^ 17 < p)] : (65535 : ZMod p).val = 65535 := by have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega exact ZMod.val_natCast_of_lt (show (65535 : ℕ) < p by omega) +lemma val_65535_ne_zero [NeZero p] [Fact (2 ^ 17 < p)] : (65535 : ZMod p) ≠ 0 := by + simp [← ZMod.val_eq_zero, val_65535_zmod_p] + /-- The 16-bit complement `65535 - x` has a clean `.val` (= `65535 - x.val` in `ℕ`). Used by the Sub borrow-chain limb lift: feeding `bbar := 65535 - b[i]` (a genuine 16-bit value) into the unchanged `limb_lift` keeps every additive sum `< p`, exactly diff --git a/SP1Clean/Model/InteractionBus.lean b/SP1Clean/Model/InteractionBus.lean index e112f9cc..d65d47fe 100644 --- a/SP1Clean/Model/InteractionBus.lean +++ b/SP1Clean/Model/InteractionBus.lean @@ -64,9 +64,7 @@ theorem multiplicitySum_cons (head : LookupAccess) (tail : LookupAccessList) (k multiplicitySum (head :: tail) k = (if keyOf head = k then multOf head else 0) + multiplicitySum tail k := by simp only [multiplicitySum, filterKey, List.filter_cons] - by_cases h : keyOf head = k - · simp [h] - · simp [h] + by_cases h : keyOf head = k <;> simp [h] theorem multiplicitySum_append (l1 l2 : LookupAccessList) (k : LookupKey) : multiplicitySum (l1 ++ l2) k = multiplicitySum l1 k + multiplicitySum l2 k := by @@ -181,7 +179,7 @@ theorem provider_touches_pos_send -- so the provider's sum at this key is non-zero, hence its filtered list is non-empty have hprov_ne : multiplicitySum prov (keyOf a) ≠ 0 := by omega by_contra h - push_neg at h + push Not at h refine hprov_ne ?_ simp only [multiplicitySum, filterKey] rw [List.filter_eq_nil_iff.mpr ?_]; · rfl @@ -207,7 +205,7 @@ theorem balanced_pos_has_neg (a : LookupAccess) (ha : a ∈ accesses) (hak : keyOf a = k) (h_pos : 0 < multOf a) : ∃ b ∈ accesses, keyOf b = k ∧ multOf b < 0 := by by_contra h - push_neg at h + push Not at h -- if no contribution at key `k` is strictly negative, every summand of `multiplicitySum … k` is `≥ 0` have h_nonneg : ∀ x ∈ (filterKey accesses k).map multOf, 0 ≤ x := by intro x hx diff --git a/SP1Clean/Model/InteractionProjection.lean b/SP1Clean/Model/InteractionProjection.lean index 1bcee991..d467b2be 100644 --- a/SP1Clean/Model/InteractionProjection.lean +++ b/SP1Clean/Model/InteractionProjection.lean @@ -39,9 +39,8 @@ omit [NeZero p] in lemma val_of_binary (hp : 2 < p) {is_real : ZMod p} (h : is_real = 0 ∨ is_real = 1) : is_real.val = 0 ∨ is_real.val = 1 := by haveI : NeZero p := ⟨by omega⟩ - rcases h with h | h <;> subst h - · exact Or.inl ZMod.val_zero - · exact Or.inr (by rw [ZMod.val_one_eq_one_mod, Nat.mod_eq_of_lt (show 1 < p by omega)]) + rcases h with h | h <;> subst h <;> + simp [ZMod.val_one_eq_one_mod, Nat.mod_eq_of_lt (show 1 < p by omega)] omit [NeZero p] in /-- For a binary `is_real`, `signedVal is_real = is_real.val` (the positive/`send` multiplicity; both diff --git a/SP1Clean/Model/InteractionRecovery.lean b/SP1Clean/Model/InteractionRecovery.lean index 886882ea..db20d236 100644 --- a/SP1Clean/Model/InteractionRecovery.lean +++ b/SP1Clean/Model/InteractionRecovery.lean @@ -42,9 +42,7 @@ lemma interactionsWith_formalSubcircuit_eq_nil {Input Output : TypeMap} rw [FlatOperation.channels]; exact List.mem_map.mpr ⟨i, hi, rfl⟩ have hcontra := hsub hmem rw [hci'] at hcontra - rcases List.mem_append.mp hcontra with h | h - · exact h_g h - · exact h_r h + exact (List.mem_append.mp hcontra).elim h_g h_r /-- **`.main`-form companion** (matches what `circuit_norm` leaves after reducing a formal subcircuit): a circuit's `main` emits nothing on a channel outside its declared `channels` (= guarantees ++ @@ -88,9 +86,7 @@ lemma filter_interactions_formalAssertion_eq_nil {Input : TypeMap} [ProvableType rw [FlatOperation.channels]; exact List.mem_map.mpr ⟨i, hi, rfl⟩ have hcontra := hsub hmem rw [hci'] at hcontra - rcases List.mem_append.mp hcontra with h | h - · exact h_g h - · exact h_r h + exact (List.mem_append.mp hcontra).elim h_g h_r /-- `.2`-projection form of `interactionsWith_main_eq_nil` — `circuit_norm` unfolds `Circuit.operations` to `(circuit.main input offset).2`, so this is the shape that actually appears after reducing diff --git a/SP1Clean/Model/Register.lean b/SP1Clean/Model/Register.lean index f35f36a0..eaafdadf 100644 --- a/SP1Clean/Model/Register.lean +++ b/SP1Clean/Model/Register.lean @@ -64,7 +64,7 @@ def reg_idx_to_Register (idx : BitVec 5) : Register := /-- All of the registers corresponding to a `BitVec 5` contain 64-bit values. -/ lemma reg_idx_must_64 (idx : BitVec 5) : RegisterType (reg_idx_to_Register idx) = BitVec 64 := by - simp [reg_idx_to_Register] + simp only [reg_idx_to_Register] split <;> rfl /-- Interpret a bitvec into a register type for some vector index. -/ @@ -269,10 +269,6 @@ reg_idx_to_Register idx = .x31 ↔ idx = 31#5 ∨ idx = 0#5 := by fin_cases idx lemma regidxToRegister_inj (idx idx' : BitVec 5) : reg_idx_to_Register idx = reg_idx_to_Register idx' ↔ idx = idx' ∨ (idx = 0#5 ∧ idx' = 31#5) ∨ (idx = 31#5 ∧ idx' = 0#5) := by - fin_cases idx - all_goals { - simp [eq_comm (a := idx')] - try {aesop} - } + fin_cases idx <;> (simp [eq_comm (a := idx')]; try aesop) end regidx diff --git a/SP1Clean/Model/SP1Constraint.lean b/SP1Clean/Model/SP1Constraint.lean index 0b27d699..44d98035 100644 --- a/SP1Clean/Model/SP1Constraint.lean +++ b/SP1Clean/Model/SP1Constraint.lean @@ -86,8 +86,7 @@ SP1's RISC-V `Opcode` enum, emitted by the constraint compiler as `Opcode.ofNat `.program` bus interactions (the chip adapters' instruction reads). The *semantics* of program interactions are deferred (their `toProp` is `True`, see `Extracted/ExtractionDSL.lean`), so — unlike the fully-modelled `ByteOpcode` — `Opcode` only needs to carry the numeric tag and elaborate. -It is -a thin `ℕ` wrapper with an `ofNat` that, like `ByteOpcode.ofNat`, composes with the +It is a thin `ℕ` wrapper with an `ofNat` that, like `ByteOpcode.ofNat`, composes with the `[CoeHead F ℕ]` hypothesis so `Opcode.ofNat (opcode : F)` typechecks. -/ abbrev Opcode := ℕ diff --git a/SP1Clean/Model/SailMemory.lean b/SP1Clean/Model/SailMemory.lean index 2bcf6a01..68b09e3f 100644 --- a/SP1Clean/Model/SailMemory.lean +++ b/SP1Clean/Model/SailMemory.lean @@ -115,7 +115,7 @@ lemma run_within_mmio_readable_mmio (reg_val : BitVec 64) (offset : BitVec 64) (h_htif : s.regs.get Register.htif_tohost_base (hs _) = none) : (within_mmio_readable (physaddr.Physaddr (zero_extend (BitVec.addInt (reg_val + offset) 0))) width).run s = .ok false s := by - have h_base : BitVec.toNat plat_clint_base = 33554432 := by native_decide + have h_base : BitVec.toNat plat_clint_base = 33554432 := by rfl have h_bv : (reg_val + offset).toNat = (reg_val.toNat + offset.toNat) % 18446744073709551616 := BitVec.toNat_add reg_val offset simp [within_mmio_readable, get_config_rvfi, within_clint] @@ -130,7 +130,7 @@ lemma run_within_mmio_writable_mmio (reg_val : BitVec 64) (offset : BitVec 64) (h_htif : s.regs.get Register.htif_tohost_base (hs _) = none) : (within_mmio_writable (physaddr.Physaddr (zero_extend (BitVec.addInt (reg_val + offset) 0))) width).run s = .ok false s := by - have h_base : BitVec.toNat plat_clint_base = 33554432 := by native_decide + have h_base : BitVec.toNat plat_clint_base = 33554432 := by rfl have h_bv : (reg_val + offset).toNat = (reg_val.toNat + offset.toNat) % 18446744073709551616 := BitVec.toNat_add reg_val offset simp [within_mmio_writable, get_config_rvfi, within_clint] diff --git a/SP1Clean/Model/SailWrap.lean b/SP1Clean/Model/SailWrap.lean index bb8be6a5..61c39e4b 100644 --- a/SP1Clean/Model/SailWrap.lean +++ b/SP1Clean/Model/SailWrap.lean @@ -57,7 +57,6 @@ section sailboats namespace Option - /-- Version of `Option.getM` using `throw` instead of `failure`. -/ def toSailM {α} (x : Option α) : SailM α := Option.elim x (throw Sail.Error.Unreachable) pure diff --git a/SP1Clean/Native/Chips/JalrChip/Defs.lean b/SP1Clean/Native/Chips/JalrChip/Defs.lean index ce3656d7..8b49ca75 100644 --- a/SP1Clean/Native/Chips/JalrChip/Defs.lean +++ b/SP1Clean/Native/Chips/JalrChip/Defs.lean @@ -37,13 +37,6 @@ def rs1WordI (input : Inputs (ZMod p)) : Word (ZMod p) := #v[input.adapter.op_b_memory.prev_value[0], input.adapter.op_b_memory.prev_value[1], input.adapter.op_b_memory.prev_value[2], input.adapter.op_b_memory.prev_value[3]] -/-- The committed, **LSB-cleared** next-pc word the chip feeds `CPUState` — the jump target -`add_operation.value` with its low bit removed (`value[0] - lsb`), faithful to RISC-V JALR's -`(rs1 + imm) & ~1` and the Sail `BitVec.update target 0 0#1`. Used by the Sail bridge (not the `Spec`). -/ -def nextPcWord (cols : JalrColumns (ZMod p)) : Word (ZMod p) := - #v[cols.add_operation.value[0] - cols.lsb, cols.add_operation.value[1], - cols.add_operation.value[2], 0] - /-- The jump-target word the chip witnesses for `add_operation.value` (`rs1 + op_c_imm`, base-2^16). -/ def jumpTargetWord (input : Inputs (ZMod p)) : Word (ZMod p) := AddOperation.populate (rs1WordI input) input.adapter.op_c_imm diff --git a/SP1Clean/Native/Operations/AddressOperation.lean b/SP1Clean/Native/Operations/AddressOperation.lean index b330c91d..58a08062 100644 --- a/SP1Clean/Native/Operations/AddressOperation.lean +++ b/SP1Clean/Native/Operations/AddressOperation.lean @@ -86,10 +86,9 @@ theorem addressSemantics_of_raw {input : Inputs (ZMod p)} Word.isU64_of_cases (hcc 0) (hcc 1) (hcc 2) (hcc 3) have h_sum := AddrAddOperation.addrAddSemantics_of_carries hbW hccW h_aa_raw refine ⟨?_, hob0, hob1, hob2⟩ - rw [show Word.toNat #v[input.b[0], input.b[1], input.b[2], input.b[3]] = Word.toNat input.b from rfl, + rwa [show Word.toNat #v[input.b[0], input.b[1], input.b[2], input.b[3]] = Word.toNat input.b from rfl, show Word.toNat #v[input.cc[0], input.cc[1], input.cc[2], input.cc[3]] = Word.toNat input.cc from rfl] at h_sum - exact h_sum /-! ## The witnessed `FormalCircuit` diff --git a/SP1Clean/Native/Operations/AddwOperation/RawSpec.lean b/SP1Clean/Native/Operations/AddwOperation/RawSpec.lean index 4783dff5..cdfe062e 100644 --- a/SP1Clean/Native/Operations/AddwOperation/RawSpec.lean +++ b/SP1Clean/Native/Operations/AddwOperation/RawSpec.lean @@ -63,11 +63,6 @@ theorem addwSemantics_of_carries {a b : Word (ZMod p)} {cols : Extracted.AddwOpe (BitVec.setWidth 32 (Word.toBitVec64 a + Word.toBitVec64 b)) cols.msb.msb hv0 hv1 h_msb rfl rfl hX -/-- `(65535 : ZMod p) ≠ 0` (its `val` is `65535`). -/ -private lemma c65535_ne_zero : (65535 : ZMod p) ≠ 0 := by - haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - intro h; have := congrArg ZMod.val h; rw [val_65535_zmod_p, ZMod.val_zero] at this; omega - set_option maxHeartbeats 16000000 in /-- Backward (completeness) core — the converse of `addwSemantics_of_carries`: a 64-bit value equal to the sign extension of the low-32 add witnesses the unique boolean carry chain + ranges **and** the sign @@ -80,7 +75,6 @@ theorem carries_of_addwSemantics {a b : Word (ZMod p)} {cols : Extracted.AddwOpe = (BitVec.setWidth 32 (Word.toBitVec64 a + Word.toBitVec64 b)).signExtend 64) : RawSpec a b cols ∧ cols.msb.msb = if cols.value[1].val ≥ 32768 then 1 else 0 := by haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - have hp : 2 ^ 17 < p := Fact.out obtain ⟨ha0, ha1, ha2, ha3⟩ := Word.lt_cases_of_isU64 ha obtain ⟨hbb0, hbb1, hbb2, hbb3⟩ := Word.lt_cases_of_isU64 hb obtain ⟨hv0, hv1, hm2, _⟩ := Word.lt_cases_of_isU64 hU @@ -164,7 +158,7 @@ theorem carries_of_addwSemantics {a b : Word (ZMod p)} {cols : Extracted.AddwOpe have hvaleq : (m * 65535 : ZMod p).val = (65535 : ZMod p).val := by rw [hMdef, hMval, val_65535_zmod_p] have hmul : m * 65535 = (1 : ZMod p) * 65535 := by rw [one_mul]; exact (ZMod.val_injective _) hvaleq - exact mul_right_cancel₀ c65535_ne_zero hmul + exact mul_right_cancel₀ val_65535_ne_zero hmul · rw [if_neg hcond] at hbvN have hmsblt : (a[0].val + b[0].val + (a[1].val + b[1].val) * 2 ^ 16) % 2 ^ 32 < 2 ^ 31 := by have h := hcond @@ -179,6 +173,6 @@ theorem carries_of_addwSemantics {a b : Word (ZMod p)} {cols : Extracted.AddwOpe have hvaleq : (m * 65535 : ZMod p).val = (0 : ZMod p).val := by rw [hMdef, hMval, ZMod.val_zero] have hmul : m * 65535 = (0 : ZMod p) * 65535 := by rw [zero_mul]; exact (ZMod.val_injective _) hvaleq - exact mul_right_cancel₀ c65535_ne_zero hmul + exact mul_right_cancel₀ val_65535_ne_zero hmul end SP1Clean.AddwOperation diff --git a/SP1Clean/Native/Operations/BitwiseU16Operation.lean b/SP1Clean/Native/Operations/BitwiseU16Operation.lean index 14b67641..5279a5c4 100644 --- a/SP1Clean/Native/Operations/BitwiseU16Operation.lean +++ b/SP1Clean/Native/Operations/BitwiseU16Operation.lean @@ -71,8 +71,7 @@ def Spec (input : Inputs (ZMod p)) : Prop := lemma reassemble (w low : ZMod p) : w = low + (w - low) * (256 : ZMod p)⁻¹ * 256 := by haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - have h256 : (256 : ZMod p)⁻¹ * 256 = 1 := inv_mul_cancel₀ val_256_ne_zero - rw [mul_assoc, h256, mul_one]; ring + rw [mul_assoc, inv_mul_cancel₀ val_256_ne_zero, mul_one]; ring /-- A limb's value splits as its low byte plus 256× its high byte, given both are bytes. -/ lemma limb_split {w low : ZMod p} (hlo : low.val < 256) @@ -261,8 +260,7 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨_h_gate, h_bw⟩ := h_holds have h_spec := h_bw ⟨hop, hbin⟩ refine ⟨?_, Or.inr ⟨hop, hbin⟩⟩ - simp only [decompBytes, eb, ec, elb, elc, ← sub_eq_add_neg] at h_spec ⊢ - exact h_spec + simpa only [decompBytes, eb, ec, elb, elc, ← sub_eq_add_neg] using h_spec set_option maxHeartbeats 4000000 in theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Spec := by @@ -283,8 +281,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp fun k _ => by rw [← hilc]; simp only [Vector.getElem_map] refine ⟨?_, ⟨hop, hbin⟩, ?_⟩ · rcases hbin with h | h <;> rw [h] <;> simp - · simp only [decompBytes, eb, ec, elb, elc, ← sub_eq_add_neg] at h_spec ⊢ - exact h_spec + · simpa only [decompBytes, eb, ec, elb, elc, ← sub_eq_add_neg] using h_spec /-- SP1's `BitwiseU16Operation::eval` as a Clean-native `FormalAssertion`: the `is_real` binary gate plus the composed `BitwiseOperation` on the two `U16toU8` byte decompositions, witnessing nothing. -/ diff --git a/SP1Clean/Native/Operations/IsEqualWordOperation/RawSpec.lean b/SP1Clean/Native/Operations/IsEqualWordOperation/RawSpec.lean index 3d787a57..e21c61f1 100644 --- a/SP1Clean/Native/Operations/IsEqualWordOperation/RawSpec.lean +++ b/SP1Clean/Native/Operations/IsEqualWordOperation/RawSpec.lean @@ -31,9 +31,7 @@ theorem isEqualWord_of_raw {a b : Word (ZMod p)} {cols : Extracted.IsEqualWordOp (h_raw : RawSpec a b cols) : cols.is_diff_zero.result = if (a[0] = b[0] ∧ a[1] = b[1] ∧ a[2] = b[2] ∧ a[3] = b[3]) then 1 else 0 := by - have h := IsZeroWordOperation.isZeroWord_of_raw h_raw - simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, - List.getElem_cons_succ, sub_eq_zero] at h - exact h + simpa only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, + List.getElem_cons_succ, sub_eq_zero] using IsZeroWordOperation.isZeroWord_of_raw h_raw end SP1Clean.IsEqualWordOperation diff --git a/SP1Clean/Native/Operations/LtOperationUnsigned/RawSpec.lean b/SP1Clean/Native/Operations/LtOperationUnsigned/RawSpec.lean index 293c8e1c..5d9d5f34 100644 --- a/SP1Clean/Native/Operations/LtOperationUnsigned/RawSpec.lean +++ b/SP1Clean/Native/Operations/LtOperationUnsigned/RawSpec.lean @@ -1,4 +1,5 @@ import SP1Clean.Math.Word +import SP1Clean.Math.Gate import SP1Clean.Extracted.LtOperationUnsigned import SP1Clean.Native.Operations.U16CompareOperation.RawSpec import SP1Clean.Proofs.Operations.U16CompareOperation.Formal @@ -23,12 +24,6 @@ variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] instance : NeZero p := ⟨(Fact.out : p.Prime).pos.ne'⟩ -omit [Fact (2 ^ 17 < p)] in -/-- A boolean field element has `val ≤ 1`. -/ -private lemma bool_val_le {x : ZMod p} (h : x = 0 ∨ x = 1) : x.val ≤ 1 := by - haveI : Fact (1 < p) := ⟨(Fact.out : p.Prime).one_lt⟩ - rcases h with h | h <;> simp [h, ZMod.val_one] - set_option maxHeartbeats 1000000 in /-- Four boolean flags whose field sum is `0` or `1` have **at most one** set: the assignment is one of the five all-but-one-zero patterns. The key step lifts the field sum-bound to `ℕ` @@ -93,11 +88,6 @@ def Selectors (b cc : Word (ZMod p)) (cols : Extracted.LtOperationUnsigned (ZMod ((cc[3] * f3 + cc[2] * f2 + cc[1] * f1 + cc[0] * f0) - cl1 = 0) ∧ (((1 - sumf) - 1) * (cols.not_eq_inv * (cl0 - cl1) - 1) = 0) -omit [Fact (2 ^ 17 < p)] in -/-- Distinct field elements have distinct `val`s. -/ -private lemma val_ne {x y : ZMod p} (h : x ≠ y) : x.val ≠ y.val := fun hv => - h (by rw [← ZMod.natCast_zmod_val x, ← ZMod.natCast_zmod_val y, hv]) - set_option maxHeartbeats 1000000 in /-- Soundness core: the boolean flags select the most-significant differing limb (all higher limbs forced equal by the prefix-sum selectors, the selected limb forced distinct by the `not_eq_inv` diff --git a/SP1Clean/Native/Operations/MulOperation/RawSpec.lean b/SP1Clean/Native/Operations/MulOperation/RawSpec.lean index fb7ed866..505ae263 100644 --- a/SP1Clean/Native/Operations/MulOperation/RawSpec.lean +++ b/SP1Clean/Native/Operations/MulOperation/RawSpec.lean @@ -535,18 +535,14 @@ lemma lower_toNat (w : Word (ZMod p)) (lower : Extracted.U16toU8Operation (ZMod have h1 : w[1].val = (lower.low_bytes[1]).val + ((w[1] - lower.low_bytes[1]) * 256⁻¹).val * 256 := hw 1 have h2 : w[2].val = (lower.low_bytes[2]).val + ((w[2] - lower.low_bytes[2]) * 256⁻¹).val * 256 := hw 2 have h3 : w[3].val = (lower.low_bytes[3]).val + ((w[3] - lower.low_bytes[3]) * 256⁻¹).val * 256 := hw 3 - have e0 : byteAt (extendedBytes w lower s) 0 = lower.low_bytes[0] := by rfl - have e1 : byteAt (extendedBytes w lower s) 1 = (w[0] - lower.low_bytes[0]) * 256⁻¹ := by - rfl - have e2 : byteAt (extendedBytes w lower s) 2 = lower.low_bytes[1] := by rfl - have e3 : byteAt (extendedBytes w lower s) 3 = (w[1] - lower.low_bytes[1]) * 256⁻¹ := by - rfl - have e4 : byteAt (extendedBytes w lower s) 4 = lower.low_bytes[2] := by rfl - have e5 : byteAt (extendedBytes w lower s) 5 = (w[2] - lower.low_bytes[2]) * 256⁻¹ := by - rfl - have e6 : byteAt (extendedBytes w lower s) 6 = lower.low_bytes[3] := by rfl - have e7 : byteAt (extendedBytes w lower s) 7 = (w[3] - lower.low_bytes[3]) * 256⁻¹ := by - rfl + have e0 : byteAt (extendedBytes w lower s) 0 = lower.low_bytes[0] := rfl + have e1 : byteAt (extendedBytes w lower s) 1 = (w[0] - lower.low_bytes[0]) * 256⁻¹ := rfl + have e2 : byteAt (extendedBytes w lower s) 2 = lower.low_bytes[1] := rfl + have e3 : byteAt (extendedBytes w lower s) 3 = (w[1] - lower.low_bytes[1]) * 256⁻¹ := rfl + have e4 : byteAt (extendedBytes w lower s) 4 = lower.low_bytes[2] := rfl + have e5 : byteAt (extendedBytes w lower s) 5 = (w[2] - lower.low_bytes[2]) * 256⁻¹ := rfl + have e6 : byteAt (extendedBytes w lower s) 6 = lower.low_bytes[3] := rfl + have e7 : byteAt (extendedBytes w lower s) 7 = (w[3] - lower.low_bytes[3]) * 256⁻¹ := rfl rw [e0, e1, e2, e3, e4, e5, e6, e7, Word.toNat_def, h0, h1, h2, h3] ring @@ -742,14 +738,14 @@ lemma extendedBytes_toNat (w : Word (ZMod p)) (lower : Extracted.U16toU8Operatio (byteAt (extendedBytes w lower s) 0).val + (byteAt (extendedBytes w lower s) 1).val*256^1 + (byteAt (extendedBytes w lower s) 2).val*256^2 + (byteAt (extendedBytes w lower s) 3).val*256^3 + (byteAt (extendedBytes w lower s) 4).val*256^4 + (byteAt (extendedBytes w lower s) 5).val*256^5 + (byteAt (extendedBytes w lower s) 6).val*256^6 + (byteAt (extendedBytes w lower s) 7).val*256^7 + (byteAt (extendedBytes w lower s) 8).val*256^8 + (byteAt (extendedBytes w lower s) 9).val*256^9 + (byteAt (extendedBytes w lower s) 10).val*256^10 + (byteAt (extendedBytes w lower s) 11).val*256^11 + (byteAt (extendedBytes w lower s) 12).val*256^12 + (byteAt (extendedBytes w lower s) 13).val*256^13 + (byteAt (extendedBytes w lower s) 14).val*256^14 + (byteAt (extendedBytes w lower s) 15).val*256^15 = w.toNat + (s * 255).val * (256^8 + 256^9 + 256^10 + 256^11 + 256^12 + 256^13 + 256^14 + 256^15) := by have hlow := lower_toNat w lower s hspec - have e8 : byteAt (extendedBytes w lower s) 8 = s * 255 := by rfl - have e9 : byteAt (extendedBytes w lower s) 9 = s * 255 := by rfl - have e10 : byteAt (extendedBytes w lower s) 10 = s * 255 := by rfl - have e11 : byteAt (extendedBytes w lower s) 11 = s * 255 := by rfl - have e12 : byteAt (extendedBytes w lower s) 12 = s * 255 := by rfl - have e13 : byteAt (extendedBytes w lower s) 13 = s * 255 := by rfl - have e14 : byteAt (extendedBytes w lower s) 14 = s * 255 := by rfl - have e15 : byteAt (extendedBytes w lower s) 15 = s * 255 := by rfl + have e8 : byteAt (extendedBytes w lower s) 8 = s * 255 := rfl + have e9 : byteAt (extendedBytes w lower s) 9 = s * 255 := rfl + have e10 : byteAt (extendedBytes w lower s) 10 = s * 255 := rfl + have e11 : byteAt (extendedBytes w lower s) 11 = s * 255 := rfl + have e12 : byteAt (extendedBytes w lower s) 12 = s * 255 := rfl + have e13 : byteAt (extendedBytes w lower s) 13 = s * 255 := rfl + have e14 : byteAt (extendedBytes w lower s) 14 = s * 255 := rfl + have e15 : byteAt (extendedBytes w lower s) 15 = s * 255 := rfl rw [e8, e9, e10, e11, e12, e13, e14, e15] omega diff --git a/SP1Clean/Native/Operations/SubwOperation/RawSpec.lean b/SP1Clean/Native/Operations/SubwOperation/RawSpec.lean index 7a8907dd..23d02b52 100644 --- a/SP1Clean/Native/Operations/SubwOperation/RawSpec.lean +++ b/SP1Clean/Native/Operations/SubwOperation/RawSpec.lean @@ -70,11 +70,6 @@ theorem subwSemantics_of_carries {a b : Word (ZMod p)} {cols : Extracted.SubwOpe (BitVec.setWidth 32 (Word.toBitVec64 a - Word.toBitVec64 b)) cols.msb.msb hv0 hv1 h_msb rfl rfl hX -/-- `(65535 : ZMod p) ≠ 0` (its `val` is `65535`). -/ -private lemma c65535_ne_zero : (65535 : ZMod p) ≠ 0 := by - haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - intro h; have := congrArg ZMod.val h; rw [val_65535_zmod_p, ZMod.val_zero] at this; omega - set_option maxHeartbeats 16000000 in /-- Backward (completeness) core — the converse of `subwSemantics_of_carries`. Borrow-form analog of `AddwOperation.carries_of_addwSemantics`. -/ @@ -85,7 +80,6 @@ theorem carries_of_subwSemantics {a b : Word (ZMod p)} {cols : Extracted.SubwOpe = (BitVec.setWidth 32 (Word.toBitVec64 a - Word.toBitVec64 b)).signExtend 64) : RawSpec a b cols ∧ cols.msb.msb = if cols.value[1].val ≥ 32768 then 1 else 0 := by haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - have hp : 2 ^ 17 < p := Fact.out obtain ⟨ha0, ha1, ha2, ha3⟩ := Word.lt_cases_of_isU64 ha obtain ⟨hbb0, hbb1, hbb2, hbb3⟩ := Word.lt_cases_of_isU64 hb obtain ⟨hv0, hv1, hm2, _⟩ := Word.lt_cases_of_isU64 hU @@ -167,7 +161,7 @@ theorem carries_of_subwSemantics {a b : Word (ZMod p)} {cols : Extracted.SubwOpe have hvaleq : (m * 65535 : ZMod p).val = (65535 : ZMod p).val := by rw [hMdef, hMval, val_65535_zmod_p] have hmul : m * 65535 = (1 : ZMod p) * 65535 := by rw [one_mul]; exact (ZMod.val_injective _) hvaleq - exact mul_right_cancel₀ c65535_ne_zero hmul + exact mul_right_cancel₀ val_65535_ne_zero hmul · rw [if_neg hcond] at hbvN have hmsblt : ((a[0].val + 65535 - b[0].val + 1) + (a[1].val + 65535 - b[1].val) * 2 ^ 16) % 2 ^ 32 < 2 ^ 31 := by have h := hcond @@ -182,6 +176,6 @@ theorem carries_of_subwSemantics {a b : Word (ZMod p)} {cols : Extracted.SubwOpe have hvaleq : (m * 65535 : ZMod p).val = (0 : ZMod p).val := by rw [hMdef, hMval, ZMod.val_zero] have hmul : m * 65535 = (0 : ZMod p) * 65535 := by rw [zero_mul]; exact (ZMod.val_injective _) hvaleq - exact mul_right_cancel₀ c65535_ne_zero hmul + exact mul_right_cancel₀ val_65535_ne_zero hmul end SP1Clean.SubwOperation diff --git a/SP1Clean/Native/Operations/U16toU8OperationUnsafe.lean b/SP1Clean/Native/Operations/U16toU8OperationUnsafe.lean index d96f5f7b..4118c8c2 100644 --- a/SP1Clean/Native/Operations/U16toU8OperationUnsafe.lean +++ b/SP1Clean/Native/Operations/U16toU8OperationUnsafe.lean @@ -58,8 +58,7 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := circuit_proof_start haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ have h256 : (256 : ZMod p)⁻¹ * 256 = 1 := inv_mul_cancel₀ val_256_ne_zero - refine ⟨?_, ?_, ?_, ?_⟩ <;> - · rw [mul_assoc, h256, mul_one]; ring + refine ⟨?_, ?_, ?_, ?_⟩ <;> rw [mul_assoc, h256, mul_one] <;> ring omit [Fact (2 ^ 17 < p)] in theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Spec := by diff --git a/SP1Clean/Native/Readers/ALUTypeReader.lean b/SP1Clean/Native/Readers/ALUTypeReader.lean index bf2e495c..1339632e 100644 --- a/SP1Clean/Native/Readers/ALUTypeReader.lean +++ b/SP1Clean/Native/Readers/ALUTypeReader.lean @@ -134,9 +134,8 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := Or.inr h_assumptions, Or.inr h_assumptions, Or.inr (bool_of_mul_pred h_immbin), fun _ _ => bool_of_mul_pred hbin⟩ -- the four immediate gates: bridge `input_cols_op_c[i]` / `…prev_value[i]` (value-level) to the -- `Expression.eval env …[i]` form `h_holds` carries, via the `h_input` Word equalities + `getElem_map`. - have hoc := h_input.1.2.2.2.2.2.1 - have hpv := h_input.1.2.2.2.2.2.2.1.1 - rw [← hoc, ← hpv]; simp only [Vector.getElem_map]; exact ⟨i0, i1, i2, i3⟩ + rw [← h_input.1.2.2.2.2.2.1, ← h_input.1.2.2.2.2.2.2.1.1] + simp only [Vector.getElem_map]; exact ⟨i0, i1, i2, i3⟩ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Spec := by circuit_proof_start diff --git a/SP1Clean/Native/Readers/ALUTypeReaderImmutable.lean b/SP1Clean/Native/Readers/ALUTypeReaderImmutable.lean index e9ba5930..0584fefd 100644 --- a/SP1Clean/Native/Readers/ALUTypeReaderImmutable.lean +++ b/SP1Clean/Native/Readers/ALUTypeReaderImmutable.lean @@ -122,15 +122,12 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := -- bridge op_a_memory.prev_value eval → value for the four read-zeroing gates (nested vector field). have hmap_a : Vector.map (Expression.eval env) input_var_cols_op_a_memory_prev_value = input_cols_op_a_memory_prev_value := h_input.1.2.1.1 - have ea0 : Expression.eval env input_var_cols_op_a_memory_prev_value[0] - = input_cols_op_a_memory_prev_value[0] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_cols_op_a_memory_prev_value[1] - = input_cols_op_a_memory_prev_value[1] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env input_var_cols_op_a_memory_prev_value[2] - = input_cols_op_a_memory_prev_value[2] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env input_var_cols_op_a_memory_prev_value[3] - = input_cols_op_a_memory_prev_value[3] := by rw [← hmap_a]; simp only [Vector.getElem_map] - rw [ea0] at z0; rw [ea1] at z1; rw [ea2] at z2; rw [ea3] at z3 + have ea : ∀ (i : ℕ) (hi : i < 4), + Expression.eval env (input_var_cols_op_a_memory_prev_value[i]'hi) + = input_cols_op_a_memory_prev_value[i]'hi := by + intro i hi; rw [← hmap_a, Vector.getElem_map] + rw [ea 0 (by norm_num)] at z0; rw [ea 1 (by norm_num)] at z1 + rw [ea 2 (by norm_num)] at z2; rw [ea 3 (by norm_num)] at z3 -- the immediate gates bridge (op_c + op_c_memory.prev_value), as in `ALUTypeReader.soundness`. have hoc := h_input.1.2.2.2.2.2.1 have hpv := h_input.1.2.2.2.2.2.2.1.1 @@ -146,15 +143,12 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp -- bridges: op_a_memory.prev_value (zeroing gates) and op_c / op_c_memory.prev_value (immediate gates). have hmap_a : Vector.map (Expression.eval env.toEnvironment) input_var_cols_op_a_memory_prev_value = input_cols_op_a_memory_prev_value := h_input.1.2.1.1 - have ea0 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[0] - = input_cols_op_a_memory_prev_value[0] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[1] - = input_cols_op_a_memory_prev_value[1] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[2] - = input_cols_op_a_memory_prev_value[2] := by rw [← hmap_a]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[3] - = input_cols_op_a_memory_prev_value[3] := by rw [← hmap_a]; simp only [Vector.getElem_map] - rw [← ea0] at z0; rw [← ea1] at z1; rw [← ea2] at z2; rw [← ea3] at z3 + have ea : ∀ (i : ℕ) (hi : i < 4), + Expression.eval env.toEnvironment (input_var_cols_op_a_memory_prev_value[i]'hi) + = input_cols_op_a_memory_prev_value[i]'hi := by + intro i hi; rw [← hmap_a, Vector.getElem_map] + rw [← ea 0 (by norm_num)] at z0; rw [← ea 1 (by norm_num)] at z1 + rw [← ea 2 (by norm_num)] at z2; rw [← ea 3 (by norm_num)] at z3 have hoc := h_input.1.2.2.2.2.2.1 have hpv := h_input.1.2.2.2.2.2.2.1.1 have eoc : ∀ (i : ℕ) (hi : i < 4), diff --git a/SP1Clean/Native/Readers/CPUState.lean b/SP1Clean/Native/Readers/CPUState.lean index 03a04048..77547816 100644 --- a/SP1Clean/Native/Readers/CPUState.lean +++ b/SP1Clean/Native/Readers/CPUState.lean @@ -94,11 +94,9 @@ theorem soundness [Fact (2 ^ 17 < p)] : FormalAssertion.Soundness (ZMod p) main -- (`Guarantees := True`), so the Spec implication is the only goal. intro hr1 have hneg : -input_is_real = -1 := by rw [hr1] - have hb1 := h_holds.1 hneg - have hb2 := h_holds.2 hneg - refine ⟨(byteRowSpec_range _ h13p).mp ?_, ((byteRowSpec_u8range_pair _ _).mp hb2).1⟩ + refine ⟨(byteRowSpec_range _ h13p).mp ?_, ((byteRowSpec_u8range_pair _ _).mp (h_holds.2 hneg)).1⟩ rw [sub_eq_add_neg, Nat.cast_ofNat] - exact hb1 + exact h_holds.1 hneg theorem completeness [Fact (2 ^ 17 < p)] : FormalAssertion.Completeness (ZMod p) main Assumptions Spec := by circuit_proof_start @@ -109,15 +107,13 @@ theorem completeness [Fact (2 ^ 17 < p)] : FormalAssertion.Completeness (ZMod p) simp only [circuit_norm, byteChannel] refine ⟨?_, ?_⟩ · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hb1, _⟩ := h_spec hr1 + obtain ⟨hb1, _⟩ := h_spec (neg_inj.mp hneg) rw [sub_eq_add_neg] at hb1 have key := (byteRowSpec_range _ h13p).mpr hb1 rw [Nat.cast_ofNat] at key exact key · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨_, hb2⟩ := h_spec hr1 + obtain ⟨_, hb2⟩ := h_spec (neg_inj.mp hneg) exact (byteRowSpec_u8range_pair _ _).mpr ⟨hb2, by rw [ZMod.val_zero]; norm_num⟩ /-- The native CPUState reader as a Clean `FormalAssertion`: takes the chip-owned `cols` block plus diff --git a/SP1Clean/Native/Readers/ITypeReaderImmutable.lean b/SP1Clean/Native/Readers/ITypeReaderImmutable.lean index 71683beb..01e740c8 100644 --- a/SP1Clean/Native/Readers/ITypeReaderImmutable.lean +++ b/SP1Clean/Native/Readers/ITypeReaderImmutable.lean @@ -96,13 +96,13 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := have hmap : Vector.map (Expression.eval env) input_var_cols_op_a_memory_prev_value = input_cols_op_a_memory_prev_value := h_input.1.2.1.1 have ev0 : Expression.eval env input_var_cols_op_a_memory_prev_value[0] - = input_cols_op_a_memory_prev_value[0] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[0] := by simp [← hmap] have ev1 : Expression.eval env input_var_cols_op_a_memory_prev_value[1] - = input_cols_op_a_memory_prev_value[1] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[1] := by simp [← hmap] have ev2 : Expression.eval env input_var_cols_op_a_memory_prev_value[2] - = input_cols_op_a_memory_prev_value[2] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[2] := by simp [← hmap] have ev3 : Expression.eval env input_var_cols_op_a_memory_prev_value[3] - = input_cols_op_a_memory_prev_value[3] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[3] := by simp [← hmap] rw [ev0] at z0; rw [ev1] at z1; rw [ev2] at z2; rw [ev3] at z3 exact ⟨⟨⟨z0, z1, z2, z3⟩, bool_of_mul_pred hbin, h_rac_a h_assumptions, h_rac_b h_assumptions⟩, @@ -115,13 +115,13 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp have hmap : Vector.map (Expression.eval env.toEnvironment) input_var_cols_op_a_memory_prev_value = input_cols_op_a_memory_prev_value := h_input.1.2.1.1 have ev0 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[0] - = input_cols_op_a_memory_prev_value[0] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[0] := by simp [← hmap] have ev1 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[1] - = input_cols_op_a_memory_prev_value[1] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[1] := by simp [← hmap] have ev2 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[2] - = input_cols_op_a_memory_prev_value[2] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[2] := by simp [← hmap] have ev3 : Expression.eval env.toEnvironment input_var_cols_op_a_memory_prev_value[3] - = input_cols_op_a_memory_prev_value[3] := by rw [← hmap]; simp only [Vector.getElem_map] + = input_cols_op_a_memory_prev_value[3] := by simp [← hmap] rw [← ev0] at z0; rw [← ev1] at z1; rw [← ev2] at z2; rw [← ev3] at z3 refine ⟨⟨h_assumptions, hrac_a⟩, ⟨h_assumptions, hrac_b⟩, ?_, z0, z1, z2, z3⟩ rcases hbin with h | h <;> rw [h] <;> simp diff --git a/SP1Clean/Native/Readers/RegisterAccessCols.lean b/SP1Clean/Native/Readers/RegisterAccessCols.lean index 3901d7ab..167f04d7 100644 --- a/SP1Clean/Native/Readers/RegisterAccessCols.lean +++ b/SP1Clean/Native/Readers/RegisterAccessCols.lean @@ -85,12 +85,10 @@ set_option linter.unusedSectionVars false in @[circuit_norm] lemma localLength_eq (x : Var Inputs (ZMod p)) : (elaborated (p := p)).localLength x = 0 := rfl - /-- `is_real` is binary — the precondition for the genuinely `is_real`-gated byte receives (and threaded down to the composed `RegisterAccessTimestamp`). Discharged by the chip's gate. -/ def Assumptions (input : Inputs (ZMod p)) : Prop := input.is_real = 0 ∨ input.is_real = 1 - theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := by -- `Spec` = the composed timestamp sub-assertion's `Spec` (the two byte bounds); the sub gives it from -- `h_holds`, and the requirement tail is the sub's `is_real`-binary `Assumptions`. diff --git a/SP1Clean/Proofs/Chips/AddChip/Formal.lean b/SP1Clean/Proofs/Chips/AddChip/Formal.lean index c333f75c..93057f96 100644 --- a/SP1Clean/Proofs/Chips/AddChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/AddChip/Formal.lean @@ -1,7 +1,11 @@ import SP1Clean.Native.Chips.AddChip.Defs import SP1Clean.FormalModel.Contracts.ChipAssumptions -/-! # `SP1Clean.AddChip` — contract: `Assumptions` / soundness / completeness / `circuit` -/ +/-! # `SP1Clean.AddChip` — contract: `Assumptions` / soundness / completeness / `circuit` + +This is the canonical "template" chip for the porting recipe (`docs/agents/porting-recipe.md`). When +golfing / cleaning proofs here or in any sibling chip, follow `docs/agents/proof-patterns.md` § +"Golf & cleanup discipline" (and the backlog in `docs/agents/cleanup-backlog.md`). -/ namespace SP1Clean.AddChip @@ -33,18 +37,13 @@ theorem completeness : obtain ⟨ha, hb, hbin, hop_a_0, h_cpu, hrac_a, hrac_b, hrac_c⟩ := h_assumptions obtain ⟨-, -, -, -, -, -, ⟨hob, -, -⟩, -, hoc, -, -⟩ := h_input have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] - have hbeq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_b_memory_prev_value := by - rw [← hob]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl - have hceq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_c_memory_prev_value := by - rw [← hoc]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have mapEq : ∀ (vv : Word (Expression (ZMod p))) (v : Word (ZMod p)), + Vector.map (Expression.eval env.toEnvironment) vv = v → + (#v[Expression.eval env.toEnvironment vv[0], Expression.eval env.toEnvironment vv[1], + Expression.eval env.toEnvironment vv[2], Expression.eval env.toEnvironment vv[3]] : Word (ZMod p)) = v := + fun vv v h => by rw [← h]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have hbeq := mapEq input_var_adapter_op_b_memory_prev_value _ hob + have hceq := mapEq input_var_adapter_op_c_memory_prev_value _ hoc -- The witnessed `value` is `populate op_b op_c` (`h_env` per-limb). have hval : (Vector.map (Expression.eval env.toEnvironment) (Vector.mapRange 4 fun i => var {index := i₀ + i}) : Word (ZMod p)) diff --git a/SP1Clean/Proofs/Chips/AddiChip/Formal.lean b/SP1Clean/Proofs/Chips/AddiChip/Formal.lean index 527d074b..8a61d973 100644 --- a/SP1Clean/Proofs/Chips/AddiChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/AddiChip/Formal.lean @@ -30,18 +30,13 @@ theorem completeness : obtain ⟨ha, hb, hbin, hop_a_0, h_cpu, hrac_a, hrac_b⟩ := h_assumptions obtain ⟨-, -, -, -, -, -, ⟨hob, -, -⟩, hoc⟩ := h_input have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] - have hbeq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_b_memory_prev_value := by - rw [← hob]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl - have hceq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_c_imm[0], - Expression.eval env.toEnvironment input_var_adapter_op_c_imm[1], - Expression.eval env.toEnvironment input_var_adapter_op_c_imm[2], - Expression.eval env.toEnvironment input_var_adapter_op_c_imm[3]] : Word (ZMod p)) - = input_adapter_op_c_imm := by - rw [← hoc]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have mapEq : ∀ (vv : Word (Expression (ZMod p))) (v : Word (ZMod p)), + Vector.map (Expression.eval env.toEnvironment) vv = v → + (#v[Expression.eval env.toEnvironment vv[0], Expression.eval env.toEnvironment vv[1], + Expression.eval env.toEnvironment vv[2], Expression.eval env.toEnvironment vv[3]] : Word (ZMod p)) = v := + fun vv v h => by rw [← h]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have hbeq := mapEq input_var_adapter_op_b_memory_prev_value _ hob + have hceq := mapEq input_var_adapter_op_c_imm _ hoc have hval : (Vector.map (Expression.eval env.toEnvironment) (Vector.mapRange 4 fun i => var {index := i₀ + i}) : Word (ZMod p)) = AddOperation.populate input_adapter_op_b_memory_prev_value input_adapter_op_c_imm := by diff --git a/SP1Clean/Proofs/Chips/AddwChip/Bridge.lean b/SP1Clean/Proofs/Chips/AddwChip/Bridge.lean index 1e16ddfd..975bd08c 100644 --- a/SP1Clean/Proofs/Chips/AddwChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/AddwChip/Bridge.lean @@ -76,6 +76,55 @@ theorem addw_chip_reaches_sail ((h_chip.2.2 h_real).trans (AddwChip.rv64_addw_eq (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val))) +/-- The RISC-V spec: advance `nextPC ← PC + 4`, then execute the Sail `ADDIW` (I-type add-word-immediate). -/ +noncomputable def spec_addiw (imm : BitVec 12) (rs1 rd : regidx) : SailM Unit := do + Sail.writeReg Register.nextPC ((← Sail.readReg Register.PC) + 4#64) + _ ← execute_ADDIW imm rs1 rd + pure () + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence for ADDIW: the chip's semantic fact (`h_addw`, with the immediate operand +`op_c_val` bound to `sign_extend imm` via `h_dec`) plus the rs1/PC reads drive `spec_addiw ≡ sp1_addw`. The +Sail `ADDIW` writes `sign_extend (extractLsb (rX rs1 + immext) 31 0)`, which `addw_pure_eq` ties to the +chip's `signExtend 64 (setWidth 32 (op_b + op_c))`. -/ +theorem correct_addiw_native + (op_b_val op_c_val a_val : Word (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_addw : Word.toBitVec64 a_val = + (BitVec.setWidth 32 (Word.toBitVec64 op_b_val + Word.toBitVec64 op_c_val)).signExtend 64) : + (spec_addiw imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_addw (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 a_val + = sign_extend (m := 64) (Sail.BitVec.extractLsb + (Word.toBitVec64 op_b_val + sign_extend (m := 64) imm) 31 0) := by + rw [h_addw, ← h_dec] + simp only [sign_extend, Sail.BitVec.signExtend] + congr 1 + simp [spec_addiw, sp1_addw, execute_ADDIW, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (ADDIW): from the ADDW chip's verified `Spec` plus the rs1/PC reads and the immediate decode +(`op_c_val = sign_extend imm`), Sail's `ADDIW` agrees with the SP1 chip emulation. -/ +theorem addiw_chip_reaches_sail + (input : AddwChip.Inputs (ZMod p)) (cols : Extracted.AddwCols (ZMod p)) (data : ProverData (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_real : input.is_real = 1) + (h_chip : AddwChip.Spec input cols data) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 input.op_b_val)) + (h_dec : Word.toBitVec64 input.op_c_val = sign_extend (m := 64) imm) : + (spec_addiw imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_addw (.Regidx rd_idx) pc (AddwChip.resultWord cols)).run s := + correct_addiw_native input.op_b_val input.op_c_val (AddwChip.resultWord cols) + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec + ((h_chip.2.2 h_real).trans + (AddwChip.rv64_addw_eq (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val))) + end SP1Clean.AddwSail namespace SP1Clean.AddwChip @@ -95,13 +144,23 @@ def kind : Soundness.ChipKind p where #v[cols.state.pc[0] + 4, cols.state.pc[1], cols.state.pc[2]], cols.adapter.toAdapterView, inp.is_real, AddwChip.resultWord cols, 19⟩ chipSpec := fun inp cols data => Spec inp cols data - sailEquiv := fun inp cols s => ∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), - s.regs.get? Register.PC = some pc → - s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → - s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → - (spec_addw (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s - = (sp1_addw (.Regidx rd) pc (AddwChip.resultWord cols)).run s - reaches_sail := fun inp cols data s h_real h_chip rs1 rs2 rd pc h_pc h_rs1 h_rs2 => - addw_chip_reaches_sail inp cols data rs1 rs2 rd pc s h_real h_chip h_pc h_rs1 h_rs2 + sailEquiv := fun inp cols s => + (∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → + (spec_addw (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s + = (sp1_addw (.Regidx rd) pc (AddwChip.resultWord cols)).run s) ∧ + (∀ (rs1 rd : BitVec 5) (imm : BitVec 12) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + Word.toBitVec64 inp.op_c_val = sign_extend (m := 64) imm → + (spec_addiw imm (.Regidx rs1) (.Regidx rd)).run s + = (sp1_addw (.Regidx rd) pc (AddwChip.resultWord cols)).run s) + reaches_sail := fun inp cols data s h_real h_chip => + ⟨fun rs1 rs2 rd pc h_pc h_rs1 h_rs2 => + addw_chip_reaches_sail inp cols data rs1 rs2 rd pc s h_real h_chip h_pc h_rs1 h_rs2, + fun rs1 rd imm pc h_pc h_rs1 h_dec => + addiw_chip_reaches_sail inp cols data rs1 rd imm pc s h_real h_chip h_pc h_rs1 h_dec⟩ end SP1Clean.AddwChip diff --git a/SP1Clean/Proofs/Chips/AddwChip/Formal.lean b/SP1Clean/Proofs/Chips/AddwChip/Formal.lean index 8f897819..71cec214 100644 --- a/SP1Clean/Proofs/Chips/AddwChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/AddwChip/Formal.lean @@ -48,18 +48,13 @@ theorem completeness : obtain ⟨-, -, -, -, -, -, ⟨hob, -, -⟩, -, ⟨hoc, -, -⟩, -⟩ := h_input obtain ⟨h_env_val, h_env_msb⟩ := h_env have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] - have hbeq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_b_memory_prev_value := by - rw [← hob]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl - have hceq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_c_memory_prev_value := by - rw [← hoc]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have mapEq : ∀ (vv : Word (Expression (ZMod p))) (v : Word (ZMod p)), + Vector.map (Expression.eval env.toEnvironment) vv = v → + (#v[Expression.eval env.toEnvironment vv[0], Expression.eval env.toEnvironment vv[1], + Expression.eval env.toEnvironment vv[2], Expression.eval env.toEnvironment vv[3]] : Word (ZMod p)) = v := + fun vv v h => by rw [← h]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have hbeq := mapEq input_var_adapter_op_b_memory_prev_value _ hob + have hceq := mapEq input_var_adapter_op_c_memory_prev_value _ hoc have hval : (Vector.map (Expression.eval env.toEnvironment) (Vector.mapRange 2 fun i => var { index := i₀ + i }) : Vector (ZMod p) 2) = AddwOperation.addwValueWitness input_adapter_op_b_memory_prev_value diff --git a/SP1Clean/Proofs/Chips/AluX0Chip/Formal.lean b/SP1Clean/Proofs/Chips/AluX0Chip/Formal.lean index 02628b21..a2ec483a 100644 --- a/SP1Clean/Proofs/Chips/AluX0Chip/Formal.lean +++ b/SP1Clean/Proofs/Chips/AluX0Chip/Formal.lean @@ -24,12 +24,8 @@ lemma byteRowSpec_ltu_29 {op : ZMod p} (h : op.val < 29) : rw [show (29 : ZMod p) = ((29 : ℕ) : ZMod p) from by norm_cast, ZMod.val_natCast_of_lt h29p] haveI : Fact (1 < p) := ⟨hp2⟩ refine ⟨ByteOpcode.LTU, by norm_cast, ?_⟩ - simp only [ByteOpcode.constrain_LTU] - refine ⟨⟨?_, ?_, ?_⟩, Or.inr trivial, ?_⟩ - · rw [ZMod.val_one]; norm_num - · omega - · rw [h29]; norm_num - · rw [h29]; exact ⟨fun _ => h, fun _ => trivial⟩ + simp only [ByteOpcode.constrain_LTU, ZMod.val_one, h29] + exact ⟨⟨by norm_num, by omega, by norm_num⟩, Or.inr trivial, fun _ => h, fun _ => trivial⟩ /-- Verifier-side `Assumptions` — trivial. `AluX0` discards its result, so there are no operand well-formedness obligations; `is_real` binary and the reader contract are derived in soundness from the @@ -44,8 +40,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe simp only [← sub_eq_add_neg] at h_oa1 h_oa2 simp only [isReal, clkLow, opcodeVal] -- the per-emitter channel-requirement tail: CPUState / reader (the gated byte pull owes no requirement). - exact ⟨⟨h_reader h_bin, h_bin, h_oa1, h_oa2⟩, - Or.inr h_bin, Or.inr h_bin⟩ + exact ⟨⟨h_reader h_bin, h_bin, h_oa1, h_oa2⟩, Or.inr h_bin, Or.inr h_bin⟩ /-- Honest prover-side row well-formedness: `is_real` binary, the two `op_a_0` forcing gates, the CPUState clock bounds + the immutable-ALU-reader contract, and the dynamic opcode in ALU range @@ -69,16 +64,13 @@ theorem completeness : simp only [isReal, clkLow, opcodeVal] at h_assumptions obtain ⟨h_bin, h_oa1, h_oa2, h_cpu, h_reader, h_op_lt⟩ := h_assumptions simp only [sub_eq_add_neg] at h_oa1 h_oa2 - refine ⟨⟨h_bin, h_cpu⟩, ?_, ⟨h_bin, h_reader⟩, ?_, ?_, ?_⟩ + refine ⟨⟨h_bin, h_cpu⟩, ?_, ⟨h_bin, h_reader⟩, ?_, h_oa1, h_oa2⟩ · -- the LTU `opcode < 29` byte pull (fires on real rows). intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg simp only [byteChannel] - exact byteRowSpec_ltu_29 (h_op_lt hr1) + exact byteRowSpec_ltu_29 (h_op_lt (neg_inj.mp hneg)) · -- `is_real` binary gate. rcases h_bin with h | h <;> rw [h] <;> simp - · exact h_oa1 - · exact h_oa2 /-- The `AluX0` chip row as a `GeneralFormalCircuit`: validates the ALU-into-`x0` program/register accesses and advances state (the result discarded); output is the extracted `AluX0Cols`. -/ diff --git a/SP1Clean/Proofs/Chips/BitwiseChip/Bridge.lean b/SP1Clean/Proofs/Chips/BitwiseChip/Bridge.lean index afa7d5f7..77a2846a 100644 --- a/SP1Clean/Proofs/Chips/BitwiseChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/BitwiseChip/Bridge.lean @@ -46,6 +46,71 @@ theorem correct_bitwise_native PreSail.readReg, PreSail.writeReg, Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, h_rs2, h_op] +/-- The RISC-V spec: advance `nextPC ← PC + 4`, then execute the Sail I-type op (ANDI/ORI/XORI). -/ +noncomputable def spec_bitwise_imm (imm : BitVec 12) (rs1 rd : regidx) (op : iop) : SailM Unit := do + Sail.writeReg Register.nextPC ((← Sail.readReg Register.PC) + 4#64) + _ ← execute_ITYPE imm rs1 rd op + pure () + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence (ANDI): the chip's bitwise fact (`h_op`, with the immediate operand `op_c_val` +bound to `sign_extend imm` via `h_dec`) drives `spec_bitwise_imm … ANDI ≡ sp1_bitwise`. The Sail `ANDI` arm +`rX rs1 &&& immext` is the same body as `execute_RTYPE_pure … .AND`. -/ +theorem correct_andi_native + (op_b_val op_c_val : Word (ZMod p)) (a_val : Vector (ZMod p) 8) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_op : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = execute_RTYPE_pure (Word.toBitVec64 op_b_val) (Word.toBitVec64 op_c_val) rop.AND) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.ANDI).run s + = (sp1_bitwise (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = (Word.toBitVec64 op_b_val) &&& (sign_extend (m := 64) imm) := by + rw [h_op, ← h_dec]; simp [execute_RTYPE_pure] + simp [spec_bitwise_imm, sp1_bitwise, execute_ITYPE, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence (ORI): `h_op` + reads drive `spec_bitwise_imm … ORI ≡ sp1_bitwise`. -/ +theorem correct_ori_native + (op_b_val op_c_val : Word (ZMod p)) (a_val : Vector (ZMod p) 8) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_op : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = execute_RTYPE_pure (Word.toBitVec64 op_b_val) (Word.toBitVec64 op_c_val) rop.OR) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.ORI).run s + = (sp1_bitwise (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = (Word.toBitVec64 op_b_val) ||| (sign_extend (m := 64) imm) := by + rw [h_op, ← h_dec]; simp [execute_RTYPE_pure] + simp [spec_bitwise_imm, sp1_bitwise, execute_ITYPE, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence (XORI): `h_op` + reads drive `spec_bitwise_imm … XORI ≡ sp1_bitwise`. -/ +theorem correct_xori_native + (op_b_val op_c_val : Word (ZMod p)) (a_val : Vector (ZMod p) 8) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_op : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = execute_RTYPE_pure (Word.toBitVec64 op_b_val) (Word.toBitVec64 op_c_val) rop.XOR) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.XORI).run s + = (sp1_bitwise (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 (BitwiseU16Operation.resultWord a_val) + = (Word.toBitVec64 op_b_val) ^^^ (sign_extend (m := 64) imm) := by + rw [h_op, ← h_dec]; simp [execute_RTYPE_pure] + simp [spec_bitwise_imm, sp1_bitwise, execute_ITYPE, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + omit [Fact (2 ^ 17 < p)] in /-- End-to-end (AND): a real Bitwise chip row with `is_and = 1` reaches the Sail `AND`. -/ theorem bitwise_chip_reaches_sail_and @@ -94,6 +159,55 @@ theorem bitwise_chip_reaches_sail_xor rop.XOR h_pc h_rs1 h_rs2 ?_ exact (h_chip.2 h_real).2.2 h_xor +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (ANDI): a real Bitwise chip row with `is_and = 1` whose `op_c` is the sign-extended +immediate (`h_dec`) reaches the Sail `ANDI`. -/ +theorem bitwise_chip_reaches_sail_andi + (input : BitwiseChip.Inputs (ZMod p)) (cols : Extracted.BitwiseCols (ZMod p)) (data : ProverData (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_real : input.is_real = 1) (h_and : cols.is_and = 1) + (h_chip : BitwiseChip.Spec input cols data) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 input.op_b_val)) + (h_dec : Word.toBitVec64 input.op_c_val = sign_extend (m := 64) imm) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.ANDI).run s + = (sp1_bitwise (.Regidx rd_idx) pc cols.bitwise_operation.bitwise_operation.result).run s := by + refine correct_andi_native input.op_b_val input.op_c_val cols.bitwise_operation.bitwise_operation.result + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec ?_ + exact (h_chip.2 h_real).1 h_and + +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (ORI): a real Bitwise chip row with `is_or = 1` and immediate `op_c` reaches the Sail `ORI`. -/ +theorem bitwise_chip_reaches_sail_ori + (input : BitwiseChip.Inputs (ZMod p)) (cols : Extracted.BitwiseCols (ZMod p)) (data : ProverData (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_real : input.is_real = 1) (h_or : cols.is_or = 1) + (h_chip : BitwiseChip.Spec input cols data) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 input.op_b_val)) + (h_dec : Word.toBitVec64 input.op_c_val = sign_extend (m := 64) imm) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.ORI).run s + = (sp1_bitwise (.Regidx rd_idx) pc cols.bitwise_operation.bitwise_operation.result).run s := by + refine correct_ori_native input.op_b_val input.op_c_val cols.bitwise_operation.bitwise_operation.result + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec ?_ + exact (h_chip.2 h_real).2.1 h_or + +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (XORI): a real Bitwise chip row with `is_xor = 1` and immediate `op_c` reaches the Sail `XORI`. -/ +theorem bitwise_chip_reaches_sail_xori + (input : BitwiseChip.Inputs (ZMod p)) (cols : Extracted.BitwiseCols (ZMod p)) (data : ProverData (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_real : input.is_real = 1) (h_xor : cols.is_xor = 1) + (h_chip : BitwiseChip.Spec input cols data) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 input.op_b_val)) + (h_dec : Word.toBitVec64 input.op_c_val = sign_extend (m := 64) imm) : + (spec_bitwise_imm imm (.Regidx rs1_idx) (.Regidx rd_idx) iop.XORI).run s + = (sp1_bitwise (.Regidx rd_idx) pc cols.bitwise_operation.bitwise_operation.result).run s := by + refine correct_xori_native input.op_b_val input.op_c_val cols.bitwise_operation.bitwise_operation.result + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec ?_ + exact (h_chip.2 h_real).2.2 h_xor + end SP1Clean.BitwiseSail namespace SP1Clean.BitwiseChip @@ -116,22 +230,41 @@ def kind : Soundness.ChipKind p where BitwiseU16Operation.resultWord cols.bitwise_operation.bitwise_operation.result, cols.is_xor * 3 + cols.is_or * 4 + cols.is_and * 5⟩ chipSpec := fun inp cols data => Spec inp cols data - sailEquiv := fun inp cols s => ∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), - s.regs.get? Register.PC = some pc → - s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → - s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → - (cols.is_and = 1 → - (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.AND).run s - = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ - (cols.is_or = 1 → - (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.OR).run s - = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ - (cols.is_xor = 1 → - (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.XOR).run s - = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) - reaches_sail := fun inp cols data s h_real h_chip rs1 rs2 rd pc h_pc h_rs1 h_rs2 => - ⟨fun ha => bitwise_chip_reaches_sail_and inp cols data rs1 rs2 rd pc s h_real ha h_chip h_pc h_rs1 h_rs2, - fun ho => bitwise_chip_reaches_sail_or inp cols data rs1 rs2 rd pc s h_real ho h_chip h_pc h_rs1 h_rs2, - fun hx => bitwise_chip_reaches_sail_xor inp cols data rs1 rs2 rd pc s h_real hx h_chip h_pc h_rs1 h_rs2⟩ + sailEquiv := fun inp cols s => + (∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → + (cols.is_and = 1 → + (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.AND).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ + (cols.is_or = 1 → + (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.OR).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ + (cols.is_xor = 1 → + (spec_bitwise (.Regidx rs2) (.Regidx rs1) (.Regidx rd) rop.XOR).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s)) ∧ + (∀ (rs1 rd : BitVec 5) (imm : BitVec 12) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + Word.toBitVec64 inp.op_c_val = sign_extend (m := 64) imm → + (cols.is_and = 1 → + (spec_bitwise_imm imm (.Regidx rs1) (.Regidx rd) iop.ANDI).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ + (cols.is_or = 1 → + (spec_bitwise_imm imm (.Regidx rs1) (.Regidx rd) iop.ORI).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s) ∧ + (cols.is_xor = 1 → + (spec_bitwise_imm imm (.Regidx rs1) (.Regidx rd) iop.XORI).run s + = (sp1_bitwise (.Regidx rd) pc cols.bitwise_operation.bitwise_operation.result).run s)) + reaches_sail := fun inp cols data s h_real h_chip => + ⟨fun rs1 rs2 rd pc h_pc h_rs1 h_rs2 => + ⟨fun ha => bitwise_chip_reaches_sail_and inp cols data rs1 rs2 rd pc s h_real ha h_chip h_pc h_rs1 h_rs2, + fun ho => bitwise_chip_reaches_sail_or inp cols data rs1 rs2 rd pc s h_real ho h_chip h_pc h_rs1 h_rs2, + fun hx => bitwise_chip_reaches_sail_xor inp cols data rs1 rs2 rd pc s h_real hx h_chip h_pc h_rs1 h_rs2⟩, + fun rs1 rd imm pc h_pc h_rs1 h_dec => + ⟨fun ha => bitwise_chip_reaches_sail_andi inp cols data rs1 rd imm pc s h_real ha h_chip h_pc h_rs1 h_dec, + fun ho => bitwise_chip_reaches_sail_ori inp cols data rs1 rd imm pc s h_real ho h_chip h_pc h_rs1 h_dec, + fun hx => bitwise_chip_reaches_sail_xori inp cols data rs1 rd imm pc s h_real hx h_chip h_pc h_rs1 h_dec⟩⟩ end SP1Clean.BitwiseChip diff --git a/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean b/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean index 76de84d7..e4f22787 100644 --- a/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean @@ -69,16 +69,11 @@ private lemma one_hot3 {x o a : ZMod p} (x = 1 → o = 0 ∧ a = 0) ∧ (o = 1 → x = 0 ∧ a = 0) ∧ (a = 1 → x = 0 ∧ o = 0) := by have hp : 2 ^ 17 < p := Fact.out haveI : Fact (1 < p) := ⟨by omega⟩ - have h2 : (2 : ZMod p) ≠ 0 := by - have h : ((2 : ℕ) : ZMod p) ≠ 0 := by - rw [Ne, CharP.cast_eq_zero_iff (ZMod p) p]; intro hd - have := Nat.le_of_dvd (by norm_num) hd; omega - simpa using h - have h6 : (6 : ZMod p) ≠ 0 := by - have h : ((6 : ℕ) : ZMod p) ≠ 0 := by - rw [Ne, CharP.cast_eq_zero_iff (ZMod p) p]; intro hd - have := Nat.le_of_dvd (by norm_num) hd; omega - simpa using h + have hne : ∀ k : ℕ, 0 < k → k < p → ((k : ℕ) : ZMod p) ≠ 0 := fun k hk hkp => by + rw [Ne, CharP.cast_eq_zero_iff (ZMod p) p]; intro hd + exact absurd (Nat.le_of_dvd hk hd) (by omega) + have h2 : (2 : ZMod p) ≠ 0 := by simpa using hne 2 (by norm_num) (by omega) + have h6 : (6 : ZMod p) ≠ 0 := by simpa using hne 6 (by norm_num) (by omega) rcases hx with rfl | rfl <;> rcases ho with rfl | rfl <;> rcases ha with rfl | rfl <;> refine ⟨fun h => ?_, fun h => ?_, fun h => ?_⟩ <;> first @@ -87,6 +82,16 @@ private lemma one_hot3 {x o a : ZMod p} | (exfalso; apply h2; linear_combination hsum) | (exfalso; apply h6; linear_combination hsum) +/-- The byte opcode `is_xor·2 + is_or·1 + is_and·0` lands in `{0,1,2}` (one-hot), so its `val < 3` — +the operand-range part of the composed `BitwiseU16Operation.circuit`'s `Assumptions`. -/ +private lemma val_lt_three {x : ZMod p} (h : x = 0 ∨ x = 1 ∨ x = 2) : x.val < 3 := by + haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ + have hp : 2 < p := by have := Fact.out (p := 2 ^ 17 < p); omega + rcases h with rfl | rfl | rfl + · simp + · rw [ZMod.val_one]; omega + · rw [show (2 : ZMod p) = ((2 : ℕ) : ZMod p) by norm_cast, ZMod.val_natCast_of_lt hp]; omega + set_option maxHeartbeats 2000000 in theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spec := by circuit_proof_start [Spec] @@ -99,23 +104,14 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have h_and_bool := bool_of_mul_pred h_and_bin have hoh := one_hot3 h_xor_bool h_or_bool h_and_bool h_sum haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - -- the byte opcode `is_xor·2 + is_or·1` lands in `{0,1,2}` (one-hot), so it is `< 3` — the operand - -- range part of the composed `BitwiseU16Operation.circuit`'s `Assumptions`. - have hop3 : (env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0).val < 3 := by - have key : env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 0 - ∨ env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 1 - ∨ env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 2 := by + have hop3 : (env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0).val < 3 := + val_lt_three <| by rcases h_xor_bool with hx | hx · rcases h_or_bool with ho | ho · exact Or.inl (by rw [hx, ho]; ring) · exact Or.inr (Or.inl (by rw [hx, ho]; ring)) · obtain ⟨ho, _⟩ := hoh.1 hx exact Or.inr (Or.inr (by rw [hx, ho]; ring)) - rcases key with h | h | h <;> rw [h] - · rw [ZMod.val_zero]; omega - · rw [ZMod.val_one]; omega - · rw [show (2 : ZMod p) = ((2 : ℕ) : ZMod p) by norm_cast, - ZMod.val_natCast_of_lt (by have := Fact.out (p := 2 ^ 17 < p); omega : 2 < p)]; omega -- once the active flag forces the others to 0, the byte opcode reduces to a literal refine ⟨⟨h_bin, fun hr => ⟨fun hand => ?_, fun hor => ?_, fun hxor => ?_⟩⟩, Or.inr h_bin, Or.inr ⟨ha, hb, hop3, h_bin⟩, Or.inr h_bin⟩ @@ -132,12 +128,8 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe · obtain ⟨ho0, _ha0⟩ := hoh.1 hxor have hopc : env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 2 := by rw [hxor, ho0]; ring - have h2 : (2 : ZMod p).val < 3 := by - rw [show (2 : ZMod p) = ((2 : ℕ) : ZMod p) by norm_cast, - ZMod.val_natCast_of_lt (by have := Fact.out (p := 2 ^ 17 < p); omega : 2 < p)] - omega exact (BitwiseU16Operation.result_semantic _ hr - (h_bw ⟨ha, hb, by rw [hopc]; exact h2, h_bin⟩)).2.2 hopc + (h_bw ⟨ha, hb, by rw [hopc]; exact val_lt_three (Or.inr (Or.inr rfl)), h_bin⟩)).2.2 hopc set_option linter.unusedSectionVars false in /-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator — lets the witness @@ -178,10 +170,8 @@ theorem completeness : have hpvc : Vector.map (Expression.eval env.toEnvironment) input_var_adapter_op_c_memory_prev_value = input_adapter_op_c_memory_prev_value := h_input.2.2.2.2.2.2.2.2.1.1 simp only [Inputs.op_b_val, Inputs.op_c_val, vec4_eval, hpvb, hpvc] at h_env_cols - have hop3 : (env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0).val < 3 := by - have key : env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 0 - ∨ env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 1 - ∨ env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0 = 2 := by + have hop3 : (env.get i₀ * 2 + env.get (i₀ + 1) * 1 + env.get (i₀ + 2) * 0).val < 3 := + val_lt_three <| by rw [hflag0, hflag1, hflag2] rcases hf0 with hx | hx · rcases hf1 with ho | ho @@ -189,11 +179,6 @@ theorem completeness : · exact Or.inr (Or.inl (by rw [hx, ho]; ring)) · obtain ⟨ho, -⟩ := hone0 hx exact Or.inr (Or.inr (by rw [hx, ho]; ring)) - rcases key with h | h | h <;> rw [h] - · rw [ZMod.val_zero]; omega - · rw [ZMod.val_one]; omega - · rw [show (2 : ZMod p) = ((2 : ℕ) : ZMod p) by norm_cast, - ZMod.val_natCast_of_lt (by have := Fact.out (p := 2 ^ 17 < p); omega : 2 < p)]; omega refine ⟨⟨hbin, h_cpu⟩, ⟨⟨ha, hb, hop3, hbin⟩, ?_⟩, diff --git a/SP1Clean/Proofs/Chips/BranchChip/Bridge.lean b/SP1Clean/Proofs/Chips/BranchChip/Bridge.lean index aec362fd..42c3a382 100644 --- a/SP1Clean/Proofs/Chips/BranchChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/BranchChip/Bridge.lean @@ -85,9 +85,9 @@ theorem correct_branch_native have hsp_pc : sp.regs.get Register.PC (hsp_init _) = pc := by rw [key Register.PC (hs _) (hsp_init _) (by decide)]; exact hpc_get have hsp_rs1 : SailState.get_reg? sp rs1 = some rs1_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hsp_rs2 : SailState.get_reg? sp rs2 = some rs2_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs2 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hexec : EStateM.run (execute_BTYPE imm (.Regidx rs2) (.Regidx rs1) op) sp = .ok RETIRE_SUCCESS { s with regs := s.regs.insert Register.nextPC (Word.toBitVec64 next_pc_word) } := by rcases em (branchCond op rs1_val rs2_val) with hc | hc @@ -113,7 +113,9 @@ theorem correct_branch_native /-- End-to-end: from the BRANCH chip's verified `Spec` (on a real row) plus the PC/rs1/rs2 reads, the committed rs1/rs2 ↔ Sail reassembly, the immediate decode, and the committed-pc reassembly, the RISC-V -`BTYPE` execution agrees with the SP1 chip emulation — flag-dispatched over the six opcodes. -/ +`BTYPE` execution agrees with the SP1 chip emulation — flag-dispatched over the six opcodes. The +branch-target 4-byte alignment is **derived** from the `Spec`'s divisibility conjunct (the in-circuit +`÷4` range check), not assumed. -/ theorem branch_chip_reaches_sail (inp : BranchChip.Inputs (ZMod p)) (cols : Extracted.BranchColumns (ZMod p)) (data : ProverData (ZMod p)) @@ -127,8 +129,7 @@ theorem branch_chip_reaches_sail (h_rs1v : Word.toBitVec64 (BranchChip.rs1Word cols) = rs1_val) (h_rs2v : Word.toBitVec64 (BranchChip.rs2Word cols) = rs2_val) (h_dec : Word.toBitVec64 cols.adapter.op_c_imm = sign_extend (m := 64) imm) - (h_pcw : Word.toBitVec64 (BranchChip.pcWord cols) = pc) - (h_align : (Word.toBitVec64 (BranchChip.nextPcWord cols)).toNat % 4 = 0) : + (h_pcw : Word.toBitVec64 (BranchChip.pcWord cols) = pc) : (cols.is_beq = 1 → (spec_btype imm (.Regidx rs2) (.Regidx rs1) bop.BEQ).run s = (sp1_branch (BranchChip.nextPcWord cols)).run s) ∧ @@ -147,7 +148,13 @@ theorem branch_chip_reaches_sail (cols.is_bgeu = 1 → (spec_btype imm (.Regidx rs2) (.Regidx rs1) bop.BGEU).run s = (sp1_branch (BranchChip.nextPcWord cols)).run s) := by - obtain ⟨_, _, h_flags, h_taken_gated, h_fall_gated, h_decision⟩ := h_chip + obtain ⟨_, _, h_flags, h_taken_gated, h_fall_gated, h_decision, h_div⟩ := h_chip + -- 4-byte alignment is now a verified `Spec` conjunct (the in-circuit `÷4` range check), not an assumed + -- precondition: lift the low-limb divisibility to the committed `next_pc` word. + have h_align : (Word.toBitVec64 (BranchChip.nextPcWord cols)).toNat % 4 = 0 := by + rw [Word.toBitVec64_toNat_mod_four] + simpa only [BranchChip.nextPcWord, Vector.getElem_mk, List.getElem_toArray, + List.getElem_cons_zero] using h_div h_real have h_brbin : cols.is_branching = 0 ∨ cols.is_branching = 1 := h_flags.2.2.2.2.2.2 have key : ∀ op : bop, (cols.is_branching = 1 ↔ branchCond op rs1_val rs2_val) → (spec_btype imm (.Regidx rs2) (.Regidx rs1) op).run s @@ -166,12 +173,12 @@ theorem branch_chip_reaches_sail obtain ⟨d_beq, d_bne, d_blt, d_bge, d_bltu, d_bgeu⟩ := h_decision h_real refine ⟨fun hf => key bop.BEQ ?_, fun hf => key bop.BNE ?_, fun hf => key bop.BLT ?_, fun hf => key bop.BGE ?_, fun hf => key bop.BLTU ?_, fun hf => key bop.BGEU ?_⟩ - · have := d_beq hf; rw [h_rs1v, h_rs2v] at this; exact this - · have := d_bne hf; rw [h_rs1v, h_rs2v] at this; exact this - · have := d_blt hf; rw [h_rs1v, h_rs2v] at this; exact this - · have := d_bge hf; rw [h_rs1v, h_rs2v] at this; exact this - · have := d_bltu hf; rw [h_rs1v, h_rs2v] at this; exact this - · have := d_bgeu hf; rw [h_rs1v, h_rs2v] at this; exact this + · have := d_beq hf; rwa [h_rs1v, h_rs2v] at this + · have := d_bne hf; rwa [h_rs1v, h_rs2v] at this + · have := d_blt hf; rwa [h_rs1v, h_rs2v] at this + · have := d_bge hf; rwa [h_rs1v, h_rs2v] at this + · have := d_bltu hf; rwa [h_rs1v, h_rs2v] at this + · have := d_bgeu hf; rwa [h_rs1v, h_rs2v] at this end SP1Clean.BranchSail @@ -183,8 +190,9 @@ open Sail LeanRV64D LeanRV64D.Functions variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] /-- BRANCH's `ChipKind` registration. `view` threads `next_pc = cols.next_pc`, I-type adapter, opcode -`Σ is_b*·k`; no destination write. `sailEquiv` is the six-way flag-dispatched conjunction; `reaches_sail` -is `branch_chip_reaches_sail`. -/ +`Σ is_b*·k`; no destination write. `sailEquiv` is the six-way flag-dispatched conjunction; the +branch-target 4-byte alignment is no longer a precondition — `reaches_sail` derives it from the chip +`Spec`. `reaches_sail` is `branch_chip_reaches_sail`. -/ def kind : Soundness.ChipKind p where name := "Branch" Inputs := BranchChip.Inputs @@ -203,7 +211,6 @@ def kind : Soundness.ChipKind p where Word.toBitVec64 (BranchChip.rs2Word cols) = rs2_val → Word.toBitVec64 cols.adapter.op_c_imm = sign_extend (m := 64) imm → Word.toBitVec64 (BranchChip.pcWord cols) = pc → - (Word.toBitVec64 (BranchChip.nextPcWord cols)).toNat % 4 = 0 → (cols.is_beq = 1 → (spec_btype imm (.Regidx rs2) (.Regidx rs1) bop.BEQ).run s = (sp1_branch (BranchChip.nextPcWord cols)).run s) ∧ @@ -223,8 +230,8 @@ def kind : Soundness.ChipKind p where (spec_btype imm (.Regidx rs2) (.Regidx rs1) bop.BGEU).run s = (sp1_branch (BranchChip.nextPcWord cols)).run s) reaches_sail := fun inp cols data s h_real h_chip rs1 rs2 imm pc rs1_val rs2_val - hs h_pc h_rs1 h_rs2 h_rs1v h_rs2v h_dec h_pcw h_align => + hs h_pc h_rs1 h_rs2 h_rs1v h_rs2v h_dec h_pcw => branch_chip_reaches_sail inp cols data rs1 rs2 imm pc rs1_val rs2_val s hs h_real h_chip - h_pc h_rs1 h_rs2 h_rs1v h_rs2v h_dec h_pcw h_align + h_pc h_rs1 h_rs2 h_rs1v h_rs2v h_dec h_pcw end SP1Clean.BranchChip diff --git a/SP1Clean/Proofs/Chips/BranchChip/Decision.lean b/SP1Clean/Proofs/Chips/BranchChip/Decision.lean index bd49296c..ad218da0 100644 --- a/SP1Clean/Proofs/Chips/BranchChip/Decision.lean +++ b/SP1Clean/Proofs/Chips/BranchChip/Decision.lean @@ -1,4 +1,5 @@ import SP1Clean.Math.Word +import SP1Clean.Math.Gate /-! # `SP1Clean.BranchChip` — the six-way decision dispatch (shared field lemmas) @@ -32,10 +33,7 @@ local instance : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ /-- `(0 : ZMod p) ≠ 1` (since `2^17 < p`). -/ lemma zero_ne_one' : (0 : ZMod p) ≠ 1 := by haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - intro h - have := congrArg ZMod.val h - rw [ZMod.val_zero, ZMod.val_one] at this - exact absurd this (by norm_num) + exact zero_ne_one /-- The `ℕ`-value of a binary field element (`0` or `1`). -/ lemma val_of_bool {b : ZMod p} (h : b = 0 ∨ b = 1) : b.val = 0 ∨ b.val = 1 := by @@ -65,42 +63,6 @@ lemma one_hot6 {b0 b1 b2 b3 b4 b5 : ZMod p} · right; rw [h, ZMod.val_one] omega -/-- A binary field element that equals `if P then 1 else 0` is `1` iff `P` holds. -/ -private lemma eq_one_iff_of_ite {x : ZMod p} {P : Prop} [Decidable P] - (h : x = if P then 1 else 0) : x = 1 ↔ P := by - by_cases hP : P - · simp [h, hP] - · simp only [h, if_neg hP] - exact ⟨fun he => absurd he zero_ne_one', fun hp => absurd hp hP⟩ - -/-- A field element equal to `1 - (if P then 1 else 0)` is `1` iff `P` fails. -/ -private lemma eq_one_iff_of_one_sub_ite {x : ZMod p} {P : Prop} [Decidable P] - (h : x = 1 - (if P then 1 else 0)) : x = 1 ↔ ¬ P := by - by_cases hP : P - · simp only [h, if_pos hP, sub_self] - exact ⟨fun he => absurd he zero_ne_one', fun hp => absurd hP hp⟩ - · simp [h, hP] - -omit [Fact (2 ^ 17 < p)] in -/-- A binary `x` with `x = 1 ↔ P` is `if P then 1 else 0` (completeness direction of `eq_one_iff_of_ite`). -/ -private lemma bool_eq_ite_of_iff {x : ZMod p} (hx : x = 0 ∨ x = 1) {P : Prop} [Decidable P] - (h : x = 1 ↔ P) : x = if P then 1 else 0 := by - by_cases hP : P - · rw [if_pos hP]; exact h.mpr hP - · rw [if_neg hP]; rcases hx with h0 | h1 - · exact h0 - · exact absurd (h.mp h1) hP - -omit [Fact (2 ^ 17 < p)] in -/-- A binary `x` with `x = 1 ↔ y = 0` (for binary `y`) is `1 - y`. -/ -private lemma bool_eq_one_sub {x y : ZMod p} (hx : x = 0 ∨ x = 1) (hy : y = 0 ∨ y = 1) - (h : x = 1 ↔ y = 0) : x = 1 - y := by - rcases hy with hy | hy - · rw [hy, sub_zero]; exact h.mpr hy - · rw [hy]; rcases hx with hx | hx - · rw [hx]; simp - · have := h.mp hx; rw [hy] at this; exact absurd this one_ne_zero - omit [Fact (2 ^ 17 < p)] in /-- A binary `x` with `x = 1 ↔ ¬ P` (where `bit = if P then 1 else 0`) is `1 - bit`. -/ private lemma bool_eq_one_sub_ite {x bit : ZMod p} (hx : x = 0 ∨ x = 1) {P : Prop} [Decidable P] @@ -121,22 +83,6 @@ private lemma bool_eq_of_iff_ne {x y : ZMod p} (hx : x = 0 ∨ x = 1) (hy : y = · exact absurd (h.mp hx) (by rw [hy]; simp) · rw [hy]; exact h.mpr (by rw [hy]; exact one_ne_zero) -/-- `BitVec.slt` as a `toInt` comparison. -/ -private lemma slt_true_iff (x y : BitVec 64) : x.slt y = true ↔ x.toInt < y.toInt := by - simp [BitVec.slt] - -/-- `BitVec.slt` false as the negated `toInt` comparison. -/ -private lemma slt_false_iff (x y : BitVec 64) : x.slt y = false ↔ ¬ x.toInt < y.toInt := by - simp [BitVec.slt] - -/-- `BitVec.ult` as a `toNat` comparison. -/ -private lemma ult_true_iff (x y : BitVec 64) : x.ult y = true ↔ x.toNat < y.toNat := by - simp [BitVec.ult] - -/-- `BitVec.ult` false as the negated `toNat` comparison. -/ -private lemma ult_false_iff (x y : BitVec 64) : x.ult y = false ↔ ¬ x.toNat < y.toNat := by - simp [BitVec.ult] - /-! ## The six-way decision dispatch `branchDecision` is the in-circuit decision expression; the two lemmas below are the dual readings of @@ -184,8 +130,7 @@ lemma branch_conditions_of_decision_eq {rs1 rs2 : Word (ZMod p)} have e5 := zero_of _ hb5 (by omega) have hsig0 : b2 + b3 = 0 := by rw [e2, e3]; simp rw [hf, e1, e2, e3, e4, e5] at hbrdec - have heq := h_eqf hsig0 - refine Iff.trans ?_ heq + refine Iff.trans ?_ (h_eqf hsig0) constructor · intro hib; linear_combination hbrdec - hib · intro hF; linear_combination hbrdec - hF @@ -196,8 +141,7 @@ lemma branch_conditions_of_decision_eq {rs1 rs2 : Word (ZMod p)} have e5 := zero_of _ hb5 (by omega) have hsig0 : b2 + b3 = 0 := by rw [e2, e3]; simp rw [hf, e0, e2, e3, e4, e5] at hbrdec - have heq := h_eqf hsig0 - refine Iff.trans ?_ (not_congr heq) + refine Iff.trans ?_ (not_congr (h_eqf hsig0)) constructor · intro hib hF; exact zero_ne_one' (by linear_combination -hbrdec + hib - hF) · intro hF; rcases hbr with h0 | h1 @@ -308,9 +252,8 @@ lemma branch_decision_eq_of_conditions {rs1 rs2 : Word (ZMod p)} simp only [eq_false hsigne] at h_bit have hiff := (hd4 hf4).trans (ult_true_iff _ _) rw [Word.toBitVec64_toNat hrs1U, Word.toBitVec64_toNat hrs2U] at hiff - have hbreq := (bool_eq_ite_of_iff hbr hiff).trans h_bit.symm rw [e0, e1, e2, e3, hf4, e5] - linear_combination hbreq + linear_combination (bool_eq_ite_of_iff hbr hiff).trans h_bit.symm · -- BGE (`b3 = 1`): `br = 1 - bit`, `is_signed = 1`. have v1 : b3.val = 1 := by rw [hf3]; simp [ZMod.val_one] have e0 := zero_of _ (show b0.val = 0 by omega) diff --git a/SP1Clean/Proofs/Chips/BranchChip/Formal.lean b/SP1Clean/Proofs/Chips/BranchChip/Formal.lean index 675007a7..f1eedabe 100644 --- a/SP1Clean/Proofs/Chips/BranchChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/BranchChip/Formal.lean @@ -120,12 +120,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have hpcU : Word.isU64 (#v[Expression.eval env input_var_state_pc[0], Expression.eval env input_var_state_pc[1], Expression.eval env input_var_state_pc[2], 0] : Word (ZMod p)) := hpceq ▸ h_pcU - have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := by - have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - refine Word.isU64_of_cases ?_ ?_ ?_ ?_ <;> - simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, - List.getElem_cons_succ, show (4 : ZMod p) = ((4 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt h4lt, ZMod.val_zero] <;> norm_num + have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := Word.isU64_four have h_onehot := one_hot6 hbeq hbne hblt hbge hbltu hbgeu h_sumbin have h_sig_bin : env.get (i₀ + 2) + env.get (i₀ + 3) = 0 ∨ env.get (i₀ + 2) + env.get (i₀ + 3) = 1 := by rcases hblt with hl | hl <;> rcases hbge with hg | hg @@ -138,7 +133,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ rw [hl, hg, ZMod.val_one] at h_onehot omega - refine ⟨⟨?_, h_bin, ⟨hbeq, hbne, hblt, hbge, hbltu, hbgeu, hisbr⟩, ?_, ?_, ?_⟩, ?_⟩ + refine ⟨⟨?_, h_bin, ⟨hbeq, hbne, hblt, hbge, hbltu, hbgeu, hisbr⟩, ?_, ?_, ?_, ?_⟩, ?_⟩ · exact h_itype h_bin · intro hr1 hbr1 have hav := (h_add1 ⟨fun _ => ⟨hpcU, h_imm⟩, hisbr⟩ hbr1).2 @@ -205,6 +200,15 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe simp only [rs1Word, rs2Word] exact branch_conditions_of_decision_eq h_rs1U h_rs2U hbeq hbne hblt hbge hbltu hbgeu hisbr hone h_bit h_eqf (by simp only [branchDecision]; linear_combination h_isbr) + · -- 4-byte alignment of the branch target's low limb, from the in-circuit `÷4` byte-range pull + -- `h_byte1` (`(next_pc[0] · 4⁻¹).val < 2^14 ⇒ next_pc[0].val % 4 = 0`). Lifted to the whole word + -- at the Sail bridge. Mirrors `JalChip.soundness`. + intro hr1 + have c14 : ((14 : ℕ) : ZMod p) = (14 : ZMod p) := by norm_cast + have hguar := h_byte1 (by rw [hr1]) + simp only [byteChannel] at hguar + rw [← c14] at hguar + exact val_mod_four_of_mul_inv_four_lt ((byteRowSpec_range _ h14p).mp hguar) · refine ⟨Or.inr ⟨hrs1U, hrs2U, h_bin, h_sig_bin⟩, Or.inr h_bin, Or.inr ⟨fun _ => ⟨hpcU, h_imm⟩, hisbr⟩, Or.inr ⟨fun _ => ⟨hpcU, h4U⟩, ?_⟩, Or.inr h_bin⟩ -- AddOp2 gate `is_real - is_branching` is binary: on padding `is_branching = 0` from `h_pad`. @@ -218,14 +222,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe · right; rw [h1, hb0]; simp · left; rw [h1, hb1]; simp -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations folds to the `Vector.map` of the evaluator. -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 4000000 in /-- Completeness via honest `ProverHint` flag witnesses (`hintFlags`/`hintBranching`). The six-way decision constraint is discharged by mirroring soundness's case analysis in reverse via `Decision`. -/ @@ -267,12 +263,7 @@ theorem completeness : have ha1U : Word.isU64 (#v[Expression.eval env.toEnvironment input_var_state_pc[0], Expression.eval env.toEnvironment input_var_state_pc[1], Expression.eval env.toEnvironment input_var_state_pc[2], 0] : Word (ZMod p)) := ha1eq ▸ h_pcU - have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := by - have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - refine Word.isU64_of_cases ?_ ?_ ?_ ?_ <;> - simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, - List.getElem_cons_succ, show (4 : ZMod p) = ((4 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt h4lt, ZMod.val_zero] <;> norm_num + have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := Word.isU64_four have hcimm : Vector.map (Expression.eval env.toEnvironment) input_var_adapter_op_c_imm = input_adapter_op_c_imm := h_input.2.2.2.2.2.2.2 have hcimmeq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_c_imm[0], diff --git a/SP1Clean/Proofs/Chips/ByteChip.lean b/SP1Clean/Proofs/Chips/ByteChip.lean index 4a6af8db..bed94374 100644 --- a/SP1Clean/Proofs/Chips/ByteChip.lean +++ b/SP1Clean/Proofs/Chips/ByteChip.lean @@ -53,8 +53,7 @@ theorem byteRowSpec_of_provider {row : ByteRow (ZMod p)} {prov : LookupAccessLis (hk : keyOf b = byteRowKey row) : ByteRowSpec row := by obtain ⟨row', h_spec, h_key⟩ := h_prov b hb rw [hk] at h_key - rw [byteRow_eq_of_key h_key] - exact h_spec + rwa [byteRow_eq_of_key h_key] /-! ## The `Range` vs `Byte` receiver split (two preprocessed chips, one Byte bus) diff --git a/SP1Clean/Proofs/Chips/JalChip/Bridge.lean b/SP1Clean/Proofs/Chips/JalChip/Bridge.lean index f00beaa4..ab503575 100644 --- a/SP1Clean/Proofs/Chips/JalChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/JalChip/Bridge.lean @@ -68,6 +68,47 @@ theorem correct_jal_native hsp_pc, hjump, RETIRE_SUCCESS, h_jump, h_link] simp [hsp] +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence for `jal x0` (the `j` pseudo-instruction). When `rd = x0` the link write is a +no-op on both sides (`wX_bits 0` is dropped in RV64), so only the jump must match — `h_link` is not needed, +which is exactly why the chip leaves `op_a_operation.value` unconstrained on `op_a_0 = 1` rows. -/ +theorem correct_jal_native_x0 + (_op_b_imm next_pc_word op_a_word : Word (ZMod p)) + (imm : BitVec 21) (pc : BitVec 64) (s : SailState) + (hs : SailState.isInitialized s) + (h_pc : s.regs.get? Register.PC = some pc) + (h_jump : Word.toBitVec64 next_pc_word = pc + sign_extend (m := 64) imm) + (h_align : (Word.toBitVec64 next_pc_word).toNat % 4 = 0) : + (spec_jal imm (.Regidx 0#5)).run s + = (sp1_jal (.Regidx 0#5) pc next_pc_word op_a_word).run s := by + have hpc_get : s.regs.get Register.PC (hs _) = pc := by + rw [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc; exact h_pc + set sp : SailState := { s with regs := s.regs.insert Register.nextPC (pc + 4#64) } with hsp + have hsp_init : SailState.isInitialized sp := + SailState.isInitialized_insert s hs Register.nextPC (pc + 4#64) + have key : ∀ (reg : Register) (h : reg ∈ s.regs) (h' : reg ∈ sp.regs), + reg ≠ Register.nextPC → sp.regs.get reg h' = s.regs.get reg h := by + intro reg h h' hne + show (s.regs.insert Register.nextPC (pc + 4#64)).get reg _ = _ + rw [Std.ExtDHashMap.get_insert]; simp [Ne.symm hne] + have hsp_pc : sp.regs.get Register.PC (hsp_init _) = pc := by + rw [key Register.PC (hs _) (hsp_init _) (by decide)]; exact hpc_get + have hsp_npc : sp.regs.get Register.nextPC (hsp_init _) = pc + 4#64 := by + show (s.regs.insert Register.nextPC (pc + 4#64)).get Register.nextPC _ = _ + rw [Std.ExtDHashMap.get_insert]; simp + have htgt4 : Word.toBitVec64 next_pc_word % 4#64 = 0 := by + apply BitVec.eq_of_toNat_eq; rw [BitVec.toNat_umod]; simpa using h_align + have hjump : EStateM.run (jump_to (pc + sign_extend (m := 64) imm)) sp + = .ok RETIRE_SUCCESS { sp with regs := sp.regs.insert Register.nextPC (Word.toBitVec64 next_pc_word) } := by + rw [← h_jump]; exact jump_to_of_mod4_eq_zero _ sp hsp_init htgt4 + simp [spec_jal, sp1_jal, execute_JAL, hsp.symm, + Sail.run_readReg_bind_of_isInitialized _ _ hs, hpc_get, + Sail.run_readReg_bind_of_isInitialized _ _ hsp_init, + hsp_pc, hjump, RETIRE_SUCCESS, h_jump] + simp [hsp] + intro x' hx' + simp [Std.ExtDHashMap.get?_insert, beq_iff_eq, hx'] + /-- `Word.toBitVec64 #v[4,0,0,0] = 4#64`. -/ theorem toBitVec64_four : Word.toBitVec64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) = 4#64 := by have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega @@ -84,8 +125,10 @@ theorem toBitVec64_four : Word.toBitVec64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMo decide /-- End-to-end: from the JAL chip's verified `Spec` (on a real row) plus the PC read, the committed-pc ↔ -Sail-PC reassembly, the immediate decode, `op_a_0 = 0` (rd ≠ x0), and the jump-target alignment, the -RISC-V `JAL` execution agrees with the SP1 chip emulation. -/ +Sail-PC reassembly, and the immediate decode, the RISC-V `JAL` execution agrees with the SP1 chip +emulation. The jump-target 4-byte alignment is **derived** from the `Spec`'s divisibility conjunct (the +in-circuit `÷4` range check), not assumed. Covers **both** `rd ≠ x0` (`op_a_0 = 0`, link write proven) and +`jal x0` (`op_a_0 = 1 ∧ rd = x0`, link write a no-op on both sides) — closing the `j` pseudo-instruction. -/ theorem jal_chip_reaches_sail (inp : JalChip.Inputs (ZMod p)) (cols : Extracted.JalColumns (ZMod p)) (data : ProverData (ZMod p)) (rd : BitVec 5) (imm : BitVec 21) (pc : BitVec 64) (s : SailState) @@ -95,16 +138,24 @@ theorem jal_chip_reaches_sail (h_pc : s.regs.get? Register.PC = some pc) (h_pcw : Word.toBitVec64 (JalChip.pcWord cols) = pc) (h_dec : Word.toBitVec64 cols.adapter.op_b_imm = sign_extend (m := 64) imm) - (h_op_a_0 : cols.adapter.op_a_0 = 0) - (h_align : (Word.toBitVec64 cols.add_operation.value).toNat % 4 = 0) : + (h_op_a : cols.adapter.op_a_0 = 0 ∨ (cols.adapter.op_a_0 = 1 ∧ rd = 0#5)) : (spec_jal imm (.Regidx rd)).run s = (sp1_jal (.Regidx rd) pc cols.add_operation.value cols.op_a_operation.value).run s := by have h_jump : Word.toBitVec64 cols.add_operation.value = pc + sign_extend (m := 64) imm := by rw [h_chip.2.2.1 h_real, h_pcw, h_dec] - have h_link : Word.toBitVec64 cols.op_a_operation.value = pc + 4#64 := by - rw [h_chip.2.2.2 h_real h_op_a_0, h_pcw, toBitVec64_four] - exact correct_jal_native cols.adapter.op_b_imm cols.add_operation.value cols.op_a_operation.value - rd imm pc s hs h_pc h_jump h_link h_align + -- 4-byte alignment is now a verified `Spec` conjunct (the in-circuit `÷4` range check), not an assumed + -- precondition: lift the jump-target low-limb divisibility to the committed word. + have h_align : (Word.toBitVec64 cols.add_operation.value).toNat % 4 = 0 := by + rw [Word.toBitVec64_toNat_mod_four] + exact h_chip.2.2.2.2 h_real + rcases h_op_a with h_op_a_0 | ⟨_, hrd0⟩ + · have h_link : Word.toBitVec64 cols.op_a_operation.value = pc + 4#64 := by + rw [h_chip.2.2.2.1 h_real h_op_a_0, h_pcw, toBitVec64_four] + exact correct_jal_native cols.adapter.op_b_imm cols.add_operation.value cols.op_a_operation.value + rd imm pc s hs h_pc h_jump h_link h_align + · subst hrd0 + exact correct_jal_native_x0 cols.adapter.op_b_imm cols.add_operation.value cols.op_a_operation.value + imm pc s hs h_pc h_jump h_align end SP1Clean.JalSail @@ -116,8 +167,9 @@ open Sail LeanRV64D LeanRV64D.Functions variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] /-- JAL's `ChipKind` registration. `view` threads `next_pc = add_operation.value` (the data-dependent -jump target), J-type adapter, opcode 46. `sailEquiv` quantifies the PC/decode/alignment preconditions -internally (JAL reads no source registers); `reaches_sail` is `jal_chip_reaches_sail`. -/ +jump target), J-type adapter, opcode 46. `sailEquiv` quantifies the PC/decode preconditions internally +(JAL reads no source registers); the jump-target alignment is no longer a precondition — `reaches_sail` +derives it from the chip `Spec`. `reaches_sail` is `jal_chip_reaches_sail`. -/ def kind : Soundness.ChipKind p where name := "Jal" Inputs := JalChip.Inputs @@ -131,11 +183,10 @@ def kind : Soundness.ChipKind p where s.regs.get? Register.PC = some pc → Word.toBitVec64 (JalChip.pcWord cols) = pc → Word.toBitVec64 cols.adapter.op_b_imm = sign_extend (m := 64) imm → - cols.adapter.op_a_0 = 0 → - (Word.toBitVec64 cols.add_operation.value).toNat % 4 = 0 → + (cols.adapter.op_a_0 = 0 ∨ (cols.adapter.op_a_0 = 1 ∧ rd = 0#5)) → (spec_jal imm (.Regidx rd)).run s = (sp1_jal (.Regidx rd) pc cols.add_operation.value cols.op_a_operation.value).run s - reaches_sail := fun inp cols data s h_real h_chip rd imm pc hs h_pc h_pcw h_dec h_op_a_0 h_align => - jal_chip_reaches_sail inp cols data rd imm pc s hs h_real h_chip h_pc h_pcw h_dec h_op_a_0 h_align + reaches_sail := fun inp cols data s h_real h_chip rd imm pc hs h_pc h_pcw h_dec h_op_a => + jal_chip_reaches_sail inp cols data rd imm pc s hs h_real h_chip h_pc h_pcw h_dec h_op_a end SP1Clean.JalChip diff --git a/SP1Clean/Proofs/Chips/JalChip/Formal.lean b/SP1Clean/Proofs/Chips/JalChip/Formal.lean index 92eead0f..d7903ab3 100644 --- a/SP1Clean/Proofs/Chips/JalChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/JalChip/Formal.lean @@ -57,32 +57,23 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have h_op_a_0 : input_adapter_op_a_0 = 0 ∨ input_adapter_op_a_0 = 1 := h_jt.2.1 -- eval-of-pc rewrites: circuit's `a` operand `#v[eval pc[i], 0]` equals the concrete `pcWord`. have hpc : Vector.map (Expression.eval env) input_var_state_pc = input_state_pc := h_input.2.1.2.2.2 - have ep0 : Expression.eval env input_var_state_pc[0] = input_state_pc[0] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep1 : Expression.eval env input_var_state_pc[1] = input_state_pc[1] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep2 : Expression.eval env input_var_state_pc[2] = input_state_pc[2] := by - rw [← hpc]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env input_var_state_pc[i] = input_state_pc[i] := + fun i hi => by rw [← hpc]; simp only [Vector.getElem_map] have ha1eq : (#v[Expression.eval env input_var_state_pc[0], Expression.eval env input_var_state_pc[1], Expression.eval env input_var_state_pc[2], 0] : Word (ZMod p)) - = #v[input_state_pc[0], input_state_pc[1], input_state_pc[2], 0] := by rw [ep0, ep1, ep2] + = #v[input_state_pc[0], input_state_pc[1], input_state_pc[2], 0] := by + rw [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)] have ha1U : Word.isU64 (#v[Expression.eval env input_var_state_pc[0], Expression.eval env input_var_state_pc[1], Expression.eval env input_var_state_pc[2], 0] : Word (ZMod p)) := ha1eq ▸ h_pcU - -- `#v[4,0,0,0]` is a 64-bit word. - have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := by - have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - refine Word.isU64_of_cases ?_ ?_ ?_ ?_ <;> - simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, - List.getElem_cons_succ, show (4 : ZMod p) = ((4 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt h4lt, ZMod.val_zero] <;> norm_num + have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := Word.isU64_four -- the link gate `is_real - op_a_0` is binary on every row (real: `op_a_0` binary; padding: `op_a_0 = 0`). -- `is_real - op_a_0` is binary: on real rows from `op_a_0 ∈ {0,1}`, on padding from `h_pad`. have h_gate2 : input_is_real + -input_adapter_op_a_0 = 0 ∨ input_is_real + -input_adapter_op_a_0 = 1 := by rcases h_bin with h | h · rw [h, h_pad h]; simp · rcases h_op_a_0 with h0 | h0 <;> rw [h, h0] <;> simp - refine ⟨⟨h_jt, h_bin, ?_, ?_⟩, ?_, ?_, ?_, ?_⟩ + refine ⟨⟨h_jt, h_bin, ?_, ?_, ?_⟩, ?_, ?_, ?_, ?_⟩ · intro hr1 have := (h_add1 ⟨fun _ => ⟨ha1U, h_imm⟩, h_bin⟩ hr1).2 rw [ha1eq] at this @@ -92,6 +83,14 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have := (h_add2 ⟨fun _ => ⟨ha1U, h4U⟩, h_gate2⟩ hg1).2 rw [ha1eq] at this simpa only [pcWord] using this + · -- 4-byte alignment of the jump target, from the in-circuit `÷4` byte-range pull `h_align` + -- (`(value[0] · 4⁻¹).val < 2^14 ⇒ value[0].val % 4 = 0`). The Sail bridge lifts it to the whole word. + intro hr1 + have c14 : ((14 : ℕ) : ZMod p) = (14 : ZMod p) := by norm_cast + have hguar := h_align (by rw [hr1]) + simp only [byteChannel] at hguar + rw [← c14] at hguar + exact val_mod_four_of_mul_inv_four_lt ((byteRowSpec_range _ h14p).mp hguar) · exact Or.inr h_bin · exact Or.inr ⟨fun _ => ⟨ha1U, h_imm⟩, h_bin⟩ · exact Or.inr ⟨fun _ => ⟨ha1U, h4U⟩, h_gate2⟩ @@ -108,25 +107,18 @@ theorem completeness : h_input.2.1.2.2.2 have hob : Vector.map (Expression.eval env.toEnvironment) input_var_adapter_op_b_imm = input_adapter_op_b_imm := h_input.2.2.2.2.2.1 - have ep0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hpc]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), + Expression.eval env.toEnvironment input_var_state_pc[i] = input_state_pc[i] := + fun i hi => by rw [← hpc]; simp only [Vector.getElem_map] have ha1eq : (#v[Expression.eval env.toEnvironment input_var_state_pc[0], Expression.eval env.toEnvironment input_var_state_pc[1], Expression.eval env.toEnvironment input_var_state_pc[2], 0] : Word (ZMod p)) - = #v[input_state_pc[0], input_state_pc[1], input_state_pc[2], 0] := by rw [ep0, ep1, ep2] + = #v[input_state_pc[0], input_state_pc[1], input_state_pc[2], 0] := by + rw [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)] have ha1U : Word.isU64 (#v[Expression.eval env.toEnvironment input_var_state_pc[0], Expression.eval env.toEnvironment input_var_state_pc[1], Expression.eval env.toEnvironment input_var_state_pc[2], 0] : Word (ZMod p)) := ha1eq ▸ h_pcU - have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := by - have h4lt : (4 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - refine Word.isU64_of_cases ?_ ?_ ?_ ?_ <;> - simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, - List.getElem_cons_succ, show (4 : ZMod p) = ((4 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt h4lt, ZMod.val_zero] <;> norm_num + have h4U : Word.isU64 (#v[(4 : ZMod p), 0, 0, 0] : Word (ZMod p)) := Word.isU64_four have hb1eq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_b_imm[0], Expression.eval env.toEnvironment input_var_adapter_op_b_imm[1], Expression.eval env.toEnvironment input_var_adapter_op_b_imm[2], diff --git a/SP1Clean/Proofs/Chips/JalrChip/Bridge.lean b/SP1Clean/Proofs/Chips/JalrChip/Bridge.lean index 77595c1a..db63c677 100644 --- a/SP1Clean/Proofs/Chips/JalrChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/JalrChip/Bridge.lean @@ -79,9 +79,10 @@ theorem correct_jalr_native simp [hsp] /-- End-to-end: from the JALR chip's verified `Spec` (on a real row) plus the PC read, the rs1 register -read, the committed rs1 ↔ Sail reassembly, the immediate decode, `op_a_0 = 0` (rd ≠ x0), the committed-pc -reassembly, the LSB-clearing relation, and the cleared-target alignment, the RISC-V `JALR` execution agrees -with the SP1 chip emulation. -/ +read, the committed rs1 ↔ Sail reassembly, the immediate decode, `op_a_0 = 0` (rd ≠ x0), and the +committed-pc reassembly, the RISC-V `JALR` execution agrees with the SP1 chip emulation. Both the +LSB-clearing relation and the cleared-target 4-byte alignment are **derived** from the chip `Spec` (the +in-circuit binary `lsb` gate + `÷4` range check), not assumed. -/ theorem jalr_chip_reaches_sail (inp : JalrChip.Inputs (ZMod p)) (cols : Extracted.JalrColumns (ZMod p)) (data : ProverData (ZMod p)) (rd rs1 : BitVec 5) (imm : BitVec 12) (pc rs1_val : BitVec 64) (s : SailState) @@ -93,18 +94,24 @@ theorem jalr_chip_reaches_sail (h_rs1v : Word.toBitVec64 (JalrChip.rs1Word cols) = rs1_val) (h_dec : Word.toBitVec64 cols.adapter.op_c_imm = sign_extend (m := 64) imm) (h_op_a_0 : cols.adapter.op_a_0 = 0) - (h_pcw : Word.toBitVec64 (JalrChip.pcWord cols) = pc) - (h_lsbclear : Word.toBitVec64 (JalrChip.nextPcWord cols) - = BitVec.update (Word.toBitVec64 cols.add_operation.value) 0 0#1) - (h_align : (Word.toBitVec64 (JalrChip.nextPcWord cols)).toNat % 4 = 0) : + (h_pcw : Word.toBitVec64 (JalrChip.pcWord cols) = pc) : (spec_jalr imm (.Regidx rs1) (.Regidx rd)).run s = (sp1_jalr (.Regidx rd) (JalrChip.nextPcWord cols) cols.op_a_operation.value).run s := by have h_jump : Word.toBitVec64 cols.add_operation.value = rs1_val + sign_extend (m := 64) imm := by rw [h_chip.2.2.2.1 h_real, h_rs1v, h_dec] have h_link : Word.toBitVec64 cols.op_a_operation.value = pc + 4#64 := by - rw [h_chip.2.2.2.2 h_real h_op_a_0, h_pcw, JalSail.toBitVec64_four] + rw [h_chip.2.2.2.2.1 h_real h_op_a_0, h_pcw, JalSail.toBitVec64_four] + -- The LSB-clearing relation is now a verified `Spec` conjunct (the in-circuit binary `lsb` gate + the + -- `÷4` byte-range), not an assumed precondition; unfold the Sail `BitVec.update` to its `~~~1#64 &&&` form. have h_lsbclear' : Word.toBitVec64 (JalrChip.nextPcWord cols) - = BitVec.update (rs1_val + sign_extend (m := 64) imm) 0 0#1 := by rw [h_lsbclear, h_jump] + = BitVec.update (rs1_val + sign_extend (m := 64) imm) 0 0#1 := by + rw [h_chip.2.2.2.2.2.2 h_real, h_jump] + simp [Sail.BitVec.update, Sail.BitVec.updateSubrange'] + -- 4-byte alignment is now a verified `Spec` conjunct (the in-circuit `÷4` range check), not an + -- assumed precondition: lift the cleared low-limb divisibility to the committed `next_pc` word. + have h_align : (Word.toBitVec64 (JalrChip.nextPcWord cols)).toNat % 4 = 0 := by + rw [Word.toBitVec64_toNat_mod_four] + exact h_chip.2.2.2.2.2.1 h_real exact correct_jalr_native cols.adapter.op_c_imm (JalrChip.nextPcWord cols) cols.op_a_operation.value rd rs1 imm pc rs1_val s hs hconfig h_pc h_rs1 h_lsbclear' h_link h_align @@ -119,8 +126,9 @@ open Sail LeanRV64D LeanRV64D.Functions variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] /-- JALR's `ChipKind` registration. `view` threads the LSB-cleared `next_pc`, I-type adapter, opcode 47. -`sailEquiv` quantifies PC/rs1 read, decode, LSB-clearing, and alignment preconditions internally; -`reaches_sail` is `jalr_chip_reaches_sail`. -/ +`sailEquiv` quantifies the PC/rs1 read and decode preconditions internally; neither the LSB-clearing +relation nor the cleared-target 4-byte alignment is a precondition — `reaches_sail` derives both from the +chip `Spec`. `reaches_sail` is `jalr_chip_reaches_sail`. -/ def kind : Soundness.ChipKind p where name := "Jalr" Inputs := JalrChip.Inputs @@ -137,14 +145,11 @@ def kind : Soundness.ChipKind p where Word.toBitVec64 cols.adapter.op_c_imm = sign_extend (m := 64) imm → cols.adapter.op_a_0 = 0 → Word.toBitVec64 (JalrChip.pcWord cols) = pc → - Word.toBitVec64 (JalrChip.nextPcWord cols) - = BitVec.update (Word.toBitVec64 cols.add_operation.value) 0 0#1 → - (Word.toBitVec64 (JalrChip.nextPcWord cols)).toNat % 4 = 0 → (spec_jalr imm (.Regidx rs1) (.Regidx rd)).run s = (sp1_jalr (.Regidx rd) (JalrChip.nextPcWord cols) cols.op_a_operation.value).run s reaches_sail := fun inp cols data s h_real h_chip rd rs1 imm pc rs1_val hs hconfig h_pc h_rs1 h_rs1v h_dec - h_op_a_0 h_pcw h_lsbclear h_align => + h_op_a_0 h_pcw => jalr_chip_reaches_sail inp cols data rd rs1 imm pc rs1_val s hs hconfig h_real h_chip h_pc h_rs1 h_rs1v h_dec - h_op_a_0 h_pcw h_lsbclear h_align + h_op_a_0 h_pcw end SP1Clean.JalrChip diff --git a/SP1Clean/Proofs/Chips/JalrChip/Formal.lean b/SP1Clean/Proofs/Chips/JalrChip/Formal.lean index 6148648d..55b86f13 100644 --- a/SP1Clean/Proofs/Chips/JalrChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/JalrChip/Formal.lean @@ -99,7 +99,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rcases h_bin with h | h · rw [h, h_pad h]; simp · rcases h_op_a_0 with h0 | h0 <;> rw [h, h0] <;> simp - refine ⟨⟨h_it, h_bin, h_lsb, ?_, ?_⟩, Or.inr h_bin, Or.inr ⟨fun _ => ⟨hrs1U, h_imm⟩, h_bin⟩, + refine ⟨⟨h_it, h_bin, h_lsb, ?_, ?_, ?_, ?_⟩, Or.inr h_bin, Or.inr ⟨fun _ => ⟨hrs1U, h_imm⟩, h_bin⟩, Or.inr ⟨fun _ => ⟨hpcU, h4U⟩, h_gate2⟩, Or.inr h_bin⟩ · intro hr1 have := (h_add1 ⟨fun _ => ⟨hrs1U, h_imm⟩, h_bin⟩ hr1).2 @@ -110,6 +110,56 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have := (h_add2 ⟨fun _ => ⟨hpcU, h4U⟩, h_gate2⟩ hg1).2 rw [hpceq] at this simpa only [pcWord] using this + · -- 4-byte alignment of the cleared low limb, from the (otherwise-unused) byte-range pull `h_align`: + -- `((add_value[0] - lsb) · 4⁻¹).val < 2^14 ⇒ (add_value[0] - lsb).val % 4 = 0`. + intro hr1 + have c14 : ((14 : ℕ) : ZMod p) = (14 : ZMod p) := by norm_cast + have hguar := h_align (by rw [hr1]) + simp only [byteChannel] at hguar + rw [← c14] at hguar + rw [sub_eq_add_neg] + exact val_mod_four_of_mul_inv_four_lt ((byteRowSpec_range _ h14p).mp hguar) + · -- LSB-clearing: the committed `nextPcWord` is `add_value` with bit 0 cleared (`~~~1#64 &&&`). + -- The binary `lsb` gate + the `÷4` byte-range pin `lsb = bit0(add_value[0])`; with `add_value[3] = 0` + -- this makes `toNat add_value = toNat nextPcWord + lsb`, so `ofNat64_clear_lsb_and` applies. + intro hr1 + have hguar := h_align (by rw [hr1]) + simp only [byteChannel] at hguar + have c14 : ((14 : ℕ) : ZMod p) = (14 : ZMod p) := by norm_cast + rw [← c14] at hguar + have hlt := (byteRowSpec_range _ h14p).mp hguar + have hdlt : (env.get i₀ + -env.get (i₀ + 4 + 4)).val < 2 ^ 16 := + val_lt_65536_of_mul_inv_four_lt hlt + have hdmod : (env.get i₀ + -env.get (i₀ + 4 + 4)).val % 4 = 0 := + val_mod_four_of_mul_inv_four_lt hlt + have hlsble : (env.get (i₀ + 4 + 4)).val ≤ 1 := by + haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ + rcases h_lsb with h | h <;> rw [h] <;> simp [ZMod.val_one] + have hp : 2 ^ 17 < p := Fact.out + have e16 : (2 : ℕ) ^ 16 = 65536 := by norm_num + have e17 : (2 : ℕ) ^ 17 = 131072 := by norm_num + have hav0 : (env.get i₀).val + = (env.get i₀ + -env.get (i₀ + 4 + 4)).val + (env.get (i₀ + 4 + 4)).val := by + conv_lhs => rw [show env.get i₀ + = (env.get i₀ + -env.get (i₀ + 4 + 4)) + env.get (i₀ + 4 + 4) from by ring] + rw [ZMod.val_add_of_lt (by omega)] + have hav3 : (env.get (i₀ + 3)).val = 0 := by rw [h_av3]; simp + have e32 : (2 : ℕ) ^ 32 = 4294967296 := by norm_num + have e48 : (2 : ℕ) ^ 48 = 281474976710656 := by norm_num + simp only [JalrChip.nextPcWord, Word.toBitVec64, Word.toNat_def, Vector.getElem_map, + Vector.getElem_mapRange, Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, + List.getElem_cons_succ, ZMod.val_zero, circuit_norm] + -- goal: `ofNat 64 (toNat nextPcWord) = ~~~1#64 &&& ofNat 64 (toNat add_value)`; the two sums differ + -- only by `lsb` in the low limb (`add_value[3] = 0`), so `ofNat64_clear_lsb_and` closes it. + have hMeven : ((env.get i₀ + -env.get (i₀ + 4 + 4)).val + (env.get (i₀ + 1)).val * 2 ^ 16 + + (env.get (i₀ + 2)).val * 2 ^ 32 + 0 * 2 ^ 48) % 2 = 0 := by omega + have hrel : (env.get i₀).val + (env.get (i₀ + 1)).val * 2 ^ 16 + (env.get (i₀ + 2)).val * 2 ^ 32 + + (env.get (i₀ + 3)).val * 2 ^ 48 + = ((env.get i₀ + -env.get (i₀ + 4 + 4)).val + (env.get (i₀ + 1)).val * 2 ^ 16 + + (env.get (i₀ + 2)).val * 2 ^ 32 + 0 * 2 ^ 48) + (env.get (i₀ + 4 + 4)).val := by + rw [hav0, hav3]; ring + rw [sub_eq_add_neg, hrel] + exact ofNat64_clear_lsb_and hlsble hMeven set_option maxHeartbeats 2000000 in theorem completeness : diff --git a/SP1Clean/Proofs/Chips/LoadByteChip/Bridge.lean b/SP1Clean/Proofs/Chips/LoadByteChip/Bridge.lean index d3a6906a..b46ed2d8 100644 --- a/SP1Clean/Proofs/Chips/LoadByteChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LoadByteChip/Bridge.lean @@ -137,7 +137,7 @@ theorem correct_load_byte_native h_htif_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hhtif h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega diff --git a/SP1Clean/Proofs/Chips/LoadByteChip/Formal.lean b/SP1Clean/Proofs/Chips/LoadByteChip/Formal.lean index 3f30b3ae..fad48ca9 100644 --- a/SP1Clean/Proofs/Chips/LoadByteChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LoadByteChip/Formal.lean @@ -38,22 +38,13 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe simp only [circuit_norm, byteChannel] at hu8 hmsb_rcv -- eval→value bridges (extracted directly from `h_input`; `tauto` over this context is too slow). obtain ⟨_, _, _, _, ⟨hmap_pv, _, _, _, _, _⟩, hmap_ob, _, _, _, _⟩ := h_input - have eob0 : Expression.eval env input_var_offset_bit[0] = input_offset_bit[0] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env input_var_offset_bit[1] = input_offset_bit[1] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env input_var_offset_bit[2] = input_offset_bit[2] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - simp only [eob1, eob2, epv0, epv1, epv2, epv3, ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 - simp only [eob0, ← sub_eq_add_neg] at hmux + have eob : ∀ i (hi : i < 3), Expression.eval env input_var_offset_bit[i] = input_offset_bit[i] := + fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + simp only [eob 1 (by omega), eob 2 (by omega), epv 0 (by omega), epv 1 (by omega), + epv 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 + simp only [eob 0 (by omega), ← sub_eq_add_neg] at hmux -- the byte-mux equation in value form. have hmux_eq : input_selected_byte = input_offset_bit[0] * ((input_selected_limb - input_selected_limb_low_byte) * (256 : ZMod p)⁻¹) @@ -86,23 +77,23 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe exact ⟨this.2.1, this.2.2⟩ -- the `AddressOperation` Assumptions (eval form). have hob0' : Expression.eval env input_var_offset_bit[0] = 0 - ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env input_var_offset_bit[1] = 0 - ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env input_var_offset_bit[2] = 0 - ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env input_var_offset_bit[0]).val + 2 * (Expression.eval env input_var_offset_bit[1]).val + 4 * (Expression.eval env input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env input_var_offset_bit[0], Expression.eval env input_var_offset_bit[1], Expression.eval env input_var_offset_bit[2]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, hob0', hob1', hob2', h_ge, h_off'⟩ have h_addr_spec := h_addr h_addr_as - simp only [eob0, eob1, eob2] at h_addr_spec + simp only [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)] at h_addr_spec have h_it := h_itype h_bin refine ⟨⟨h_addr_spec, h_mem h_bin, h_it, fun h1 => ⟨(h_u8 h1).1, (h_u8 h1).2, h_byte_lt h1⟩, h_msb_fact, @@ -161,37 +152,23 @@ theorem completeness : haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ have h_msb_lt : input_msb.val < 256 := by rcases h_msb_bin with h | h <;> rw [h] <;> simp [ZMod.val_one] - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env.toEnvironment input_var_offset_bit[0] - = input_offset_bit[0] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env.toEnvironment input_var_offset_bit[1] - = input_offset_bit[1] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env.toEnvironment input_var_offset_bit[2] - = input_offset_bit[2] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_offset_bit[i] + = input_offset_bit[i] := fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] have hob0' : Expression.eval env.toEnvironment input_var_offset_bit[0] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env.toEnvironment input_var_offset_bit[1] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env.toEnvironment input_var_offset_bit[2] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env.toEnvironment input_var_offset_bit[0]).val + 2 * (Expression.eval env.toEnvironment input_var_offset_bit[1]).val + 4 * (Expression.eval env.toEnvironment input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env.toEnvironment input_var_offset_bit[0], Expression.eval env.toEnvironment input_var_offset_bit[1], @@ -199,7 +176,7 @@ theorem completeness : ⟨ha, hb, hfit, hob0', hob1', hob2', h_ge, h_off'⟩ refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ?_, ?_, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · -- U8Range-pair receive obligation (real row); value is raw (`toRaw` (gated post-#398)). @@ -212,11 +189,11 @@ theorem completeness : exact (byteRowSpec_msb _ _).mpr ⟨⟨h_msb_lt, hbyte_pa⟩, h_msb_bin, h_msb_iff⟩ · exact hbin · exact h_it - · simp only [eob1, eob2, epv0, ← sub_eq_add_neg]; exact hsel0 - · simp only [eob1, eob2, epv1, ← sub_eq_add_neg]; exact hsel1 - · simp only [eob1, eob2, epv2, ← sub_eq_add_neg]; exact hsel2 - · simp only [eob1, eob2, epv3, ← sub_eq_add_neg]; exact hsel3 - · simp only [eob0, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hmux_pa + · simp only [eob 1 (by omega), eob 2 (by omega), epv 0 (by omega), ← sub_eq_add_neg]; exact hsel0 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 1 (by omega), ← sub_eq_add_neg]; exact hsel1 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 2 (by omega), ← sub_eq_add_neg]; exact hsel2 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg]; exact hsel3 + · simp only [eob 0 (by omega), ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hmux_pa · exact h_op_a_0 · exact h_msbgate · rcases h_lb_bin with h | h <;> rw [h] <;> simp diff --git a/SP1Clean/Proofs/Chips/LoadDoubleChip/Bridge.lean b/SP1Clean/Proofs/Chips/LoadDoubleChip/Bridge.lean index 766d7428..60997ce9 100644 --- a/SP1Clean/Proofs/Chips/LoadDoubleChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LoadDoubleChip/Bridge.lean @@ -163,34 +163,34 @@ theorem correct_load_double_native h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } -- Register read of `rs1` survives the `nextPC` write. have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] -- The eight memory bytes on `sp` (same memory as `s`), in the `reg_val.toNat + offset.toNat` form. have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? - = some (BitVec.ofNat 8 loaded[0].val) := by rw [hmem_eq, ← hadd]; exact hmem₀ + = some (BitVec.ofNat 8 loaded[0].val) := by rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? - = some (BitVec.ofNat 8 (loaded[0].val >>> 8)) := by rw [hmem_eq, ← hadd]; exact hmem₁ + = some (BitVec.ofNat 8 (loaded[0].val >>> 8)) := by rwa [hmem_eq, ← hadd] have hm₂ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 2]? - = some (BitVec.ofNat 8 loaded[1].val) := by rw [hmem_eq, ← hadd]; exact hmem₂ + = some (BitVec.ofNat 8 loaded[1].val) := by rwa [hmem_eq, ← hadd] have hm₃ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 3]? - = some (BitVec.ofNat 8 (loaded[1].val >>> 8)) := by rw [hmem_eq, ← hadd]; exact hmem₃ + = some (BitVec.ofNat 8 (loaded[1].val >>> 8)) := by rwa [hmem_eq, ← hadd] have hm₄ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 4]? - = some (BitVec.ofNat 8 loaded[2].val) := by rw [hmem_eq, ← hadd]; exact hmem₄ + = some (BitVec.ofNat 8 loaded[2].val) := by rwa [hmem_eq, ← hadd] have hm₅ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 5]? - = some (BitVec.ofNat 8 (loaded[2].val >>> 8)) := by rw [hmem_eq, ← hadd]; exact hmem₅ + = some (BitVec.ofNat 8 (loaded[2].val >>> 8)) := by rwa [hmem_eq, ← hadd] have hm₆ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 6]? - = some (BitVec.ofNat 8 loaded[3].val) := by rw [hmem_eq, ← hadd]; exact hmem₆ + = some (BitVec.ofNat 8 loaded[3].val) := by rwa [hmem_eq, ← hadd] have hm₇ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 7]? - = some (BitVec.ofNat 8 (loaded[3].val >>> 8)) := by rw [hmem_eq, ← hadd]; exact hmem₇ + = some (BitVec.ofNat 8 (loaded[3].val >>> 8)) := by rwa [hmem_eq, ← hadd] -- The alignment fact in Sail form, and the range-subset PMA fact. have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 8 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 8) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 8 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 8 (by omega) h_lo (by rwa [hadd]) -- The read result on `sp`. have hread := run_vmem_read_of_width_8' rs1_idx reg_val (BitVec.signExtend 64 imm) (BitVec.ofNat 8 loaded[0].val) (BitVec.ofNat 8 (loaded[0].val >>> 8)) @@ -281,11 +281,11 @@ theorem ld_chip_reaches_sail cols.memory_access.prev_value pc s hs hconfig h_pc h_rs1 ?_ ?_ ?_ ?_ hloaded hmem₀ hmem₁ hmem₂ hmem₃ hmem₄ hmem₅ hmem₆ hmem₇ · -- alignment: `sum % 8 = 0` from the chip's `sum % 2^48 % 8 = 0` - rw [hreg, hoff, ← Nat.mod_mod_of_dvd _ (by norm_num : (8 : ℕ) ∣ 2 ^ 48)]; exact h_align48 + rwa [hreg, hoff, ← Nat.mod_mod_of_dvd _ (by norm_num : (8 : ℕ) ∣ 2 ^ 48)] · -- fits in 64 bits (from the read-fits bound) rw [hreg, hoff]; omega · -- read fits in the 48-bit physical window - rw [hreg, hoff]; exact h_hi + rwa [hreg, hoff] · -- non-reserved: `2^16 ≤ (reg_val + offset).toNat`, no wrap since `sum < 2^48` rw [BitVec.toNat_add, hreg, hoff, Nat.mod_eq_of_lt (by omega)]; omega diff --git a/SP1Clean/Proofs/Chips/LoadDoubleChip/Formal.lean b/SP1Clean/Proofs/Chips/LoadDoubleChip/Formal.lean index bb613b7f..f73b93ee 100644 --- a/SP1Clean/Proofs/Chips/LoadDoubleChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LoadDoubleChip/Formal.lean @@ -37,24 +37,18 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe -- nested `memory_access.prev_value` (which `ITypeReader.Spec`'s zeroing gates reference). have hmap : Vector.map (Expression.eval env) input_var_memory_access_prev_value = input_memory_access_prev_value := h_input.2.2.2.1 - have ev0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap]; simp only [Vector.getElem_map] - have ev2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap]; simp only [Vector.getElem_map] - have ev3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap]; simp only [Vector.getElem_map] + have ev : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap]; simp only [Vector.getElem_map] have h_it := h_itype h_bin - rw [ev0, ev1, ev2, ev3] at h_it + rw [ev 0 (by omega), ev 1 (by omega), ev 2 (by omega), ev 3 (by omega)] at h_it -- the `AddressOperation` Assumptions: operand `isU64`s + fits, the offset bits boolean (literal `0`), -- and the address-validity (non-reserved + 8-aligned, so the inverse gate / offset range check hold). have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, 0, 0⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, Or.inl rfl, Or.inl rfl, h_ge, by simp only [ZMod.val_zero]; omega⟩ - refine ⟨⟨h_addr h_addr_as, h_mem h_bin, h_it, h_op_a_0, h_bin⟩, ?_⟩ -- the per-subcircuit channel-requirement tail (`channels = [] ∨ .Assumptions`). - exact ⟨Or.inr h_bin, Or.inr h_addr_as, Or.inr h_bin, Or.inr h_bin⟩ + exact ⟨⟨h_addr h_addr_as, h_mem h_bin, h_it, h_op_a_0, h_bin⟩, + Or.inr h_bin, Or.inr h_addr_as, Or.inr h_bin, Or.inr h_bin⟩ /-- Prover-side row well-formedness: operand `isU64`s + address-fits bound plus the `is_real` binary selector. -/ def ProverAssumptions (input : Inputs (ZMod p)) (_ : ProverData (ZMod p)) (_ : ProverHint (ZMod p)) : Prop := @@ -86,30 +80,16 @@ theorem completeness : = input_memory_access_prev_value := h_input.2.2.2.1 have hmap_pc : Vector.map (Expression.eval env.toEnvironment) input_var_state_pc = input_state_pc := h_input.2.1.2.2.2 - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] - = input_state_pc[0] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] - = input_state_pc[1] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] - = input_state_pc[2] := by rw [← hmap_pc]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, 0, 0⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, Or.inl rfl, Or.inl rfl, h_ge, by simp only [ZMod.val_zero]; omega⟩ - refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, h_op_a_0, ?_⟩ - · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu - · exact hbin - · exact h_mem - · exact hbin - · simp only [epv0, epv1, epv2, epv3]; exact h_it + refine ⟨⟨hbin, ?_⟩, h_addr_as, ⟨hbin, h_mem⟩, ⟨hbin, ?_⟩, h_op_a_0, ?_⟩ + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu + · simp only [epv 0 (by omega), epv 1 (by omega), epv 2 (by omega), epv 3 (by omega)]; exact h_it · rcases hbin with h | h <;> rw [h] <;> simp /-- The `LoadDouble` chip row as a `GeneralFormalCircuit`; output is the extracted `LoadDoubleColumns`. -/ diff --git a/SP1Clean/Proofs/Chips/LoadHalfChip/Bridge.lean b/SP1Clean/Proofs/Chips/LoadHalfChip/Bridge.lean index 25a9b2b4..7ddace85 100644 --- a/SP1Clean/Proofs/Chips/LoadHalfChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LoadHalfChip/Bridge.lean @@ -31,8 +31,7 @@ private lemma toNat_concat_half_bytes [NeZero p] (h : ZMod p) (hh : h.val < 6553 have h_decomp : h.val % 256 + (h.val >>> 8) * 256 = h.val := by rw [Nat.shiftRight_eq_div_pow]; omega simp only [BitVec.toNat_append, BitVec.toNat_ofNat, show (2 ^ 8 : ℕ) = 256 from rfl] - rw [Nat.mod_eq_of_lt h_hi] - rw [show h.val >>> 8 <<< 8 ||| h.val % 256 = h.val by + rw [Nat.mod_eq_of_lt h_hi, show h.val >>> 8 <<< 8 ||| h.val % 256 = h.val by rw [← Nat.shiftLeft_add_eq_or_of_lt (i := 8) (by omega), Nat.shiftLeft_eq]; omega] omit [Fact p.Prime] [Fact (2 ^ 17 < p)] in @@ -88,7 +87,7 @@ private lemma signExtend64_ofNat16_concat_of_ge_32768 [NeZero p] simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ] have hp : 131072 < p := by have := Fact.out (p := 2 ^ 17 < p); omega have h65535 : (65535 : ZMod p).val = 65535 := by - rw [show (65535 : ZMod p) = ((65535 : ℕ) : ZMod p) from by norm_cast, ZMod.val_natCast, + rw [show (65535 : ZMod p) = ((65535 : ℕ) : ZMod p) by norm_cast, ZMod.val_natCast, Nat.mod_eq_of_lt (by omega)] rw [h65535] omega @@ -128,7 +127,7 @@ theorem correct_load_half_native (spec_lh imm rs1_idx rd_idx is_unsigned).run s = (sp1_lh rd_idx pc val64).run s := by have hse : (sign_extend imm : BitVec 64) = BitVec.signExtend 64 imm := by simp [sign_extend] have hpc_get : s.regs.get Register.PC (hs _) = pc := by - rw [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc; exact h_pc + rwa [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc set sp : SailState := { s with regs := s.regs.insert Register.nextPC (pc + 4#64) } with hsp have hsp_init : SailState.isInitialized sp := SailState.isInitialized_insert s hs Register.nextPC (pc + 4#64) @@ -141,26 +140,26 @@ theorem correct_load_half_native show (s.regs.insert Register.nextPC (pc + 4#64)).get reg _ = _ rw [Std.ExtDHashMap.get_insert]; simp [Ne.symm hne] exact - { h_cur_privilege := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hcp - h_mprv_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hmprv - h_mseccfg_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hmsec - h_htif_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hhtif - h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } + { h_cur_privilege := by rwa [key _ (hs _) (hsp_init _) (by decide)] + h_mprv_disabled := by rwa [key _ (hs _) (hsp_init _) (by decide)] + h_mseccfg_disabled := by rwa [key _ (hs _) (hsp_init _) (by decide)] + h_htif_disabled := by rwa [key _ (hs _) (hsp_init _) (by decide)] + h_pma_regions := by rwa [key _ (hs _) (hsp_init _) (by decide)] } have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? - = some data₀ := by rw [hmem_eq, ← hadd]; exact hmem₀ + = some data₀ := by rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? - = some data₁ := by rw [hmem_eq, ← hadd]; exact hmem₁ + = some data₁ := by rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 2 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 2) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 2 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 2 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_2' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ data₁ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ hm₁ simp only at hread diff --git a/SP1Clean/Proofs/Chips/LoadHalfChip/Formal.lean b/SP1Clean/Proofs/Chips/LoadHalfChip/Formal.lean index 1fe4f06a..c22fac9a 100644 --- a/SP1Clean/Proofs/Chips/LoadHalfChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LoadHalfChip/Formal.lean @@ -43,35 +43,28 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe h_input.2.2.2.2.2.1 have hmap_pv : Vector.map (Expression.eval env) input_var_memory_access_prev_value = input_memory_access_prev_value := h_input.2.2.2.2.1.1 - have eob0 : Expression.eval env input_var_offset_bit[0] = input_offset_bit[0] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env input_var_offset_bit[1] = input_offset_bit[1] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - simp only [eob0, eob1, epv0, epv1, epv2, epv3, ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 + have eob : ∀ i (hi : i < 2), Expression.eval env input_var_offset_bit[i] = input_offset_bit[i] := + fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + simp only [eob 0 (by omega), eob 1 (by omega), epv 0 (by omega), epv 1 (by omega), + epv 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 have h_it := h_itype h_bin -- the `AddressOperation` Assumptions (eval form, matching the subcircuit input). have hob0' : Expression.eval env input_var_offset_bit[0] = 0 - ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env input_var_offset_bit[1] = 0 - ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have h_off' : (0 : ZMod p).val + 2 * (Expression.eval env input_var_offset_bit[0]).val + 4 * (Expression.eval env input_var_offset_bit[1]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1]; simp only [ZMod.val_zero]; omega + rw [eob 0 (by omega), eob 1 (by omega)]; simp only [ZMod.val_zero]; omega have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, Expression.eval env input_var_offset_bit[0], Expression.eval env input_var_offset_bit[1]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, hob0', hob1', h_ge, h_off'⟩ have h_addr_spec := h_addr h_addr_as - simp only [eob0, eob1] at h_addr_spec + simp only [eob 0 (by omega), eob 1 (by omega)] at h_addr_spec -- `selected_half < 2^16` on a real row: it equals one of the four 16-bit limbs by the offset corner. have h_sel_lt : input_selected_half.val < 2 ^ 16 := by rcases hob0 with hb0 | hb0 @@ -158,32 +151,20 @@ theorem completeness : = input_offset_bit := h_input.2.2.2.2.2.1 have hmap_pv : Vector.map (Expression.eval env.toEnvironment) input_var_memory_access_prev_value = input_memory_access_prev_value := h_input.2.2.2.2.1.1 - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] - = input_state_pc[0] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] - = input_state_pc[1] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] - = input_state_pc[2] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env.toEnvironment input_var_offset_bit[0] - = input_offset_bit[0] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env.toEnvironment input_var_offset_bit[1] - = input_offset_bit[1] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 2), Expression.eval env.toEnvironment input_var_offset_bit[i] + = input_offset_bit[i] := fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] have hob0' : Expression.eval env.toEnvironment input_var_offset_bit[0] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env.toEnvironment input_var_offset_bit[1] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have h_off' : (0 : ZMod p).val + 2 * (Expression.eval env.toEnvironment input_var_offset_bit[0]).val + 4 * (Expression.eval env.toEnvironment input_var_offset_bit[1]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1]; simp only [ZMod.val_zero]; omega + rw [eob 0 (by omega), eob 1 (by omega)]; simp only [ZMod.val_zero]; omega have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, Expression.eval env.toEnvironment input_var_offset_bit[0], Expression.eval env.toEnvironment input_var_offset_bit[1]⟩ : AddressOperation.Inputs (ZMod p)) := @@ -211,16 +192,16 @@ theorem completeness : refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨⟨fun _ => h_sel_lt, h_lh_bin⟩, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, h_op_a_0, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · exact h_msb_spec · exact hbin · exact h_it - · simp only [eob0, eob1, epv0, ← sub_eq_add_neg]; exact hsel0 - · simp only [eob0, eob1, epv1, ← sub_eq_add_neg]; exact hsel1 - · simp only [eob0, eob1, epv2, ← sub_eq_add_neg]; exact hsel2 - · simp only [eob0, eob1, epv3, ← sub_eq_add_neg]; exact hsel3 + · simp only [eob 0 (by omega), eob 1 (by omega), epv 0 (by omega), ← sub_eq_add_neg]; exact hsel0 + · simp only [eob 0 (by omega), eob 1 (by omega), epv 1 (by omega), ← sub_eq_add_neg]; exact hsel1 + · simp only [eob 0 (by omega), eob 1 (by omega), epv 2 (by omega), ← sub_eq_add_neg]; exact hsel2 + · simp only [eob 0 (by omega), eob 1 (by omega), epv 3 (by omega), ← sub_eq_add_neg]; exact hsel3 · exact h_msbgate · rcases h_lh_bin with h | h <;> rw [h] <;> simp · rcases h_lhu_bin with h | h <;> rw [h] <;> simp diff --git a/SP1Clean/Proofs/Chips/LoadWordChip/Bridge.lean b/SP1Clean/Proofs/Chips/LoadWordChip/Bridge.lean index 8a987910..f9424a4f 100644 --- a/SP1Clean/Proofs/Chips/LoadWordChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LoadWordChip/Bridge.lean @@ -112,7 +112,7 @@ private lemma signExtend64_ofNat32_concat_of_ge_32768 [NeZero p] simp only [Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ] have hp : 131072 < p := by have := Fact.out (p := 2 ^ 17 < p); omega have h65535 : (65535 : ZMod p).val = 65535 := by - rw [show (65535 : ZMod p) = ((65535 : ℕ) : ZMod p) from by norm_cast, ZMod.val_natCast, + rw [show (65535 : ZMod p) = ((65535 : ℕ) : ZMod p) by norm_cast, ZMod.val_natCast, Nat.mod_eq_of_lt (by omega)] rw [h65535, Nat.mod_eq_of_lt (by omega), Nat.mod_eq_of_lt (by omega)] change x.val + y.val * 2 ^ 16 + (2 ^ 64 - 2 ^ (8 + 8 + 8 + 8)) = @@ -175,24 +175,24 @@ theorem correct_load_word_native h_htif_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hhtif h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? - = some data₀ := by rw [hmem_eq, ← hadd]; exact hmem₀ + = some data₀ := by rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? - = some data₁ := by rw [hmem_eq, ← hadd]; exact hmem₁ + = some data₁ := by rwa [hmem_eq, ← hadd] have hm₂ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 2]? - = some data₂ := by rw [hmem_eq, ← hadd]; exact hmem₂ + = some data₂ := by rwa [hmem_eq, ← hadd] have hm₃ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 3]? - = some data₃ := by rw [hmem_eq, ← hadd]; exact hmem₃ + = some data₃ := by rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 4 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 4) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 4 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 4 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_4' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ data₁ data₂ data₃ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ hm₁ hm₂ hm₃ diff --git a/SP1Clean/Proofs/Chips/LoadWordChip/Formal.lean b/SP1Clean/Proofs/Chips/LoadWordChip/Formal.lean index d9a4a277..ce39b0a1 100644 --- a/SP1Clean/Proofs/Chips/LoadWordChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LoadWordChip/Formal.lean @@ -42,23 +42,16 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe h_input.2.2.2.2.2.2.1 have hmap_pv : Vector.map (Expression.eval env) input_var_memory_access_prev_value = input_memory_access_prev_value := h_input.2.2.2.2.1.1 - have esw0 : Expression.eval env input_var_selected_word[0] = input_selected_word[0] := by - rw [← hmap_sw]; simp only [Vector.getElem_map] - have esw1 : Expression.eval env input_var_selected_word[1] = input_selected_word[1] := by - rw [← hmap_sw]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - simp only [esw0, esw1, epv0, epv1, epv2, epv3, ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 + have esw : ∀ i (hi : i < 2), Expression.eval env input_var_selected_word[i] + = input_selected_word[i] := fun i hi => by rw [← hmap_sw]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + simp only [esw 0 (by omega), esw 1 (by omega), epv 0 (by omega), epv 1 (by omega), + epv 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 simp only [← sub_eq_add_neg] at h_msbgate - rw [esw1] at h_msb + rw [esw 1 (by omega)] at h_msb have h_it := h_itype h_bin - simp only [esw0, esw1] at h_it + simp only [esw 0 (by omega), esw 1 (by omega)] at h_it -- the `AddressOperation` Assumptions: operand `isU64`s + fits, offset bits boolean (0, 0, the witnessed -- `offset_bit`), non-reserved, and the offset decomposition `4·offset_bit = addr % 8`. have h_off' : (0 : ZMod p).val + 2 * (0 : ZMod p).val + 4 * input_offset_bit.val @@ -90,7 +83,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [heq]; exact hpv3 -- eval-form variant of the 16-bit bound, for the `U16MSBOperation` channel-requirement tail. have h_sel1_lt_eval : (Expression.eval env input_var_selected_word[1]).val < 2 ^ 16 := by - rw [esw1]; exact h_sel1_lt + rw [esw 1 (by omega)]; exact h_sel1_lt have h_msb_as : U16MSBOperation.circuit.Assumptions (⟨input_selected_word[1], ⟨input_msb⟩, input_is_lw⟩ : U16MSBOperation.Inputs (ZMod p)) := ⟨fun _ => h_sel1_lt, h_lw_bin⟩ @@ -146,24 +139,12 @@ theorem completeness : = input_selected_word := h_input.2.2.2.2.2.2.1 have hmap_pv : Vector.map (Expression.eval env.toEnvironment) input_var_memory_access_prev_value = input_memory_access_prev_value := h_input.2.2.2.2.1.1 - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] - = input_state_pc[0] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] - = input_state_pc[1] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] - = input_state_pc[2] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have esw0 : Expression.eval env.toEnvironment input_var_selected_word[0] - = input_selected_word[0] := by rw [← hmap_sw]; simp only [Vector.getElem_map] - have esw1 : Expression.eval env.toEnvironment input_var_selected_word[1] - = input_selected_word[1] := by rw [← hmap_sw]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have esw : ∀ i (hi : i < 2), Expression.eval env.toEnvironment input_var_selected_word[i] + = input_selected_word[i] := fun i hi => by rw [← hmap_sw]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] -- the `AddressOperation` subcircuit `Assumptions`. have h_off' : (0 : ZMod p).val + 2 * (0 : ZMod p).val + 4 * input_offset_bit.val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by @@ -186,20 +167,20 @@ theorem completeness : · have hne : input_offset_bit ≠ 0 := by rw [h1]; exact one_ne_zero rw [sub_eq_zero.mp ((mul_eq_zero.mp hsel3).resolve_right hne)]; exact hpv3 have h_sel1_lt_eval : (Expression.eval env.toEnvironment input_var_selected_word[1]).val < 2 ^ 16 := by - rw [esw1]; exact h_sel1_lt + rw [esw 1 (by omega)]; exact h_sel1_lt refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨⟨fun _ => h_sel1_lt_eval, h_lw_bin⟩, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, h_op_a_0, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem - · simp only [esw1]; exact h_msb_spec + · simp only [esw 1 (by omega)]; exact h_msb_spec · exact hbin - · simp only [esw0, esw1]; exact h_it - · simp only [esw0, epv0, ← sub_eq_add_neg]; exact hsel0 - · simp only [esw1, epv1, ← sub_eq_add_neg]; exact hsel1 - · simp only [esw0, epv2, ← sub_eq_add_neg]; exact hsel2 - · simp only [esw1, epv3, ← sub_eq_add_neg]; exact hsel3 + · simp only [esw 0 (by omega), esw 1 (by omega)]; exact h_it + · simp only [esw 0 (by omega), epv 0 (by omega), ← sub_eq_add_neg]; exact hsel0 + · simp only [esw 1 (by omega), epv 1 (by omega), ← sub_eq_add_neg]; exact hsel1 + · simp only [esw 0 (by omega), epv 2 (by omega), ← sub_eq_add_neg]; exact hsel2 + · simp only [esw 1 (by omega), epv 3 (by omega), ← sub_eq_add_neg]; exact hsel3 · simp only [← sub_eq_add_neg]; exact h_msbgate · rcases h_lw_bin with h | h <;> rw [h] <;> simp · rcases h_lwu_bin with h | h <;> rw [h] <;> simp diff --git a/SP1Clean/Proofs/Chips/LoadX0Chip/Bridge.lean b/SP1Clean/Proofs/Chips/LoadX0Chip/Bridge.lean index 7fe02796..bf44d1a5 100644 --- a/SP1Clean/Proofs/Chips/LoadX0Chip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LoadX0Chip/Bridge.lean @@ -70,7 +70,7 @@ private lemma persist_nextPC (rs1_idx : BitVec 5) (reg_val pc : BitVec 64) h_mseccfg_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hmsec h_htif_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hhtif h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } - · rw [SailState.get_reg?_insert_nextPC]; exact h_rs1 + · rwa [SailState.get_reg?_insert_nextPC] /-! ## The four width-core lemmas @@ -100,13 +100,13 @@ theorem loadX0_w1 (is_unsigned : Bool) = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? = some data₀ := by - rw [hmem_eq, ← hadd]; exact hmem₀ + rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 1 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 1) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 1 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 1 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_1' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ simp only at hread @@ -139,15 +139,15 @@ theorem loadX0_w2 (is_unsigned : Bool) = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? = some data₀ := by - rw [hmem_eq, ← hadd]; exact hmem₀ + rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? = some data₁ := by - rw [hmem_eq, ← hadd]; exact hmem₁ + rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 2 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 2) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 2 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 2 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_2' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ data₁ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ hm₁ simp only at hread @@ -183,19 +183,19 @@ theorem loadX0_w4 (is_unsigned : Bool) = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? = some data₀ := by - rw [hmem_eq, ← hadd]; exact hmem₀ + rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? = some data₁ := by - rw [hmem_eq, ← hadd]; exact hmem₁ + rwa [hmem_eq, ← hadd] have hm₂ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 2]? = some data₂ := by - rw [hmem_eq, ← hadd]; exact hmem₂ + rwa [hmem_eq, ← hadd] have hm₃ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 3]? = some data₃ := by - rw [hmem_eq, ← hadd]; exact hmem₃ + rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 4 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 4) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 4 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 4 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_4' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ data₁ data₂ data₃ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ hm₁ hm₂ hm₃ @@ -236,27 +236,27 @@ theorem loadX0_w8 (is_unsigned : Bool) = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have hm₀ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat]? = some data₀ := by - rw [hmem_eq, ← hadd]; exact hmem₀ + rwa [hmem_eq, ← hadd] have hm₁ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 1]? = some data₁ := by - rw [hmem_eq, ← hadd]; exact hmem₁ + rwa [hmem_eq, ← hadd] have hm₂ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 2]? = some data₂ := by - rw [hmem_eq, ← hadd]; exact hmem₂ + rwa [hmem_eq, ← hadd] have hm₃ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 3]? = some data₃ := by - rw [hmem_eq, ← hadd]; exact hmem₃ + rwa [hmem_eq, ← hadd] have hm₄ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 4]? = some data₄ := by - rw [hmem_eq, ← hadd]; exact hmem₄ + rwa [hmem_eq, ← hadd] have hm₅ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 5]? = some data₅ := by - rw [hmem_eq, ← hadd]; exact hmem₅ + rwa [hmem_eq, ← hadd] have hm₆ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 6]? = some data₆ := by - rw [hmem_eq, ← hadd]; exact hmem₆ + rwa [hmem_eq, ← hadd] have hm₇ : sp.mem[reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 7]? = some data₇ := by - rw [hmem_eq, ← hadd]; exact hmem₇ + rwa [hmem_eq, ← hadd] have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 8 = true := by - rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned + rwa [is_aligned_vaddr_iff_mod, hadd] have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 8) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := - range_subset_sp1_pma _ 8 (by omega) h_lo (by rw [hadd]; exact h_hi) + range_subset_sp1_pma _ 8 (by omega) h_lo (by rwa [hadd]) have hread := run_vmem_read_of_width_8' rs1_idx reg_val (BitVec.signExtend 64 imm) data₀ data₁ data₂ data₃ data₄ data₅ data₆ data₇ sp hsp_init hsp_rs1 h_align' hsp_config h_fits h_in_range hm₀ hm₁ hm₂ hm₃ hm₄ hm₅ hm₆ hm₇ diff --git a/SP1Clean/Proofs/Chips/LoadX0Chip/Formal.lean b/SP1Clean/Proofs/Chips/LoadX0Chip/Formal.lean index c33646fe..b19c2757 100644 --- a/SP1Clean/Proofs/Chips/LoadX0Chip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LoadX0Chip/Formal.lean @@ -36,34 +36,30 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have h_bin := bool_of_mul_pred h_gate -- eval→value bridge for the offset bits (the only nested vector field the gates reference in value form). obtain ⟨_, _, _, _, _, _, _, _, _, _, hmap_ob⟩ := h_input - have eob0 : Expression.eval env input_var_offset_bit[0] = input_offset_bit[0] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env input_var_offset_bit[1] = input_offset_bit[1] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env input_var_offset_bit[2] = input_offset_bit[2] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 3), Expression.eval env input_var_offset_bit[i] = input_offset_bit[i] := + fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] -- the `AddressOperation` Assumptions (eval form). have hob0' : Expression.eval env input_var_offset_bit[0] = 0 - ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env input_var_offset_bit[1] = 0 - ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env input_var_offset_bit[2] = 0 - ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env input_var_offset_bit[0]).val + 2 * (Expression.eval env input_var_offset_bit[1]).val + 4 * (Expression.eval env input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env input_var_offset_bit[0], Expression.eval env input_var_offset_bit[1], Expression.eval env input_var_offset_bit[2]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, hob0', hob1', hob2', h_ge, h_off'⟩ have h_addr_spec := h_addr h_addr_as - simp only [eob0, eob1, eob2] at h_addr_spec + simp only [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)] at h_addr_spec have h_it := h_itype h_bin -- the alignment gates, in value form. - simp only [eob0, eob1, eob2] at h_al0 h_al1 h_al2 + simp only [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)] at h_al0 h_al1 h_al2 simp only [← sub_eq_add_neg] at h_oa1 h_oa2 simp only [isReal, opcodeVal] refine ⟨⟨h_addr_spec, h_mem h_bin, h_it, @@ -113,29 +109,21 @@ theorem completeness : h_al2, h_al1, h_al0, h_oa1, h_oa2, h_cpu, h_mem, h_it⟩ := h_assumptions simp only [sub_eq_add_neg] at h_oa1 h_oa2 obtain ⟨_, _, _, _, _, _, _, ⟨_, _, _, hmap_pc⟩, _, _, hmap_ob⟩ := h_input - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env.toEnvironment input_var_offset_bit[0] - = input_offset_bit[0] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env.toEnvironment input_var_offset_bit[1] - = input_offset_bit[1] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env.toEnvironment input_var_offset_bit[2] - = input_offset_bit[2] := by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_offset_bit[i] + = input_offset_bit[i] := fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] have hob0' : Expression.eval env.toEnvironment input_var_offset_bit[0] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env.toEnvironment input_var_offset_bit[1] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env.toEnvironment input_var_offset_bit[2] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env.toEnvironment input_var_offset_bit[0]).val + 2 * (Expression.eval env.toEnvironment input_var_offset_bit[1]).val + 4 * (Expression.eval env.toEnvironment input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env.toEnvironment input_var_offset_bit[0], Expression.eval env.toEnvironment input_var_offset_bit[1], @@ -144,7 +132,7 @@ theorem completeness : refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · exact hbin @@ -157,9 +145,9 @@ theorem completeness : · rcases h_b5 with h | h <;> rw [h] <;> simp · rcases h_b6 with h | h <;> rw [h] <;> simp · rcases hbin with h | h <;> rw [h] <;> simp - · simp only [eob2]; exact h_al2 - · simp only [eob1]; exact h_al1 - · simp only [eob0]; exact h_al0 + · simp only [eob 2 (by omega)]; exact h_al2 + · simp only [eob 1 (by omega)]; exact h_al1 + · simp only [eob 0 (by omega)]; exact h_al0 · exact h_oa1 · exact h_oa2 diff --git a/SP1Clean/Proofs/Chips/LtChip/Bridge.lean b/SP1Clean/Proofs/Chips/LtChip/Bridge.lean index 108d9c05..f0382f3a 100644 --- a/SP1Clean/Proofs/Chips/LtChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/LtChip/Bridge.lean @@ -94,6 +94,63 @@ theorem correct_slt_native Sail.run_rX_bits, Sail.run_wX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, h_rs2, h_slt] +/-- The RISC-V spec: advance `nextPC ← PC + 4`, then execute the Sail `SLTI` (I-type signed +less-than-immediate). -/ +noncomputable def spec_slti (imm : BitVec 12) (rs1 rd : regidx) : SailM Unit := do + Sail.writeReg Register.nextPC ((← Sail.readReg Register.PC) + 4#64) + _ ← execute_ITYPE imm rs1 rd iop.SLTI + pure () + +/-- The RISC-V spec: advance `nextPC ← PC + 4`, then execute the Sail `SLTIU` (unsigned). -/ +noncomputable def spec_sltiu (imm : BitVec 12) (rs1 rd : regidx) : SailM Unit := do + Sail.writeReg Register.nextPC ((← Sail.readReg Register.PC) + 4#64) + _ ← execute_ITYPE imm rs1 rd iop.SLTIU + pure () + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence (signed immediate `SLTI`): the chip's RV64 `slt` fact (`h_slt`, with the +immediate operand `op_c_val` bound to `sign_extend imm` via `h_dec`) plus the rs1/PC reads drive +`spec_slti ≡ sp1_lt`. The Sail `SLTI` arm `zero_extend (bool_to_bit (zopz0zI_s (rX rs1) immext))` is the +same body as the `execute_RTYPE_pure .SLT`, so `execute_RTYPE_pure_slt` retires it. -/ +theorem correct_slti_native + (op_b_val op_c_val a_val : Word (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_slt : Word.toBitVec64 a_val + = RV64.slt (Word.toBitVec64 op_c_val) (Word.toBitVec64 op_b_val)) : + (spec_slti imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_lt (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 a_val + = zero_extend (m := 64) (bool_to_bit (zopz0zI_s (Word.toBitVec64 op_b_val) + (sign_extend (m := 64) imm))) := by + rw [h_slt, ← h_dec, ← execute_RTYPE_pure_slt]; simp [execute_RTYPE_pure] + simp [spec_slti, sp1_lt, execute_ITYPE, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + +set_option linter.unusedSimpArgs false in +omit [Fact (2 ^ 17 < p)] in +/-- Native Sail equivalence (unsigned immediate `SLTIU`): `h_sltu` + rs1/PC reads drive +`spec_sltiu ≡ sp1_lt`, via the `execute_RTYPE_pure_sltu` Sail-side identity. -/ +theorem correct_sltiu_native + (op_b_val op_c_val a_val : Word (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 op_b_val)) + (h_dec : Word.toBitVec64 op_c_val = sign_extend (m := 64) imm) + (h_sltu : Word.toBitVec64 a_val + = RV64.sltu (Word.toBitVec64 op_c_val) (Word.toBitVec64 op_b_val)) : + (spec_sltiu imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_lt (.Regidx rd_idx) pc a_val).run s := by + have harm : Word.toBitVec64 a_val + = zero_extend (m := 64) (bool_to_bit (zopz0zI_u (Word.toBitVec64 op_b_val) + (sign_extend (m := 64) imm))) := by + rw [h_sltu, ← h_dec, ← execute_RTYPE_pure_sltu]; simp [execute_RTYPE_pure] + simp [spec_sltiu, sp1_lt, execute_ITYPE, PreSail.readReg, PreSail.writeReg, + Sail.run_rX_bits, SailState.get_reg?_insert_nextPC, h_pc, h_rs1, harm] + omit [Fact (2 ^ 17 < p)] in /-- End-to-end: a real `Lt` chip row reaches the RISC-V Sail set-less-than, flag-dispatched. Both SLT and SLTU conjuncts are proven and axiom-clean. -/ @@ -118,6 +175,32 @@ theorem lt_chip_reaches_sail · exact correct_sltu_native input.op_b_val input.op_c_val (LtChip.resultWord cols) rs1_idx rs2_idx rd_idx pc s h_pc h_rs1 h_rs2 ((h_gated h_real).2 hsltu) +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (immediate forms SLTI/SLTIU): a real `Lt` chip row whose `op_c` operand is the +sign-extended 12-bit immediate (`h_dec`, supplied at the bridge from the program bus) reaches the RISC-V +Sail I-type set-less-than, flag-dispatched. The chip's `Spec` proves the same RV64 `slt`/`sltu` equation for +immediate (`imm_c = 1`) rows as for register rows, so both conjuncts are proven and axiom-clean. -/ +theorem lt_chip_reaches_sail_imm + (input : LtChip.Inputs (ZMod p)) (cols : Extracted.LtCols (ZMod p)) (data : ProverData (ZMod p)) + (rs1_idx rd_idx : BitVec 5) (imm : BitVec 12) (pc : BitVec 64) (s : SailState) + (h_real : input.is_real = 1) + (h_chip : LtChip.Spec input cols data) + (h_pc : s.regs.get? Register.PC = some pc) + (h_rs1 : s.get_reg? rs1_idx = some (Word.toBitVec64 input.op_b_val)) + (h_dec : Word.toBitVec64 input.op_c_val = sign_extend (m := 64) imm) : + (cols.is_slt = 1 → + (spec_slti imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_lt (.Regidx rd_idx) pc (LtChip.resultWord cols)).run s) ∧ + (cols.is_sltu = 1 → + (spec_sltiu imm (.Regidx rs1_idx) (.Regidx rd_idx)).run s + = (sp1_lt (.Regidx rd_idx) pc (LtChip.resultWord cols)).run s) := by + obtain ⟨-, -, h_gated⟩ := h_chip + refine ⟨fun hslt => ?_, fun hsltu => ?_⟩ + · exact correct_slti_native input.op_b_val input.op_c_val (LtChip.resultWord cols) + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec ((h_gated h_real).1 hslt) + · exact correct_sltiu_native input.op_b_val input.op_c_val (LtChip.resultWord cols) + rs1_idx rd_idx imm pc s h_pc h_rs1 h_dec ((h_gated h_real).2 hsltu) + end SP1Clean.LtSail namespace SP1Clean.LtChip @@ -128,7 +211,9 @@ open Sail LeanRV64D LeanRV64D.Functions variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] /-- **Lt's `ChipKind` registration.** Program-bus opcode `is_slt·9 + is_sltu·10`; `sailEquiv` is the -flag-dispatched SLT/SLTU conjunction; `reaches_sail` is `lt_chip_reaches_sail`. -/ +conjunction of the **register** SLT/SLTU guarantee (gated on the rs2 read) and the **immediate** SLTI/SLTIU +guarantee (gated on the program-bus immediate decode `op_c_val = sign_extend imm`), each flag-dispatched. +A row is register (`imm_c = 0`) or immediate (`imm_c = 1`); the consumer picks the matching conjunct. -/ def kind : Soundness.ChipKind p where name := "Lt" Inputs := LtChip.Inputs @@ -138,17 +223,31 @@ def kind : Soundness.ChipKind p where cols.adapter.toAdapterView, inp.is_real, resultWord cols, cols.is_slt * 9 + cols.is_sltu * 10⟩ chipSpec := fun inp cols data => Spec inp cols data - sailEquiv := fun inp cols s => ∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), - s.regs.get? Register.PC = some pc → - s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → - s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → - (cols.is_slt = 1 → - (spec_slt (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s - = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s) ∧ - (cols.is_sltu = 1 → - (spec_sltu (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s - = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s) - reaches_sail := fun inp cols data s h_real h_chip rs1 rs2 rd pc h_pc h_rs1 h_rs2 => - lt_chip_reaches_sail inp cols data rs1 rs2 rd pc s h_real h_chip h_pc h_rs1 h_rs2 + sailEquiv := fun inp cols s => + (∀ (rs1 rs2 rd : BitVec 5) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + s.get_reg? rs2 = some (Word.toBitVec64 inp.op_c_val) → + (cols.is_slt = 1 → + (spec_slt (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s + = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s) ∧ + (cols.is_sltu = 1 → + (spec_sltu (.Regidx rs2) (.Regidx rs1) (.Regidx rd)).run s + = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s)) ∧ + (∀ (rs1 rd : BitVec 5) (imm : BitVec 12) (pc : BitVec 64), + s.regs.get? Register.PC = some pc → + s.get_reg? rs1 = some (Word.toBitVec64 inp.op_b_val) → + Word.toBitVec64 inp.op_c_val = sign_extend (m := 64) imm → + (cols.is_slt = 1 → + (spec_slti imm (.Regidx rs1) (.Regidx rd)).run s + = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s) ∧ + (cols.is_sltu = 1 → + (spec_sltiu imm (.Regidx rs1) (.Regidx rd)).run s + = (sp1_lt (.Regidx rd) pc (resultWord cols)).run s)) + reaches_sail := fun inp cols data s h_real h_chip => + ⟨fun rs1 rs2 rd pc h_pc h_rs1 h_rs2 => + lt_chip_reaches_sail inp cols data rs1 rs2 rd pc s h_real h_chip h_pc h_rs1 h_rs2, + fun rs1 rd imm pc h_pc h_rs1 h_dec => + lt_chip_reaches_sail_imm inp cols data rs1 rd imm pc s h_real h_chip h_pc h_rs1 h_dec⟩ end SP1Clean.LtChip diff --git a/SP1Clean/Proofs/Chips/LtChip/Formal.lean b/SP1Clean/Proofs/Chips/LtChip/Formal.lean index 28fc135f..427a9661 100644 --- a/SP1Clean/Proofs/Chips/LtChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LtChip/Formal.lean @@ -115,10 +115,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe exact h2 (by exact_mod_cast h20) have h01 : (0 : ZMod p) ≠ 1 := by haveI : Fact (1 < p) := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ - intro h - have := congrArg ZMod.val h - rw [ZMod.val_zero, ZMod.val_one] at this - exact absurd this (by norm_num) + exact zero_ne_one rw [h_slt0] at h_lt_spec simp only [if_neg h01] at h_lt_spec simp only [resultWord, rv64_sltu_eq, Word.toBitVec64_toNat ha, diff --git a/SP1Clean/Proofs/Chips/MemoryProvider.lean b/SP1Clean/Proofs/Chips/MemoryProvider.lean index c1cb0670..4dd813bf 100644 --- a/SP1Clean/Proofs/Chips/MemoryProvider.lean +++ b/SP1Clean/Proofs/Chips/MemoryProvider.lean @@ -70,7 +70,7 @@ theorem memRow_eq_of_key {r1 r2 : MemInitRow (ZMod p)} (h : memRowKey r1 = memRo · exact ZMod.val_injective p hv1 · exact ZMod.val_injective p hv2 · exact ZMod.val_injective p hv3 - · exact absurd hi (by omega) + · omega /-- The **`MemoryProvider`**: any Memory-bus contribution list whose every entry sits at the key of some `initSpec`-valid boundary record. The native, predicate-characterized content of SP1's preprocessed Memory @@ -86,7 +86,7 @@ cross-bus-disjointness input `memoryBalance_of_machine` consumes (mirrors `progr theorem memProvider_kind {initSpec : MemInitRow (ZMod p) → Prop} {prov : LookupAccessList} (h : MemoryProvider initSpec prov) : ∀ b ∈ prov, (keyOf b).1 = InteractionKind.Memory := by intro b hb - obtain ⟨row, _, hk⟩ := h b hb + obtain ⟨_, _, hk⟩ := h b hb rw [hk, memRowKey] /-- A boundary contribution whose key a `MemoryProvider` carries is an `initSpec`-valid record. (The @@ -97,7 +97,6 @@ theorem initRow_of_provider {initSpec : MemInitRow (ZMod p) → Prop} {row : Mem (hk : keyOf b = memRowKey row) : initSpec row := by obtain ⟨row', h_spec, h_key⟩ := h_prov b hb rw [hk] at h_key - rw [memRow_eq_of_key h_key] - exact h_spec + rwa [memRow_eq_of_key h_key] end SP1Clean.MemoryGlobalChip diff --git a/SP1Clean/Proofs/Chips/MulChip/Formal.lean b/SP1Clean/Proofs/Chips/MulChip/Formal.lean index 390272fd..0b6cf65b 100644 --- a/SP1Clean/Proofs/Chips/MulChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/MulChip/Formal.lean @@ -157,18 +157,16 @@ theorem completeness : have hmw' : env.get (i₀ + 4) = 1 → env.get i₀ + env.get (i₀ + 1) + env.get (i₀ + 2) + env.get (i₀ + 3) + env.get (i₀ + 4) = 1 := fun h => by rw [hsumc]; exact hmulw_real (by rw [← hflag4]; exact h) - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hpc, Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hpc, Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hpc, Vector.getElem_map] + have epc : ∀ (i : ℕ) (hi : i < 3), + Expression.eval env.toEnvironment input_var_state_pc[i] = input_state_pc[i] := + fun i hi => by rw [← hpc, Vector.getElem_map] have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] have hbool : ∀ x : ZMod p, x = 0 ∨ x = 1 → x * (x + -1) = 0 := by rintro x (h | h) <;> rw [h] <;> simp -- fold the witness hint's `populate` operands to the evaluated input words simp only [Inputs.op_b_val, Inputs.op_c_val, vec4_eval, hob, hoc] at h_env_cols - refine ⟨⟨hbin, by rw [← epc0, ← epc1, ← epc2] at h_cpu; exact h_cpu⟩, + rw [← epc 0 (by norm_num), ← epc 1 (by norm_num), ← epc 2 (by norm_num)] at h_cpu + refine ⟨⟨hbin, h_cpu⟩, ⟨⟨fun _ => ⟨hbU, hcU⟩, hsum01', hmw', hf0', hf1', hf2', hf3', hf4', hsum01'⟩, ?_⟩, by simpa using h_env_a 0, by simpa using h_env_a 1, by simpa using h_env_a 2, by simpa using h_env_a 3, diff --git a/SP1Clean/Proofs/Chips/ProgramChip.lean b/SP1Clean/Proofs/Chips/ProgramChip.lean index 21cbead0..30abf180 100644 --- a/SP1Clean/Proofs/Chips/ProgramChip.lean +++ b/SP1Clean/Proofs/Chips/ProgramChip.lean @@ -72,14 +72,14 @@ theorem programRow_eq_of_key {r1 r2 : ProgramRow (ZMod p)} (h : programRowKey r1 · exact ZMod.val_injective p hb1 · exact ZMod.val_injective p hb2 · exact ZMod.val_injective p hb3 - · exact absurd hi (by omega) + · omega · apply Vector.ext; intro i hi rcases i with _|_|_|_|i · exact ZMod.val_injective p hc0 · exact ZMod.val_injective p hc1 · exact ZMod.val_injective p hc2 · exact ZMod.val_injective p hc3 - · exact absurd hi (by omega) + · omega /-- The **rich ROM-membership predicate** SP1's program/decode chip establishes for every received row — the *received* facts `Foundations/Channels.lean`'s `ProgramMsg.Spec` defers: the register indices are @@ -105,7 +105,6 @@ theorem inROM_of_provider {inROM : ProgramRow (ZMod p) → Prop} {row : ProgramR (hk : keyOf b = programRowKey row) : inROM row := by obtain ⟨row', h_spec, h_key⟩ := h_prov b hb rw [hk] at h_key - rw [programRow_eq_of_key h_key] - exact h_spec + rwa [programRow_eq_of_key h_key] end SP1Clean.ProgramChip diff --git a/SP1Clean/Proofs/Chips/ShiftLeftChip/Core.lean b/SP1Clean/Proofs/Chips/ShiftLeftChip/Core.lean index 7bebe542..787d00e4 100644 --- a/SP1Clean/Proofs/Chips/ShiftLeftChip/Core.lean +++ b/SP1Clean/Proofs/Chips/ShiftLeftChip/Core.lean @@ -21,10 +21,7 @@ variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] /-! ## Field constants for the shift-amount modulus -/ -@[simp] lemma val_64_zmod_p [NeZero p] : (64 : ZMod p).val = 64 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (64 : ℕ) < p by omega) - +-- `val_64_zmod_p` now lives in `Math/Word.lean`; `val_64_ne_zero` is local. lemma val_64_ne_zero [NeZero p] : (64 : ZMod p) ≠ 0 := by have h : (64 : ZMod p).val = 64 := val_64_zmod_p intro hz; rw [hz] at h; simp at h @@ -259,6 +256,29 @@ lemma sll_within_byte_shift_1 /-! ## Byte-shift placement wrappers (one per `(cb4, cb5)` combination) -/ +/-- The bit-decomposition exponent `(cb0 + 2cb1 + 4cb2 + 8cb3).val = S` (the within-byte shift count), +shared by every close-case/sub-case wrapper. -/ +lemma inner_val {S : ℕ} (hS_le : S ≤ 15) {cb0 cb1 cb2 cb3 : ZMod p} + (h_inner_eq : cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) + cb3 * 8 + = ((S : ℕ) : ZMod p)) : + (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) + cb3 * 8 : ZMod p).val = S := by + have hp : 2 ^ 17 < p := Fact.out + haveI : NeZero p := ⟨by omega⟩ + rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) + +/-- The complementary high exponent `(16 - (cb0 + 2cb1 + 4cb2 + 8cb3)).val = 16 - S`. -/ +lemma inner_hi_val {S : ℕ} (hS_le : S ≤ 15) {cb0 cb1 cb2 cb3 : ZMod p} + (h_inner_eq : cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) + cb3 * 8 + = ((S : ℕ) : ZMod p)) : + (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) + cb3 * 8) : ZMod p).val + = 16 - S := by + have hp : 2 ^ 17 < p := Fact.out + haveI : NeZero p := ⟨by omega⟩ + rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) + + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by + rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] + exact ZMod.val_natCast_of_lt (by omega) + set_option maxHeartbeats 4000000 in /-- `cb4=cb5=0` (byte_shift=0): `limb_result` placed at limb 0. -/ lemma sll_close_cb4cb5_zero_case @@ -300,15 +320,8 @@ lemma sll_close_cb4cb5_zero_case rw [BitVec.toNat_shiftLeft] have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 lt_lh2 lt_lh3 rw [h_inner_hi_val] at lt_ll0 lt_ll1 lt_ll2 lt_ll3 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16 + cb5 * 32).val @@ -380,15 +393,8 @@ lemma sll_close_cb4cb5_one_one_case rw [BitVec.toNat_shiftLeft] have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 lt_lh2 lt_lh3 rw [h_inner_hi_val] at lt_ll0 lt_ll1 lt_ll2 lt_ll3 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16 + cb5 * 32).val @@ -460,15 +466,8 @@ lemma sll_close_cb4cb5_zero_one_case rw [BitVec.toNat_shiftLeft] have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 lt_lh2 lt_lh3 rw [h_inner_hi_val] at lt_ll0 lt_ll1 lt_ll2 lt_ll3 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16 + cb5 * 32).val @@ -540,15 +539,8 @@ lemma sll_close_cb4cb5_one_zero_case rw [BitVec.toNat_shiftLeft] have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 lt_lh2 lt_lh3 rw [h_inner_hi_val] at lt_ll0 lt_ll1 lt_ll2 lt_ll3 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16 + cb5 * 32).val @@ -979,15 +971,8 @@ lemma sllw_close_cb4_zero_case % 2 ^ 32 := by have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 rw [h_inner_hi_val] at lt_ll0 lt_ll1 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16).val = S := by @@ -1039,15 +1024,8 @@ lemma sllw_close_cb4_one_case % 2 ^ 32 := by have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq rw [h_inner_val] at lt_lh0 lt_lh1 rw [h_inner_hi_val] at lt_ll0 lt_ll1 have h_total_val : (cb0 + cb1 * (2 : ZMod p) + cb2 * 4 + cb3 * 8 + cb4 * 16).val = S + 16 := by @@ -1105,15 +1083,8 @@ lemma sllw_subcase_cb4_zero BitVec.setWidth 5 (HWord.toBitVec32 #v[c0, c1])) := by have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq have h_lt_ll0_N : ll0.val < N := by rw [h_N_eq]; rw [h_inner_hi_val] at lt_ll0; exact lt_ll0 have h_lt_ll1_N : ll1.val < N := by rw [h_N_eq]; rw [h_inner_hi_val] at lt_ll1; exact lt_ll1 have h_lt_lh0_M : hl0.val < M := by rw [h_M_eq]; rw [h_inner_val] at lt_lh0; exact lt_lh0 @@ -1191,15 +1162,8 @@ lemma sllw_subcase_cb4_one BitVec.setWidth 5 (HWord.toBitVec32 #v[c0, c1])) := by have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have h_inner_val : (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8 : ZMod p).val = S := by - rw [h_inner_eq]; exact ZMod.val_natCast_of_lt (by omega) - have h_inner_hi_val : (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p).val = 16 - S := by - rw [show (16 - (cb0 + cb1 * ((2 : ℕ) : ZMod p) + cb2 * ((4 : ℕ) : ZMod p) - + cb3 * 8) : ZMod p) = (((16 - S) : ℕ) : ZMod p) from by - rw [h_inner_eq, Nat.cast_sub (by omega : S ≤ 16)]; push_cast; ring] - exact ZMod.val_natCast_of_lt (by omega) + have h_inner_val := inner_val h_S_le h_inner_eq + have h_inner_hi_val := inner_hi_val h_S_le h_inner_eq have h_lt_ll0_N : ll0.val < N := by rw [h_N_eq]; rw [h_inner_hi_val] at lt_ll0; exact lt_ll0 have h_ll0v_val : (ll0 * v0123).val = ll0.val * M := by rw [ZMod.val_mul_of_lt, h_v_val]; rw [h_v_val]; nlinarith [h_lt_ll0_N, h_MN] diff --git a/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean b/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean index 56adcd5a..37c019fd 100644 --- a/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean @@ -102,11 +102,9 @@ theorem completeness : obtain ⟨h_env_a, h_env_cb, h_env_v, h_env_s, h_env_lo, h_env_hi, h_env_lr, h_env_tail⟩ := h_env obtain ⟨h_env_msb, h_env_fl⟩ := h_env_tail -- project (not destructure — rcases on `h_input` disturbs the bound `h_env_*` hypotheses) - -- the three input equalities the proof reads have hpc := h_input.2.1.2.2.2 have hbpv := h_input.2.2.2.2.2.2.1.1 have hcpv := h_input.2.2.2.2.2.2.2.2.1.1 - -- fold the closures' eval'd operands to the value-level inputs have ec0 : Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[0] = input_adapter_op_c_memory_prev_value[0] := by rw [← hcpv, Vector.getElem_map] @@ -124,11 +122,9 @@ theorem completeness : rw [← hpc, Vector.getElem_map] simp only [vec4_eval, hbpv, ec0] at h_env_a h_env_cb h_env_v h_env_s h_env_lo h_env_hi simp only [vec4_eval, hbpv, ec0] at h_env_lr h_env_msb - -- the operand word / shift-amount limb / hint flags, value-level set B := input_adapter_op_b_memory_prev_value with hB set c0 := input_adapter_op_c_memory_prev_value[0] with hc0 set F := hintFlags env.hint with hF - -- pin every witnessed cell to its populate projection have hA0 : env.get i₀ = (populateA B c0 F)[0] := by simpa using h_env_a 0 have hA1 : env.get (i₀ + 1) = (populateA B c0 F)[1] := by simpa using h_env_a 1 have hA2 : env.get (i₀ + 2) = (populateA B c0 F)[2] := by simpa using h_env_a 2 @@ -172,11 +168,9 @@ theorem completeness : simpa using h_env_fl 1 have hfl2 : env.get (i₀ + 4 + 6 + 3 + 4 + 4 + 4 + 4 + 1 + 2) = F[1] * input_adapter_imm_c := by simpa using h_env_fl 2 - -- derived flag facts have hsum01 : F[0] + F[1] = 0 ∨ F[0] + F[1] = 1 := by rw [← hsum]; exact hbin have hbUw : Word.isU64 B := hbU obtain ⟨hc0v, -, -, -⟩ := Word.lt_cases_of_isU64 hcU - -- the constraint bundles at the populate values obtain ⟨hcba0, hcba1, hcba2, hcba3, hcba4, hcba5⟩ := cBits_asserts c0 obtain ⟨hsel0, hsb0, hsel1, hsb1, hsel2, hsb2, hsel3, hsb3, hone⟩ := shiftU16_asserts c0 F hf0 hsum01 @@ -186,7 +180,6 @@ theorem completeness : obtain ⟨hp1, hp2, hp3, hp4, hp5, hp6, hp7, hp8, hp9, hp10, hp11, hp12, hp13, hp14, hp15, hp16, hp17, hp18, hp19, hp20, hp21, hp22⟩ := place_asserts B c0 F hf0 hf1 hsum01 have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] - -- rewrite every witnessed cell in the goal to its populate value simp only [hA0, hA1, hA2, hA3, hcb0, hcb1, hcb2, hcb3, hcb4, hcb5, hv0, hv1, hv2, hs0, hs1, hs2, hs3, hlo0, hlo1, hlo2, hlo3, hhi0, hhi1, hhi2, hhi3, hlr0, hlr1, hlr2, hlr3, hmsbc, hfl0, hfl1, hfl2, heb0, heb1, heb2, heb3, ec0, epc0, epc1, epc2] @@ -210,8 +203,7 @@ theorem completeness : hp17, hp18, hp19, hp20, hp21, hp22, by simp, hop_a_0, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ - · -- the SLLW `U16MSBOperation` high-bit equation at the witnessed msb - rw [show sllwMsb B c0 F = U16MSBOperation.populate_msb (populateA B c0 F)[1] from by + · rw [show sllwMsb B c0 F = U16MSBOperation.populate_msb (populateA B c0 F)[1] by rw [sllwMsb, if_pos h1]] exact (U16MSBOperation.spec_populate (populateA_val_lt B c0 F hbUw 1 (by norm_num)) 1).2 rfl -- the nine `gate`-gated byte-range pulls: the populate bounds hold unconditionally diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Core.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Core.lean index 0910b024..5592ebfc 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Core.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Core.lean @@ -28,22 +28,7 @@ variable {p : ℕ} [Fact (Nat.Prime p)] [Fact (2 ^ 17 < p)] local instance : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ --- Local field-constant helpers not yet in `Foundations/Word.lean` (which has `val_2/65535/65536`). -@[simp] lemma val_4_zmod_p : (4 : ZMod p).val = 4 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (4 : ℕ) < p by omega) -@[simp] lemma val_8_zmod_p : (8 : ZMod p).val = 8 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (8 : ℕ) < p by omega) -@[simp] lemma val_16_zmod_p : (16 : ZMod p).val = 16 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (16 : ℕ) < p by omega) -@[simp] lemma val_32_zmod_p : (32 : ZMod p).val = 32 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (32 : ℕ) < p by omega) -@[simp] lemma val_64_zmod_p : (64 : ZMod p).val = 64 := by - have : (131072 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega - exact ZMod.val_natCast_of_lt (show (64 : ℕ) < p by omega) +-- Field constants `val_4/8/16/32/64_zmod_p` now live in `Math/Word.lean`; `val_64_ne_zero` is local. lemma val_64_ne_zero : (64 : ZMod p) ≠ 0 := by have h : (64 : ZMod p).val = 64 := val_64_zmod_p intro hz; rw [hz] at h; simp at h diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Flags.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Flags.lean index bd6d6558..3c5405d8 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Flags.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Flags.lean @@ -39,7 +39,6 @@ lemma single_flag {a b c d : ZMod p} (ha : a = 1) (hb : b = 0 ∨ b = 1) (hc : c = 0 ∨ c = 1) (hd : d = 0 ∨ d = 1) (hsum : a + b + c + d = 0 ∨ a + b + c + d = 1) : b = 0 ∧ c = 0 ∧ d = 0 := by have hp : 2 ^ 17 < p := Fact.out - haveI : NeZero p := ⟨by omega⟩ have hva : a.val = 1 := by rw [ha]; exact ZMod.val_one p have vb : b.val ≤ 1 := by rcases hb with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] have vc : c.val ≤ 1 := by rcases hc with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] @@ -64,7 +63,6 @@ lemma srlw_sraw_gate {a b c d : ZMod p} (hsum : a + b + c + d = 0 ∨ a + b + c + d = 1) (hcd : c + d = 1) : a = 0 ∧ b = 0 ∧ a + b + c + d = 1 := by have hp : 2 ^ 17 < p := Fact.out - haveI : NeZero p := ⟨by omega⟩ have va : a.val ≤ 1 := by rcases ha with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] have vb : b.val ≤ 1 := by rcases hb with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] have vc : c.val ≤ 1 := by rcases hc with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] @@ -90,7 +88,6 @@ lemma pair_flag {a b c d : ZMod p} (ha : a = 0 ∨ a = 1) (hb : b = 0 ∨ b = 1) (hc : c = 0 ∨ c = 1) (hd : d = 0 ∨ d = 1) (hsum : a + b + c + d = 0 ∨ a + b + c + d = 1) : c + d = 0 ∨ c + d = 1 := by have hp : 2 ^ 17 < p := Fact.out - haveI : NeZero p := ⟨by omega⟩ have va : a.val ≤ 1 := by rcases ha with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] have vb : b.val ≤ 1 := by rcases hb with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] have vc : c.val ≤ 1 := by rcases hc with rfl | rfl <;> simp [ZMod.val_zero, ZMod.val_one] diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean index d6e4f425..80e48884 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean @@ -108,7 +108,6 @@ theorem completeness : have hpc := h_input.2.1.2.2.2 have hbpv := h_input.2.2.2.2.2.2.1.1 have hcpv := h_input.2.2.2.2.2.2.2.2.1.1 - -- fold the closures' eval'd operands to the value-level inputs have ec0 : Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[0] = input_adapter_op_c_memory_prev_value[0] := by rw [← hcpv, Vector.getElem_map] @@ -129,7 +128,6 @@ theorem completeness : set B := input_adapter_op_b_memory_prev_value with hB set c0 := input_adapter_op_c_memory_prev_value[0] with hc0 set F := hintFlags env.hint with hF - -- pin every witnessed cell to its populate projection have hA0 : env.get i₀ = (populateA B c0 F)[0] := by simpa using h_env_a 0 have hA1 : env.get (i₀ + 1) = (populateA B c0 F)[1] := by simpa using h_env_a 1 have hA2 : env.get (i₀ + 2) = (populateA B c0 F)[2] := by simpa using h_env_a 2 @@ -202,7 +200,6 @@ theorem completeness : simp only [hA0, hA1, hA2, hA3, hbm, hsw, hcb0, hcb1, hcb2, hcb3, hcb4, hcb5, hsram, hv0, hv1, hv2, hlo0, hlo1, hlo2, hlo3, hhi0, hhi1, hhi2, hhi3, hlr0, hlr1, hlr2, hlr3, hs0, hs1, hs2, hs3, hfl0, hfl1, hfl2, hfl3, hfl4, heb0, heb1, heb2, heb3, ec0, epc0, epc1, epc2] - -- derived flag facts have hbUw : Word.isU64 B := hbU obtain ⟨hbU0, hbU1, hbU2, hbU3⟩ := Word.lt_cases_of_isU64 hbUw obtain ⟨ho0, ho1, ho2, ho3⟩ := one_hot_resolve F hf0 hf1 hf2 hf3 hsum01 @@ -221,7 +218,6 @@ theorem completeness : · obtain ⟨-, -, h3z⟩ := ho2 h2 right; rw [h2, h3z, add_zero] have hbmB := bMsb_bool B F hbUw hf1 hf3 (fun h => (ho1 h).2.2) - -- the constraint bundles at the populate values obtain ⟨hcba0, hcba1, hcba2, hcba3, hcba4, hcba5⟩ := ShiftLeftChip.cBits_asserts c0 obtain ⟨hsel0, hsb0, hsel1, hsb1, hsel2, hsb2, hsel3, hsb3, hone⟩ := shiftU16_asserts c0 F he14 hsum01 @@ -272,7 +268,7 @@ theorem completeness : rw [bMsb_eq_sraw B F h3 (ho3 h3).2.1] exact (U16MSBOperation.spec_populate hbU1 1).2 rfl · -- the word-variant sign witness is the high bit of the placed `a[1]` - rw [show srwMsb B c0 F = U16MSBOperation.populate_msb (populateA B c0 F)[1] from by + rw [show srwMsb B c0 F = U16MSBOperation.populate_msb (populateA B c0 F)[1] by rw [srwMsb, if_pos h13]] exact (U16MSBOperation.spec_populate (populateA_val_lt B c0 F hbUw hf0 hf1 hf2 hf3 hsum01 1 (by norm_num)) 1).2 rfl diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Math.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Math.lean index 665f9410..62201c09 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Math.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Math.lean @@ -9,42 +9,71 @@ functions (`RV64.srl`/`sra`/`srlw`/`sraw` from `RISCV/Instructions.lean`). Split (the chip-folder convention, mirroring `DivRemChip/Math.lean`): these are pure `Word`/`BitVec`/`ZMod p` lemmas with no circuit context. -Several carry `set_option debug.skipKernelTC true in` to isolate the documented `2^64` kernel -deep-recursion landmine (`BitVec.toNat_ushiftRight` at `BitVec 64` plants `2^64`; see `docs/agents/proof-patterns.md`). -The option only skips a kernel *re-check* — it adds **no** axiom (`#print axioms` still shows the standard -`[propext, Classical.choice, Quot.sound]`). Keep those `set_option`/`omit` lines verbatim. -/ +The `RV64.srl`/`sra` `.toNat` reductions plant a `2^64` that the kernel deep-recurses on when reduced +over a concrete `Word`-derived `BitVec 64` (`BitVec.toNat_ushiftRight`/`sshiftRight` via the +`@[expose]` shift bodies; see `docs/agents/proof-patterns.md`). Rather than suppress the kernel, +we isolate that unfold into abstract-`BitVec` bridges +(`srl_toNat`/`sra_toNat_{false,true}`, kernel-checked once over variables) and mask every `2^N` power +to a concrete literal — the `ShiftLeftChip` discipline. All lemmas are kernel-clean and axiom-clean +(`#print axioms` = `[propext, Classical.choice, Quot.sound]`). -/ namespace SP1Clean.ShiftRightChip variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 17 < p)] local instance : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩ -set_option debug.skipKernelTC true in +/-! ### Abstract-`BitVec` RV64 shift bridges (kernel-clean isolation of the `2^64` landmine) + +`RV64.srl`/`RV64.sra` unfold (via `BitVec.ushiftRight`/`sshiftRight`'s `@[expose]` bodies) plants a +`2^64` the kernel deep-recurses on **when reduced over a concrete `Word`-derived `BitVec 64`**. We +isolate that unfold into these three lemmas over **abstract** `BitVec 64` arguments, where the `2^64` +body is kernel-checked once over variables (the discipline `ShiftLeftChip`'s `sll_rv64_eq` uses). The +`_div_to_bitvec` wrappers then only apply `BitVec.eq_of_toNat_eq` plus the clean `Word`-level +shift-count bridge — they never re-unfold a shift over a `Word` value, so they stay kernel-clean. -/ + +/-- `(RV64.srl c b).toNat` as a Nat division, over abstract `BitVec 64` (isolates the `2^64` unfold). -/ +lemma srl_toNat (c b : BitVec 64) : (RV64.srl c b).toNat = b.toNat / 2 ^ (c.toNat % 64) := by + have hsh : (BitVec.extractLsb 5 0 c).toNat = c.toNat % 64 := by + simp only [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero] + simp only [RV64.srl] + rw [BitVec.ushiftRight_eq', BitVec.toNat_ushiftRight, Nat.shiftRight_eq_div_pow, hsh] + +/-- `(RV64.sra c b).toNat` on a non-negative `b` (msb = 0): arithmetic = logical shift. -/ +lemma sra_toNat_false (c b : BitVec 64) (h_msb : b.msb = false) : + (RV64.sra c b).toNat = b.toNat / 2 ^ (c.toNat % 64) := by + have hsh : (BitVec.extractLsb 5 0 c).toNat = c.toNat % 64 := by + simp only [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero] + simp only [RV64.sra] + rw [BitVec.sshiftRight_eq', BitVec.toNat_sshiftRight_of_msb_false h_msb, + Nat.shiftRight_eq_div_pow, hsh] + +/-- `(RV64.sra c b).toNat` on a negative `b` (msb = 1): the sign-filled complement form. -/ +lemma sra_toNat_true (c b : BitVec 64) (h_msb : b.msb = true) : + (RV64.sra c b).toNat = 2 ^ 64 - 1 - (2 ^ 64 - 1 - b.toNat) / 2 ^ (c.toNat % 64) := by + have hsh : (BitVec.extractLsb 5 0 c).toNat = c.toNat % 64 := by + simp only [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero] + simp only [RV64.sra] + rw [BitVec.sshiftRight_eq', BitVec.toNat_sshiftRight_of_msb_true h_msb, + Nat.shiftRight_eq_div_pow, hsh] + omit [Fact (Nat.Prime p)] in -/-- **Goal-shape conversion for SRL, lifted to dodge the `2^64` kernel deep-recursion.** -`BitVec.toNat_ushiftRight` at `BitVec 64` plants `2^64` via `BitVec.ushiftRight`'s body, tripping -the kernel re-check. Lifting isolates the trigger to this lemma, and `debug.skipKernelTC` skips its -kernel re-check (`#print axioms` still shows the standard set — the option only removes the -re-verification layer for the known `2^64` shape). -Reduces the `RV64.srl` Spec equality to the Nat division form the `srl_close_su16_*` lemmas produce, with -the shift count already normalised to `rs2[0].val % 64`. -/ +/-- **Goal-shape conversion for SRL.** Reduces the `RV64.srl` Spec equality to the Nat division form the +`srl_close_su16_*` lemmas produce, with the shift count normalised to `rs2[0].val % 64`. The `2^64` +kernel landmine is isolated in `srl_toNat`; here only `BitVec.eq_of_toNat_eq` and the clean shift-count +bridge are used. -/ lemma srl_div_to_bitvec (W rs1 rs2 : Word (ZMod p)) (h_rs2U : Word.isU64 rs2) (hdiv : (Word.toBitVec64 W).toNat = (Word.toBitVec64 rs1).toNat / 2 ^ (rs2[0].val % 64)) : Word.toBitVec64 W = RV64.srl (Word.toBitVec64 rs2) (Word.toBitVec64 rs1) := by - have he : (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat = rs2[0].val % 64 := by - rw [show (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat - = (Word.toBitVec64 rs2).toNat % 2 ^ 6 from by - simp [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero]] - rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def]; omega - rw [← BitVec.toNat_inj] - simp only [RV64.srl] - rw [BitVec.ushiftRight_eq'] - rw [BitVec.toNat_ushiftRight] - simp only [Nat.shiftRight_eq_div_pow] - rw [he]; exact hdiv + have hsh : (Word.toBitVec64 rs2).toNat % 64 = rs2[0].val % 64 := by + rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def, + show (2:ℕ)^16 = 65536 by norm_num, show (2:ℕ)^32 = 4294967296 by norm_num, + show (2:ℕ)^48 = 281474976710656 by norm_num] + omega + apply BitVec.eq_of_toNat_eq + rw [srl_toNat, hsh] + exact hdiv -set_option debug.skipKernelTC true in omit [Fact (Nat.Prime p)] in /-- **SRA goal-shape conversion, MSB = 0 arm.** On a non-negative `rs1` (`toBitVec64 rs1`.msb = false), arithmetic shift = logical shift, so `RV64.sra` reduces to the same Nat division form as `RV64.srl`. -/ @@ -52,18 +81,15 @@ lemma sra_div_to_bitvec_false (W rs1 rs2 : Word (ZMod p)) (h_rs2U : Word.isU64 r (h_msb : (Word.toBitVec64 rs1).msb = false) (hdiv : (Word.toBitVec64 W).toNat = (Word.toBitVec64 rs1).toNat / 2 ^ (rs2[0].val % 64)) : Word.toBitVec64 W = RV64.sra (Word.toBitVec64 rs2) (Word.toBitVec64 rs1) := by - have he : (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat = rs2[0].val % 64 := by - rw [show (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat - = (Word.toBitVec64 rs2).toNat % 2 ^ 6 from by - simp [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero]] - rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def]; omega - rw [← BitVec.toNat_inj] - simp only [RV64.sra] - rw [BitVec.sshiftRight_eq', BitVec.toNat_sshiftRight_of_msb_false h_msb] - simp only [Nat.shiftRight_eq_div_pow] - rw [he]; exact hdiv + have hsh : (Word.toBitVec64 rs2).toNat % 64 = rs2[0].val % 64 := by + rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def, + show (2:ℕ)^16 = 65536 by norm_num, show (2:ℕ)^32 = 4294967296 by norm_num, + show (2:ℕ)^48 = 281474976710656 by norm_num] + omega + apply BitVec.eq_of_toNat_eq + rw [sra_toNat_false _ _ h_msb, hsh] + exact hdiv -set_option debug.skipKernelTC true in omit [Fact (Nat.Prime p)] in /-- **SRA goal-shape conversion, MSB = 1 arm.** On a negative `rs1`, arithmetic shift fills the high bits with the sign, giving the `2^64 - 1 - (2^64 - 1 - rs1) / 2^shamt` form (`toNat_sshiftRight_of_msb_true`). -/ @@ -72,32 +98,30 @@ lemma sra_div_to_bitvec_true (W rs1 rs2 : Word (ZMod p)) (h_rs2U : Word.isU64 rs (hsra : (Word.toBitVec64 W).toNat = 2 ^ 64 - 1 - (2 ^ 64 - 1 - (Word.toBitVec64 rs1).toNat) / 2 ^ (rs2[0].val % 64)) : Word.toBitVec64 W = RV64.sra (Word.toBitVec64 rs2) (Word.toBitVec64 rs1) := by - have he : (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat = rs2[0].val % 64 := by - rw [show (BitVec.extractLsb 5 0 (Word.toBitVec64 rs2)).toNat - = (Word.toBitVec64 rs2).toNat % 2 ^ 6 from by - simp [BitVec.extractLsb, BitVec.extractLsb'_toNat, Nat.shiftRight_zero]] - rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def]; omega - rw [← BitVec.toNat_inj] - simp only [RV64.sra] - rw [BitVec.sshiftRight_eq', BitVec.toNat_sshiftRight_of_msb_true h_msb] - simp only [Nat.shiftRight_eq_div_pow] - rw [he]; exact hsra + have hsh : (Word.toBitVec64 rs2).toNat % 64 = rs2[0].val % 64 := by + rw [Word.toBitVec64_toNat h_rs2U, Word.toNat_def, + show (2:ℕ)^16 = 65536 by norm_num, show (2:ℕ)^32 = 4294967296 by norm_num, + show (2:ℕ)^48 = 281474976710656 by norm_num] + omega + apply BitVec.eq_of_toNat_eq + rw [sra_toNat_true _ _ h_msb, hsh] + exact hsra -set_option debug.skipKernelTC true in omit [Fact (Nat.Prime p)] [Fact (2 ^ 17 < p)] in /-- The MSB of `rs1`'s low 32 bits is the high bit of limb 1 (`rs1[1] ≥ 2^15`). The SRAW sign bit. -Lifted with `debug.skipKernelTC` to dodge the `2^64` kernel deep-recursion (same landmine as the -`*_div_to_bitvec` helpers — `Word.toBitVec64` plants `2^64` via `BitVec` reduction). -/ +Mirrors the kernel-clean `ShiftRightMath.toBitVec64_msb_eq_b3_ge` template: a `rw` chain (not +`simp only`) plus full concrete-literal masking of every `2^N` power (incl. `2^48`), so the kernel +never deep-recurses on a symbolic power. -/ lemma low32_msb_eq_b1 [NeZero p] {b : Word (ZMod p)} (h_isU64 : Word.isU64 b) : (BitVec.extractLsb' 0 32 (Word.toBitVec64 b)).msb = decide (b[1].val ≥ 32768) := by obtain ⟨b0_16, b1_16, _, _⟩ := Word.lt_cases_of_isU64 h_isU64 - rw [BitVec.msb_eq_decide] - simp only [BitVec.extractLsb'_toNat, Word.toBitVec64_toNat h_isU64, Word.toNat_def, - Nat.shiftRight_zero] + rw [BitVec.msb_eq_decide, BitVec.extractLsb'_toNat, Nat.shiftRight_zero, + Word.toBitVec64_toNat h_isU64, Word.toNat_def] have e16 : (2 : ℕ) ^ 16 = 65536 := by decide have e31 : (2 : ℕ) ^ (32 - 1) = 2147483648 := by decide have e32 : (2 : ℕ) ^ 32 = 4294967296 := by decide - rw [e16, e31, e32] at * + have e48 : (2 : ℕ) ^ 48 = 281474976710656 := by decide + rw [e16, e31, e32, e48] at * congr 1 apply propext constructor @@ -114,7 +138,6 @@ lemma hword_toNat {a b : ZMod p} (ha : a.val < 2 ^ 16) (hb : b.val < 2 ^ 16) : List.getElem_toArray, List.getElem_cons_zero, List.getElem_cons_succ] omega -set_option debug.skipKernelTC true in /-- **SRLW goal-shape conversion.** The committed result word `Wd` is the 64-bit sign-extension of the low-32 logical shift. Uses `toBitVec64_signExtend_word` (the W-variant keystone): `Wd[2] = Wd[3] = m*65535` sign-fill with `m` the output high bit (`srw_msb` on `a[1]`), and the low two limbs carry the 32-bit logical @@ -145,7 +168,6 @@ lemma srlw_div_to_bitvec (Wd rs1 rs2 : Word (ZMod p)) hlow, hshamt]; exact hdiv.symm rw [toBitVec64_signExtend_word Wd _ m hr0 hr1 hm hr2 hr3 hX]; rfl -set_option debug.skipKernelTC true in /-- **SRAW goal-shape conversion, MSB = 0 arm.** On a non-negative low-32 input, the arithmetic word-shift equals the logical one, so the result is `signExtend 64` of the same division form as SRLW. -/ lemma sraw_div_to_bitvec_false (Wd rs1 rs2 : Word (ZMod p)) @@ -174,7 +196,6 @@ lemma sraw_div_to_bitvec_false (Wd rs1 rs2 : Word (ZMod p)) rw [toBitVec64_signExtend_word Wd _ m hr0 hr1 hm hr2 hr3 hX] simp only [RV64.sraw, BitVec.extractLsb] -set_option debug.skipKernelTC true in /-- **SRAW goal-shape conversion, MSB = 1 arm.** On a negative low-32 input, the arithmetic word-shift fills with the sign, giving the `2^32 - 1 - (2^32 - 1 - rs1.low32) / 2^shamt` complement form (`toNat_sshiftRight_of_msb_true` at width 32), then `signExtend 64`. -/ diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sra.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sra.lean index 09741849..c37fc996 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sra.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sra.lean @@ -163,9 +163,9 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have he := hbyte_fact h_byte0 rw [hc0e] at he have h10v : ((10 : ZMod p)).val = 10 := by - rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by have := Fact.out (p := 2 ^ 17 < p); omega) - rw [h10v, show (2 : ℕ) ^ 10 = 1024 from by norm_num] at he + rw [h10v, show (2 : ℕ) ^ 10 = 1024 by norm_num] at he rw [c0mod_inv_bridge]; exact he -- Bridge the byte-pull range facts to the `srl_close_su16_*_case` exponent form. rw [cb4sum_natCast] at lt_ll0 lt_ll1 lt_ll2 lt_ll3 @@ -175,7 +175,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) -- Alias the shift-result columns. @@ -496,7 +496,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[3] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 3, bool_of_mul_pred h_sra_b⟩ case msb2A => @@ -504,7 +504,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[1] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 1, bool_of_mul_pred h_sraw_b⟩ case msb3A => @@ -584,7 +584,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) obtain ⟨-, h_ll2_0⟩ := ShiftRightMath.higher_lower_zero b_cb0 b_cb1 b_cb2 b_cb3 diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sraw.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sraw.lean index 696d666b..020934bd 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sraw.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Sraw.lean @@ -143,7 +143,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) obtain ⟨-, h_ll2_0⟩ := ShiftRightMath.higher_lower_zero b_cb0 b_cb1 b_cb2 b_cb3 @@ -154,9 +154,9 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have he := hbyte_fact h_byte0 rw [hc0e] at he have h10v : ((10 : ZMod p)).val = 10 := by - rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) - rw [h10v, show (2 : ℕ) ^ 10 = 1024 from by norm_num] at he + rw [h10v, show (2 : ℕ) ^ 10 = 1024 by norm_num] at he rw [c0mod_inv_bridge]; exact he set su0 := env.get (i₀ + 4 + 1 + 1 + 6 + 1 + 3 + 4 + 4 + 4) with hsu0_def set su1 := env.get (i₀ + 4 + 1 + 1 + 6 + 1 + 3 + 4 + 4 + 4 + 1) with hsu1_def @@ -433,7 +433,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[3] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 3, bool_of_mul_pred h_sra_b⟩ case msb2A => @@ -441,7 +441,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[1] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 1, bool_of_mul_pred h_sraw_b⟩ case msb3A => @@ -521,7 +521,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) obtain ⟨-, h_ll2_0⟩ := ShiftRightMath.higher_lower_zero b_cb0 b_cb1 b_cb2 b_cb3 diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srl.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srl.lean index ff2379f8..e6748741 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srl.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srl.lean @@ -167,9 +167,9 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have he := hbyte_fact h_byte0 rw [hc0e] at he have h10v : ((10 : ZMod p)).val = 10 := by - rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by have := Fact.out (p := 2 ^ 17 < p); omega) - rw [h10v, show (2 : ℕ) ^ 10 = 1024 from by norm_num] at he + rw [h10v, show (2 : ℕ) ^ 10 = 1024 by norm_num] at he rw [c0mod_inv_bridge]; exact he rw [h_c0mod] -- Bridge the byte-pull range facts to the `srl_close_su16_*_case` exponent form. @@ -180,7 +180,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) -- Alias the shift-result columns. @@ -346,7 +346,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[3] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 3, bool_of_mul_pred h_sra_b⟩ case msb2A => @@ -354,7 +354,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[1] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 1, bool_of_mul_pred h_sraw_b⟩ case msb3A => @@ -434,7 +434,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) obtain ⟨-, h_ll2_0⟩ := ShiftRightMath.higher_lower_zero b_cb0 b_cb1 b_cb2 b_cb3 diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srlw.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srlw.lean index c1628e28..e67241ba 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srlw.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Soundness/Srlw.lean @@ -147,7 +147,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) -- `ll2 = 0`. @@ -160,9 +160,9 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have he := hbyte_fact h_byte0 rw [hc0e] at he have h10v : ((10 : ZMod p)).val = 10 := by - rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (10 : ZMod p) = ((10 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) - rw [h10v, show (2 : ℕ) ^ 10 = 1024 from by norm_num] at he + rw [h10v, show (2 : ℕ) ^ 10 = 1024 by norm_num] at he rw [c0mod_inv_bridge]; exact he -- Alias the byte-shift selectors + limb_result; one-hot + reassembly. set su0 := env.get (i₀ + 4 + 1 + 1 + 6 + 1 + 3 + 4 + 4 + 4) with hsu0_def @@ -311,7 +311,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[3] - = input_adapter_op_b_memory_prev_value[3] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[3] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 3, bool_of_mul_pred h_sra_b⟩ case msb2A => @@ -319,7 +319,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨-, -, -, -, -, -, ⟨h_obmap, -, -⟩, -, -, -⟩ := h_input exact Or.inr ⟨fun _ => by rw [show Expression.eval env input_var_adapter_op_b_memory_prev_value[1] - = input_adapter_op_b_memory_prev_value[1] from by rw [← h_obmap]; simp only [Vector.getElem_map]] + = input_adapter_op_b_memory_prev_value[1] by rw [← h_obmap]; simp only [Vector.getElem_map]] exact h_rs1U 1, bool_of_mul_pred h_sraw_b⟩ case msb3A => @@ -399,7 +399,7 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe intro h; have := val_2_zmod_p (p := p); rw [h, ZMod.val_zero] at this; exact absurd this (by norm_num) have hne3 : (3 : ZMod p) ≠ 0 := by intro h; have h3 : (3 : ZMod p).val = 3 := by - rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by push_cast; rfl] + rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) by push_cast; rfl] exact ZMod.val_natCast_of_lt (by omega) rw [h, ZMod.val_zero] at h3; exact absurd h3 (by norm_num) obtain ⟨-, h_ll2_0⟩ := ShiftRightMath.higher_lower_zero b_cb0 b_cb1 b_cb2 b_cb3 diff --git a/SP1Clean/Proofs/Chips/StoreByteChip/Bridge.lean b/SP1Clean/Proofs/Chips/StoreByteChip/Bridge.lean index 1babf855..977f338f 100644 --- a/SP1Clean/Proofs/Chips/StoreByteChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/StoreByteChip/Bridge.lean @@ -47,7 +47,7 @@ theorem correct_store_byte_native (Sail.BitVec.extractLsb stored 7 0)).run s := by have hse : (sign_extend imm : BitVec 64) = BitVec.signExtend 64 imm := by simp [sign_extend] have hpc_get : s.regs.get Register.PC (hs _) = pc := by - rw [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc; exact h_pc + rwa [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc set sp : SailState := { s with regs := s.regs.insert Register.nextPC (pc + 4#64) } with hsp have hsp_init : SailState.isInitialized sp := SailState.isInitialized_insert s hs Register.nextPC (pc + 4#64) @@ -81,10 +81,6 @@ theorem correct_store_byte_native have hwrite := run_vmem_write_of_width_1 rs1_idx reg_val (BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 7 0) sp hsp_init hsp_rs1 h_align' hsp_config h_does_fit h_in_range simp only [hmem_eq] at hwrite - have hrx2 : rX_bits (regidx.Regidx rs2_idx) sp = .ok stored sp := by - have h := @run_rX_bits rs2_idx sp - simp only [EStateM.run] at h - rw [h, hsp_rs2] have hsp1 : (sp1_sb pc (reg_val + BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 7 0)).run s = .ok RETIRE_SUCCESS { sp with mem := diff --git a/SP1Clean/Proofs/Chips/StoreByteChip/Formal.lean b/SP1Clean/Proofs/Chips/StoreByteChip/Formal.lean index c3da7ef3..f0bcf771 100644 --- a/SP1Clean/Proofs/Chips/StoreByteChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/StoreByteChip/Formal.lean @@ -37,32 +37,18 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe hmap_ob, _, _, _, _, hmap_sv⟩ := h_input have eoap0 : Expression.eval env input_var_adapter_op_a_memory_prev_value[0] = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env input_var_offset_bit[1] = input_offset_bit[1] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env input_var_offset_bit[2] = input_offset_bit[2] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env input_var_offset_bit[0] = input_offset_bit[0] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have esv0 : Expression.eval env input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - simp only [eob1, eob2, epv0, epv1, epv2, epv3, ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 - simp only [eob0, ← sub_eq_add_neg] at hincr - simp only [esv0, esv1, esv2, esv3, epv0, epv1, epv2, epv3, eob1, eob2, ← sub_eq_add_neg] - at hr0 hr1 hr2 hr3 + have eob : ∀ i (hi : i < 3), Expression.eval env input_var_offset_bit[i] = input_offset_bit[i] := + fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + have esv : ∀ i (hi : i < 4), Expression.eval env input_var_store_value[i] = input_store_value[i] := + fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] + simp only [eob 1 (by omega), eob 2 (by omega), epv 0 (by omega), epv 1 (by omega), + epv 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg] at hsel0 hsel1 hsel2 hsel3 + simp only [eob 0 (by omega), ← sub_eq_add_neg] at hincr + simp only [esv 0 (by omega), esv 1 (by omega), esv 2 (by omega), esv 3 (by omega), + epv 0 (by omega), epv 1 (by omega), epv 2 (by omega), epv 3 (by omega), + eob 1 (by omega), eob 2 (by omega), ← sub_eq_add_neg] at hr0 hr1 hr2 hr3 -- the real-row byte bounds, from the two inline U8Range-pair receives. have h_bytes : input_is_real = 1 → input_register_low_byte.val < 256 ∧ @@ -78,23 +64,23 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe rw [show (2:ℕ)^8 = 256 from by norm_num, ← sub_eq_add_neg] at hbm exact ⟨hbr.1, hbr.2, hbm.1, hbm.2⟩ have hob0' : Expression.eval env input_var_offset_bit[0] = 0 - ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env input_var_offset_bit[1] = 0 - ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env input_var_offset_bit[2] = 0 - ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env input_var_offset_bit[0]).val + 2 * (Expression.eval env input_var_offset_bit[1]).val + 4 * (Expression.eval env input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env input_var_offset_bit[0], Expression.eval env input_var_offset_bit[1], Expression.eval env input_var_offset_bit[2]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, hob0', hob1', hob2', h_ge, h_off'⟩ have h_addr_spec := h_addr h_addr_as - simp only [eob0, eob1, eob2] at h_addr_spec + simp only [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)] at h_addr_spec have h_it := h_itype h_bin refine ⟨⟨h_addr_spec, h_mem h_bin, h_it, fun h1 => ⟨(h_bytes h1).1, (h_bytes h1).2.1, (h_bytes h1).2.2.1, (h_bytes h1).2.2.2⟩, @@ -156,45 +142,25 @@ theorem completeness : have eoap0 : Expression.eval env.toEnvironment input_var_adapter_op_a_memory_prev_value[0] = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] simp only [regHigh, memHigh] at hreghi_pa hmemhi_pa - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env.toEnvironment input_var_offset_bit[0] - = input_offset_bit[0] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env.toEnvironment input_var_offset_bit[1] - = input_offset_bit[1] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob2 : Expression.eval env.toEnvironment input_var_offset_bit[2] - = input_offset_bit[2] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have esv0 : Expression.eval env.toEnvironment input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env.toEnvironment input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env.toEnvironment input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env.toEnvironment input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_offset_bit[i] + = input_offset_bit[i] := fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + have esv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_store_value[i] + = input_store_value[i] := fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] have hob0' : Expression.eval env.toEnvironment input_var_offset_bit[0] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env.toEnvironment input_var_offset_bit[1] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have hob2' : Expression.eval env.toEnvironment input_var_offset_bit[2] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob2]; exact hob2 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[2] = 1 := by rw [eob 2 (by omega)]; exact hob2 have h_off' : (Expression.eval env.toEnvironment input_var_offset_bit[0]).val + 2 * (Expression.eval env.toEnvironment input_var_offset_bit[1]).val + 4 * (Expression.eval env.toEnvironment input_var_offset_bit[2]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1, eob2]; exact h_off + rw [eob 0 (by omega), eob 1 (by omega), eob 2 (by omega)]; exact h_off have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, Expression.eval env.toEnvironment input_var_offset_bit[0], Expression.eval env.toEnvironment input_var_offset_bit[1], @@ -202,7 +168,7 @@ theorem completeness : ⟨ha, hb, hfit, hob0', hob1', hob2', h_ge, h_off'⟩ refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · exact hbin @@ -213,15 +179,23 @@ theorem completeness : · intro _ simp only [byteChannel]; rw [← sub_eq_add_neg] exact (byteRowSpec_u8range_pair _ _).mpr ⟨hmem_pa, hmemhi_pa⟩ - · simp only [eob1, eob2, epv0, ← sub_eq_add_neg]; exact hsel0 - · simp only [eob1, eob2, epv1, ← sub_eq_add_neg]; exact hsel1 - · simp only [eob1, eob2, epv2, ← sub_eq_add_neg]; exact hsel2 - · simp only [eob1, eob2, epv3, ← sub_eq_add_neg]; exact hsel3 - · simp only [eob0, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hincr_pa - · simp only [esv0, eob1, eob2, epv0, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr0 - · simp only [esv1, eob1, eob2, epv1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr1 - · simp only [esv2, eob1, eob2, epv2, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr2 - · simp only [esv3, eob1, eob2, epv3, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr3 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 0 (by omega), ← sub_eq_add_neg]; exact hsel0 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 1 (by omega), ← sub_eq_add_neg]; exact hsel1 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 2 (by omega), ← sub_eq_add_neg]; exact hsel2 + · simp only [eob 1 (by omega), eob 2 (by omega), epv 3 (by omega), ← sub_eq_add_neg]; exact hsel3 + · simp only [eob 0 (by omega), ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hincr_pa + · simp only [esv 0 (by omega), eob 1 (by omega), eob 2 (by omega), epv 0 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr0 + · simp only [esv 1 (by omega), eob 1 (by omega), eob 2 (by omega), epv 1 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr1 + · simp only [esv 2 (by omega), eob 1 (by omega), eob 2 (by omega), epv 2 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr2 + · simp only [esv 3 (by omega), eob 1 (by omega), eob 2 (by omega), epv 3 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr3 · rcases hbin with h | h <;> rw [h] <;> simp /-- The `StoreByte` chip row as a `GeneralFormalCircuit`; output is the extracted `StoreByteColumns`. -/ diff --git a/SP1Clean/Proofs/Chips/StoreDoubleChip/Bridge.lean b/SP1Clean/Proofs/Chips/StoreDoubleChip/Bridge.lean index 64311c3a..b7e06789 100644 --- a/SP1Clean/Proofs/Chips/StoreDoubleChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/StoreDoubleChip/Bridge.lean @@ -83,29 +83,24 @@ theorem correct_store_double_native h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } -- Register reads of `rs1` (base) and `rs2` (stored value) survive the `nextPC` write. have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hsp_rs2 : sp.get_reg? rs2_idx = some (Word.toBitVec64 stored) := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs2 + rwa [hsp, SailState.get_reg?_insert_nextPC] -- The alignment fact in Sail form, and the range-subset PMA fact. have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega have h_align' : is_aligned_vaddr (virtaddr.Virtaddr (reg_val + BitVec.signExtend 64 imm)) 8 = true := by rw [is_aligned_vaddr_iff_mod, hadd]; exact h_aligned - have h_does_fit' : reg_val.toNat + (BitVec.signExtend 64 imm).toNat + 8 < 2 ^ 64 := _h_does_fit have h_in_range : range_subset (zero_extend (BitVec.addInt (reg_val + BitVec.signExtend 64 imm) 0)) (to_bits 8) (2#64 ^ 16) (2#64 ^ 48 - 2#64 ^ 16) = true := range_subset_sp1_pma _ 8 (by omega) h_lo (by rw [hadd]; exact h_hi) -- The write result on `sp` (same memory as `s`). have hwrite := run_vmem_write_of_width_8 rs1_idx reg_val (BitVec.signExtend 64 imm) - (Word.toBitVec64 stored) sp hsp_init hsp_rs1 h_align' hsp_config h_does_fit' h_in_range + (Word.toBitVec64 stored) sp hsp_init hsp_rs1 h_align' hsp_config _h_does_fit h_in_range simp only [hmem_eq] at hwrite - -- Resolve `rX_bits rs2` (the stored-value register) on `sp`, and collapse the `extractLsb`. - have hrx2 : rX_bits (regidx.Regidx rs2_idx) sp = .ok (Word.toBitVec64 stored) sp := by - have h := @run_rX_bits rs2_idx sp - simp only [EStateM.run] at h - rw [h, hsp_rs2] + -- Collapse the `extractLsb` on the stored value. have hext : Sail.BitVec.extractLsb (Word.toBitVec64 stored) 63 0 = Word.toBitVec64 stored := by simp [Sail.BitVec.extractLsb, BitVec.extractLsb, BitVec.extractLsb'] -- Reduce the SP1 side: thread the `nextPC` write, then run the `modify` of memory. @@ -128,9 +123,7 @@ theorem correct_store_double_native (BitVec.ofNat 8 ((Word.toBitVec64 stored).toNat >>> 48))).insert ((reg_val + BitVec.signExtend 64 imm).toNat + 7) (BitVec.ofNat 8 ((Word.toBitVec64 stored).toNat >>> 56))) } := by - rw [sp1_sd] - rw [EStateM.run_bind, run_writeReg] - rfl + rw [sp1_sd, EStateM.run_bind, run_writeReg]; rfl rw [hsp1] simp only [spec_sd] rw [EStateM.run_bind, run_readReg_of_isInitialized _ _ hs, hpc_get] @@ -158,7 +151,7 @@ theorem sd_chip_reaches_sail = (sp1_sd pc (Word.toBitVec64 input.op_b_val + BitVec.signExtend 64 imm) input.adapter.op_a_memory.prev_value).run s := by haveI : NeZero p := ⟨(Fact.out (p := p.Prime)).pos.ne'⟩ - obtain ⟨h_b, h_c, _h_fits48, h_nonres, h_align48⟩ := h_assum + obtain ⟨h_b, h_c, _h_fits48, _h_nonres, h_align48⟩ := h_assum have hreg : (Word.toBitVec64 input.op_b_val).toNat = Word.toNat input.op_b_val := Word.toBitVec64_toNat h_b have hoff : (BitVec.signExtend 64 imm).toNat = Word.toNat input.op_c_imm := by diff --git a/SP1Clean/Proofs/Chips/StoreDoubleChip/Formal.lean b/SP1Clean/Proofs/Chips/StoreDoubleChip/Formal.lean index 51867bec..483e55eb 100644 --- a/SP1Clean/Proofs/Chips/StoreDoubleChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/StoreDoubleChip/Formal.lean @@ -29,15 +29,14 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe obtain ⟨ha, hb, hfit, h_ge, h_align⟩ := h_assumptions obtain ⟨_h_cpu, h_addr, h_mem, h_itype, h_gate⟩ := h_holds have h_bin := bool_of_mul_pred h_gate - have h_it := h_itype h_bin -- the `AddressOperation` Assumptions: operand `isU64`s + fits, the offset bits boolean (literal `0`), -- and the address-validity (non-reserved + 8-aligned, so the inverse gate / offset range check hold). have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, 0, 0⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, Or.inl rfl, Or.inl rfl, h_ge, by simp only [ZMod.val_zero]; omega⟩ - refine ⟨⟨h_addr h_addr_as, h_mem h_bin, h_it, h_bin⟩, ?_⟩ -- the per-subcircuit channel-requirement tail (`channels = [] ∨ .Assumptions`). - exact ⟨Or.inr h_bin, Or.inr h_addr_as, Or.inr h_bin, Or.inr h_bin⟩ + exact ⟨⟨h_addr h_addr_as, h_mem h_bin, h_itype h_bin, h_bin⟩, + Or.inr h_bin, Or.inr h_addr_as, Or.inr h_bin, Or.inr h_bin⟩ /-- Prover-side row well-formedness (3-arg form): operand `isU64`s + address-fits bound + the reader clock/timestamp `Spec`s + `is_real` binary. -/ @@ -65,22 +64,13 @@ theorem completeness : -- eval→value bridge for the nested `pc` vector the CPUState `Spec` references. have hmap_pc : Vector.map (Expression.eval env.toEnvironment) input_var_state_pc = input_state_pc := h_input.2.1.2.2.2 - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] - = input_state_pc[0] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] - = input_state_pc[1] := by rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] - = input_state_pc[2] := by rw [← hmap_pc]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, 0, 0⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, Or.inl rfl, Or.inl rfl, h_ge, by simp only [ZMod.val_zero]; omega⟩ - refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, ?_⟩ - · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu - · exact hbin - · exact h_mem - · exact hbin - · exact h_it + refine ⟨⟨hbin, ?_⟩, h_addr_as, ⟨hbin, h_mem⟩, ⟨hbin, h_it⟩, ?_⟩ + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · rcases hbin with h | h <;> rw [h] <;> simp /-- The `StoreDouble` chip row as a `GeneralFormalCircuit`; output is the extracted `StoreDoubleColumns`. -/ diff --git a/SP1Clean/Proofs/Chips/StoreHalfChip/Bridge.lean b/SP1Clean/Proofs/Chips/StoreHalfChip/Bridge.lean index 14280af7..b24033dc 100644 --- a/SP1Clean/Proofs/Chips/StoreHalfChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/StoreHalfChip/Bridge.lean @@ -50,7 +50,7 @@ theorem correct_store_half_native (Sail.BitVec.extractLsb stored 15 0)).run s := by have hse : (sign_extend imm : BitVec 64) = BitVec.signExtend 64 imm := by simp [sign_extend] have hpc_get : s.regs.get Register.PC (hs _) = pc := by - rw [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc; exact h_pc + rwa [Std.ExtDHashMap.get?_eq_some_get (hs _), Option.some_inj] at h_pc set sp : SailState := { s with regs := s.regs.insert Register.nextPC (pc + 4#64) } with hsp have hsp_init : SailState.isInitialized sp := SailState.isInitialized_insert s hs Register.nextPC (pc + 4#64) @@ -84,10 +84,6 @@ theorem correct_store_half_native have hwrite := run_vmem_write_of_width_2 rs1_idx reg_val (BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 15 0) sp hsp_init hsp_rs1 h_align' hsp_config h_does_fit h_in_range simp only [hmem_eq] at hwrite - have hrx2 : rX_bits (regidx.Regidx rs2_idx) sp = .ok stored sp := by - have h := @run_rX_bits rs2_idx sp - simp only [EStateM.run] at h - rw [h, hsp_rs2] have hsp1 : (sp1_sh pc (reg_val + BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 15 0)).run s = .ok RETIRE_SUCCESS { sp with mem := diff --git a/SP1Clean/Proofs/Chips/StoreHalfChip/Formal.lean b/SP1Clean/Proofs/Chips/StoreHalfChip/Formal.lean index 98a9a867..7bcbdfae 100644 --- a/SP1Clean/Proofs/Chips/StoreHalfChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/StoreHalfChip/Formal.lean @@ -42,45 +42,32 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe = input_memory_access_prev_value := h_input.2.2.2.1.1 have hmap_oap : Vector.map (Expression.eval env) input_var_adapter_op_a_memory_prev_value = input_adapter_op_a_memory_prev_value := h_input.2.2.1.2.1.1 - have eob0 : Expression.eval env input_var_offset_bit[0] = input_offset_bit[0] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env input_var_offset_bit[1] = input_offset_bit[1] := by - rw [← hmap_ob]; simp only [Vector.getElem_map] - have esv0 : Expression.eval env input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 2), Expression.eval env input_var_offset_bit[i] = input_offset_bit[i] := + fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have esv : ∀ i (hi : i < 4), Expression.eval env input_var_store_value[i] = input_store_value[i] := + fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] have eoap0 : Expression.eval env input_var_adapter_op_a_memory_prev_value[0] = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] - simp only [esv0, esv1, esv2, esv3, epv0, epv1, epv2, epv3, eoap0, eob0, eob1, ← sub_eq_add_neg] - at hr0 hr1 hr2 hr3 + simp only [esv 0 (by omega), esv 1 (by omega), esv 2 (by omega), esv 3 (by omega), + epv 0 (by omega), epv 1 (by omega), epv 2 (by omega), epv 3 (by omega), + eoap0, eob 0 (by omega), eob 1 (by omega), ← sub_eq_add_neg] at hr0 hr1 hr2 hr3 have h_it := h_itype h_bin have hob0' : Expression.eval env input_var_offset_bit[0] = 0 - ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env input_var_offset_bit[1] = 0 - ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have h_off' : (0 : ZMod p).val + 2 * (Expression.eval env input_var_offset_bit[0]).val + 4 * (Expression.eval env input_var_offset_bit[1]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1]; simp only [ZMod.val_zero]; omega + rw [eob 0 (by omega), eob 1 (by omega)]; simp only [ZMod.val_zero]; omega have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, Expression.eval env input_var_offset_bit[0], Expression.eval env input_var_offset_bit[1]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, hob0', hob1', h_ge, h_off'⟩ have h_addr_spec := h_addr h_addr_as - simp only [eob0, eob1] at h_addr_spec + simp only [eob 0 (by omega), eob 1 (by omega)] at h_addr_spec refine ⟨⟨h_addr_spec, h_mem h_bin, h_it, ⟨sub_eq_zero.mp hr0, sub_eq_zero.mp hr1, sub_eq_zero.mp hr2, sub_eq_zero.mp hr3⟩, h_bin⟩, ?_⟩ exact ⟨Or.inr h_bin, Or.inr h_addr_as, Or.inr h_bin, Or.inr h_bin⟩ @@ -136,57 +123,47 @@ theorem completeness : = input_memory_access_prev_value := h_input.2.2.2.1.1 have hmap_oap : Vector.map (Expression.eval env.toEnvironment) input_var_adapter_op_a_memory_prev_value = input_adapter_op_a_memory_prev_value := h_input.2.2.1.2.1.1 - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have eob0 : Expression.eval env.toEnvironment input_var_offset_bit[0] - = input_offset_bit[0] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have eob1 : Expression.eval env.toEnvironment input_var_offset_bit[1] - = input_offset_bit[1] := by rw [← hmap_ob]; simp only [Vector.getElem_map] - have esv0 : Expression.eval env.toEnvironment input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env.toEnvironment input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env.toEnvironment input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env.toEnvironment input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have eob : ∀ i (hi : i < 2), Expression.eval env.toEnvironment input_var_offset_bit[i] + = input_offset_bit[i] := fun i hi => by rw [← hmap_ob]; simp only [Vector.getElem_map] + have esv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_store_value[i] + = input_store_value[i] := fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] have eoap0 : Expression.eval env.toEnvironment input_var_adapter_op_a_memory_prev_value[0] = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] have hob0' : Expression.eval env.toEnvironment input_var_offset_bit[0] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob0]; exact hob0 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[0] = 1 := by rw [eob 0 (by omega)]; exact hob0 have hob1' : Expression.eval env.toEnvironment input_var_offset_bit[1] = 0 - ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob1]; exact hob1 + ∨ Expression.eval env.toEnvironment input_var_offset_bit[1] = 1 := by rw [eob 1 (by omega)]; exact hob1 have h_off' : (0 : ZMod p).val + 2 * (Expression.eval env.toEnvironment input_var_offset_bit[0]).val + 4 * (Expression.eval env.toEnvironment input_var_offset_bit[1]).val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by - rw [eob0, eob1]; simp only [ZMod.val_zero]; omega + rw [eob 0 (by omega), eob 1 (by omega)]; simp only [ZMod.val_zero]; omega have h_addr_as : AddressOperation.circuit.Assumptions (⟨input_adapter_op_b_memory_prev_value, input_adapter_op_c_imm, 0, Expression.eval env.toEnvironment input_var_offset_bit[0], Expression.eval env.toEnvironment input_var_offset_bit[1]⟩ : AddressOperation.Inputs (ZMod p)) := ⟨ha, hb, hfit, Or.inl rfl, hob0', hob1', h_ge, h_off'⟩ refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · exact hbin · exact h_it - · simp only [esv0, epv0, eoap0, eob0, eob1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr0 - · simp only [esv1, epv1, eoap0, eob0, eob1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr1 - · simp only [esv2, epv2, eoap0, eob0, eob1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr2 - · simp only [esv3, epv3, eoap0, eob0, eob1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr3 + · simp only [esv 0 (by omega), epv 0 (by omega), eoap0, eob 0 (by omega), eob 1 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr0 + · simp only [esv 1 (by omega), epv 1 (by omega), eoap0, eob 0 (by omega), eob 1 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr1 + · simp only [esv 2 (by omega), epv 2 (by omega), eoap0, eob 0 (by omega), eob 1 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr2 + · simp only [esv 3 (by omega), epv 3 (by omega), eoap0, eob 0 (by omega), eob 1 (by omega), + ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr3 · rcases hbin with h | h <;> rw [h] <;> simp /-- The `StoreHalf` chip row as a `GeneralFormalCircuit`; output is the extracted `StoreHalfColumns`. -/ diff --git a/SP1Clean/Proofs/Chips/StoreWordChip/Bridge.lean b/SP1Clean/Proofs/Chips/StoreWordChip/Bridge.lean index d83cdead..48b8c11d 100644 --- a/SP1Clean/Proofs/Chips/StoreWordChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/StoreWordChip/Bridge.lean @@ -71,9 +71,9 @@ theorem correct_store_word_native h_htif_disabled := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hhtif h_pma_regions := by rw [key _ (hs _) (hsp_init _) (by decide)]; exact hpma } have hsp_rs1 : sp.get_reg? rs1_idx = some reg_val := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs1 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hsp_rs2 : sp.get_reg? rs2_idx = some stored := by - rw [hsp, SailState.get_reg?_insert_nextPC]; exact h_rs2 + rwa [hsp, SailState.get_reg?_insert_nextPC] have hadd : (reg_val + BitVec.signExtend 64 imm).toNat = reg_val.toNat + (BitVec.signExtend 64 imm).toNat := by rw [BitVec.toNat_add, Nat.mod_eq_of_lt]; omega @@ -86,10 +86,6 @@ theorem correct_store_word_native have hwrite := run_vmem_write_of_width_4 rs1_idx reg_val (BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 31 0) sp hsp_init hsp_rs1 h_align' hsp_config h_does_fit h_in_range simp only [hmem_eq] at hwrite - have hrx2 : rX_bits (regidx.Regidx rs2_idx) sp = .ok stored sp := by - have h := @run_rX_bits rs2_idx sp - simp only [EStateM.run] at h - rw [h, hsp_rs2] have hsp1 : (sp1_sw pc (reg_val + BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb stored 31 0)).run s = .ok RETIRE_SUCCESS { sp with mem := @@ -128,7 +124,7 @@ theorem sw_chip_reaches_sail = (sp1_sw pc (Word.toBitVec64 input.op_b_val + BitVec.signExtend 64 imm) (Sail.BitVec.extractLsb (Word.toBitVec64 input.adapter.op_a_memory.prev_value) 31 0)).run s := by haveI : NeZero p := ⟨(Fact.out (p := p.Prime)).pos.ne'⟩ - obtain ⟨h_b, h_c, _h_fits48, h_nonres, h_align4, _h_off⟩ := h_assum + obtain ⟨h_b, h_c, _h_fits48, _h_nonres, h_align4, _h_off⟩ := h_assum have hreg : (Word.toBitVec64 input.op_b_val).toNat = Word.toNat input.op_b_val := Word.toBitVec64_toNat h_b have hoff : (BitVec.signExtend 64 imm).toNat = Word.toNat input.op_c_imm := by diff --git a/SP1Clean/Proofs/Chips/StoreWordChip/Formal.lean b/SP1Clean/Proofs/Chips/StoreWordChip/Formal.lean index 68d0fdb3..39277250 100644 --- a/SP1Clean/Proofs/Chips/StoreWordChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/StoreWordChip/Formal.lean @@ -37,28 +37,15 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe = input_memory_access_prev_value := h_input.2.2.2.1.1 have hmap_oap : Vector.map (Expression.eval env) input_var_adapter_op_a_memory_prev_value = input_adapter_op_a_memory_prev_value := h_input.2.2.1.2.1.1 - have esv0 : Expression.eval env input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have eoap0 : Expression.eval env input_var_adapter_op_a_memory_prev_value[0] - = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] - have eoap1 : Expression.eval env input_var_adapter_op_a_memory_prev_value[1] - = input_adapter_op_a_memory_prev_value[1] := by rw [← hmap_oap]; simp only [Vector.getElem_map] - simp only [esv0, esv1, esv2, esv3, epv0, epv1, epv2, epv3, eoap0, eoap1, ← sub_eq_add_neg] - at hr0 hr1 hr2 hr3 + have esv : ∀ i (hi : i < 4), Expression.eval env input_var_store_value[i] = input_store_value[i] := + fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + have eoap : ∀ i (hi : i < 2), Expression.eval env input_var_adapter_op_a_memory_prev_value[i] + = input_adapter_op_a_memory_prev_value[i] := fun i hi => by rw [← hmap_oap]; simp only [Vector.getElem_map] + simp only [esv 0 (by omega), esv 1 (by omega), esv 2 (by omega), esv 3 (by omega), + epv 0 (by omega), epv 1 (by omega), epv 2 (by omega), epv 3 (by omega), + eoap 0 (by omega), eoap 1 (by omega), ← sub_eq_add_neg] at hr0 hr1 hr2 hr3 have h_it := h_itype h_bin have h_off' : (0 : ZMod p).val + 2 * (0 : ZMod p).val + 4 * input_offset_bit.val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by @@ -119,32 +106,14 @@ theorem completeness : = input_memory_access_prev_value := h_input.2.2.2.1.1 have hmap_oap : Vector.map (Expression.eval env.toEnvironment) input_var_adapter_op_a_memory_prev_value = input_adapter_op_a_memory_prev_value := h_input.2.2.1.2.1.1 - have epc0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have epc2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hmap_pc]; simp only [Vector.getElem_map] - have esv0 : Expression.eval env.toEnvironment input_var_store_value[0] = input_store_value[0] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv1 : Expression.eval env.toEnvironment input_var_store_value[1] = input_store_value[1] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv2 : Expression.eval env.toEnvironment input_var_store_value[2] = input_store_value[2] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have esv3 : Expression.eval env.toEnvironment input_var_store_value[3] = input_store_value[3] := by - rw [← hmap_sv]; simp only [Vector.getElem_map] - have epv0 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[0] - = input_memory_access_prev_value[0] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv1 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[1] - = input_memory_access_prev_value[1] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv2 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[2] - = input_memory_access_prev_value[2] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have epv3 : Expression.eval env.toEnvironment input_var_memory_access_prev_value[3] - = input_memory_access_prev_value[3] := by rw [← hmap_pv]; simp only [Vector.getElem_map] - have eoap0 : Expression.eval env.toEnvironment input_var_adapter_op_a_memory_prev_value[0] - = input_adapter_op_a_memory_prev_value[0] := by rw [← hmap_oap]; simp only [Vector.getElem_map] - have eoap1 : Expression.eval env.toEnvironment input_var_adapter_op_a_memory_prev_value[1] - = input_adapter_op_a_memory_prev_value[1] := by rw [← hmap_oap]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env.toEnvironment input_var_state_pc[i] + = input_state_pc[i] := fun i hi => by rw [← hmap_pc]; simp only [Vector.getElem_map] + have esv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_store_value[i] + = input_store_value[i] := fun i hi => by rw [← hmap_sv]; simp only [Vector.getElem_map] + have epv : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_memory_access_prev_value[i] + = input_memory_access_prev_value[i] := fun i hi => by rw [← hmap_pv]; simp only [Vector.getElem_map] + have eoap : ∀ i (hi : i < 2), Expression.eval env.toEnvironment input_var_adapter_op_a_memory_prev_value[i] + = input_adapter_op_a_memory_prev_value[i] := fun i hi => by rw [← hmap_oap]; simp only [Vector.getElem_map] have h_off' : (0 : ZMod p).val + 2 * (0 : ZMod p).val + 4 * input_offset_bit.val = (Word.toNat input_adapter_op_b_memory_prev_value + Word.toNat input_adapter_op_c_imm) % 2 ^ 48 % 8 := by simp only [ZMod.val_zero]; omega @@ -160,15 +129,19 @@ theorem completeness : ⟨ha, hb, hfit, Or.inl rfl, Or.inl rfl, h_off_bin, h_ge, h_off'⟩ refine ⟨⟨?_, ?_⟩, h_addr_as, ⟨?_, ?_⟩, ⟨?_, ?_⟩, ?_, ?_, ?_, ?_, ?_⟩ · exact hbin - · simp only [epc0, epc1, epc2]; exact h_cpu + · simp only [epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; exact h_cpu · exact hbin · exact h_mem · exact hbin · exact h_it - · simp only [esv0, epv0, eoap0, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr0 - · simp only [esv1, epv1, eoap1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr1 - · simp only [esv2, epv2, eoap0, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr2 - · simp only [esv3, epv3, eoap1, ← sub_eq_add_neg]; exact sub_eq_zero_of_eq hr3 + · simp only [esv 0 (by omega), epv 0 (by omega), eoap 0 (by omega), ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr0 + · simp only [esv 1 (by omega), epv 1 (by omega), eoap 1 (by omega), ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr1 + · simp only [esv 2 (by omega), epv 2 (by omega), eoap 0 (by omega), ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr2 + · simp only [esv 3 (by omega), epv 3 (by omega), eoap 1 (by omega), ← sub_eq_add_neg] + exact sub_eq_zero_of_eq hr3 · rcases hbin with h | h <;> rw [h] <;> simp /-- The `StoreWord` chip row as a `GeneralFormalCircuit`; output is the extracted `StoreWordColumns`. -/ diff --git a/SP1Clean/Proofs/Chips/SubChip/Formal.lean b/SP1Clean/Proofs/Chips/SubChip/Formal.lean index 18d716d0..d53f51fb 100644 --- a/SP1Clean/Proofs/Chips/SubChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/SubChip/Formal.lean @@ -31,18 +31,13 @@ theorem completeness : obtain ⟨ha, hb, hbin, hop_a_0, h_cpu, hrac_a, hrac_b, hrac_c⟩ := h_assumptions obtain ⟨-, -, -, -, -, -, ⟨hob, -, -⟩, -, hoc, -, -⟩ := h_input have hz : ∀ w : ZMod p, input_adapter_op_a_0 * w = 0 := fun w => by rw [hop_a_0, zero_mul] - have hbeq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_b_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_b_memory_prev_value := by - rw [← hob]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl - have hceq : (#v[Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[0], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[1], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[2], - Expression.eval env.toEnvironment input_var_adapter_op_c_memory_prev_value[3]] : Word (ZMod p)) - = input_adapter_op_c_memory_prev_value := by - rw [← hoc]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have mapEq : ∀ (vv : Word (Expression (ZMod p))) (v : Word (ZMod p)), + Vector.map (Expression.eval env.toEnvironment) vv = v → + (#v[Expression.eval env.toEnvironment vv[0], Expression.eval env.toEnvironment vv[1], + Expression.eval env.toEnvironment vv[2], Expression.eval env.toEnvironment vv[3]] : Word (ZMod p)) = v := + fun vv v h => by rw [← h]; apply Vector.ext; intro i hi; simp only [Vector.getElem_map]; interval_cases i <;> rfl + have hbeq := mapEq input_var_adapter_op_b_memory_prev_value _ hob + have hceq := mapEq input_var_adapter_op_c_memory_prev_value _ hoc have hval : (Vector.map (Expression.eval env.toEnvironment) (Vector.mapRange 4 fun i => var {index := i₀ + i}) : Word (ZMod p)) = SubOperation.populate input_adapter_op_b_memory_prev_value input_adapter_op_c_memory_prev_value := by diff --git a/SP1Clean/Proofs/Chips/UTypeChip/Bridge.lean b/SP1Clean/Proofs/Chips/UTypeChip/Bridge.lean index 0a21feaa..d29137ca 100644 --- a/SP1Clean/Proofs/Chips/UTypeChip/Bridge.lean +++ b/SP1Clean/Proofs/Chips/UTypeChip/Bridge.lean @@ -55,33 +55,58 @@ theorem correct_utype_auipc_native PreSail.readReg, PreSail.writeReg, Sail.run_wX_bits, Std.ExtDHashMap.get?_insert, SailState.get_reg?_insert_nextPC, h_pc, h_result] +set_option linter.unusedSimpArgs false in omit [Fact (2 ^ 17 < p)] in -/-- End-to-end (LUI): a real U-type chip row with `is_auipc = 0` and `rd ≠ x0` reaches the Sail `LUI`. -/ +/-- Native Sail equivalence for U-type to `x0` (`lui x0` / `auipc x0`). When `rd = x0` the result write is a +no-op on both sides (`wX_bits 0` is dropped in RV64) and U-type touches nothing but `nextPC = pc + 4`, so +the row reaches Sail for **either** op with no result fact — which is why the chip leaves +`add_operation.value` unconstrained on `op_a_0 = 1` rows. -/ +theorem correct_utype_native_x0 + (result : Word (ZMod p)) (imm : BitVec 20) (pc : BitVec 64) (s : SailState) (op : uop) + (h_pc : s.regs.get? Register.PC = some pc) : + (spec_utype imm (.Regidx 0#5) op).run s = (sp1_utype (.Regidx 0#5) pc result).run s := by + cases op <;> + simp [spec_utype, sp1_utype, execute_UTYPE, get_arch_pc, sign_extend, Sail.BitVec.signExtend, RV64.lui, + PreSail.readReg, PreSail.writeReg, Sail.run_wX_bits, Std.ExtDHashMap.get?_insert, + SailState.get_reg?_insert_nextPC, h_pc] + +omit [Fact (2 ^ 17 < p)] in +/-- End-to-end (LUI): a real U-type chip row with `is_auipc = 0` reaches the Sail `LUI`, for `rd ≠ x0` +(`op_a_0 = 0`, result proven) **or** `lui x0` (`op_a_0 = 1 ∧ rd = x0`, result write a no-op). -/ theorem utype_chip_reaches_sail_lui (input : UTypeChip.Inputs (ZMod p)) (cols : Extracted.UTypeColumns (ZMod p)) (data : ProverData (ZMod p)) (rd : BitVec 5) (pc : BitVec 64) (s : SailState) - (h_real : input.is_real = 1) (h_iaui0 : input.is_auipc = 0) (h_op0 : cols.adapter.op_a_0 = 0) + (h_real : input.is_real = 1) (h_iaui0 : input.is_auipc = 0) + (h_op_a : cols.adapter.op_a_0 = 0 ∨ (cols.adapter.op_a_0 = 1 ∧ rd = 0#5)) (h_chip : UTypeChip.Spec input cols data) (h_pc : s.regs.get? Register.PC = some pc) : (spec_utype (UTypeChip.immOf cols.adapter) (.Regidx rd) uop.LUI).run s - = (sp1_utype (.Regidx rd) pc cols.add_operation.value).run s := - correct_utype_lui_native cols.add_operation.value rd (UTypeChip.immOf cols.adapter) pc s h_pc - (h_chip.2.2.2.1 h_real h_op0 h_iaui0) + = (sp1_utype (.Regidx rd) pc cols.add_operation.value).run s := by + rcases h_op_a with h_op0 | ⟨_, hrd0⟩ + · exact correct_utype_lui_native cols.add_operation.value rd (UTypeChip.immOf cols.adapter) pc s h_pc + (h_chip.2.2.2.1 h_real h_op0 h_iaui0) + · subst hrd0 + exact correct_utype_native_x0 cols.add_operation.value (UTypeChip.immOf cols.adapter) pc s uop.LUI h_pc omit [Fact (2 ^ 17 < p)] in -/-- End-to-end (AUIPC): a real U-type chip row with `is_auipc = 1` and `rd ≠ x0` reaches the Sail `AUIPC`. -/ +/-- End-to-end (AUIPC): a real U-type chip row with `is_auipc = 1` reaches the Sail `AUIPC`, for `rd ≠ x0` +(`op_a_0 = 0`, result proven) **or** `auipc x0` (`op_a_0 = 1 ∧ rd = x0`, result write a no-op). -/ theorem utype_chip_reaches_sail_auipc (input : UTypeChip.Inputs (ZMod p)) (cols : Extracted.UTypeColumns (ZMod p)) (data : ProverData (ZMod p)) (rd : BitVec 5) (pc : BitVec 64) (s : SailState) - (h_real : input.is_real = 1) (h_iaui1 : input.is_auipc = 1) (h_op0 : cols.adapter.op_a_0 = 0) + (h_real : input.is_real = 1) (h_iaui1 : input.is_auipc = 1) + (h_op_a : cols.adapter.op_a_0 = 0 ∨ (cols.adapter.op_a_0 = 1 ∧ rd = 0#5)) (h_chip : UTypeChip.Spec input cols data) (h_pc : s.regs.get? Register.PC = some pc) (h_pcw : Word.toBitVec64 (UTypeChip.pcWord cols) = pc) : (spec_utype (UTypeChip.immOf cols.adapter) (.Regidx rd) uop.AUIPC).run s = (sp1_utype (.Regidx rd) pc cols.add_operation.value).run s := by - have hr := h_chip.2.2.2.2 h_real h_op0 h_iaui1 - rw [h_pcw] at hr - exact correct_utype_auipc_native cols.add_operation.value rd (UTypeChip.immOf cols.adapter) pc s h_pc hr + rcases h_op_a with h_op0 | ⟨_, hrd0⟩ + · have hr := h_chip.2.2.2.2 h_real h_op0 h_iaui1 + rw [h_pcw] at hr + exact correct_utype_auipc_native cols.add_operation.value rd (UTypeChip.immOf cols.adapter) pc s h_pc hr + · subst hrd0 + exact correct_utype_native_x0 cols.add_operation.value (UTypeChip.immOf cols.adapter) pc s uop.AUIPC h_pc end SP1Clean.UTypeSail @@ -107,15 +132,15 @@ def kind : Soundness.ChipKind p where sailEquiv := fun inp cols s => ∀ (rd : BitVec 5) (pc : BitVec 64), s.regs.get? Register.PC = some pc → Word.toBitVec64 (UTypeChip.pcWord cols) = pc → - cols.adapter.op_a_0 = 0 → + (cols.adapter.op_a_0 = 0 ∨ (cols.adapter.op_a_0 = 1 ∧ rd = 0#5)) → (inp.is_auipc = 0 → (spec_utype (UTypeChip.immOf cols.adapter) (.Regidx rd) uop.LUI).run s = (sp1_utype (.Regidx rd) pc cols.add_operation.value).run s) ∧ (inp.is_auipc = 1 → (spec_utype (UTypeChip.immOf cols.adapter) (.Regidx rd) uop.AUIPC).run s = (sp1_utype (.Regidx rd) pc cols.add_operation.value).run s) - reaches_sail := fun inp cols data s h_real h_chip rd pc h_pc h_pcw h_op0 => - ⟨fun h0 => utype_chip_reaches_sail_lui inp cols data rd pc s h_real h0 h_op0 h_chip h_pc, - fun h1 => utype_chip_reaches_sail_auipc inp cols data rd pc s h_real h1 h_op0 h_chip h_pc h_pcw⟩ + reaches_sail := fun inp cols data s h_real h_chip rd pc h_pc h_pcw h_op_a => + ⟨fun h0 => utype_chip_reaches_sail_lui inp cols data rd pc s h_real h0 h_op_a h_chip h_pc, + fun h1 => utype_chip_reaches_sail_auipc inp cols data rd pc s h_real h1 h_op_a h_chip h_pc h_pcw⟩ end SP1Clean.UTypeChip diff --git a/SP1Clean/Proofs/Chips/UTypeChip/Formal.lean b/SP1Clean/Proofs/Chips/UTypeChip/Formal.lean index 5d51e681..adc08d5b 100644 --- a/SP1Clean/Proofs/Chips/UTypeChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/UTypeChip/Formal.lean @@ -63,18 +63,14 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe have h_jt := h_jt0 h_bin have h_op_a_0 : input_adapter_op_a_0 = 0 ∨ input_adapter_op_a_0 = 1 := h_jt.2.1 have hpc : Vector.map (Expression.eval env) input_var_state_pc = input_state_pc := h_input.2.1.2.2.2 - have ep0 : Expression.eval env input_var_state_pc[0] = input_state_pc[0] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep1 : Expression.eval env input_var_state_pc[1] = input_state_pc[1] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep2 : Expression.eval env input_var_state_pc[2] = input_state_pc[2] := by - rw [← hpc]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), Expression.eval env input_var_state_pc[i] = input_state_pc[i] := + fun i hi => by rw [← hpc]; simp only [Vector.getElem_map] have ha0 : env.get i₀ = input_is_auipc * input_state_pc[0] := by - rw [ep0] at h_ad0; exact add_neg_eq_zero.mp h_ad0 + rw [epc 0 (by omega)] at h_ad0; exact add_neg_eq_zero.mp h_ad0 have ha1 : env.get (i₀ + 1) = input_is_auipc * input_state_pc[1] := by - rw [ep1] at h_ad1; exact add_neg_eq_zero.mp h_ad1 + rw [epc 1 (by omega)] at h_ad1; exact add_neg_eq_zero.mp h_ad1 have ha2 : env.get (i₀ + 2) = input_is_auipc * input_state_pc[2] := by - rw [ep2] at h_ad2; exact add_neg_eq_zero.mp h_ad2 + rw [epc 2 (by omega)] at h_ad2; exact add_neg_eq_zero.mp h_ad2 have haddend0 : input_is_auipc = 0 → (#v[env.get i₀, env.get (i₀ + 1), env.get (i₀ + 2), 0] : Word (ZMod p)) = #v[0, 0, 0, 0] := by intro h; rw [ha0, ha1, ha2, h]; simp @@ -112,12 +108,9 @@ theorem completeness : obtain ⟨h_imm, h_pcU, h_bin, h_iaui, h_op0, h_cpu, h_rac, _h_dec⟩ := h_assumptions obtain ⟨_, ⟨_, _, _, hpc⟩, ⟨_, _, _, hob, _⟩, _⟩ := h_input obtain ⟨he_addend, he_addval⟩ := h_env - have ep0 : Expression.eval env.toEnvironment input_var_state_pc[0] = input_state_pc[0] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep1 : Expression.eval env.toEnvironment input_var_state_pc[1] = input_state_pc[1] := by - rw [← hpc]; simp only [Vector.getElem_map] - have ep2 : Expression.eval env.toEnvironment input_var_state_pc[2] = input_state_pc[2] := by - rw [← hpc]; simp only [Vector.getElem_map] + have epc : ∀ i (hi : i < 3), + Expression.eval env.toEnvironment input_var_state_pc[i] = input_state_pc[i] := + fun i hi => by rw [← hpc]; simp only [Vector.getElem_map] have hg0 : env.get i₀ = input_is_auipc * Expression.eval env.toEnvironment input_var_state_pc[0] := by simpa using he_addend 0 have hg1 : env.get (i₀ + 1) = input_is_auipc * Expression.eval env.toEnvironment input_var_state_pc[1] := by @@ -149,7 +142,7 @@ theorem completeness : rw [e]; refine Word.isU64_of_cases ?_ ?_ ?_ ?_ <;> simp · have e : (#v[env.get i₀, env.get (i₀ + 1), env.get (i₀ + 2), 0] : Word (ZMod p)) = #v[input_state_pc[0], input_state_pc[1], input_state_pc[2], 0] := by - rw [hg0, hg1, hg2, h, ep0, ep1, ep2]; simp + rw [hg0, hg1, hg2, h, epc 0 (by omega), epc 1 (by omega), epc 2 (by omega)]; simp rw [e]; exact h_pcU have h_gate2 : input_is_real + -input_adapter_op_a_0 = 0 ∨ input_is_real + -input_adapter_op_a_0 = 1 := by rw [h_op0]; simpa using h_bin diff --git a/SP1Clean/Proofs/Operations/AddOperation/Formal.lean b/SP1Clean/Proofs/Operations/AddOperation/Formal.lean index a9111bd2..f6f4512d 100644 --- a/SP1Clean/Proofs/Operations/AddOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/AddOperation/Formal.lean @@ -29,24 +29,10 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨hia, hib, hiv, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast have h65536 : (2 : ℕ) ^ 16 = 65536 := by norm_num - -- vector-indexed inputs need manual eval rewrites (circuit_norm only handles scalar is_real). - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev2 : Expression.eval env input_var_cols_value[2] = input_cols_value[2] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev3 : Expression.eval env input_var_cols_value[3] = input_cols_value[3] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3, ev0, ev1, ev2, ev3] - at h_holds ⊢ + have ev {w : Word (Expression (ZMod p))} {v : Word (ZMod p)} (h : Vector.map (Expression.eval env) w = v) : + ∀ i (_ : i < 4), Expression.eval env w[i] = v[i] := fun i _ => by rw [← h, Vector.getElem_map] + simp only [circuit_norm, byteChannel, ev hia, ev hib, ev hiv] at h_holds ⊢ obtain ⟨hr0, hr1, hr2, hr3, _hbool, hgc0, hgc1, hgc2, hgc3⟩ := h_holds - -- `circuit_norm` already discharged the carry asserts' (empty) requirements, and post-#398 the byte - -- receives owe no padding requirement — the goal is exactly `Spec`. intro hr1eq obtain ⟨ha, hb⟩ := hab_imp hr1eq have hneg : -input_is_real = -1 := by rw [hr1eq] @@ -67,42 +53,20 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨hab_imp, hbin⟩ := h_assumptions obtain ⟨hia, hib, hiv, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env.toEnvironment input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env.toEnvironment input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev2 : Expression.eval env.toEnvironment input_var_cols_value[2] = input_cols_value[2] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev3 : Expression.eval env.toEnvironment input_var_cols_value[3] = input_cols_value[3] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3, ev0, ev1, ev2, ev3] + have ev {w : Word (Expression (ZMod p))} {v : Word (ZMod p)} + (h : Vector.map (Expression.eval env.toEnvironment) w = v) : + ∀ i (_ : i < 4), Expression.eval env.toEnvironment w[i] = v[i] := + fun i _ => by rw [← h, Vector.getElem_map] + simp only [circuit_norm, byteChannel, ev hia, ev hib, ev hiv] refine ⟨?_, ?_, ?_, ?_, ?_⟩ - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨hv0, _, _, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv0 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, hv1, _, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv1 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, _, hv2, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv2 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, _, _, hv3⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv3 - -- four gated carry asserts: vacuous on padding; from `carries_of_addSemantics` on real rows. - -- Carries arrive in `+ -` form; normalise with `sub_eq_add_neg`. + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.2.1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.2.2 · rcases hbin with h0 | h1 · simp [h0] · obtain ⟨hv, hbv⟩ := h_spec h1 diff --git a/SP1Clean/Proofs/Operations/AddwOperation/Formal.lean b/SP1Clean/Proofs/Operations/AddwOperation/Formal.lean index 95c34f7a..8482ac6b 100644 --- a/SP1Clean/Proofs/Operations/AddwOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/AddwOperation/Formal.lean @@ -40,13 +40,14 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨hia, hib, ⟨hiv, _⟩, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast have h65536 : (2 : ℕ) ^ 16 = 65536 := by norm_num - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, eb0, eb1, ev0, ev1] at h_holds ⊢ + have ea : ∀ i (hi : i < 4), Expression.eval env input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ev : ∀ i (hi : i < 2), + Expression.eval env input_var_cols_value[i] = input_cols_value[i] := by + intro i hi; rw [← hiv]; simp only [Vector.getElem_map] + simp only [circuit_norm, byteChannel, ea, eb, ev] at h_holds ⊢ obtain ⟨h_msb, hr0, hr1, _hgate, hgc0, hgc1⟩ := h_holds -- the U16MSB sub-assertion's `Assumptions` (gated `value[1] < 2^16` from the `value[1]` byte pull). have h_msb_as : U16MSBOperation.circuit.Assumptions @@ -77,12 +78,13 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨ha, hb, hbin⟩ := h_assumptions obtain ⟨hia, hib, ⟨hiv, _⟩, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast - have ev0 : Expression.eval env.toEnvironment input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env.toEnvironment input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] + have ev : ∀ i (hi : i < 2), + Expression.eval env.toEnvironment input_var_cols_value[i] = input_cols_value[i] := by + intro i hi; rw [← hiv]; simp only [Vector.getElem_map] + have ea : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] -- the converse facts, all valid only on real rows have key : input_is_real = 1 → (input_cols_value[0].val < 2 ^ 16 ∧ input_cols_value[1].val < 2 ^ 16) ∧ @@ -95,7 +97,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp List.getElem_cons_zero, List.getElem_cons_succ] at hUv0 hUv1 obtain ⟨h_raw, h_msbval⟩ := carries_of_addwSemantics ha hb hU hbveq exact ⟨⟨hUv0, hUv1⟩, h_raw, h_msbval⟩ - simp only [circuit_norm, byteChannel, ea0, ea1, eb0, eb1, ev0, ev1] + simp only [circuit_norm, byteChannel, ea, eb, ev] refine ⟨⟨⟨fun hr1 => (key hr1).1.2, hbin⟩, ⟨h_spec.1, fun hr1 => (key hr1).2.2⟩⟩, ?_, ?_, ?_, ?_, ?_⟩ · intro hneg have hr1 : input_is_real = 1 := neg_inj.mp hneg diff --git a/SP1Clean/Proofs/Operations/BitwiseOperation/Formal.lean b/SP1Clean/Proofs/Operations/BitwiseOperation/Formal.lean index 63c6fe25..c00eb6eb 100644 --- a/SP1Clean/Proofs/Operations/BitwiseOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/BitwiseOperation/Formal.lean @@ -27,33 +27,14 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := circuit_proof_start obtain ⟨hopcode, _hbin⟩ := h_assumptions obtain ⟨hia, hib, hir, _, _⟩ := h_input - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have ea4 : Expression.eval env input_var_a[4] = input_a[4] := by rw [← hia]; simp only [Vector.getElem_map] - have ea5 : Expression.eval env input_var_a[5] = input_a[5] := by rw [← hia]; simp only [Vector.getElem_map] - have ea6 : Expression.eval env input_var_a[6] = input_a[6] := by rw [← hia]; simp only [Vector.getElem_map] - have ea7 : Expression.eval env input_var_a[7] = input_a[7] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have eb4 : Expression.eval env input_var_b[4] = input_b[4] := by rw [← hib]; simp only [Vector.getElem_map] - have eb5 : Expression.eval env input_var_b[5] = input_b[5] := by rw [← hib]; simp only [Vector.getElem_map] - have eb6 : Expression.eval env input_var_b[6] = input_b[6] := by rw [← hib]; simp only [Vector.getElem_map] - have eb7 : Expression.eval env input_var_b[7] = input_b[7] := by rw [← hib]; simp only [Vector.getElem_map] - have er0 : Expression.eval env input_var_cols_result[0] = input_cols_result[0] := by rw [← hir]; simp only [Vector.getElem_map] - have er1 : Expression.eval env input_var_cols_result[1] = input_cols_result[1] := by rw [← hir]; simp only [Vector.getElem_map] - have er2 : Expression.eval env input_var_cols_result[2] = input_cols_result[2] := by rw [← hir]; simp only [Vector.getElem_map] - have er3 : Expression.eval env input_var_cols_result[3] = input_cols_result[3] := by rw [← hir]; simp only [Vector.getElem_map] - have er4 : Expression.eval env input_var_cols_result[4] = input_cols_result[4] := by rw [← hir]; simp only [Vector.getElem_map] - have er5 : Expression.eval env input_var_cols_result[5] = input_cols_result[5] := by rw [← hir]; simp only [Vector.getElem_map] - have er6 : Expression.eval env input_var_cols_result[6] = input_cols_result[6] := by rw [← hir]; simp only [Vector.getElem_map] - have er7 : Expression.eval env input_var_cols_result[7] = input_cols_result[7] := by rw [← hir]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, ea4, ea5, ea6, ea7, - eb0, eb1, eb2, eb3, eb4, eb5, eb6, eb7, - er0, er1, er2, er3, er4, er5, er6, er7] at h_holds ⊢ + have ea : ∀ i (hi : i < 8), Expression.eval env input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 8), Expression.eval env input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have er : ∀ i (hi : i < 8), + Expression.eval env input_var_cols_result[i] = input_cols_result[i] := by + intro i hi; rw [← hir]; simp only [Vector.getElem_map] + simp only [circuit_norm, byteChannel, ea, eb, er] at h_holds ⊢ obtain ⟨hg0, hg1, hg2, hg3, hg4, hg5, hg6, hg7⟩ := h_holds -- post-#398 the byte receives owe no padding requirement, so the goal is exactly `Spec`. intro h1 @@ -85,30 +66,13 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨hia, hib, hir, _, _⟩ := h_input have hp : 2 ^ 17 < p := Fact.out haveI : NeZero p := ⟨by omega⟩ - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have ea4 : Expression.eval env.toEnvironment input_var_a[4] = input_a[4] := by rw [← hia]; simp only [Vector.getElem_map] - have ea5 : Expression.eval env.toEnvironment input_var_a[5] = input_a[5] := by rw [← hia]; simp only [Vector.getElem_map] - have ea6 : Expression.eval env.toEnvironment input_var_a[6] = input_a[6] := by rw [← hia]; simp only [Vector.getElem_map] - have ea7 : Expression.eval env.toEnvironment input_var_a[7] = input_a[7] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have eb4 : Expression.eval env.toEnvironment input_var_b[4] = input_b[4] := by rw [← hib]; simp only [Vector.getElem_map] - have eb5 : Expression.eval env.toEnvironment input_var_b[5] = input_b[5] := by rw [← hib]; simp only [Vector.getElem_map] - have eb6 : Expression.eval env.toEnvironment input_var_b[6] = input_b[6] := by rw [← hib]; simp only [Vector.getElem_map] - have eb7 : Expression.eval env.toEnvironment input_var_b[7] = input_b[7] := by rw [← hib]; simp only [Vector.getElem_map] - have er0 : Expression.eval env.toEnvironment input_var_cols_result[0] = input_cols_result[0] := by rw [← hir]; simp only [Vector.getElem_map] - have er1 : Expression.eval env.toEnvironment input_var_cols_result[1] = input_cols_result[1] := by rw [← hir]; simp only [Vector.getElem_map] - have er2 : Expression.eval env.toEnvironment input_var_cols_result[2] = input_cols_result[2] := by rw [← hir]; simp only [Vector.getElem_map] - have er3 : Expression.eval env.toEnvironment input_var_cols_result[3] = input_cols_result[3] := by rw [← hir]; simp only [Vector.getElem_map] - have er4 : Expression.eval env.toEnvironment input_var_cols_result[4] = input_cols_result[4] := by rw [← hir]; simp only [Vector.getElem_map] - have er5 : Expression.eval env.toEnvironment input_var_cols_result[5] = input_cols_result[5] := by rw [← hir]; simp only [Vector.getElem_map] - have er6 : Expression.eval env.toEnvironment input_var_cols_result[6] = input_cols_result[6] := by rw [← hir]; simp only [Vector.getElem_map] - have er7 : Expression.eval env.toEnvironment input_var_cols_result[7] = input_cols_result[7] := by rw [← hir]; simp only [Vector.getElem_map] + have ea : ∀ i (hi : i < 8), Expression.eval env.toEnvironment input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 8), Expression.eval env.toEnvironment input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have er : ∀ i (hi : i < 8), + Expression.eval env.toEnvironment input_var_cols_result[i] = input_cols_result[i] := by + intro i hi; rw [← hir]; simp only [Vector.getElem_map] have key : input_is_real = 1 → ∀ i : Fin 8, ByteRowSpec (⟨input_opcode, input_cols_result[↑i], input_a[↑i], input_b[↑i]⟩ : ByteRow (ZMod p)) := by intro hr1 i @@ -123,9 +87,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp · rw [h, byteOp_two]; exact hxor (by rw [← hcast, h]; norm_num) i exact (byteRowSpec_byteOp _ _ _ hopcode).mpr ⟨⟨by rw [hres]; exact byteOp_lt256 _ _ _ hb.1 hb.2, hb.1, hb.2⟩, hres⟩ - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, ea4, ea5, ea6, ea7, - eb0, eb1, eb2, eb3, eb4, eb5, eb6, eb7, - er0, er1, er2, er3, er4, er5, er6, er7] + simp only [circuit_norm, byteChannel, ea, eb, er] refine ⟨fun hneg => ?_, fun hneg => ?_, fun hneg => ?_, fun hneg => ?_, fun hneg => ?_, fun hneg => ?_, fun hneg => ?_, fun hneg => ?_⟩ · exact key (neg_inj.mp hneg) 0 diff --git a/SP1Clean/Proofs/Operations/IsEqualWordOperation/Formal.lean b/SP1Clean/Proofs/Operations/IsEqualWordOperation/Formal.lean index 1726695d..8700011b 100644 --- a/SP1Clean/Proofs/Operations/IsEqualWordOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/IsEqualWordOperation/Formal.lean @@ -37,38 +37,19 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := circuit_proof_start obtain ⟨hsub, _hbool⟩ := h_holds obtain ⟨hia, hib, -⟩ := h_input - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp [Vector.getElem_map] - have ea2 : Expression.eval env input_var_a[2] = input_a[2] := by rw [← hia]; simp [Vector.getElem_map] - have ea3 : Expression.eval env input_var_a[3] = input_a[3] := by rw [← hia]; simp [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp [Vector.getElem_map] have S := hsub h_assumptions - simp only [ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3] at S refine ⟨?_, Or.inl rfl⟩ - simp only [diff, sub_eq_add_neg] - exact S + simpa only [diff, ← hia, ← hib, Vector.getElem_map, sub_eq_add_neg] using S omit [Fact (2 ^ 17 < p)] in set_option maxHeartbeats 1000000 in theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Spec := by circuit_proof_start obtain ⟨hia, hib, -⟩ := h_input - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_a[2] = input_a[2] := by rw [← hia]; simp [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_a[3] = input_a[3] := by rw [← hia]; simp [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp [Vector.getElem_map] refine ⟨⟨h_assumptions, ?_⟩, ?_⟩ - · simp only [ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3] - have hs := h_spec + · have hs := h_spec simp only [diff, sub_eq_add_neg] at hs - exact hs + simpa only [← hia, ← hib, Vector.getElem_map] using hs · rcases h_assumptions with h | h <;> simp [h] omit [Fact (2 ^ 17 < p)] in diff --git a/SP1Clean/Proofs/Operations/IsZeroOperation/Formal.lean b/SP1Clean/Proofs/Operations/IsZeroOperation/Formal.lean index 7ef9ed09..b24dced3 100644 --- a/SP1Clean/Proofs/Operations/IsZeroOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/IsZeroOperation/Formal.lean @@ -34,10 +34,8 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := intro hr1 simp only [circuit_norm, hr1, one_mul] at h_holds obtain ⟨h_eq, h_bool, h_mul⟩ := h_holds - have hA : AssertSpec input_a ⟨input_cols_inverse, input_cols_result⟩ := by - refine ⟨?_, bool_of_mul_pred h_bool, h_mul⟩ - show (1 : ZMod p) - input_cols_inverse * input_a - input_cols_result = 0 - simp only [sub_eq_add_neg]; exact h_eq + have hA : AssertSpec input_a ⟨input_cols_inverse, input_cols_result⟩ := + ⟨by simpa [sub_eq_add_neg] using h_eq, bool_of_mul_pred h_bool, h_mul⟩ exact ⟨isZero_of_assert hA, fun ha => inverse_of_assert hA ha⟩ omit [Fact (2 ^ 17 < p)] in @@ -49,8 +47,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp · obtain ⟨hres, hinv⟩ := h_spec h1 simp only [circuit_norm, h1, one_mul] refine ⟨?_, ?_, ?_⟩ - · -- 1 - inverse*a - result = 0 - by_cases ha : input_a = 0 + · by_cases ha : input_a = 0 · simp [hres, ha] · rw [hres, if_neg ha, hinv ha]; simp · rw [hres]; by_cases ha : input_a = 0 <;> simp [ha] @@ -63,9 +60,9 @@ theorem spec_populate (a is_real : ZMod p) : Spec (⟨a, populate a, is_real⟩ : Inputs (ZMod p)) := by intro _ by_cases ha : a = 0 - · subst ha; refine ⟨by simp [populate], ?_⟩; intro hne; exact absurd rfl hne - · refine ⟨by simp [populate, ha], ?_⟩ - intro _; simp only [populate, if_neg ha]; exact inv_mul_cancel₀ ha + · subst ha; exact ⟨by simp [populate], fun hne => absurd rfl hne⟩ + · refine ⟨by simp [populate, ha], fun _ => ?_⟩ + simp only [populate, if_neg ha]; exact inv_mul_cancel₀ ha /-- SP1's `IsZeroOperation::eval` as a Clean-native `FormalAssertion`. -/ def circuit : FormalAssertion (ZMod p) Inputs := diff --git a/SP1Clean/Proofs/Operations/IsZeroWordOperation/Formal.lean b/SP1Clean/Proofs/Operations/IsZeroWordOperation/Formal.lean index df02d4eb..872a8b65 100644 --- a/SP1Clean/Proofs/Operations/IsZeroWordOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/IsZeroWordOperation/Formal.lean @@ -42,14 +42,12 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := circuit_proof_start obtain ⟨hs0, hs1, hs2, hs3, _hbreal, hbres, hf, hsec, hg⟩ := h_holds obtain ⟨hia, -⟩ := h_input - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp [Vector.getElem_map] - have ea2 : Expression.eval env input_var_a[2] = input_a[2] := by rw [← hia]; simp [Vector.getElem_map] - have ea3 : Expression.eval env input_var_a[3] = input_a[3] := by rw [← hia]; simp [Vector.getElem_map] - have S0 := hs0 h_assumptions; rw [ea0] at S0 - have S1 := hs1 h_assumptions; rw [ea1] at S1 - have S2 := hs2 h_assumptions; rw [ea2] at S2 - have S3 := hs3 h_assumptions; rw [ea3] at S3 + have ea : ∀ i (hi : i < 4), Expression.eval env input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp [Vector.getElem_map] + have S0 := hs0 h_assumptions; rw [ea 0 (by norm_num)] at S0 + have S1 := hs1 h_assumptions; rw [ea 1 (by norm_num)] at S1 + have S2 := hs2 h_assumptions; rw [ea 2 (by norm_num)] at S2 + have S3 := hs3 h_assumptions; rw [ea 3 (by norm_num)] at S3 rw [← sub_eq_add_neg] at hf hsec refine ⟨⟨bool_of_mul_pred hbres, eq_of_sub_eq_zero hf, eq_of_sub_eq_zero hsec, S0, S1, S2, S3, ?_⟩, Or.inl rfl, Or.inl rfl, Or.inl rfl, Or.inl rfl⟩ @@ -63,15 +61,13 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp circuit_proof_start obtain ⟨hbres, hf, hsec, hS0, hS1, hS2, hS3, hg⟩ := h_spec obtain ⟨hia, -⟩ := h_input - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_a[2] = input_a[2] := by rw [← hia]; simp [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_a[3] = input_a[3] := by rw [← hia]; simp [Vector.getElem_map] + have ea : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp [Vector.getElem_map] refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ - · exact ⟨h_assumptions, by rw [ea0]; exact hS0⟩ - · exact ⟨h_assumptions, by rw [ea1]; exact hS1⟩ - · exact ⟨h_assumptions, by rw [ea2]; exact hS2⟩ - · exact ⟨h_assumptions, by rw [ea3]; exact hS3⟩ + · exact ⟨h_assumptions, by rw [ea 0 (by norm_num)]; exact hS0⟩ + · exact ⟨h_assumptions, by rw [ea 1 (by norm_num)]; exact hS1⟩ + · exact ⟨h_assumptions, by rw [ea 2 (by norm_num)]; exact hS2⟩ + · exact ⟨h_assumptions, by rw [ea 3 (by norm_num)]; exact hS3⟩ · rcases h_assumptions with h | h <;> simp [h] · rcases hbres with h | h <;> simp [h] · simp [hf] @@ -80,18 +76,24 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp · simp [h] · rw [h, one_mul, hg h]; simp +omit [Fact (2 ^ 17 < p)] in +/-- The populated `result` is boolean for **any** source word (a product of per-limb `0/1` +indicators) — the ungated structural fact `spec_populate_offGate` needs. -/ +theorem populate_result_bool (w : Word (ZMod p)) : + (populate w).result = 0 ∨ (populate w).result = 1 := by + simp only [populate, IsZeroOperation.populate] + by_cases h0 : w[0] = 0 <;> by_cases h1 : w[1] = 0 <;> by_cases h2 : w[2] = 0 <;> + by_cases h3 : w[3] = 0 <;> simp [h0, h1, h2, h3] + omit [Fact (2 ^ 17 < p)] in /-- The witnessed columns `populate a` satisfy the gadget `Spec` for any `is_real`. The composing op (`IsEqualWord`) / top-level chip uses this to discharge the `assertion IsZeroWordOperation.circuit` prover obligation. -/ theorem spec_populate (a : Word (ZMod p)) (is_real : ZMod p) : - Spec (⟨a, populate a, is_real⟩ : Inputs (ZMod p)) := by - refine ⟨?_, rfl, rfl, IsZeroOperation.spec_populate a[0] is_real, + Spec (⟨a, populate a, is_real⟩ : Inputs (ZMod p)) := + ⟨populate_result_bool a, rfl, rfl, IsZeroOperation.spec_populate a[0] is_real, IsZeroOperation.spec_populate a[1] is_real, IsZeroOperation.spec_populate a[2] is_real, IsZeroOperation.spec_populate a[3] is_real, fun _ => rfl⟩ - simp only [populate, IsZeroOperation.populate] - by_cases h0 : a[0] = 0 <;> by_cases h1 : a[1] = 0 <;> by_cases h2 : a[2] = 0 <;> - by_cases h3 : a[3] = 0 <;> simp [h0, h1, h2, h3] omit [Fact (2 ^ 17 < p)] in /-- Semantic exposure: on a real row the `Spec` forces `result` to be the word zero-indicator. -/ @@ -101,15 +103,6 @@ theorem result_semantic {input : Inputs (ZMod p)} (h : Spec input) (hr : input.i obtain ⟨_, hf, hs, hS0, hS1, hS2, hS3, hg⟩ := h exact result_collapse (hS0 hr).1 (hS1 hr).1 (hS2 hr).1 (hS3 hr).1 hf hs (hg hr) -omit [Fact (2 ^ 17 < p)] in -/-- The populated `result` is boolean for **any** source word (a product of per-limb `0/1` -indicators) — the ungated structural fact `spec_populate_offGate` needs. -/ -theorem populate_result_bool (w : Word (ZMod p)) : - (populate w).result = 0 ∨ (populate w).result = 1 := by - simp only [populate, IsZeroOperation.populate] - by_cases h0 : w[0] = 0 <;> by_cases h1 : w[1] = 0 <;> by_cases h2 : w[2] = 0 <;> - by_cases h3 : w[3] = 0 <;> simp [h0, h1, h2, h3] - omit [Fact (2 ^ 17 < p)] in /-- `Spec` with the gate off (`is_real = 0`) holds at the populate of **any** word `w` — not just the operand `a`. A composing chip can share one witnessed struct between two differently-gated diff --git a/SP1Clean/Proofs/Operations/LtOperationSigned/Formal.lean b/SP1Clean/Proofs/Operations/LtOperationSigned/Formal.lean index b3f34fb0..b4a8009e 100644 --- a/SP1Clean/Proofs/Operations/LtOperationSigned/Formal.lean +++ b/SP1Clean/Proofs/Operations/LtOperationSigned/Formal.lean @@ -187,25 +187,21 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := circuit_proof_start obtain ⟨hb_u64, hcc_u64, hir_bin, his_bin⟩ := h_assumptions obtain ⟨hib, hicc, ⟨⟨_, hflags, _, hcl⟩, _, _⟩, _, _⟩ := h_input - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ec0 : Expression.eval env input_var_cc[0] = input_cc[0] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec1 : Expression.eval env input_var_cc[1] = input_cc[1] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec2 : Expression.eval env input_var_cc[2] = input_cc[2] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec3 : Expression.eval env input_var_cc[3] = input_cc[3] := by rw [← hicc]; simp only [Vector.getElem_map] - have ef0 : Expression.eval env input_var_cols_result_u16_flags[0] = input_cols_result_u16_flags[0] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef1 : Expression.eval env input_var_cols_result_u16_flags[1] = input_cols_result_u16_flags[1] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef2 : Expression.eval env input_var_cols_result_u16_flags[2] = input_cols_result_u16_flags[2] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef3 : Expression.eval env input_var_cols_result_u16_flags[3] = input_cols_result_u16_flags[3] := by rw [← hflags]; simp only [Vector.getElem_map] - have ecl0 : Expression.eval env input_var_cols_result_comparison_limbs[0] = input_cols_result_comparison_limbs[0] := by rw [← hcl]; simp only [Vector.getElem_map] - have ecl1 : Expression.eval env input_var_cols_result_comparison_limbs[1] = input_cols_result_comparison_limbs[1] := by rw [← hcl]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ec : ∀ i (hi : i < 4), Expression.eval env input_var_cc[i] = input_cc[i] := by + intro i hi; rw [← hicc]; simp only [Vector.getElem_map] + have ef : ∀ i (hi : i < 4), + Expression.eval env input_var_cols_result_u16_flags[i] = input_cols_result_u16_flags[i] := by + intro i hi; rw [← hflags]; simp only [Vector.getElem_map] + have ecl : ∀ i (hi : i < 2), Expression.eval env input_var_cols_result_comparison_limbs[i] + = input_cols_result_comparison_limbs[i] := by + intro i hi; rw [← hcl]; simp only [Vector.getElem_map] obtain ⟨hb0, hb1, hb2, hb3⟩ := Word.lt_cases_of_isU64 hb_u64 obtain ⟨hc0, hc1, hc2, hc3⟩ := Word.lt_cases_of_isU64 hcc_u64 obtain ⟨h_msb_b, h_msb_c, h_lt, _, _, hE5, hE7, hE9⟩ := h_holds - simp only [id_eq, eb0, eb1, eb2, eb3, ec0, ec1, ec2, ec3, ef0, ef1, ef2, ef3, ecl0, ecl1, - vec4_eta, vec2_eta, ← sub_eq_add_neg] at h_msb_b h_msb_c h_lt hE5 hE7 hE9 ⊢ + simp only [id_eq, eb, ec, ef, ecl, vec4_eta, vec2_eta, ← sub_eq_add_neg] + at h_msb_b h_msb_c h_lt hE5 hE7 hE9 ⊢ -- the two `U16MSBOperation` sub-assertion `Assumptions` (16-bit operands, `is_signed` binary). have hAb : U16MSBOperation.circuit.Assumptions ⟨input_b[3], ⟨input_cols_b_msb_msb⟩, input_is_signed⟩ := ⟨fun _ => hb3, his_bin⟩ @@ -249,20 +245,17 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨hb_u64, hcc_u64, hir_bin, his_bin⟩ := h_assumptions obtain ⟨_, _, hbm_bool, hcm_bool, hg5, hg7, hg9, hbm_eq, hcm_eq, h_uns_spec⟩ := h_spec obtain ⟨hib, hicc, ⟨⟨_, hflags, _, hcl⟩, _, _⟩, _, _⟩ := h_input - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ec0 : Expression.eval env.toEnvironment input_var_cc[0] = input_cc[0] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec1 : Expression.eval env.toEnvironment input_var_cc[1] = input_cc[1] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec2 : Expression.eval env.toEnvironment input_var_cc[2] = input_cc[2] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec3 : Expression.eval env.toEnvironment input_var_cc[3] = input_cc[3] := by rw [← hicc]; simp only [Vector.getElem_map] - have ef0 : Expression.eval env.toEnvironment input_var_cols_result_u16_flags[0] = input_cols_result_u16_flags[0] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef1 : Expression.eval env.toEnvironment input_var_cols_result_u16_flags[1] = input_cols_result_u16_flags[1] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef2 : Expression.eval env.toEnvironment input_var_cols_result_u16_flags[2] = input_cols_result_u16_flags[2] := by rw [← hflags]; simp only [Vector.getElem_map] - have ef3 : Expression.eval env.toEnvironment input_var_cols_result_u16_flags[3] = input_cols_result_u16_flags[3] := by rw [← hflags]; simp only [Vector.getElem_map] - have ecl0 : Expression.eval env.toEnvironment input_var_cols_result_comparison_limbs[0] = input_cols_result_comparison_limbs[0] := by rw [← hcl]; simp only [Vector.getElem_map] - have ecl1 : Expression.eval env.toEnvironment input_var_cols_result_comparison_limbs[1] = input_cols_result_comparison_limbs[1] := by rw [← hcl]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ec : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_cc[i] = input_cc[i] := by + intro i hi; rw [← hicc]; simp only [Vector.getElem_map] + have ef : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_cols_result_u16_flags[i] + = input_cols_result_u16_flags[i] := by + intro i hi; rw [← hflags]; simp only [Vector.getElem_map] + have ecl : ∀ i (hi : i < 2), + Expression.eval env.toEnvironment input_var_cols_result_comparison_limbs[i] + = input_cols_result_comparison_limbs[i] := by + intro i hi; rw [← hcl]; simp only [Vector.getElem_map] obtain ⟨hb0, hb1, hb2, hb3⟩ := Word.lt_cases_of_isU64 hb_u64 obtain ⟨hc0, hc1, hc2, hc3⟩ := Word.lt_cases_of_isU64 hcc_u64 have hbtop : (input_b[3] + input_is_signed * 32768 - 65536 * input_cols_b_msb_msb).val < 2 ^ 16 := by @@ -277,8 +270,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp have hcm0 : input_cols_c_msb_msb = 0 := by simpa using h9 rw [h, hcm0]; simpa using hc3 · rw [h, one_mul, hcm_eq h]; simpa using (adj_limb hc3).2 - simp only [id_eq, eb0, eb1, eb2, eb3, ec0, ec1, ec2, ec3, ef0, ef1, ef2, ef3, ecl0, ecl1, - vec4_eta, vec2_eta, ← sub_eq_add_neg] at ⊢ + simp only [id_eq, eb, ec, ef, ecl, vec4_eta, vec2_eta, ← sub_eq_add_neg] refine ⟨⟨⟨fun _ => hb3, his_bin⟩, hbm_bool, hbm_eq⟩, ⟨⟨fun _ => hc3, his_bin⟩, hcm_bool, hcm_eq⟩, ⟨⟨fun _ => ⟨Word.isU64_of_cases (by simpa using hb0) (by simpa using hb1) (by simpa using hb2) diff --git a/SP1Clean/Proofs/Operations/LtOperationUnsigned/Formal.lean b/SP1Clean/Proofs/Operations/LtOperationUnsigned/Formal.lean index 335d40f7..cddb62ab 100644 --- a/SP1Clean/Proofs/Operations/LtOperationUnsigned/Formal.lean +++ b/SP1Clean/Proofs/Operations/LtOperationUnsigned/Formal.lean @@ -150,21 +150,17 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨hbcc_imp, hbin⟩ := h_assumptions obtain ⟨hib, hicc, hicols, hir⟩ := h_input obtain ⟨_hibit, hiflags, _hineinv, hicl⟩ := hicols - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ec0 : Expression.eval env input_var_cc[0] = input_cc[0] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec1 : Expression.eval env input_var_cc[1] = input_cc[1] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec2 : Expression.eval env input_var_cc[2] = input_cc[2] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec3 : Expression.eval env input_var_cc[3] = input_cc[3] := by rw [← hicc]; simp only [Vector.getElem_map] - have ef0 : Expression.eval env input_var_cols_u16_flags[0] = input_cols_u16_flags[0] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef1 : Expression.eval env input_var_cols_u16_flags[1] = input_cols_u16_flags[1] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef2 : Expression.eval env input_var_cols_u16_flags[2] = input_cols_u16_flags[2] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef3 : Expression.eval env input_var_cols_u16_flags[3] = input_cols_u16_flags[3] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ecl0 : Expression.eval env input_var_cols_comparison_limbs[0] = input_cols_comparison_limbs[0] := by rw [← hicl]; simp only [Vector.getElem_map] - have ecl1 : Expression.eval env input_var_cols_comparison_limbs[1] = input_cols_comparison_limbs[1] := by rw [← hicl]; simp only [Vector.getElem_map] - simp only [id_eq, eb0, eb1, eb2, eb3, ec0, ec1, ec2, ec3, ef0, ef1, ef2, ef3, ecl0, ecl1] at h_holds ⊢ + have eb : ∀ i (hi : i < 4), Expression.eval env input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ec : ∀ i (hi : i < 4), Expression.eval env input_var_cc[i] = input_cc[i] := by + intro i hi; rw [← hicc]; simp only [Vector.getElem_map] + have ef : ∀ i (hi : i < 4), + Expression.eval env input_var_cols_u16_flags[i] = input_cols_u16_flags[i] := by + intro i hi; rw [← hiflags]; simp only [Vector.getElem_map] + have ecl : ∀ i (hi : i < 2), + Expression.eval env input_var_cols_comparison_limbs[i] = input_cols_comparison_limbs[i] := by + intro i hi; rw [← hicl]; simp only [Vector.getElem_map] + simp only [id_eq, eb, ec, ef, ecl] at h_holds ⊢ obtain ⟨h_cmp, hE1, hE6, hE8, hE10, hE12, hE14, hE19, hE27, hE35, hE43, hE48, hE49, hE54⟩ := h_holds -- the composed `U16Compare` assertion's `Assumptions` (cl ranges on a real row, via the cores). have hCmpAs : U16CompareOperation.circuit.Assumptions @@ -199,20 +195,17 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨hbcc_imp, hbin⟩ := h_assumptions obtain ⟨hib, hicc, hicols, hir⟩ := h_input obtain ⟨_hibit, hiflags, _hineinv, hicl⟩ := hicols - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ec0 : Expression.eval env.toEnvironment input_var_cc[0] = input_cc[0] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec1 : Expression.eval env.toEnvironment input_var_cc[1] = input_cc[1] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec2 : Expression.eval env.toEnvironment input_var_cc[2] = input_cc[2] := by rw [← hicc]; simp only [Vector.getElem_map] - have ec3 : Expression.eval env.toEnvironment input_var_cc[3] = input_cc[3] := by rw [← hicc]; simp only [Vector.getElem_map] - have ef0 : Expression.eval env.toEnvironment input_var_cols_u16_flags[0] = input_cols_u16_flags[0] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef1 : Expression.eval env.toEnvironment input_var_cols_u16_flags[1] = input_cols_u16_flags[1] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef2 : Expression.eval env.toEnvironment input_var_cols_u16_flags[2] = input_cols_u16_flags[2] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ef3 : Expression.eval env.toEnvironment input_var_cols_u16_flags[3] = input_cols_u16_flags[3] := by rw [← hiflags]; simp only [Vector.getElem_map] - have ecl0 : Expression.eval env.toEnvironment input_var_cols_comparison_limbs[0] = input_cols_comparison_limbs[0] := by rw [← hicl]; simp only [Vector.getElem_map] - have ecl1 : Expression.eval env.toEnvironment input_var_cols_comparison_limbs[1] = input_cols_comparison_limbs[1] := by rw [← hicl]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ec : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_cc[i] = input_cc[i] := by + intro i hi; rw [← hicc]; simp only [Vector.getElem_map] + have ef : ∀ i (hi : i < 4), + Expression.eval env.toEnvironment input_var_cols_u16_flags[i] = input_cols_u16_flags[i] := by + intro i hi; rw [← hiflags]; simp only [Vector.getElem_map] + have ecl : ∀ i (hi : i < 2), + Expression.eval env.toEnvironment input_var_cols_comparison_limbs[i] + = input_cols_comparison_limbs[i] := by + intro i hi; rw [← hicl]; simp only [Vector.getElem_map] obtain ⟨hf0, hf1, hf2, hf3, hsum, hs3, hs2, hs1, hs0, hcl0eq, hcl1eq, hinv, hbitbool, hbitord⟩ := h_spec -- the composed `U16Compare` assertion's `Assumptions` ∧ `Spec` (cl ranges via the cores on a real row). have h_sel_real : input_is_real = 1 → Selectors input_b input_cc @@ -231,7 +224,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp have hCmpSpec : U16CompareOperation.circuit.Spec ⟨input_cols_comparison_limbs[0], input_cols_comparison_limbs[1], ⟨input_cols_u16_compare_operation_bit⟩, input_is_real⟩ := ⟨hbitbool, hbitord⟩ - simp only [id_eq, eb0, eb1, eb2, eb3, ec0, ec1, ec2, ec3, ef0, ef1, ef2, ef3, ecl0, ecl1] + simp only [id_eq, eb, ec, ef, ecl] refine ⟨⟨hCmpAs, hCmpSpec⟩, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ · rcases hbin with h | h <;> rw [h] <;> ring · rcases hf0 with h | h <;> rw [h] <;> ring diff --git a/SP1Clean/Proofs/Operations/MulOperation/Formal.lean b/SP1Clean/Proofs/Operations/MulOperation/Formal.lean index 27bf595e..ad27f00c 100644 --- a/SP1Clean/Proofs/Operations/MulOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/MulOperation/Formal.lean @@ -217,7 +217,7 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := have hmsb_bool : input_cols_product_msb_msb = 0 ∨ input_cols_product_msb_msb = 1 := hpmspec.1 have hmsb : input_is_mulw = 1 → input_cols_product_msb_msb = if (input_cols_product[2] + input_cols_product[3] * 256).val ≥ 32768 then 1 else 0 := by - intro h; have := hpmspec.2 h; rw [ep2, ep3] at this; exact this + intro h; have := hpmspec.2 h; rwa [ep2, ep3] at this have hb_msb : input_cols_b_msb = if input_b[3].val ≥ 32768 then 1 else 0 := by obtain ⟨hlo3, hhi3, hreass3⟩ := hb_low 3 dsimp only at hlo3 hhi3 hreass3 diff --git a/SP1Clean/Proofs/Operations/SubOperation/Formal.lean b/SP1Clean/Proofs/Operations/SubOperation/Formal.lean index 27eed563..c02c84df 100644 --- a/SP1Clean/Proofs/Operations/SubOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/SubOperation/Formal.lean @@ -27,28 +27,15 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨hia, hib, hiv, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast have h65536 : (2 : ℕ) ^ 16 = 65536 := by norm_num - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev2 : Expression.eval env input_var_cols_value[2] = input_cols_value[2] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev3 : Expression.eval env input_var_cols_value[3] = input_cols_value[3] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3, - ev0, ev1, ev2, ev3] at h_holds ⊢ + have ev {w : Word (Expression (ZMod p))} {v : Word (ZMod p)} (h : Vector.map (Expression.eval env) w = v) : + ∀ i (_ : i < 4), Expression.eval env w[i] = v[i] := fun i _ => by rw [← h, Vector.getElem_map] + simp only [circuit_norm, byteChannel, ev hia, ev hib, ev hiv] at h_holds ⊢ obtain ⟨hr0, hr1, hr2, hr3, _hbool, hgc0, hgc1, hgc2, hgc3⟩ := h_holds - -- post-#398 the byte receives owe no padding requirement, so the goal is exactly `Spec`. intro hr1eq have hneg : -input_is_real = -1 := by rw [hr1eq] have R0 := hr0 hneg; have R1 := hr1 hneg; have R2 := hr2 hneg; have R3 := hr3 hneg rw [← c16] at R0 R1 R2 R3 rw [hr1eq, one_mul] at hgc0 hgc1 hgc2 hgc3 - -- The generated `main` decomposes the borrow constant as `+ 65536 - 1`; fold to RawSpec's `65535`. have c65535 : ∀ x : ZMod p, x + 65536 + -1 = x + 65535 := fun x => by ring simp only [c65535] at hgc0 hgc1 hgc2 hgc3 refine subSemantics_of_carries ha hb ?_ @@ -66,48 +53,26 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨ha, hb, hbin⟩ := h_assumptions obtain ⟨hia, hib, hiv, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have ea2 : Expression.eval env.toEnvironment input_var_a[2] = input_a[2] := by rw [← hia]; simp only [Vector.getElem_map] - have ea3 : Expression.eval env.toEnvironment input_var_a[3] = input_a[3] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have eb2 : Expression.eval env.toEnvironment input_var_b[2] = input_b[2] := by rw [← hib]; simp only [Vector.getElem_map] - have eb3 : Expression.eval env.toEnvironment input_var_b[3] = input_b[3] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env.toEnvironment input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env.toEnvironment input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev2 : Expression.eval env.toEnvironment input_var_cols_value[2] = input_cols_value[2] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev3 : Expression.eval env.toEnvironment input_var_cols_value[3] = input_cols_value[3] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, ea2, ea3, eb0, eb1, eb2, eb3, - ev0, ev1, ev2, ev3] + have ev {w : Word (Expression (ZMod p))} {v : Word (ZMod p)} + (h : Vector.map (Expression.eval env.toEnvironment) w = v) : + ∀ i (_ : i < 4), Expression.eval env.toEnvironment w[i] = v[i] := + fun i _ => by rw [← h, Vector.getElem_map] + simp only [circuit_norm, byteChannel, ev hia, ev hib, ev hiv] refine ⟨?_, ?_, ?_, ?_, ?_⟩ - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨hv0, _, _, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv0 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, hv1, _, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv1 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, _, hv2, _⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv2 - · intro hneg - have hr1 : input_is_real = 1 := neg_inj.mp hneg - obtain ⟨hv, _⟩ := h_spec hr1 - obtain ⟨_, _, _, hv3⟩ := Word.lt_cases_of_isU64 hv - rw [← c16]; exact (byteRowSpec_range _ h16p).mpr hv3 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.2.1 + · intro hneg; rw [← c16] + exact (byteRowSpec_range _ h16p).mpr (Word.lt_cases_of_isU64 (h_spec (neg_inj.mp hneg)).1).2.2.2 · rcases hbin with h0 | h1 · simp [h0] · obtain ⟨hv, hbv⟩ := h_spec h1 obtain ⟨hc0, hc1, hc2, hc3, _, _, _, _⟩ := carries_of_subSemantics ha hb hv hbv simp only [Nat.cast_ofNat, sub_eq_add_neg] at hc0 hc1 hc2 hc3 rw [h1] - -- The generated `main` decomposes the borrow constant as `+ 65536 - 1`; fold to RawSpec's `65535`. have c65535 : ∀ x : ZMod p, x + 65536 + -1 = x + 65535 := fun x => by ring simp only [c65535] refine ⟨?_, ?_, ?_, ?_, ?_⟩ diff --git a/SP1Clean/Proofs/Operations/SubwOperation/Formal.lean b/SP1Clean/Proofs/Operations/SubwOperation/Formal.lean index 3a2d5065..02c2e935 100644 --- a/SP1Clean/Proofs/Operations/SubwOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/SubwOperation/Formal.lean @@ -46,13 +46,14 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := obtain ⟨hia, hib, ⟨hiv, _⟩, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast have h65536 : (2 : ℕ) ^ 16 = 65536 := by norm_num - have ea0 : Expression.eval env input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] - have ev0 : Expression.eval env input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - simp only [circuit_norm, byteChannel, ea0, ea1, eb0, eb1, ev0, ev1] at h_holds ⊢ + have ea : ∀ i (hi : i < 4), Expression.eval env input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] + have ev : ∀ i (hi : i < 2), + Expression.eval env input_var_cols_value[i] = input_cols_value[i] := by + intro i hi; rw [← hiv]; simp only [Vector.getElem_map] + simp only [circuit_norm, byteChannel, ea, eb, ev] at h_holds ⊢ obtain ⟨h_msb, hr0, hr1, _hgate, hgc0, hgc1⟩ := h_holds have h_msb_as : U16MSBOperation.circuit.Assumptions (⟨input_cols_value[1], ⟨input_cols_msb_msb⟩, input_is_real⟩ : U16MSBOperation.Inputs (ZMod p)) := by @@ -91,12 +92,13 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp obtain ⟨ha, hb, hbin⟩ := h_assumptions obtain ⟨hia, hib, ⟨hiv, _⟩, _⟩ := h_input have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast - have ev0 : Expression.eval env.toEnvironment input_var_cols_value[0] = input_cols_value[0] := by rw [← hiv]; simp only [Vector.getElem_map] - have ev1 : Expression.eval env.toEnvironment input_var_cols_value[1] = input_cols_value[1] := by rw [← hiv]; simp only [Vector.getElem_map] - have ea0 : Expression.eval env.toEnvironment input_var_a[0] = input_a[0] := by rw [← hia]; simp only [Vector.getElem_map] - have ea1 : Expression.eval env.toEnvironment input_var_a[1] = input_a[1] := by rw [← hia]; simp only [Vector.getElem_map] - have eb0 : Expression.eval env.toEnvironment input_var_b[0] = input_b[0] := by rw [← hib]; simp only [Vector.getElem_map] - have eb1 : Expression.eval env.toEnvironment input_var_b[1] = input_b[1] := by rw [← hib]; simp only [Vector.getElem_map] + have ev : ∀ i (hi : i < 2), + Expression.eval env.toEnvironment input_var_cols_value[i] = input_cols_value[i] := by + intro i hi; rw [← hiv]; simp only [Vector.getElem_map] + have ea : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_a[i] = input_a[i] := by + intro i hi; rw [← hia]; simp only [Vector.getElem_map] + have eb : ∀ i (hi : i < 4), Expression.eval env.toEnvironment input_var_b[i] = input_b[i] := by + intro i hi; rw [← hib]; simp only [Vector.getElem_map] have key : input_is_real = 1 → (input_cols_value[0].val < 2 ^ 16 ∧ input_cols_value[1].val < 2 ^ 16) ∧ RawSpec input_a input_b ⟨input_cols_value, ⟨input_cols_msb_msb⟩⟩ ∧ @@ -108,7 +110,7 @@ theorem completeness : FormalAssertion.Completeness (ZMod p) main Assumptions Sp List.getElem_cons_zero, List.getElem_cons_succ] at hUv0 hUv1 obtain ⟨h_raw, h_msbval⟩ := carries_of_subwSemantics ha hb hU hbveq exact ⟨⟨hUv0, hUv1⟩, h_raw, h_msbval⟩ - simp only [circuit_norm, byteChannel, ea0, ea1, eb0, eb1, ev0, ev1] + simp only [circuit_norm, byteChannel, ea, eb, ev] refine ⟨⟨⟨fun hr1 => (key hr1).1.2, hbin⟩, ⟨h_spec.1, fun hr1 => (key hr1).2.2⟩⟩, ?_, ?_, ?_, ?_, ?_⟩ · intro hneg have hr1 : input_is_real = 1 := neg_inj.mp hneg diff --git a/SP1Clean/Proofs/Operations/U16CompareOperation/Formal.lean b/SP1Clean/Proofs/Operations/U16CompareOperation/Formal.lean index d8d84845..6c47ff4d 100644 --- a/SP1Clean/Proofs/Operations/U16CompareOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/U16CompareOperation/Formal.lean @@ -30,7 +30,6 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast simp only [circuit_norm, byteChannel] at h_holds ⊢ obtain ⟨hr, _hbool, hgc⟩ := h_holds - -- post-#398 the byte receive owes no padding requirement, so the goal is exactly `Spec`. refine ⟨bool_of_mul_pred hgc, ?_⟩ intro hr1eq obtain ⟨ha, hb⟩ := hab hr1eq diff --git a/SP1Clean/Proofs/Operations/U16MSBOperation/Formal.lean b/SP1Clean/Proofs/Operations/U16MSBOperation/Formal.lean index 70d481f4..129758d0 100644 --- a/SP1Clean/Proofs/Operations/U16MSBOperation/Formal.lean +++ b/SP1Clean/Proofs/Operations/U16MSBOperation/Formal.lean @@ -31,14 +31,12 @@ theorem soundness : FormalAssertion.Soundness (ZMod p) main Assumptions Spec := have c16 : ((16 : ℕ) : ZMod p) = (16 : ZMod p) := by norm_cast simp only [circuit_norm, byteChannel] at h_holds ⊢ obtain ⟨hr, _hbool, hgc⟩ := h_holds - -- post-#398 the byte receive owes no padding requirement, so the goal is exactly `Spec`. refine ⟨bool_of_mul_pred hgc, ?_⟩ intro hr1eq - have hav := ha hr1eq have hneg : -input_is_real = -1 := by rw [hr1eq] have R := hr hneg rw [← c16] at R - refine msb_of_raw hav ?_ + refine msb_of_raw (ha hr1eq) ?_ simp only [RawSpec, sub_eq_add_neg] exact ⟨bool_of_mul_pred hgc, (byteRowSpec_range _ h16p).mp R⟩ diff --git a/SP1Clean/Soundness/ByteConsistency.lean b/SP1Clean/Soundness/ByteConsistency.lean index 3fbcbfa0..e463011b 100644 --- a/SP1Clean/Soundness/ByteConsistency.lean +++ b/SP1Clean/Soundness/ByteConsistency.lean @@ -97,8 +97,7 @@ private theorem multiplicitySum_eq_zero_of_multOf {l : LookupAccessList} (k : Lo simp only [multiplicitySum] apply List.sum_eq_zero intro x hx - simp only [List.mem_map] at hx - obtain ⟨a, ha, rfl⟩ := hx + obtain ⟨a, ha, rfl⟩ := List.mem_map.1 hx exact h a (List.mem_of_mem_filter ha) omit [NeZero p] in @@ -130,11 +129,10 @@ theorem byteAccessValid_of_balance [Fact (1 < p)] TraceByteLink rows := by intro r hr h_real row h_row_mem set a := byteSend r.is_real row with ha_def - have ha_mem : a ∈ aggregateChipRows rows byteLookups := by - refine List.mem_flatMap.mpr ⟨r, hr, ?_⟩ - exact List.mem_map.mpr ⟨row, h_row_mem, rfl⟩ + have ha_mem : a ∈ aggregateChipRows rows byteLookups := + List.mem_flatMap.mpr ⟨r, hr, List.mem_map.mpr ⟨row, h_row_mem, rfl⟩⟩ have h_pos : 0 < multOf a := by - rw [ha_def]; simp only [byteSend, multOf, h_real, ZMod.val_one]; norm_num + simp only [ha_def, byteSend, multOf, h_real, ZMod.val_one]; norm_num have h_nonneg : ∀ b ∈ aggregateChipRows rows byteLookups, 0 ≤ multOf b := by intro b hb obtain ⟨r', _, hb'⟩ := List.mem_flatMap.mp hb diff --git a/SP1Clean/Soundness/ChipRegistry.lean b/SP1Clean/Soundness/ChipRegistry.lean index 8e449545..72cd4bfd 100644 --- a/SP1Clean/Soundness/ChipRegistry.lean +++ b/SP1Clean/Soundness/ChipRegistry.lean @@ -60,7 +60,7 @@ namespace SP1Clean.Soundness variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 24 < p)] -local instance : Fact (2 ^ 17 < p) := ⟨by have := Fact.out (p := 2 ^ 24 < p); omega⟩ +local instance : Fact (2 ^ 17 < p) := ⟨lt_of_le_of_lt (by norm_num) (Fact.out (p := 2 ^ 24 < p))⟩ /-- Every chip with a capstone-integration `ChipKind`. **Auditable:** one entry ⇔ one wired chip; to wire a new chip, define its `kind` in its `Bridge.lean` and add it here. (Not a dispatch table — the capstone diff --git a/SP1Clean/Soundness/Coverage.lean b/SP1Clean/Soundness/Coverage.lean index ad30d6b5..c62ac865 100644 --- a/SP1Clean/Soundness/Coverage.lean +++ b/SP1Clean/Soundness/Coverage.lean @@ -19,7 +19,16 @@ the registry by `rfl` (`coverage.map (·.kind) = allChipKinds`). **Opcode → Sail.** `Opcode.toNat` is the Program-bus opcode each chip commits (`Trace.RowView.opcode`); `routeOf_reaches_sail` surfaces each routed chip's `ChipKind.reaches_sail`, i.e. on a real row its -in-circuit `Spec` drives the RISC-V Sail spec (`spec_`) — the formal bridge from this enum to Sail. -/ +in-circuit `Spec` drives the RISC-V Sail spec (`spec_`) — the formal bridge from this enum to Sail. + +**Register vs immediate ALU forms.** Routing is keyed on `(opcode, rd == x0)` *only*, exactly as SP1's +`tracing.rs`: the immediate ALU instructions (SLTI/SLTIU, XORI/ORI/ANDI, ADDIW) are **not** separate +`Opcode`s — they share their register opcode (`SLT = 9`, `XOR = 3`, `ADDW = 19`, …) and are distinguished by +the in-row `adapter.imm_c` *column*, not by routing. So `imm_c` is deliberately absent from this table. The +ALU chips each reach Sail for **both** forms: `LtChip`/`BitwiseChip`/`AddwChip`'s `sailEquiv` is the +conjunction of a register guarantee (gated on the rs2 read) and an immediate guarantee (gated on the +program-bus decode `op_c_val = sign_extend imm`), so `routeOf_reaches_sail` covers SLTI/…/ADDIW as well as +their register forms. (`ShiftLeft`/`ShiftRight` model `imm_c` in-circuit; `ADDI` has its own `AddiChip`.) -/ namespace SP1Clean.Soundness diff --git a/SP1Clean/Soundness/Decode.lean b/SP1Clean/Soundness/Decode.lean index 4fc4e4a3..8c14b48c 100644 --- a/SP1Clean/Soundness/Decode.lean +++ b/SP1Clean/Soundness/Decode.lean @@ -356,32 +356,26 @@ theorem decode_bound (prog : GuestProgram) {pi : SP1PublicIO (ZMod p)} (hw : WalkOf pi rows path) {i : ℕ} (hi : i < path.length) (s : SailState) : DecodeOperandsBound prog (path[i]'hi) s := by intro hcfg w hfetch - set r := path[i]'hi with hr_def - have hr_path : r ∈ path := List.getElem_mem hi - -- the walk row is a real row of the trace + set r := path[i]'hi have hmem : r ∈ realRowEdges (rows.map ChipRow.view) := - Multiset.mem_of_le hw.2 (Multiset.mem_coe.mpr hr_path) + Multiset.mem_of_le hw.2 (Multiset.mem_coe.mpr (List.getElem_mem hi)) rw [realRowEdges, Multiset.mem_filter] at hmem obtain ⟨hr_coe, h_real⟩ := hmem - have hr_rows : r ∈ rows.map ChipRow.view := Multiset.mem_coe.mp hr_coe - -- its committed program columns lie in the decoded ROM have ha_mem : programAccess r ∈ aggregateProgramAccesses (rows.map ChipRow.view) := by - simp only [aggregateProgramAccesses]; exact List.mem_map_of_mem hr_rows + simp only [aggregateProgramAccesses]; exact List.mem_map_of_mem (Multiset.mem_coe.mp hr_coe) have h_real' : (programAccess r).is_real ≠ 0 := by simpa only [programAccess, stateAccess] using h_real obtain ⟨w₀, hfetch₀, hdec⟩ := h_link.rom_holds (programAccess r) ha_mem h_real' - -- the fetched words agree (same pc key) have hpc : pcBitsOfRow (programAccess r).toRow = rcvPcOf (stateAccess r) := by simp only [pcBitsOfRow, rcvPcOf, programAccess, ProgramAccess.toRow, stateAccess] rw [hpc, hfetch] at hfetch₀ obtain rfl : w = w₀ := Option.some.inj hfetch₀ - -- the pc vectors agree, so the projection's target is the committed row have hpcvec : rowPcVec (programAccess r).toRow = r.state.pc := by simp only [rowPcVec, programAccess, ProgramAccess.toRow] - apply Vector.ext; intro j hj + ext j hj interval_cases j <;> simp - obtain ⟨ii, s', hrun, hproj⟩ := hdec s hcfg - exact ⟨ii, s', hrun, by rw [hpcvec] at hproj; exact hproj⟩ + rw [hpcvec] at hdec + exact hdec s hcfg /-! ## The balance-level decode discharge (removing the threaded link) — W3 deliverable B -/ @@ -477,7 +471,7 @@ each row is discharged this way. -/ /-- A one-instruction guest program: `ADD x1, x2, x3` (`0x003100B3`) at pc 0. -/ def addProgram : GuestProgram := ⟨[(0#64, 0x003100B3#32)], 0, [], by simp, - by intro a ha; simp only [List.map_cons, List.map_nil, List.mem_singleton] at ha; subst ha; simp⟩ + by simp⟩ /-- The committed Program-bus row the ADD decodes to — the `instrToProgramRow` projection at pc 0 (`op_a = rd = x1`, `op_b[0] = rs1 = x2`, `op_c[0] = rs2 = x3`, opcode `ADD`). -/ @@ -498,10 +492,7 @@ ROM word at its pc, against the official Sail `ext_decode` — `decodedInROM` ho Sail model's decoder axioms. -/ theorem decodedInROM_addRow : decodedInROM addProgram (addRow (p := p)) := by refine ⟨0x003100B3#32, ?_, ?_⟩ - · show addProgram.fetchWord (pcBitsOfRow (addRow (p := p))) = some _ - have hpc : pcBitsOfRow (addRow (p := p)) = 0#64 := by - simp only [pcBitsOfRow, addRow, pcBitsOfVals, ZMod.val_zero]; rfl - rw [hpc]; rfl + · simp only [pcBitsOfRow, addRow, pcBitsOfVals, ZMod.val_zero]; rfl · intro s hcfg refine ⟨.RTYPE (regidx.Regidx 3#5, regidx.Regidx 2#5, regidx.Regidx 1#5, rop.ADD), s, SP1Clean.SailDecode.decode_ADD_example s hcfg.1 hcfg.2, ?_⟩ diff --git a/SP1Clean/Soundness/GatedVm/BalanceMod.lean b/SP1Clean/Soundness/GatedVm/BalanceMod.lean index fe140f0c..3ea3c6a0 100644 --- a/SP1Clean/Soundness/GatedVm/BalanceMod.lean +++ b/SP1Clean/Soundness/GatedVm/BalanceMod.lean @@ -53,7 +53,6 @@ theorem isConsistentBalanced_of_intCast_zero {p : ℕ} [NeZero p] intro k have hdvd : (p : ℤ) ∣ multiplicitySum accesses k := (ZMod.intCast_zmod_eq_zero_iff_dvd _ p).mp (hmod k) - -- |multiplicitySum| ≤ (filtered count) ≤ length < p have hbound : |multiplicitySum accesses k| ≤ ((filterKey accesses k).map multOf).length := by apply abs_sum_le_length_of_binary intro x hx @@ -61,13 +60,8 @@ theorem isConsistentBalanced_of_intCast_zero {p : ℕ} [NeZero p] exact hbin a (List.mem_of_mem_filter ha) have hlen2 : ((filterKey accesses k).map multOf).length ≤ accesses.length := by rw [List.length_map, filterKey]; exact List.length_filter_le _ _ - have habs : |multiplicitySum accesses k| < (p : ℤ) := by - have : ((filterKey accesses k).map multOf).length < p := by omega - have hc : (((filterKey accesses k).map multOf).length : ℤ) < (p : ℤ) := by exact_mod_cast this - omega - -- p ∣ x and |x| < p ⇒ x = 0 - by_contra h - exact absurd (Int.le_of_dvd (abs_pos.mpr h) ((dvd_abs _ _).mpr hdvd)) (by omega) + have habs : |multiplicitySum accesses k| < (p : ℤ) := by omega + exact Int.eq_zero_of_abs_lt_dvd hdvd habs /-! ## The Clean → native translation (the `toAccess` adapter, roadmap W1a) @@ -103,7 +97,6 @@ theorem intCast_multiplicitySum_map_toAccess haveI : NeZero p := ⟨(Fact.out (p := p.Prime)).ne_zero⟩ by_cases h_ex : ∃ i ∈ interactions, keyOf (Interaction.toAccess i) = k · obtain ⟨i₀, hi₀, hk₀⟩ := h_ex - -- same-channel keys separate exactly on the message have h_pred : ∀ i ∈ interactions, decide (keyOf (Interaction.toAccess i) = k) = decide (i.msg = i₀.msg) := by intro i hi @@ -115,7 +108,6 @@ theorem intCast_multiplicitySum_map_toAccess exact Array.toList_inj.mp (List.map_injective_iff.mpr (ZMod.val_injective p) h3) · intro h simp only [Interaction.toAccess, keyOf, h_channel i hi, h_channel i₀ hi₀, h] - -- the access-key filter is the `toAccess`-image of the message filter have h_filter : (interactions.map Interaction.toAccess).filter (fun a => keyOf a = k) = (interactions.filter (fun i => i.msg = i₀.msg)).map Interaction.toAccess := by rw [List.filter_map] @@ -154,8 +146,7 @@ theorem isConsistentBalanced_of_balancedInteractions isConsistentBalanced accesses := by haveI : NeZero p := ⟨(Fact.out (p := p.Prime)).ne_zero⟩ refine isConsistentBalanced_of_intCast_zero (p := p) accesses ?_ h_bin ?_ - · -- |accesses| = |interactions| < ringChar (ZMod p) = p - rcases h_bal.1 with hlt | hchar + · rcases h_bal.1 with hlt | hchar · rw [h_perm.length_eq, List.length_map] rwa [ZMod.ringChar_zmod_n] at hlt · rw [ZMod.ringChar_zmod_n] at hchar diff --git a/SP1Clean/Soundness/GatedVm/Chain.lean b/SP1Clean/Soundness/GatedVm/Chain.lean index f3755db3..5a17951d 100644 --- a/SP1Clean/Soundness/GatedVm/Chain.lean +++ b/SP1Clean/Soundness/GatedVm/Chain.lean @@ -67,8 +67,6 @@ lemma balanced_erase {E : Multiset α} {e : α} (he : e ∈ E) (edge : α → V = (indeg (E.erase e) edge v : ℤ) + (if (edge e).2 = v then 1 else 0) := by simp only [indeg]; exact_mod_cast card_filter_erase he (fun a => (edge a).2 = v) have hv := h v - -- the erased edge `e = src → (edge e).2`, so its source-indicator is `v = src` and its - -- target-indicator is `v = (edge e).2` have e1 : (if (edge e).1 = v then (1 : ℤ) else 0) = (if v = src then 1 else 0) := by rcases eq_or_ne v src with hc | hc · rw [if_pos (h1.trans hc.symm), if_pos hc] @@ -79,8 +77,6 @@ lemma balanced_erase {E : Multiset α} {e : α} (he : e ∈ E) (edge : α → V · rw [if_neg (fun hh => hc hh.symm), if_neg hc] rw [e1] at ho rw [e2] at hi - show (outdeg (E.erase e) edge v : ℤ) - indeg (E.erase e) edge v - = (if v = (edge e).2 then 1 else 0) - (if v = snk then 1 else 0) linarith [ho, hi, hv] /-- **Balance ⇒ trail (the Eulerian core).** From degree balance alone — no clock injectivity, no @@ -94,15 +90,13 @@ theorem exists_trail (edge : α → V × V) (E : Multiset α) : intro src snk h by_cases hss : src = snk · exact ⟨[], hss, by simp⟩ - · -- the source has an outgoing edge - have hsrc := h src + · have hsrc := h src rw [if_pos rfl, if_neg hss] at hsrc have hpos : 0 < outdeg E edge src := by omega simp only [outdeg] at hpos obtain ⟨e, he_filter⟩ := Multiset.exists_mem_of_ne_zero (Multiset.card_pos.mp hpos) rw [Multiset.mem_filter] at he_filter obtain ⟨he_mem, he1⟩ := he_filter - -- erase it; the remainder is balanced with the source shifted to `(edge e).2` have hbal' : Balanced (E.erase e) edge (edge e).2 snk := balanced_erase he_mem edge h he1 have hlt : E.erase e < E := by conv_rhs => rw [← Multiset.cons_erase he_mem] @@ -110,8 +104,7 @@ theorem exists_trail (edge : α → V × V) (E : Multiset α) : obtain ⟨path', hwalk', hsub'⟩ := IH (E.erase e) hlt (edge e).2 snk hbal' refine ⟨e :: path', ⟨he1, hwalk'⟩, ?_⟩ have hle : (e ::ₘ (↑path' : Multiset α)) ≤ e ::ₘ E.erase e := Multiset.cons_le_cons e hsub' - rw [Multiset.cons_erase he_mem] at hle - exact hle + rwa [Multiset.cons_erase he_mem] at hle end GatedVm diff --git a/SP1Clean/Soundness/GatedVm/Formal.lean b/SP1Clean/Soundness/GatedVm/Formal.lean index 73e97c91..c3908b11 100644 --- a/SP1Clean/Soundness/GatedVm/Formal.lean +++ b/SP1Clean/Soundness/GatedVm/Formal.lean @@ -33,9 +33,7 @@ def GatedVm.toFormalEnsemble (vm : GatedVm F PublicIO) Assumptions := Assumptions Spec := Spec soundness := by - intro publicInput hA hStmt - obtain ⟨witness, hpub, hC, hB⟩ := hStmt - subst hpub + rintro publicInput hA ⟨witness, rfl, hC, hB⟩ exact soundness witness hA hC hB @[simp] lemma GatedVm.toFormalEnsemble_ensemble (vm : GatedVm F PublicIO) diff --git a/SP1Clean/Soundness/GatedVm/SailDispatch.lean b/SP1Clean/Soundness/GatedVm/SailDispatch.lean index 2fc2fcfa..862da108 100644 --- a/SP1Clean/Soundness/GatedVm/SailDispatch.lean +++ b/SP1Clean/Soundness/GatedVm/SailDispatch.lean @@ -26,9 +26,8 @@ Sail spec (for any Sail state honouring that row's register/PC/memory reads, qua `sailEquiv`). Dispatched generically through `r.kind.reaches_sail`. -/ theorem chipRows_step_sound (rows : List (ChipRow p)) (data : ProverData (ZMod p)) (h_spec : ∀ r ∈ rows, r.chipSpec data) : - ∀ r ∈ rows, r.is_real = 1 → ∀ s : SailState, r.sailEquiv s := by - intro r hr h_real s - exact r.kind.reaches_sail r.inputs r.cols data s h_real (h_spec r hr) + ∀ r ∈ rows, r.is_real = 1 → ∀ s : SailState, r.sailEquiv s := + fun r hr h_real s => r.kind.reaches_sail r.inputs r.cols data s h_real (h_spec r hr) /-- Single-row form (the dispatch on one row), handy when the spec quantifies rows from a trail. -/ theorem chipRow_sailEquiv (r : ChipRow p) (data : ProverData (ZMod p)) diff --git a/SP1Clean/Soundness/GatedVm/StateBridge.lean b/SP1Clean/Soundness/GatedVm/StateBridge.lean index 49a97519..fc64bb09 100644 --- a/SP1Clean/Soundness/GatedVm/StateBridge.lean +++ b/SP1Clean/Soundness/GatedVm/StateBridge.lean @@ -143,18 +143,8 @@ theorem balanced_state_bus split_ifs <;> ring rw [hbd] at hb rw [outdeg_realRowEdges rows k, indeg_realRowEdges rows k] - have ei : (if (InteractionKind.State, "SP1State", initEntry) = k then (1 : ℤ) else 0) - = (if k = (InteractionKind.State, "SP1State", initEntry) then 1 else 0) := by - by_cases h : (InteractionKind.State, "SP1State", initEntry) = k - · rw [if_pos h, if_pos h.symm] - · rw [if_neg h, if_neg (fun hh => h hh.symm)] - have ef : (if (InteractionKind.State, "SP1State", finalEntry) = k then (1 : ℤ) else 0) - = (if k = (InteractionKind.State, "SP1State", finalEntry) then 1 else 0) := by - by_cases h : (InteractionKind.State, "SP1State", finalEntry) = k - · rw [if_pos h, if_pos h.symm] - · rw [if_neg h, if_neg (fun hh => h hh.symm)] - rw [ei, ef] at hb - linarith [hb] + simp only [eq_comm] at hb + linarith /-- **Balance ⇒ a real-row execution trail.** Combining `balanced_state_bus` with the abstract `GatedVm.exists_trail`: from the balanced state bus (+ genesis/finalization boundary) there is a walk of diff --git a/SP1Clean/Soundness/InstructionTrace.lean b/SP1Clean/Soundness/InstructionTrace.lean index ed2c77f8..e78506a4 100644 --- a/SP1Clean/Soundness/InstructionTrace.lean +++ b/SP1Clean/Soundness/InstructionTrace.lean @@ -21,7 +21,7 @@ namespace SP1Clean.Soundness -- stronger bound with the project-standard `Fact (2 ^ 17 < p)` derived locally. variable {p : ℕ} [Fact p.Prime] [Fact (2 ^ 24 < p)] -local instance : Fact (2 ^ 17 < p) := ⟨by have := Fact.out (p := 2 ^ 24 < p); omega⟩ +local instance : Fact (2 ^ 17 < p) := ⟨lt_of_le_of_lt (by norm_num) (Fact.out (p := 2 ^ 24 < p))⟩ /-! ## The decoded instruction -/ diff --git a/SP1Clean/Soundness/MemoryConsistency.lean b/SP1Clean/Soundness/MemoryConsistency.lean index e6f579be..1d91ab05 100644 --- a/SP1Clean/Soundness/MemoryConsistency.lean +++ b/SP1Clean/Soundness/MemoryConsistency.lean @@ -29,6 +29,15 @@ open SP1Clean.LookupAccessList variable {p : ℕ} +private theorem two_lt_p_aux [Fact (2 ^ 17 < p)] : 2 < p := + lt_of_lt_of_le (by norm_num) (Fact.out (p := 2 ^ 17 < p)).le + +/-- A nonzero `ZMod p` element has strictly-positive `ℤ`-cast value — the multiplicity-positivity +workhorse for the Memory-bus send contributions. -/ +private theorem valCast_pos_aux [NeZero p] {x : ZMod p} (hx : x ≠ 0) : 0 < (x.val : ℤ) := by + have : x.val ≠ 0 := fun hv => hx (ZMod.val_injective p (by rw [ZMod.val_zero]; exact hv)) + omega + /-- The recombined low clock for a row (`clk_0_16 + clk_16_24 * 2^16`), matching `Extracted/AddChip`'s CS2 `clk_low` argument. -/ def rowClkLow (r : Trace.RowView (ZMod p)) : ZMod p := @@ -203,10 +212,7 @@ theorem memoryLookups_eq_emitted (r : Trace.RowView (ZMod p)) (env : Environment memoryLookups r = (((Readers.RTypeReader.main input).operations offset).interactionsWith memoryChannel.toRaw).map (AbstractInteraction.toAccess env) := by - have hp2 : 2 < p := by - have h := Fact.out (p := 2 ^ 17 < p) - have h2 : (2 : ℕ) < 2 ^ 17 := by norm_num - omega + have hp2 : 2 < p := two_lt_p_aux -- The three `RegisterAccessCols` sub-assertions emit only `byteChannel`, and the five `op_a_0` Equality -- gates emit nothing — so their `memoryChannel` filter is empty (`filter_interactions_formalAssertion_eq_nil`). have hrac := fun (n : ℕ) (inp : Var Readers.RegisterAccessCols.Inputs (ZMod p)) => @@ -314,10 +320,7 @@ theorem memAccessLookups_eq_emitted (env : Environment (ZMod p)) (offset : ℕ) memAccessLookups mem clk_high clk_low addr0 addr1 addr2 new_value is_real = (((Readers.MemoryAccess.main input).operations offset).interactionsWith memoryChannel.toRaw).map (AbstractInteraction.toAccess env) := by - have hp2 : 2 < p := by - have h := Fact.out (p := 2 ^ 17 < p) - have h2 : (2 : ℕ) < 2 ^ 17 := by norm_num - omega + have hp2 : 2 < p := two_lt_p_aux -- the three `=== 0` timestamp gates compile to `Gadgets.Equality.circuit id` subcircuits, which emit -- nothing on `memoryChannel`; the two `byteChannel.pullIf`s are filtered by channel-distinctness. have heq := fun (n : ℕ) (inp : Var (ProvablePair id id) (ZMod p)) => @@ -469,6 +472,19 @@ address). The per-address single-address chain ranges over `(eventsAt rows addr) def eventsAt (rows : List (Trace.RowView (ZMod p))) (addr : ℕ) : List (MemEvent (ZMod p)) := (memEventsFiltered rows).filter (fun e => decide (e.addr.val = addr)) +/-- A per-address access (`∈ eventsAt`) is one of the trace's bus-backed events +(`∈ memEventsFiltered`). Shared by the single-address chain proof and the `isU64` recovery. -/ +theorem mem_memEventsFiltered_of_mem_eventsAt {rows : List (Trace.RowView (ZMod p))} + {addr : ℕ} {e : MemEvent (ZMod p)} (he : e ∈ eventsAt rows addr) : + e ∈ memEventsFiltered rows := by + rw [eventsAt] at he; exact List.mem_of_mem_filter he + +/-- Membership in `eventsAt rows addr` pins the event's address (`.val`). -/ +theorem addr_of_mem_eventsAt {rows : List (Trace.RowView (ZMod p))} + {addr : ℕ} {e : MemEvent (ZMod p)} (he : e ∈ eventsAt rows addr) : + e.addr.val = addr := by + rw [eventsAt] at he; have := (List.mem_filter.mp he).2; simpa using this + /-- The vendored `filterAddress` of the filtered access list is `eventsAt` mapped through `toAccess` — the bridge that lets the per-address single-address proof work at the event level (where `prevTs` lives). -/ @@ -518,10 +534,7 @@ theorem mem_opA_read_send_matched [NeZero p] (r.is_real.val : ℤ)) with hsend have h_send_mem : send ∈ aggregateChipRows rows memoryLookups ++ memProv := List.mem_append_left _ (List.mem_flatMap.mpr ⟨r, hr, by rw [hsend]; simp [memoryLookups]⟩) - have hvne : (r.is_real.val : ℤ) ≠ 0 := by - have : r.is_real.val ≠ 0 := fun hv => h_real (ZMod.val_injective p (by rw [ZMod.val_zero]; exact hv)) - exact_mod_cast this - have hpos : 0 < multOf send := by rw [hsend]; simp only [multOf]; omega + have hpos : 0 < multOf send := by rw [hsend]; simpa only [multOf] using valCast_pos_aux h_real obtain ⟨b, hb, hbk, hbneg⟩ := balanced_pos_has_neg (aggregateChipRows rows memoryLookups ++ memProv) (keyOf send) h_bal send h_send_mem rfl hpos @@ -566,11 +579,6 @@ theorem memEvent_send_pos_in_bus [NeZero p] (rows : List (Trace.RowView (ZMod p) obtain ⟨r, hr, he⟩ := he rw [List.mem_reverse] at hr simp only [aggregateChipRows] - -- helper: turn `is_real ≠ 0` (from the gate) into `0 < is_real.val` - have valpos : ∀ x : ZMod p, x ≠ 0 → 0 < ((x.val : ℤ)) := by - intro x hx - have hxv : x.val ≠ 0 := fun hv => hx (ZMod.val_injective p (by rw [ZMod.val_zero]; exact hv)) - omega rw [rowMemEventsGated] at he by_cases hreal : r.is_real = 0 · rw [if_pos hreal] at he; exact absurd he List.not_mem_nil @@ -584,7 +592,7 @@ theorem memEvent_send_pos_in_bus [NeZero p] (rows : List (Trace.RowView (ZMod p) (r.is_real.val : ℤ)), List.mem_flatMap.mpr ⟨r, hr, by simp [memoryLookups]⟩, ?_, ?_⟩ · simp [keyOf, memEvent_sendKey, opAEvent] - · simp only [multOf]; exact valpos _ hreal + · simp only [multOf]; exact valCast_pos_aux hreal rw [List.mem_append] at he rcases he with he | he · -- op_b: present only when imm_b = 0 @@ -598,7 +606,7 @@ theorem memEvent_send_pos_in_bus [NeZero p] (rows : List (Trace.RowView (ZMod p) ((r.is_real * (1 - r.adapter.imm_b)).val : ℤ)), List.mem_flatMap.mpr ⟨r, hr, by simp [memoryLookups]⟩, ?_, ?_⟩ · simp [keyOf, memEvent_sendKey, opBEvent] - · simp only [multOf, himmb, sub_zero, mul_one]; exact valpos _ hreal + · simp only [multOf, himmb, sub_zero, mul_one]; exact valCast_pos_aux hreal · rw [if_neg himmb] at he; exact absurd he List.not_mem_nil · -- op_c: present only when imm_c = 0 by_cases himmc : r.adapter.imm_c = 0 @@ -611,7 +619,7 @@ theorem memEvent_send_pos_in_bus [NeZero p] (rows : List (Trace.RowView (ZMod p) ((r.is_real * (1 - r.adapter.imm_c)).val : ℤ)), List.mem_flatMap.mpr ⟨r, hr, by simp [memoryLookups]⟩, ?_, ?_⟩ · simp [keyOf, memEvent_sendKey, opCEvent] - · simp only [multOf, himmc, sub_zero, mul_one]; exact valpos _ hreal + · simp only [multOf, himmc, sub_zero, mul_one]; exact valCast_pos_aux hreal · rw [if_neg himmc] at he; exact absurd he List.not_mem_nil /-- **Negative entry is a receive.** A strictly-negative `memoryLookups r` entry (under per-row @@ -778,9 +786,9 @@ theorem isConsistentSingleAddress_of_balance [NeZero p] (rows : List (Trace.RowV (h_sorted : MemoryAccessList.isTimestampSorted ((eventsAt rows addr).map MemEvent.toAccess)) : MemoryAccessList.isConsistentSingleAddress ((eventsAt rows addr).map MemEvent.toAccess) h_sorted := by have hsub : ∀ {e}, e ∈ eventsAt rows addr → e ∈ memEventsFiltered rows := by - intro e he; rw [eventsAt] at he; exact List.mem_of_mem_filter he + intro e he; exact mem_memEventsFiltered_of_mem_eventsAt he have haddr : ∀ {e}, e ∈ eventsAt rows addr → e.addr.val = addr := by - intro e he; rw [eventsAt] at he; have := (List.mem_filter.mp he).2; simpa using this + intro e he; exact addr_of_mem_eventsAt he refine isConsistentSingleAddress_of_chain (eventsAt rows addr) h_sorted ?_ ?_ · -- adjacency: each read equals the next (earlier) write rw [List.isChain_iff_getElem] diff --git a/SP1Clean/Soundness/MemoryIsU64.lean b/SP1Clean/Soundness/MemoryIsU64.lean index 8dd33211..5db700eb 100644 --- a/SP1Clean/Soundness/MemoryIsU64.lean +++ b/SP1Clean/Soundness/MemoryIsU64.lean @@ -70,12 +70,12 @@ theorem memEvent_prevValue_eq_writer [NeZero p] (rows : List (Trace.RowView (ZMo (h_prov : MemProviderGenesis memProv rows) (h_bal : isConsistentBalanced (aggregateChipRows rows memoryLookups ++ memProv)) : List.IsChain limbReadEq (eventsAt rows addr) := by - have hsub : ∀ {e}, e ∈ eventsAt rows addr → e ∈ memEventsFiltered rows := by - intro e he; rw [eventsAt] at he; exact List.mem_of_mem_filter he rw [List.isChain_iff_getElem] intro i hi - have he_mem := hsub (List.getElem_mem (show i < (eventsAt rows addr).length by omega)) - have he'_mem := hsub (List.getElem_mem (show i + 1 < (eventsAt rows addr).length from hi)) + have he_mem := mem_memEventsFiltered_of_mem_eventsAt + (List.getElem_mem (show i < (eventsAt rows addr).length by omega)) + have he'_mem := mem_memEventsFiltered_of_mem_eventsAt + (List.getElem_mem (show i + 1 < (eventsAt rows addr).length from hi)) have hprevts := h_prev addr i hi rcases memEvent_canceller rows memProv hwf h_bal _ he_mem with ⟨e'', he''_mem, he''_key⟩ | ⟨b, hb, hbkey, _⟩ @@ -101,14 +101,10 @@ theorem eventsAt_genesis_reads_zero [NeZero p] (rows : List (Trace.RowView (ZMod (h_bal : isConsistentBalanced (aggregateChipRows rows memoryLookups ++ memProv)) (hne : eventsAt rows addr ≠ []) : wordToNat ((eventsAt rows addr).getLast hne).prevValue = 0 := by - have hsub : ∀ {e}, e ∈ eventsAt rows addr → e ∈ memEventsFiltered rows := by - intro e he; rw [eventsAt] at he; exact List.mem_of_mem_filter he - have haddr : ∀ {e}, e ∈ eventsAt rows addr → e.addr.val = addr := by - intro e he; rw [eventsAt] at he; have := (List.mem_filter.mp he).2; simpa using this have h_sorted : MemoryAccessList.isTimestampSorted ((eventsAt rows addr).map MemEvent.toAccess) := by rw [← filterAddress_memAccessListFiltered] exact MemoryAccessList.filterAddress_sorted _ (memAccessListFiltered_isTimestampSorted rows h_clk) addr - have he_mem := hsub (List.getLast_mem hne) + have he_mem := mem_memEventsFiltered_of_mem_eventsAt (List.getLast_mem hne) rcases memEvent_canceller rows memProv hwf h_bal _ he_mem with ⟨e'', he''_mem, he''_key⟩ | ⟨b, hb, hbkey, _⟩ · exfalso @@ -116,7 +112,7 @@ theorem eventsAt_genesis_reads_zero [NeZero p] (rows : List (Trace.RowView (ZMod have he''_at : e'' ∈ eventsAt rows addr := by rw [eventsAt] exact List.mem_filter.mpr ⟨he''_mem, - decide_eq_true_eq.mpr (haddr'.trans (haddr (List.getLast_mem hne)))⟩ + decide_eq_true_eq.mpr (haddr'.trans (addr_of_mem_eventsAt (List.getLast_mem hne)))⟩ have hle := getLast_clk_le h_sorted hne he''_at have hlt := h_prevlt _ he_mem omega @@ -219,9 +215,7 @@ theorem eventsAt_values_isU64 [NeZero p] {rows : List (Trace.RowView (ZMod p))} h.balanced hne) ?_ intro e he hprevU - have he_mem : e ∈ memEventsFiltered rows := by - rw [eventsAt] at he - exact List.mem_of_mem_filter he + have he_mem := mem_memEventsFiltered_of_mem_eventsAt he rcases memEventsFiltered_value_cases rows e he_mem with ⟨r, hr, hreal, hev⟩ | hval · rw [hev] exact h.writeU64 r hr hreal diff --git a/SP1Clean/Soundness/ProgramConsistency.lean b/SP1Clean/Soundness/ProgramConsistency.lean index a697c958..3d379d66 100644 --- a/SP1Clean/Soundness/ProgramConsistency.lean +++ b/SP1Clean/Soundness/ProgramConsistency.lean @@ -140,9 +140,8 @@ theorem programConsistent_of_balance [NeZero p] (programAccess r).op_c[2].val, (programAccess r).op_c[3].val, (programAccess r).op_a_0.val, (programAccess r).imm_b.val, (programAccess r).imm_c.val], ((programAccess r).is_real.val : ℤ)) with hsend - have h_eq : programLookups r = [send] := by rw [hsend]; rfl have h_send_mem : send ∈ aggregateChipRows rows programLookups := - List.mem_flatMap.mpr ⟨r, hr, by rw [h_eq]; exact List.mem_singleton.mpr rfl⟩ + List.mem_flatMap.mpr ⟨r, hr, List.mem_singleton.mpr rfl⟩ have hvne : (programAccess r).is_real.val ≠ 0 := fun hv => h_real (ZMod.val_injective p (by rw [ZMod.val_zero]; exact hv)) have h_pos : 0 < multOf send := by rw [hsend]; simp only [multOf]; omega @@ -197,10 +196,7 @@ theorem programLookups_eq_emitted [Fact p.Prime] [Fact (2 ^ 17 < p)] have heq := fun (n : ℕ) (inp : Var (ProvablePair id id) (ZMod p)) => filter_interactions_formalAssertion_eq_nil (Gadgets.Equality.circuit id) programChannel.toRaw (n := n) inp List.not_mem_nil List.not_mem_nil - have hp2 : 2 < p := by - have h := Fact.out (p := 2 ^ 17 < p) - have h2 : (2 : ℕ) < 2 ^ 17 := by norm_num - omega + have hp2 : 2 < p := by have := Fact.out (p := 2 ^ 17 < p); omega -- reduce `interactionsWith` to the single program emit (RAC subs + Equality gates + memory emits drop; -- the memory emits' `if memoryChannel = programChannel` collapse via the channel distinctness + `if_false`) simp only [Readers.RTypeReader.main, circuit_norm, hrac, heq, diff --git a/SP1Clean/Soundness/ProgramProviderSpike.lean b/SP1Clean/Soundness/ProgramProviderSpike.lean index 0a206c03..0320e4dc 100644 --- a/SP1Clean/Soundness/ProgramProviderSpike.lean +++ b/SP1Clean/Soundness/ProgramProviderSpike.lean @@ -100,15 +100,13 @@ omit [NeZero p] in theorem demoRomRow_valid [Fact (2 ^ 17 < p)] : ProgramRowSpec (demoRomRow (p := p)) := by have h32 : (32 : ℕ) < p := by have := Fact.out (p := 2 ^ 17 < p); omega refine ⟨?_, ?_, ?_, ?_, ?_, ?_, Or.inl rfl⟩ <;> - simp only [demoRomRow, Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero] <;> - first - | (rw [show (1 : ZMod p) = ((1 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt (by omega)]; norm_num) - | (rw [show (2 : ZMod p) = ((2 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt (by omega)]; norm_num) - | (rw [show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by norm_cast, - ZMod.val_natCast_of_lt (by omega)]; norm_num) - | (rw [ZMod.val_zero]; norm_num) + simp only [demoRomRow, Vector.getElem_mk, List.getElem_toArray, List.getElem_cons_zero, + show (1 : ZMod p) = ((1 : ℕ) : ZMod p) from by norm_cast, + show (2 : ZMod p) = ((2 : ℕ) : ZMod p) from by norm_cast, + show (3 : ZMod p) = ((3 : ℕ) : ZMod p) from by norm_cast, + ZMod.val_natCast_of_lt (show (3 : ℕ) < p by omega), + ZMod.val_natCast_of_lt (show (2 : ℕ) < p by omega), + ZMod.val_natCast_of_lt (show (1 : ℕ) < p by omega), ZMod.val_zero] <;> omega omit [NeZero p] in /-- The constructed provider for the one-row demo ROM is a genuine `ProgramProvider` (no assumption) — @@ -116,6 +114,6 @@ the `programProvider_of_validRom` construction is inhabited, not vacuous. -/ theorem demoRom_programProvider [Fact (2 ^ 17 < p)] (mult : ProgramRow (ZMod p) → ℤ) : ProgramProvider (p := p) ProgramRowSpec (romContributions [demoRomRow (p := p)] mult) := programProvider_of_validRom [demoRomRow] mult - (by intro row hrow; simp only [List.mem_singleton] at hrow; subst hrow; exact demoRomRow_valid) + fun _ hrow => List.mem_singleton.1 hrow ▸ demoRomRow_valid end SP1Clean.Soundness diff --git a/SP1Clean/Soundness/SP1GatedVm.lean b/SP1Clean/Soundness/SP1GatedVm.lean index 95ec698f..62b5ffb1 100644 --- a/SP1Clean/Soundness/SP1GatedVm.lean +++ b/SP1Clean/Soundness/SP1GatedVm.lean @@ -253,9 +253,7 @@ theorem sp1_state_balance_of_balancedInteractions exact stateLookups_mult_binary hp v (hbin v hv) a hav · -- the two constant-`±1` boundary entries simp only [List.mem_cons, List.not_mem_nil, or_false] at ha - rcases ha with rfl | rfl - · right; right; rfl - · left; rfl + rcases ha with rfl | rfl <;> simp [multOf] /-- **The witness → gated-capstone bridge (assembled; `sorry`-free given the decode seam).** From the ensemble `Statement`'s per-table constraints and balanced channels, the heterogeneous trace decoded @@ -277,8 +275,8 @@ theorem sp1_gatedExecution_prereqs (witness : EnsembleWitness (sp1GatedVm (p := -- instantiate the balanced channels at the State channel (the ensemble channel-list head) have h_balanced : BalancedInteractions (witness.interactionsWith Channels.stateChannel.toRaw) := by - have h := (hB Channels.stateChannel.toRaw (List.mem_cons_self ..)).1 - rwa [EnsembleWitness.interactionsWith_allTablesWitness] at h + rw [← EnsembleWitness.interactionsWith_allTablesWitness] + exact (hB Channels.stateChannel.toRaw (List.mem_cons_self ..)).1 exact ⟨rows, data, h_spec, hbin, sp1_state_balance_of_balancedInteractions witness.publicInput rows hbin _ (fun i hi => EnsembleWitness.channel_eq_of_mem_interactionsWith hi) h_balanced h_corr⟩ diff --git a/SP1Clean/Soundness/StateConsistency.lean b/SP1Clean/Soundness/StateConsistency.lean index 47aee3ac..21fa013d 100644 --- a/SP1Clean/Soundness/StateConsistency.lean +++ b/SP1Clean/Soundness/StateConsistency.lean @@ -141,9 +141,7 @@ theorem stateLookups_mult_binary (hp : 2 < p) (r : Trace.RowView (ZMod p)) intro a ha have hv := val_of_binary hp h_real simp only [stateLookups, List.mem_cons, List.not_mem_nil, or_false] at ha - rcases ha with rfl | rfl <;> - · simp only [multOf, stateAccess] - omega + rcases ha with rfl | rfl <;> simp only [multOf, stateAccess] <;> omega /-- **Successor exists (from State balance).** On a balanced State bus, every real row `a`'s `send` key `(clk_high, clk_low+clk_inc, next_pc)` is cancelled by the `receive` of some real row `b` — so `b`'s @@ -253,8 +251,7 @@ theorem pcChainProp_iff_isChain (l : List (StateAccess (ZMod p))) : cases t with | nil => exact ⟨fun _ => List.isChain_singleton a, fun _ => trivial⟩ | cons b t' => - rw [List.isChain_cons_cons, ← IH] - rfl + simp only [List.isChain_cons_cons, ← IH, pcChainProp, pcStep] /-- **Clock injectivity, getElem form.** `clkInjective` rephrased over row indices: two rows with equal encoded clocks are the same index. The bridge `state_adjacent_pc_handoff` consumes. -/ @@ -383,10 +380,7 @@ theorem stateLookups_eq_emitted [Fact p.Prime] [Fact (2 ^ 17 < p)] h_np0, h_np1, h_np2, h_clk] -- only the multiplicities differ: `signedVal (∓eval is_real)` vs `∓is_real.val`. `h_ir` ties the eval to -- `r.is_real`; the `signedVal` lemmas evaluate the centered representative on the gate. - have hp2 : 2 < p := by - have h := Fact.out (p := 2 ^ 17 < p) - have h2 : (2 : ℕ) < 2 ^ 17 := by norm_num - omega + have hp2 : 2 < p := by have := Fact.out (p := 2 ^ 17 < p); omega rw [h_ir, signedVal_neg_is_real hp2 h_real, signedVal_is_real hp2 h_real] end SP1Clean.Soundness diff --git a/SP1Clean/Soundness/TargetVm.lean b/SP1Clean/Soundness/TargetVm.lean index 30add78f..96a173d0 100644 --- a/SP1Clean/Soundness/TargetVm.lean +++ b/SP1Clean/Soundness/TargetVm.lean @@ -255,7 +255,6 @@ private theorem chain_to_refines congr 1 exact sndPc_eq_rcvPc (isWalk_chain hw.1 i hi) · intro idx - have hi' : i < path.length := by omega by_cases hcond : (idx.toNat : ZMod p) = (path[i]'hi').adapter.op_a · rw [heff.regs.1 idx hcond, replayVal, dif_pos hi', if_pos hcond] · rw [heff.regs.2 idx hcond, href.frame idx, replayVal, dif_pos hi', if_neg hcond] diff --git a/SP1Clean/Soundness/ValueBound.lean b/SP1Clean/Soundness/ValueBound.lean index 3d5bf762..a7953ad7 100644 --- a/SP1Clean/Soundness/ValueBound.lean +++ b/SP1Clean/Soundness/ValueBound.lean @@ -93,9 +93,7 @@ lemma walk_clk_monotone {pi : SP1PublicIO (ZMod p)} {rows : List (ChipRow p)} {path : List (Trace.RowView (ZMod p))} (hw : WalkOf pi rows path) (i : ℕ) (hi : i + 1 < path.length) : sndClkOf (stateAccess (path[i]'(by omega))) = rcvClkOf (stateAccess (path[i + 1]'hi)) := by - have h := isWalk_chain hw.1 i hi - simp only [stateEdge] at h - exact sndClk_eq_rcvClk h + exact sndClk_eq_rcvClk (by simpa only [stateEdge] using isWalk_chain hw.1 i hi) /-! ## The concrete decode∧value `OperandsBound` -/ diff --git a/docs/agents/README.md b/docs/agents/README.md index 8bbef2f5..7143b2f5 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -8,7 +8,8 @@ reader-facing docs one level up (start with [`../architecture.md`](../architectu ## Index - [porting-recipe.md](porting-recipe.md) — step-by-step checklist to port a new chip from the Add/Bitwise template: create the four artifacts, wire the root import, verify build + axioms. -- [proof-patterns.md](proof-patterns.md) — the witnessed-`FormalCircuit` soundness/completeness skeleton; `ZMod p` / `Word` / `circuit_proof_start` landmines; `maxHeartbeats` floors; the `ElaboratedCircuit` field-obligation recipe. +- [proof-patterns.md](proof-patterns.md) — the witnessed-`FormalCircuit` soundness/completeness skeleton; `ZMod p` / `Word` / `circuit_proof_start` landmines; `maxHeartbeats` floors; the `ElaboratedCircuit` field-obligation recipe; the **Golf & cleanup discipline** section (how to golf/clean proofs safely). +- [cleanup-backlog.md](cleanup-backlog.md) — the prioritized follow-up cleanup/refactor backlog (deferred cross-file refactors, `/decompose-proof` + `/split-file` candidates, deferred heavy cores, next-up linters) + the golfing/cleanup skills catalog. - [lean-sail-notes.md](lean-sail-notes.md) — the Lean 4.28 environment: toolchain pins, why public Clean `main`, the GitHub-fetched Sail deps, the `lake update` toolchain-bump trap, and the Clean-main ↔ Batteries `Fin.foldl` collision and its fix. - [extraction.md](extraction.md) — the constraint-extraction pipeline (`sp1-constraint-compiler` → `update_extracted.py` → Lean) and the DSL contract. Point-in-time snapshots (regenerate before release) live under [`../snapshots/`](../snapshots/): diff --git a/docs/agents/cleanup-backlog.md b/docs/agents/cleanup-backlog.md new file mode 100644 index 00000000..82030829 --- /dev/null +++ b/docs/agents/cleanup-backlog.md @@ -0,0 +1,134 @@ +# Cleanup / refactor backlog + +Prioritized follow-up cleanup and refactor work, plus the golfing/cleanup skills available. Distilled from the +2026-06-22/23 proof-cleanup campaign (109 hand-written files golfed, −591 lines, axiom-cleanliness preserved). +For *how* to golf safely, see `proof-patterns.md` § "Golf & cleanup discipline". + +## Already landed (for reference) + +- The per-file golf sweep (109 files) + substrate hoists: `Word.isU64_four` (6 inline sites), Faithful + `val_16`/`bool_iff` dedup → `ChipTactics` (24 files), the `ShiftLeftChip/Core` loose-variable helper dedup. +- The Soundness Memory-layer helper consolidation (`two_lt_p_aux` / `valCast_pos_aux` / + `mem_memEventsFiltered_of_mem_eventsAt`). +- Linters: `style.lambdaSyntax` / `style.dollarSyntax` enabled on the `SP1Proofs` lake library (zero fallout). +- `lake lint` (environment linters) stood up — the `sp1Lint` lintDriver (`scripts/sp1Lint.lean`) runs 11 curated + Batteries `#lint` linters over the hand-written `SP1Clean.*` decls (auto-gen excluded by module-path filter), + with a 21-entry `scripts/nolints.json` baseline; CI runs it in the build job. +- The `val_N` private-lemma sweep — the scattered `(N : ZMod p).val = N` copies (`val_13`×3, `val_14`×3, + `val_16'`/`val_16''`×4, Shift-core `val_4/8/16/32/64`, Native `c65535_ne_zero`×2) consolidated onto the + `Math/Word.lean` family (now `val_2/4/8/13/14/16/32/64/256/32768/65535/65536`); plus a new `Math/Gate.lean` + binary-field/BitVec-compare API (`bool_val_le`, `val_ne`, `eq_one_iff_of_ite` + siblings, + `slt/ult_{true,false}_iff` over `BitVec w`) absorbing the Branch/Lt decision-proof private helpers. Also + fixed the 2 `ShiftRightMath.val_4/64` `simpNF` nolints. + +## Cross-file refactors — deferred with caveats + +### R1 — NeZero / Fact boilerplate (BLOCKED by design) + +`haveI : NeZero p := …` appears ~185× (Fact-derived) + ~14× (Prime-derived). A **global** `NeZero p` instance +derived from `Fact (2^17 < p)` is *deliberately avoided* — it would make the pervasive `omit [Fact (2^17 < p)] +in` clauses illegal (`Model/ByteTable.lean:84`). A `Fact p.Prime`-derived global instance *might* survive the +`omit` pattern (it depends on primality, not the magnitude), eliminating most of the boilerplate — but it goes +against the documented local-instance discipline and risks instance-resolution surprises. **Needs an owner +decision**, not a drive-by change. + +### R2 — eval-map global lemma (MARGINAL) + +A global `eval (map) v[i] = w[i]` lemma could replace the per-file quantified `ea`/`epc` helpers across ~36 +`Formal.lean` files. But the per-file helpers already capture the dedup *within* each file; a global lemma saves +only ~1 line/helper while re-churning 36 already-clean files + a foundational rebuild + form-variation risk. +Low ROI — skip unless a foundational pass is happening anyway. + +## `/decompose-proof` candidates (long proof bodies, >50 lines) + +- `ShiftLeftChip/Formal.lean` + `ShiftRightChip/Formal.lean` — `completeness` (~130–180 lines). +- `LoadHalfChip/Formal.lean` — the 4-way `h_sel_lt` offset-selection case-bash (near-verbatim across soundness + + completeness). +- `BranchChip/Formal.lean` — `soundness` / `completeness` (per-column `env.get` plumbing). + +Caveat: several are perf-tuned heavy files — decompose with care and watch elaboration time. + +## `/split-file` candidates + +None currently exceed 1500 lines after cleanup. `Proofs/Chips/ShiftRightChip/Core.lean` (2177) is the largest +single file but is kept whole on purpose (the dispatch proof is one entangled unit). Note only. + +## Heavy cores — deferred (poor risk/reward) + +Left untouched in the campaign; revisit only with a specific goal: +- `ShiftRight/Core` (2177), `ShiftRight/Dispatch` (1591), the rest of `ShiftLeft/Core` — bv_decide/kernel-fragile. +- `Model/SailMemory` (1270) — foundational, full-rebuild cost. +- **All DivRem files** — fresh perf work landed via the `div-rem-perf` merge; leave alone. + +## Cross-file dedup candidates (re-examine before acting) + +- **SailState-staging bridge preamble** (`hpc_get` / `key` / `hsp_config`) recurs across ~10 store/jal/load + `Bridge.lean` files → a shared lemma. **Re-examine first** — upstream #101/#102 rewrote several bridges in the + 2026-06-23 merge, so the pre-merge shape may have changed. +- Mul `soundness` 5-branch tail — byte-identical block ×5, but a clean factor needs an intractable ~40-line + `resultWord` struct type; **skip** (the duplication is cheaper than the abstraction). +- **`vec4_eval`** — the `#v[eval e v[0],…,v[3]] = Vector.map (eval e) v` lemma recurs ×6 across chip + `Formal.lean` (Mul/Lt/ShiftRight/ShiftLeft/Bitwise + DivRem `Completeness/Driver`). Clean Math candidate + (Clean-DSL-generic), but **2 of the 6 are perf-sensitive** (DivRem Driver, Shift Formals) — fold into a + Shift/DivRem pass rather than a standalone touch. Not done in the val_N sweep. + +## Performance — what's actually actionable (investigated 2026-06-24) + +The "safe mechanical" perf levers were swept over the touched + fair-game heavy files and found **nothing +actionable**: +- **`maxHeartbeats` tightening is the wrong lever.** The heavy Shift/DivRem proofs are **kernel / + type-checking-bound, not heartbeat-bound** — measured `ShiftLeftChip/Soundness/Sll.soundness` at **72** + elaboration heartbeats against its 4M ceiling. The ceilings are non-binding safety margins; lowering them has + no wall-clock effect and only risks a future spike. Leave them. +- No `simp_all` / heavy `simp [circuit_norm]` in the touched files (already targeted), and none carry + `unusedSimpArgs` suppressions, so the simp-tightening / redundant-arg levers are empty there too. +- The **only** real prize is the **Shift tail-dedup** (extract the byte-identical `cpuA/msb*/aluA` tail shared + across the 6 Shift conjuncts, mirroring `DivRemChip/Soundness/Tail.lean`) — a structural change, deferred: + it overlaps the recently-golfed Shift soundness files and is a multi-hour effort. That, plus the term-intrinsic + DivRem cost (off-limits), is where the build time genuinely lives. + +## Linters — next candidates (deferred; real fallout) + +### Syntactic (build-time, lib `moreLeanArgs`) + +After `style.lambdaSyntax` / `style.dollarSyntax` (zero-fallout, enabled): `linter.oldObtain`, +`linter.style.longLine` (Model/Sail* already suppress it), `linter.style.cdot`. Each has real hits to triage +(fix or per-file suppress) across the heavy cores — enable one at a time, return to 0-warning, then the next. +Do **not** enable `linter.unusedSectionVars` / `linter.unusedSimpArgs` (structurally necessary in circuit +proofs — ~275 deliberate suppressions). Leave the auto-gen `linter.all false` (`Extracted/`, `TraceGenTests/`). +The scoping unit is the `SP1Proofs` lake library's `moreLeanArgs` (see `lakefile.toml` + the AGENTS.md "Linters" +note for the registration caveat). + +### Environment (`lake lint` / `sp1Lint`) + +The curated set runs 11 of the Batteries `#lint` linters. Deferred / dropped, with rationale: + +- **`docBlame` / `docBlameThm` / `tacticDocs` / `deprecatedNoSince`** — doc-coverage linters; thousands of hits + on undocumented private proof lemmas. Enable only after a deliberate documentation pass (not planned). +- **`unusedArguments`** — *dropped, not deferred.* It flags **only** the project's uniform signature args + (`{p} [Fact p.Prime] [Fact (2^17` masks + `omega`) — so it never + re-unfolds a shift over a `Word` value. For a non-shift `BitVec 64` msb/extract (`low32_msb_eq_b1`), mirror + `toBitVec64_msb_eq_b3_ge`: a `rw` chain (not `simp only`) + mask **every** `2^N` (incl. `2^48`) to a literal. + (Was `set_option debug.skipKernelTC true in` — removed 2026-06-18; now CI-gated by + `scripts/check_no_skipkerneltc.sh` so it can't come back. The option is the last-resort escape, not + the recipe.) Lemma name is `BitVec.extractLsb'_toNat` (not `toNat_extractLsb'`); `extractLsb 5 0 = + extractLsb' 0 6`, `.toNat = x.toNat % 2^6`. - **`simp only [id_eq]` before `linear_combination`/`ring` over field elements.** Clean wraps field elements as `id (ZMod p)`, which blocks `ring`'s instance synthesis (`IsRightCancelAdd (id (ZMod p))`). `clear_value` does **not** fix it; `simp only [id_eq] at h ⊢` does (the `BranchChip` L510 pattern). @@ -211,11 +219,18 @@ drops `section`/`end` markers and `/--` openers (strip/restore them). case-split on `op_b[3].val ≥ 32768` (BitVec msb via `ShiftRightMath.toBitVec64_msb_eq_b3_ge`). The msb=0 arm reuses the SRL dispatch (`sra_div_to_bitvec_false` → `srl_close`); the msb=1 arm uses `sra_div_to_bitvec_true` → `sra_close_su16_*_case` with `sraFill = 65536 − v0123`. Both conversion - helpers carry `skipKernelTC` (the `2^64` trigger). SRLW/SRAW are the 32-bit (2-limb `HWord`) analogs, + helpers route through the abstract-`BitVec` bridges `sra_toNat_{false,true}` (kernel-clean, no + `skipKernelTC` — see the `2^64` bullet above). SRLW/SRAW are the 32-bit (2-limb `HWord`) analogs, `signExtend 64`-packaged via `toBitVec64_signExtend_word`. ## Landmines +- **Never `set_option (debug.)skipKernelTC` (CI-gated).** It bypasses the kernel type-check re-run, defeating + the axiom-clean trust anchor. `scripts/check_no_skipkerneltc.sh` fails the audit and CI on any hit in + `SP1Clean/**/*.lean`. When a goal blocks on a kernel deep-recursion / `2^64`-unfold error, factor the + expensive compute into an abstract-`BitVec` helper proved once over variables and apply it symbolically — + see the `2^64` bullet under "Bit-shift chip soundness" above for the worked `srl_toNat`/`sra_toNat` fix. + ### Gadget-level (arithmetic, `Native/Operations/` + `Proofs/Operations/`) - **`circuit_proof_start` must be the FIRST tactic** in soundness/completeness. Any @@ -567,6 +582,62 @@ always means a *local* regression against one of these. raw binders and the `SpecObligation` indirection exists. The ShiftRight/ShiftLeft conjuncts share an analogous `cpuA/msb*/aluA` block that is a candidate for the same treatment. +## Golf & cleanup discipline + +How to golf/clean a proof without breaking the repo's invariants (axiom-clean, 0-warning, no `info:`). +Distilled from the 2026-06-22/23 cleanup campaign (109 files, −591 lines, axiom-cleanliness preserved). The +prioritized follow-up backlog + the golfing/cleanup skills (`/cleanup`, `/cleanup-all`, `/decompose-proof`, +`/split-file`, `Skill(simplify)`) live in `docs/agents/cleanup-backlog.md`. + +**Instant, always-safe wins** (the bulk of the line savings): +- `:= by rfl` → `:= rfl`; `show T from by tac` → `show T by tac`; `rw […] at h; exact h` → `rwa […] at h`; + `refine F ?_; exact e` → `exact F e`; `simp only […] at h ⊢; exact h` → `simpa only […] using h`. +- Merge adjacent identical `simp only`/`rw`; inline a single-use `have x := e` that has **no** `by` body. +- Reach for mathlib instead of a hand proof: `zero_ne_one`, `Int.eq_zero_of_abs_lt_dvd`, etc. + +**The dominant structural win — eval-map factoring.** Chip/op `Formal.lean` proofs repeat a per-limb +`have eX : Expression.eval env input_var_X[i] = input_X[i] := by rw [← hX]; simp [Vector.getElem_map]`, one per +limb. Collapse the copies into ONE quantified helper +`have eX : ∀ i (hi : i < n), … := by intro i hi; rw [← hX]; simp [Vector.getElem_map]`, then call `eX i (by omega)` +at each site (~12–25%/file on Load/Store/op `Formal.lean`). A *global* lemma for this was investigated and is +**not** worth it — see backlog R2. + +**Kernel-safe dedup on the bit-shift / DivRem cores** (the `2^64` landmine zone — read the "Bit-shift chip +soundness" § first). A heavy file may repeat a byte-identical `have` block across N sibling lemmas. You can +factor it into a single helper **iff the block is pure `ZMod.val` / `Nat` arithmetic with no `2^64`/`BitVec` +reduction** — extract it as a lemma **over loose variables** and apply it symbolically. This is kernel-safe +because a lemma application instantiates an *already-checked* body, so the kernel does no *extra* reduction +(no `skipKernelTC`; elaboration time does not regress). **Hard constraint:** the helper's conclusion must match +the original `have`'s type **character-for-character** — it's a downstream `rw`-target, and `(16 : ZMod p) - …` +is **not** interchangeable with `(16 - … : ZMod p)`. The heavy `2^64`-scale work itself is already isolated in +abstract-`BitVec` helpers (`srl_toNat`/`sra_toNat`) — leave those alone. Worked example: `inner_val`/`inner_hi_val` +in `Proofs/Chips/ShiftLeftChip/Core.lean`. + +**Traps — `have`s that look dead but are load-bearing** (verify with `lean_goal` / a build before removing): +- `have hp : 2 ^ 17 < p := Fact.out` (and `have : 131072 < p`) — feeds a downstream `omega` that needs the + magnitude; grep shows one occurrence (its own line) yet `omega` consumes it implicitly. +- `haveI : NeZero p := ⟨by have := Fact.out (p := 2 ^ 17 < p); omega⟩` — supplies an instance to later + `ZMod.val`/`omega` steps. There is **no** global `NeZero p` instance, *on purpose* (it would break the + pervasive `omit [Fact (2^17 < p)] in` clauses — `Model/ByteTable.lean:84`; see backlog R1). + +**Don't golf:** +- **`Faithful/*` anchors** — conservative only (drop `by exact` / dead `let` / `from by`); never restructure + proof terms or statement forms; they are *syntactic* faithfulness anchors. +- **`Spec`/`Assumptions`/statement forms**, `set_option`/`maxHeartbeats`, `ElaboratedCircuit` field structure. +- **Auto-gen** — `Extracted/`, `*Vectors.lean`, `Native/Operations/*/RawSpec`, etc.; banner-check the header. +- **Narrative comments on kernel-sensitive Shift/DivRem files** — they document the `2^64` / `id (ZMod p)` + landmines + proof roadmaps; they are the institutional memory of *why* the proof is shaped that way. (A + blanket comment-strip on `ShiftLeftChip/Soundness/Sll` was reverted for exactly this.) + +**Verify every batch:** `lake build SP1Clean` clean (0 warn, no `info:`), `bash scripts/check_no_skipkerneltc.sh`, +`sorry` grep = only `SP1GatedVm.lean`, then `scripts/run_audit.sh` periodically (the axiom census must stay +identical). On heavy files watch the per-file elaboration time in the build log and **revert on regression**. + +**Merge gotcha (post-`git merge`):** an auto-merge can *silently duplicate* a lemma that both branches added +near each other — no conflict marker, but `lake build` fails with "`` has already been declared". Always +run the full `lake build` after a merge even when `git status` shows no conflicts (`Proofs/Chips/BitwiseChip/ +Bridge.lean` hit this when upstream #101's immediate-type bridges met a golfed copy). + ## "emitted = projection": `Lookups_eq_emitted` (proving a trace projection IS the emission) Goal: prove a hand-written trace-level projection (`Soundness/*Consistency.lean`'s `stateLookups`, diff --git a/docs/release-audit.md b/docs/release-audit.md index a3a52cb6..d9d783dc 100644 --- a/docs/release-audit.md +++ b/docs/release-audit.md @@ -6,7 +6,7 @@ whole-machine claim. Unlike its predecessor, **every quantitative claim in this machine-derived** — by `scripts/run_audit.sh`, whose raw census output is committed at `snapshots/axiom-census.txt`. Re-run the harness before citing any number. -**Pins (audit of 2026-06-10).** +**Pins (snapshot of 2026-06-10; census regenerated 2026-06-23 — re-run `scripts/run_audit.sh` for current numbers).** | What | Value | Command | |---|---|---| @@ -23,8 +23,9 @@ project's own branch (it carries the `sp1-constraint-compiler` and witness-dump > **Read this first if you read nothing else.** *Soundness is `sorry`-free across every wired chip* — > each chip `soundness` theorem and each Sail bridge is axiom-clean (the three Lean axioms, plus > `bv_decide`/`native_decide`'s two trusted axioms where used, plus Sail-model platform axioms where the -> bridge reaches the Sail spec). The only `sorry`s are **three completeness/liveness holes and one -> capstone-packaging premise** (§III.2). The machine-level *target theorem* is now **stated in Lean and +> bridge reaches the Sail spec). The only `sorry` is **one capstone-packaging premise** — `sp1_witness_decode`, +> the W1b/W1c decode seam (§III.2); the DivRem/ShiftLeft/ShiftRight/Mul completeness holes that this report's +> 2026-06-10 snapshot listed were **closed** (2026-06-12 / 06-18). The machine-level *target theorem* is now **stated in Lean and > its simulation induction proved** (`Soundness/TargetVm.lean`): what separates today's capstone from > "the real Sail interpreter executes the guest program to the committed exit code" is exactly the > **named obligations** of `TargetObligations` (Part I) plus the one capstone premise — each mapped to @@ -196,7 +197,9 @@ any new carrier fails CI for the audit script. `scripts/run_audit.sh` (checked in — the predecessor document's "re-create the harness as needed" gap is closed): pins → green `lake build SP1Clean` (3630 jobs; the only warnings are the four known -`sorry`s) → text inventory with gates → `scripts/gen_axiom_probe.py` (namespace-tracking declaration +`sorry`s) → text inventory with gates (sorry-set, no `axiom` declarations, and the no-`skipKernelTC` +guard `scripts/check_no_skipkerneltc.sh` — also a standalone CI `guards` job) → +`scripts/gen_axiom_probe.py` (namespace-tracking declaration scanner; self-checking — a wrong FQN fails elaboration) → `lake env lean scripts/axiom_probe.lean` → bucket + gate. Extraction currency: `SP1_DIR=../sp1 python3 update_extracted.py && git diff --exit-code SP1Clean/Extracted` (the witness vectors regenerate in the same run and re-verify on the diff --git a/docs/snapshots/axiom-census.txt b/docs/snapshots/axiom-census.txt index a496c57d..014f5f82 100644 --- a/docs/snapshots/axiom-census.txt +++ b/docs/snapshots/axiom-census.txt @@ -1,5 +1,5 @@ # Raw #print axioms census — generated by scripts/run_audit.sh -# sp1-lean a6c617d56e7892bdc69c846eaa92a6e9dc22747e · 2026-06-18 +# sp1-lean 650100452a8f9b6fa644d62317078965b8469f73 · 2026-06-23 'SP1Clean.AddChip.soundness' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddChip.completeness' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddiChip.soundness' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -59,6 +59,8 @@ 'SP1Clean.AddiSail.addi_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddwSail.correct_addw_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddwSail.addw_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.AddwSail.correct_addiw_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.AddwSail.addiw_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AluX0Sail.correct_aluX0_rtype' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AluX0Sail.correct_aluX0_rtypew' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AluX0Sail.correct_aluX0_addi' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -69,9 +71,15 @@ 'SP1Clean.AluX0Sail.correct_aluX0_divw' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AluX0Sail.correct_aluX0_remw' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.BitwiseSail.correct_bitwise_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.correct_andi_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.correct_ori_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.correct_xori_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_and' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_or' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_xor' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_andi' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_ori' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_xori' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.BranchSail.correct_branch_native' depends on axioms: [propext, sys_enable_experimental_extensions, Classical.choice, @@ -99,6 +107,12 @@ Lean.ofReduceBool, Lean.trustCompiler, Quot.sound] +'SP1Clean.JalSail.correct_jal_native_x0' depends on axioms: [propext, + sys_enable_experimental_extensions, + Classical.choice, + Lean.ofReduceBool, + Lean.trustCompiler, + Quot.sound] 'SP1Clean.JalSail.jal_chip_reaches_sail' depends on axioms: [propext, sys_enable_experimental_extensions, Classical.choice, @@ -122,132 +136,103 @@ propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadByteSail.lb_chip_reaches_sail' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadSail.correct_load_double_native' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadSail.ld_chip_reaches_sail' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadHalfSail.correct_load_half_native' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadHalfSail.lh_chip_reaches_sail' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadWordSail.correct_load_word_native' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadWordSail.lw_chip_reaches_sail' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lb' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lbu' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lh' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lhu' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lw' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_lwu' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.correct_loadX0_ld' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Sail.loadX0_chip_reaches_sail' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LtSail.correct_sltu_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.LtSail.correct_slt_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.LtSail.correct_slti_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.LtSail.correct_sltiu_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.LtSail.lt_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.LtSail.lt_chip_reaches_sail_imm' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.MulSail.correct_mul_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.MulSail.correct_mulh_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.MulSail.correct_mulhu_native' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -267,64 +252,48 @@ propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreByteSail.sb_chip_reaches_sail' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreSail.correct_store_double_native' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreSail.sd_chip_reaches_sail' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreHalfSail.correct_store_half_native' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreHalfSail.sh_chip_reaches_sail' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreWordSail.correct_store_word_native' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreWordSail.sw_chip_reaches_sail' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.SubSail.correct_sub_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.SubSail.sub_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -332,6 +301,7 @@ 'SP1Clean.SubwSail.subw_chip_reaches_sail' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.UTypeSail.correct_utype_lui_native' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.UTypeSail.correct_utype_auipc_native' depends on axioms: [propext, Classical.choice, Quot.sound] +'SP1Clean.UTypeSail.correct_utype_native_x0' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.UTypeSail.utype_chip_reaches_sail_lui' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.UTypeSail.utype_chip_reaches_sail_auipc' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddChip.kind' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -363,40 +333,30 @@ propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadDoubleChip.kind' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadHalfChip.kind' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadWordChip.kind' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LoadX0Chip.kind' depends on axioms: [load_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.LtChip.kind' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.MulChip.kind' depends on axioms: [propext, Classical.choice, Quot.sound] @@ -407,32 +367,24 @@ propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreDoubleChip.kind' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreHalfChip.kind' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.StoreWordChip.kind' depends on axioms: [match_reservation, plat_term_write, propext, sys_enable_experimental_extensions, Classical.choice, - Lean.ofReduceBool, - Lean.trustCompiler, Quot.sound] 'SP1Clean.SubChip.kind' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.SubwChip.kind' depends on axioms: [propext, Classical.choice, Quot.sound] diff --git a/docs/snapshots/axiom-ledger.md b/docs/snapshots/axiom-ledger.md index 1186fb60..670f5540 100644 --- a/docs/snapshots/axiom-ledger.md +++ b/docs/snapshots/axiom-ledger.md @@ -1,10 +1,16 @@ # Axiom & trust ledger > **Machine-generated snapshot — regenerate with `scripts/run_audit.sh` before relying on it.** -> Snapshot of 2026-06-10 (sp1-lean `e076768` + audit changes + the W1a capstone-premise split); raw -> probe output: [`axiom-census.txt`](axiom-census.txt) (314 probes, generated by -> `scripts/gen_axiom_probe.py`, gated by `scripts/run_audit.sh` — the gate **passes**: `sorryAx` -> reachable only from the known-debt set). +> The detailed per-bucket tables below are the **2026-06-10** snapshot (sp1-lean `e076768`). The raw probe +> output [`axiom-census.txt`](axiom-census.txt) was regenerated **2026-06-23** (now **366 probes**, generated by +> `scripts/gen_axiom_probe.py`); `scripts/run_audit.sh` still **passes** (`sorryAx` reachable only from the +> known debt). +> +> **What changed since the 2026-06-10 tables:** DivRem / ShiftLeft / ShiftRight / Mul completeness are now +> **closed** (2026-06-12 / 06-18), so `DivRemChip.completeness` is **no longer a `sorryAx` carrier**. The single +> remaining textual `sorry` is `sp1_witness_decode` (the W1b/W1c decode seam), and the `sorryAx` set is now just +> the capstone chain reaching it — **5 carriers** in the current census, all allowed. Re-run the audit for the +> current per-bucket membership before relying on the detailed tables below. ## How to read the axiom sets diff --git a/lakefile.toml b/lakefile.toml index 9d1ac302..ca4adc12 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -3,6 +3,13 @@ defaultTargets = ["SP1Clean"] moreLeanArgs = ["--tstack=400000"] +# `lake lint` driver — the project-local environment-linter pass (`scripts/sp1Lint.lean`). It runs a +# curated subset of the Batteries `#lint` linters over the hand-written `SP1Clean.*` declarations only, +# excluding the auto-generated `Extracted/`+`*Vectors` code. We use a thin custom driver (not the stock +# `runLinter` exe) because `runLinter` scopes by namespace *root*, which cannot exclude `SP1Clean.Extracted` +# while keeping `SP1Clean.Proofs`. Needs a built project first (env linters read oleans); run after `lake build`. +lintDriver = "sp1Lint" + # Built-in options only (no Mathlib linter options here — those require a Mathlib # linter module to be imported before the package -D flags are validated, which a # Clean-only file does not pull in; see the sp1-lean Step 0 finding). @@ -33,6 +40,14 @@ name = "RISCV" git = "https://github.com/succinctlabs/riscv-lean" rev = "dtumad/clean-native" +# The `lake lint` driver executable (see `lintDriver` above). A thin wrapper around the Batteries +# `#lint` framework; `supportInterpreter` is required because the linters' `test` functions are run +# through the interpreter. Built by `lake build` along with the libraries; invoked by `lake lint`. +[[lean_exe]] +name = "sp1Lint" +srcDir = "scripts" +supportInterpreter = true + # Umbrella library — root module `SP1Clean.lean` imports every module; `lake build` (the default # target) builds everything through it. Package-level `moreLeanArgs`/`[leanOptions]` above apply to # all libraries, so the layer libraries below inherit `--tstack`/heartbeats with no duplication. @@ -65,6 +80,11 @@ globs = ["SP1Clean.Native.+"] # The proof pillar: the sound/complete proofs (Proofs/), the faithfulness anchors (Faithful/), # and the whole-machine/trace layer (Soundness/). +# Lib-scoped Mathlib style linters: these hand-written modules all transitively import the linter +# registration (unlike the Clean-only Math/Model files — see the package `[leanOptions]` note above), +# so the `-D linter.style.*` flags are safe here. Start minimal (lambda/dollar are at zero violations); +# see `docs/agents/cleanup-backlog.md` for the next candidates (oldObtain / longLine / cdot). [[lean_lib]] name = "SP1Proofs" globs = ["SP1Clean.Proofs.+", "SP1Clean.Faithful.+", "SP1Clean.Soundness.+"] +moreLeanArgs = ["-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true"] diff --git a/scripts/axiom_probe.lean b/scripts/axiom_probe.lean index db715b60..b9a2729c 100644 --- a/scripts/axiom_probe.lean +++ b/scripts/axiom_probe.lean @@ -58,6 +58,8 @@ Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). - #print axioms SP1Clean.AddiSail.addi_chip_reaches_sail #print axioms SP1Clean.AddwSail.correct_addw_native #print axioms SP1Clean.AddwSail.addw_chip_reaches_sail +#print axioms SP1Clean.AddwSail.correct_addiw_native +#print axioms SP1Clean.AddwSail.addiw_chip_reaches_sail #print axioms SP1Clean.AluX0Sail.correct_aluX0_rtype #print axioms SP1Clean.AluX0Sail.correct_aluX0_rtypew #print axioms SP1Clean.AluX0Sail.correct_aluX0_addi @@ -68,9 +70,15 @@ Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). - #print axioms SP1Clean.AluX0Sail.correct_aluX0_divw #print axioms SP1Clean.AluX0Sail.correct_aluX0_remw #print axioms SP1Clean.BitwiseSail.correct_bitwise_native +#print axioms SP1Clean.BitwiseSail.correct_andi_native +#print axioms SP1Clean.BitwiseSail.correct_ori_native +#print axioms SP1Clean.BitwiseSail.correct_xori_native #print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_and #print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_or #print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_xor +#print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_andi +#print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_ori +#print axioms SP1Clean.BitwiseSail.bitwise_chip_reaches_sail_xori #print axioms SP1Clean.BranchSail.correct_branch_native #print axioms SP1Clean.BranchSail.branch_chip_reaches_sail #print axioms SP1Clean.DivRemSail.correct_div_native @@ -83,6 +91,7 @@ Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). - #print axioms SP1Clean.DivRemSail.correct_remuw_native #print axioms SP1Clean.DivRemSail.divrem_chip_reaches_sail #print axioms SP1Clean.JalSail.correct_jal_native +#print axioms SP1Clean.JalSail.correct_jal_native_x0 #print axioms SP1Clean.JalSail.jal_chip_reaches_sail #print axioms SP1Clean.JalrSail.correct_jalr_native #print axioms SP1Clean.JalrSail.jalr_chip_reaches_sail @@ -104,7 +113,10 @@ Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). - #print axioms SP1Clean.LoadX0Sail.loadX0_chip_reaches_sail #print axioms SP1Clean.LtSail.correct_sltu_native #print axioms SP1Clean.LtSail.correct_slt_native +#print axioms SP1Clean.LtSail.correct_slti_native +#print axioms SP1Clean.LtSail.correct_sltiu_native #print axioms SP1Clean.LtSail.lt_chip_reaches_sail +#print axioms SP1Clean.LtSail.lt_chip_reaches_sail_imm #print axioms SP1Clean.MulSail.correct_mul_native #print axioms SP1Clean.MulSail.correct_mulh_native #print axioms SP1Clean.MulSail.correct_mulhu_native @@ -133,6 +145,7 @@ Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). - #print axioms SP1Clean.SubwSail.subw_chip_reaches_sail #print axioms SP1Clean.UTypeSail.correct_utype_lui_native #print axioms SP1Clean.UTypeSail.correct_utype_auipc_native +#print axioms SP1Clean.UTypeSail.correct_utype_native_x0 #print axioms SP1Clean.UTypeSail.utype_chip_reaches_sail_lui #print axioms SP1Clean.UTypeSail.utype_chip_reaches_sail_auipc #print axioms SP1Clean.AddChip.kind diff --git a/scripts/check_no_skipkerneltc.sh b/scripts/check_no_skipkerneltc.sh new file mode 100755 index 00000000..852f4d2c --- /dev/null +++ b/scripts/check_no_skipkerneltc.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Gate: no `set_option (debug.)skipKernelTC` anywhere in the Lean source tree. +# +# `skipKernelTC` bypasses the kernel's type-check re-run — the trust anchor for an +# axiom-clean proof. It was a last-resort escape used while proving the bit-shift chips; +# the last site was removed 2026-06-18 and replaced by abstract-`BitVec` helper bridges +# (see `docs/agents/proof-patterns.md` §"Bit-shift chip soundness", the `2^64` bullet). +# This gate keeps it out for good. +# +# Scope is `SP1Clean/**/*.lean` only — `docs/` legitimately *mentions* the token +# ("…NOT `skipKernelTC`"), so scanning the source tree avoids those false positives. +# There are zero legitimate uses in source, so any case-insensitive hit fails the gate. +# +# Used by `scripts/run_audit.sh` (A2 gate) and `.github/workflows/lean_action_ci.yml` +# (the lightweight `guards` job). Exit 0 = clean, 1 = reintroduced. +# +# Usage: scripts/check_no_skipkerneltc.sh (from the repo root) + +set -uo pipefail +cd "$(dirname "$0")/.." + +if grep -rniE 'skipKernelTC' SP1Clean --include='*.lean'; then + echo "FAIL: skipKernelTC reintroduced in SP1Clean/ (see lines above)." >&2 + echo " Fix the kernel error by factoring the expensive compute into an abstract-BitVec" >&2 + echo " helper proved once over variables (the srl_toNat/sra_toNat pattern) — do NOT" >&2 + echo " bypass the kernel. See docs/agents/proof-patterns.md." >&2 + exit 1 +fi +exit 0 diff --git a/scripts/nolints.json b/scripts/nolints.json new file mode 100644 index 00000000..c2328cd5 --- /dev/null +++ b/scripts/nolints.json @@ -0,0 +1,20 @@ +[["defLemma", "SP1Clean.Soundness.memBalanceHyps_of_genesis"], + ["defLemma", "SP1Clean.Soundness.Target.targetObligations_full"], + ["defLemma", "SP1Clean.Soundness.Target.targetObligations_of_decode"], + ["simpComm", "Sail.writeReg_writeReg_comm"], + ["simpComm", "Sail.write_reg'_write_reg'_comm"], + ["simpComm", "Sail.write_reg_write_reg_comm"], + ["simpComm", "Std.ExtDHashMap.insert_insert_comm"], + ["simpNF", "ExtDHashMap_insert_insert_self"], + ["simpNF", "Sail.map_const_run_readReg"], + ["simpNF", "Sail.run_readReg_insert_of_ne"], + ["simpNF", "Sail.run_readReg_insert_self"], + ["simpNF", "Sail.run_writeReg_bind"], + ["simpNF", "Sail.write_reg'_write_reg'_comm"], + ["simpNF", "Sail.write_reg_eq_write_reg'"], + ["simpNF", "Sail.write_reg_write_reg_comm"], + ["simpNF", "SailState.isInitialized_insert"], + ["simpNF", "SP1Clean.DivRemChip.val_16_zmod_p"], + ["simpNF", "Std.ExtDHashMap.insert_inj"], + ["simpNF", "Std.ExtDHashMap.insert_insert_comm"], + ["simpNF", "LeanRV64D.Functions.bool_bit_backwards.eq_2"]] diff --git a/scripts/run_audit.sh b/scripts/run_audit.sh index df7b0361..e14699dc 100755 --- a/scripts/run_audit.sh +++ b/scripts/run_audit.sh @@ -41,6 +41,14 @@ if grep -rnE '^[[:space:]]*axiom[[:space:]]' SP1Clean --include='*.lean'; then else echo "PASS: no axiom declarations" fi + +echo +echo "== A2 skipKernelTC guard (gate: none in SP1Clean/) ==" +if scripts/check_no_skipkerneltc.sh; then + echo "PASS: no skipKernelTC usage" +else + echo "FAIL: skipKernelTC reintroduced (see above)"; fail=1 +fi echo "native_decide occurrences (disclosed, witness battery + Sail memory bridges):" grep -rn 'native_decide' SP1Clean --include='*.lean' | wc -l diff --git a/scripts/sp1Lint.lean b/scripts/sp1Lint.lean new file mode 100644 index 00000000..af07dce7 --- /dev/null +++ b/scripts/sp1Lint.lean @@ -0,0 +1,128 @@ +import Batteries.Tactic.Lint + +/-! +# sp1Lint — the project's `lake lint` driver (environment linters) + +A thin wrapper around the Batteries `#lint` framework (`getChecks` / `lintCore`), adapted for this +project in the two ways the stock `runLinter` exe cannot do: + +* **Decl filter by full module path.** `runLinter` scopes by namespace *root* (it lints + `getDeclsInPackage module.getRoot`), so pointing it anywhere in this package lints every `SP1Clean.*` + declaration — including the auto-generated `SP1Clean.Extracted.*` modules and the `*Vectors` trace + batteries. We instead keep only hand-written declarations (`getHandwrittenDecls`). NOTE: the per-file + `set_option linter.all false` headers on the generated files gate only the *syntactic* linters during + elaboration; they do nothing against these *environment* linters, which run post-import over the built + environment. `nolints.json` / `@[nolint]` are the only env-linter suppressions. + + INVARIANT: keep `getHandwrittenDecls` in sync with `update_extracted.py`'s three output dirs — + `Extracted/`, `Extracted/WitnessVectors/` (both under the `SP1Clean.Extracted` prefix), and + `Proofs/TraceGenTests/*Vectors.lean` (caught by the `endsWith "Vectors"` clause; no hand-written + module ends in "Vectors"). + +* **Curated linter set.** We run a low-noise subset via `runOnly`, omitting the doc-coverage linters + (`docBlame`/`docBlameThm`/`tacticDocs`/`deprecatedNoSince`) that would swamp a proofs project. See + `curatedLinters` below; grow it deliberately (and see AGENTS.md § Linters). + +Wired as the package `lintDriver`, so `lake lint` runs it (after `lake build`, which the env linters need +for oleans). Residue is recorded in `scripts/nolints.json`; `lake exe sp1Lint --update` snapshots the +current violation set into it (the standard ratchet). Without `--update`, any *new* violation fails (exit 1). +-/ + +open Lean Core Elab Batteries.Tactic.Lint +open System (FilePath) + +/-- The curated environment-linter set (short names, as registered by `@[env_linter]`). The +doc-coverage linters (`docBlame`, `docBlameThm`, `tacticDocs`, `deprecatedNoSince`) and `structureInType` +are intentionally omitted — see AGENTS.md § Linters. + +`unusedArguments` is also intentionally omitted: it flags only the project's *uniform* signature args +(`{p} [Fact p.Prime] [Fact (2^17 < p)]` / `NeZero p` instance binders, and the `ProverData`/`ProverHint` +args every chip `Spec`/`Assumptions`/`ProverAssumptions` carries by interface) — all structurally +required, none a real defect — and it grows a fresh false-positive per new chip, so even nolinting it +would be a permanent tax. Re-run it ad hoc (`getChecks` + this set + `unusedArguments`) if hunting +genuinely-dead args. -/ +def curatedLinters : List Name := + [`dupNamespace, `defLemma, `unusedHavesSuffices, + `checkUnivs, `checkType, `synTaut, `impossibleInstance, `nonClassInstance, + `simpNF, `simpVarHead, `simpComm] + +/-- The list of `nolints` pulled from the `nolints.json` file (linter short-name × declaration). -/ +abbrev NoLints := Array (Name × Name) + +/-- Read the given file path and deserialize it as JSON. -/ +def readJsonFile (α) [FromJson α] (path : FilePath) : IO α := do + let _ : MonadExceptOf String IO := ⟨throw ∘ IO.userError, fun x _ => x⟩ + liftExcept <| fromJson? <|← liftExcept <| Json.parse <|← IO.FS.readFile path + +/-- Serialize the given value `a : α` to the file as JSON. -/ +def writeJsonFile [ToJson α] (path : FilePath) (a : α) : IO Unit := + IO.FS.writeFile path <| toJson a |>.pretty.push '\n' + +/-- Hand-written `SP1Clean.*` declarations only: drops `SP1Clean.Extracted.*` and the `*Vectors` +auto-gen batteries. Mirrors `Batteries.Tactic.Lint.getDeclsInPackage`, but filters on the full module +path instead of just the namespace root. -/ +def getHandwrittenDecls : CoreM (Array Name) := do + let env ← getEnv + let keep := env.header.moduleNames.map fun m => + (`SP1Clean).isPrefixOf m + && !(`SP1Clean.Extracted).isPrefixOf m + && !m.toString.endsWith "Vectors" + return env.constants.map₁.fold (init := #[]) fun decls declName _ => + if keep[env.const2ModIdx[declName]?.get! (α := Nat)]! then decls.push declName else decls + +/-- +Usage: `sp1Lint [--update] [-v | --trace]` + +Runs the curated environment linters on the hand-written `SP1Clean.*` declarations (excluding the +auto-generated `Extracted/` modules and `*Vectors` batteries). The project must already be built +(`lake build SP1Clean`) — the env linters read the oleans. + +If `--update` is set, `scripts/nolints.json` is overwritten with the current violation set (the standard +ratchet: accept the current state, then fail on any *new* violation). Otherwise, exits 1 if any violation +remains after subtracting `nolints.json`. +-/ +unsafe def main (args : List String) : IO Unit := do + let update := args.contains "--update" + let verbose := args.contains "-v" || args.contains "--trace" + initSearchPath (← findSysroot) + let projectModule := `SP1Clean + let lintModule := `Batteries.Tactic.Lint + -- The env linters need built oleans; require them rather than driving a build from here. + for m in [projectModule, lintModule] do + let olean ← findOLean m + unless (← olean.pathExists) do + IO.eprintln s!"sp1Lint: missing olean for `{m}` at:\n {olean}\n\ + Run `lake build SP1Clean` first." + IO.Process.exit 1 + let nolintsFile : FilePath := "scripts/nolints.json" + let nolints ← if ← nolintsFile.pathExists then readJsonFile NoLints nolintsFile else pure #[] + unsafe Lean.enableInitializersExecution + let env ← importModules #[projectModule, lintModule] {} (trustLevel := 1024) (loadExts := true) + let opts : Options := if verbose then ({} : Options).setBool `trace.Batteries.Lint true else {} + let ctx := { fileName := "", fileMap := default, options := opts } + let state := { env } + Prod.fst <$> (CoreM.toIO · ctx state) do + let decls ← getHandwrittenDecls + traceLint s!"Linting {decls.size} hand-written declarations…" + (inIO := true) (currentModule := projectModule) + let linters ← getChecks (slow := true) (runOnly := some curatedLinters) (runAlways := none) + let results ← lintCore decls linters (inIO := true) (currentModule := projectModule) + if update then + traceLint s!"Updating nolints file at {nolintsFile}" + (inIO := true) (currentModule := projectModule) + writeJsonFile (α := NoLints) nolintsFile <| + .qsort (lt := fun (a, b) (c, d) => a.lt c || (a == c && b.lt d)) <| + .flatten <| results.map fun (linter, decls) => + decls.fold (fun res decl _ => res.push (linter.name, decl)) #[] + let results := results.map fun (linter, decls) => + .mk linter <| nolints.foldl (init := decls) fun decls (linter', decl') => + if linter.name == linter' then decls.erase decl' else decls + let failed := results.any (!·.2.isEmpty) + if failed then + let fmt ← formatLinterResults results decls (groupByFilename := true) (useErrorFormat := true) + "in SP1Clean (hand-written)" (runSlowLinters := true) .medium linters.size + IO.print (← fmt.toString) + IO.Process.exit 1 + else + IO.println "-- sp1Lint: linting passed." + IO.Process.exit 0 From 7b6476af3985b131ca51b9d427d839182adf1caf Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Wed, 24 Jun 2026 12:22:45 -0500 Subject: [PATCH 4/8] ci: split `lake lint` into its own independent job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the environment-linter pass out of the `build` job into a separate `lint` job so it shows up independently in the checks list. The lint job `needs: build` and restores the project oleans from a commit-keyed cache the build job publishes (lean-action's own cache covers dependencies only, not the per-commit project build), so `lake lint` runs without a redundant full rebuild. The restore is best-effort — on a miss, lean-action rebuilds, so the job stays correct. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/lean_action_ci.yml | 44 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lean_action_ci.yml b/.github/workflows/lean_action_ci.yml index 5cd23da0..f58b4c9c 100644 --- a/.github/workflows/lean_action_ci.yml +++ b/.github/workflows/lean_action_ci.yml @@ -37,10 +37,44 @@ jobs: test: "false" lint: "false" use-github-cache: "true" - # Environment linters: the `lake lint` pass (our `sp1Lint` lintDriver — a curated subset of the - # Batteries `#lint` linters over the hand-written `SP1Clean.*` declarations; see AGENTS.md § Linters). - # Runs in the build job, after the build, so the oleans it imports are present. Explicit `lake lint` - # (rather than lean-action's `lint:` flag) keeps the driver invocation unambiguous. + # Hand the freshly-built project oleans to the `lint` job via a commit-keyed cache. + # lean-action's own `use-github-cache` covers dependencies only (keyed on the manifest), not the + # per-commit project build — so without this the lint job would rebuild the whole project. + - name: Cache build outputs for the lint job + uses: actions/cache/save@v4 + with: + path: .lake/build + key: lake-build-${{ github.sha }} + + lint: + # Environment linters (`lake lint` → the `sp1Lint` lintDriver; a curated subset of the Batteries + # `#lint` linters over the hand-written `SP1Clean.*` declarations; see AGENTS.md § Linters). Split + # into its own job so it is clearly independent in the checks list. `needs: build` so it runs after + # the build has published the commit-keyed olean cache; restoring it lets `lake lint` read the + # already-built project (the lean-action step then just replays). The restore is best-effort — if it + # misses, lean-action rebuilds the project, so the job stays correct either way. + needs: build + runs-on: + [ + runs-on, + runner=64cpu-linux-x64, + spot=false, + disk=large, + "run-id=${{ github.run_id }}", + ] + + steps: + - uses: actions/checkout@v4 + - name: Restore build outputs from the build job + uses: actions/cache/restore@v4 + with: + path: .lake/build + key: lake-build-${{ github.sha }} + - uses: leanprover/lean-action@v1 + with: + build: "true" + test: "false" + lint: "false" + use-github-cache: "true" - name: Environment linters (lake lint) run: lake lint - From 8773b4febe26b3003a50357d1076fdd1f95a8e10 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Thu, 25 Jun 2026 11:21:03 -0500 Subject: [PATCH 5/8] use an actual test driver and gate against native_decide in core --- .github/workflows/lean_action_ci.yml | 37 +++++++++ AGENTS.md | 77 ++++++++++++------ README.md | 12 ++- SP1Clean.lean | 50 ------------ .../Proofs/Chips/ShiftLeftChip/Populate.lean | 4 +- .../TraceGenTests/AddChipTraceVectors.lean | 2 +- .../TraceGenTests/AddChipTraceWitness.lean | 4 +- .../TraceGenTests/AddwChipTraceVectors.lean | 2 +- .../TraceGenTests/AddwChipTraceWitness.lean | 4 +- .../BitwiseChipTraceVectors.lean | 2 +- .../BitwiseChipTraceWitness.lean | 4 +- .../TraceGenTests/Conformance.lean | 0 .../TraceGenTests/DivRemChipTraceVectors.lean | 2 +- .../TraceGenTests/DivRemChipTraceWitness.lean | 4 +- .../TraceGenTests/EventPopulate.lean | 2 +- .../TraceGenTests/LtChipTraceVectors.lean | 2 +- .../TraceGenTests/LtChipTraceWitness.lean | 4 +- .../TraceGenTests/MulChipTraceVectors.lean | 2 +- .../TraceGenTests/MulChipTraceWitness.lean | 4 +- .../ShiftLeftChipTraceVectors.lean | 2 +- .../ShiftLeftChipTraceWitness.lean | 4 +- .../ShiftRightChipTraceVectors.lean | 2 +- .../ShiftRightChipTraceWitness.lean | 4 +- .../TraceGenTests/SubChipTraceVectors.lean | 2 +- .../TraceGenTests/SubChipTraceWitness.lean | 4 +- .../TraceGenTests/SubwChipTraceVectors.lean | 2 +- .../TraceGenTests/SubwChipTraceWitness.lean | 4 +- .../TraceGenTests/TraceGenerator.lean | 0 .../WitnessTests/AddOperationWitness.lean | 4 +- .../WitnessTests/AddrAddOperationWitness.lean | 4 +- .../WitnessTests/AddwOperationWitness.lean | 4 +- .../WitnessTests/BitwiseOperationWitness.lean | 4 +- .../IsEqualWordOperationWitness.lean | 4 +- .../WitnessTests/IsZeroOperationWitness.lean | 4 +- .../IsZeroWordOperationWitness.lean | 4 +- .../LtOperationUnsignedWitness.lean | 4 +- .../WitnessTests/MulOperationWitness.lean | 4 +- .../WitnessTests/SubOperationWitness.lean | 4 +- .../WitnessTests/SubwOperationWitness.lean | 4 +- .../U16CompareOperationWitness.lean | 4 +- .../WitnessTests/U16MSBOperationWitness.lean | 4 +- .../WitnessTests/Vectors}/AddOperation.lean | 0 .../Vectors}/AddrAddOperation.lean | 0 .../WitnessTests/Vectors}/AddwOperation.lean | 0 .../Vectors}/BitwiseOperation.lean | 0 .../Vectors}/IsEqualWordOperation.lean | 0 .../Vectors}/IsZeroOperation.lean | 0 .../Vectors}/IsZeroWordOperation.lean | 0 .../Vectors}/LtOperationUnsigned.lean | 0 .../WitnessTests/Vectors}/MulOperation.lean | 0 .../WitnessTests/Vectors}/SubOperation.lean | 0 .../WitnessTests/Vectors}/SubwOperation.lean | 0 .../Vectors}/U16CompareOperation.lean | 0 .../Vectors}/U16MSBOperation.lean | 0 .../WitnessTests/WitnessConformance.lean | 0 docs/agents/cleanup-backlog.md | 34 +++++--- docs/architecture.md | 20 +++-- docs/snapshots/axiom-census.txt | 2 +- lakefile.toml | 81 ++++++++++++++++--- scripts/axiom_probe.lean | 23 ++++++ scripts/check_no_native_decide.sh | 31 +++++++ scripts/gen_axiom_probe.py | 34 ++++++-- scripts/run_audit.sh | 13 ++- scripts/sp1Lint.lean | 23 +++--- update_extracted.py | 19 +++-- 65 files changed, 376 insertions(+), 198 deletions(-) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/AddChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/AddChipTraceWitness.lean (93%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/AddwChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/AddwChipTraceWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/BitwiseChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/BitwiseChipTraceWitness.lean (93%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/Conformance.lean (100%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/DivRemChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/DivRemChipTraceWitness.lean (96%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/EventPopulate.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/LtChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/LtChipTraceWitness.lean (93%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/MulChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/MulChipTraceWitness.lean (94%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/ShiftLeftChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/ShiftLeftChipTraceWitness.lean (95%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/ShiftRightChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/ShiftRightChipTraceWitness.lean (95%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/SubChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/SubChipTraceWitness.lean (91%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/SubwChipTraceVectors.lean (99%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/SubwChipTraceWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/TraceGenTests/TraceGenerator.lean (100%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/AddOperationWitness.lean (91%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/AddrAddOperationWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/AddwOperationWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/BitwiseOperationWitness.lean (93%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/IsEqualWordOperationWitness.lean (95%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/IsZeroOperationWitness.lean (93%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/IsZeroWordOperationWitness.lean (94%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/LtOperationUnsignedWitness.lean (94%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/MulOperationWitness.lean (95%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/SubOperationWitness.lean (91%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/SubwOperationWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/U16CompareOperationWitness.lean (92%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/U16MSBOperationWitness.lean (90%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/AddOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/AddrAddOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/AddwOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/BitwiseOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/IsEqualWordOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/IsZeroOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/IsZeroWordOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/LtOperationUnsigned.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/MulOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/SubOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/SubwOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/U16CompareOperation.lean (100%) rename {SP1Clean/Extracted/WitnessVectors => SP1CleanTest/WitnessTests/Vectors}/U16MSBOperation.lean (100%) rename {SP1Clean/Proofs => SP1CleanTest}/WitnessTests/WitnessConformance.lean (100%) create mode 100755 scripts/check_no_native_decide.sh diff --git a/.github/workflows/lean_action_ci.yml b/.github/workflows/lean_action_ci.yml index f58b4c9c..ffd6c88e 100644 --- a/.github/workflows/lean_action_ci.yml +++ b/.github/workflows/lean_action_ci.yml @@ -16,6 +16,8 @@ jobs: - uses: actions/checkout@v4 - name: No skipKernelTC in source run: scripts/check_no_skipkerneltc.sh + - name: No native_decide in the main SP1Clean library + run: scripts/check_no_native_decide.sh build: runs-on: @@ -78,3 +80,38 @@ jobs: use-github-cache: "true" - name: Environment linters (lake lint) run: lake lint + + test: + # Conformance tests (`lake test` → the `SP1CleanTest` testDriver: the witness-generation and + # whole-trace `native_decide` anchors checked against batteries dumped from SP1's real Rust prover). + # These live in their own library — the ONLY place `native_decide` is allowed — so they are gated + # here, separately from the `native_decide`-free main build. Split into its own job (in parallel with + # `lint`) so the trusted-compiler checks are clearly independent in the checks list. `needs: build` + # so it runs after the build has published the commit-keyed olean cache; restoring it lets `lake test` + # reuse the already-built main library and only elaborate the test modules. The restore is + # best-effort — if it misses, lean-action rebuilds the main library, so the job stays correct either way. + needs: build + runs-on: + [ + runs-on, + runner=64cpu-linux-x64, + spot=false, + disk=large, + "run-id=${{ github.run_id }}", + ] + + steps: + - uses: actions/checkout@v4 + - name: Restore build outputs from the build job + uses: actions/cache/restore@v4 + with: + path: .lake/build + key: lake-build-${{ github.sha }} + - uses: leanprover/lean-action@v1 + with: + build: "true" + test: "false" + lint: "false" + use-github-cache: "true" + - name: Conformance tests (lake test) + run: lake test diff --git a/AGENTS.md b/AGENTS.md index 20c3c688..db1ab6f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,11 @@ The SP1 Rust source (the extraction/spec oracle, read-only reference) lives in a ## Build - Full build: `lake build SP1Clean` (the default target). Passing = **0 errors AND 0 warnings**, and - **no stray `info:` notes** — leave the build output clean (see the `ring` note below). + **no stray `info:` notes** — leave the build output clean (see the `ring` note below). This builds + **only the main library** — it carries no `native_decide` (gated by `scripts/check_no_native_decide.sh`). +- Tests: `lake test` (the `SP1CleanTest` `testDriver`). Builds/elaborates the witness- and trace-conformance + anchors — the project's **only** `native_decide` — checked against batteries dumped from SP1's real Rust + prover. Runs on top of the cached main-library oleans (the test lib imports `SP1Clean`, never vice-versa). - Single file: `lake env lean SP1Clean/Proofs/Chips/AddChip/Formal.lean` (builds deps from cache, then elaborates). ⚠️ `lake env lean ` **exits 0 even on a Lean stack overflow**, and a stale cached olean can make downstream checks pass falsely — **always finish a phase with `lake build SP1Clean`**. @@ -91,22 +95,26 @@ Mirror-rust layout under `SP1Clean/`: - **`Proofs/`** — the "proven sound/complete" pillar: `Proofs/Chips/Chip/{Formal,Bridge,…}.lean` (soundness/completeness/`circuit` + the Sail bridge; the `Spec` is in `FormalModel/Contracts/Chips.lean`, the ALU chips' `Assumptions`/`ProverAssumptions` in `Contracts/ChipAssumptions.lean`), - `Proofs/Operations//Formal.lean` (the `FormalAssertion` soundness/completeness), and - `Proofs/WitnessTests/*.lean` (the `Witness.lean` `native_decide` conformance anchors + - `WitnessConformance.lean`, in namespace `SP1Clean.WitnessTests`; their auto-gen vectors are under - `Extracted/WitnessVectors/`). Flat receiver infra (`ByteChip`/`ProgramChip`/`MemoryProvider`) sits in + `Proofs/Operations//Formal.lean` (the `FormalAssertion` soundness/completeness). Flat receiver + infra (`ByteChip`/`ProgramChip`/`MemoryProvider`) sits in `Proofs/Chips/`. The 3 entangled complex chips (DivRem/ShiftLeft/ShiftRight) stay **whole** in `Proofs/Chips/` — their `Defs` import sibling proof files, so they are not split into `Native/`. - **`Faithful/`** — the "proven faithful" pillar: the per-operation/chip constraint anchors (`.lean`). -- **`Proofs/TraceGenTests/`** — the **full-trace** chip testing layer, kept **whole** in `Proofs/` (the - auto-gen `ChipTraceVectors.lean` batteries import the hand-written scaffold types, so they are - *not* split into `Extracted/` like `WitnessVectors/`): the **circuit-as-trace-generator** - (`TraceGenerator.lean` + `EventPopulate.lean` + `Conformance.lean` + `ChipTrace{Vectors,Witness}.lean`) - — whole chip traces derived from the chips' own `main` witness closures + output struct, - `native_decide`-checked against SP1's real `generate_trace`. 10 chips, **all unmasked**: - Add/Sub/Subw/Addw fixed-witness; Mul/DivRem/Bitwise/Lt/ShiftLeft/ShiftRight hint-driven flags (per-event - `ProverHint` from the dumped executor opcode). Namespace `SP1Clean.TraceGenTests`; vectors regenerated by - `update_extracted.py` (the `TRACEGEN_DIR` writer). +- **`SP1CleanTest/`** (top-level, **not** under `SP1Clean/`) — the **test library**, the sole home of + `native_decide` and the `lake test` target (`testDriver`). It imports the main `SP1Clean` library and + is never imported by it, so the default `lake build SP1Clean` stays `native_decide`-free (enforced by + `scripts/check_no_native_decide.sh`; `native_decide` trusts the whole compiler, adding + `Lean.ofReduceBool`/`Lean.trustCompiler`). Two layers, namespaces preserved (`SP1Clean.WitnessTests` / + `SP1Clean.TraceGenTests`, decoupled from the new module paths): + - `WitnessTests/` — the `Witness.lean` witness-generation conformance anchors + + `WitnessConformance.lean` scaffold; auto-gen vectors under `WitnessTests/Vectors/`. + - `TraceGenTests/` — the **circuit-as-trace-generator** full-trace layer (`TraceGenerator.lean` + + `EventPopulate.lean` + `Conformance.lean` + `ChipTrace{Vectors,Witness}.lean`): whole chip + traces derived from the chips' own `main` witness closures + output struct, `native_decide`-checked + against SP1's real `generate_trace`. 10 chips, **all unmasked**: Add/Sub/Subw/Addw fixed-witness; + Mul/DivRem/Bitwise/Lt/ShiftLeft/ShiftRight hint-driven flags (per-event `ProverHint` from the dumped + executor opcode). + Both vector batteries are regenerated by `update_extracted.py` (the `WITNESS_DIR`/`TRACEGEN_DIR` writers). - **`Soundness/`** — the whole-machine layer: per-bus `{State,Byte,Program,Memory}Consistency.lean`; `ChipRow.lean` (the `ChipKind` structure-of-functions — each chip registers one `kind`, carrying a `name` = its SP1 `MachineAir::name`) + `ChipRegistry.lean` (`allChipKinds`); the gated execution capstone @@ -129,12 +137,15 @@ Mirror-rust layout under `SP1Clean/`: any fully-qualified name (only `import` lines and tooling path-globs follow the move). Keep it that way. **Lake libraries** (`lakefile.toml`): the umbrella `SP1Clean` (the default target — its root index imports -everything, so `lake build` builds the whole project) plus per-pillar build-targets `SP1Math` / `SP1Model` / -`SP1Extracted` / `SP1FormalModel` / `SP1Native` / `SP1Proofs` (selected by submodule globs, e.g. +the whole **main** library, so `lake build` builds all of it) plus per-pillar build-targets `SP1Math` / +`SP1Model` / `SP1Extracted` / `SP1FormalModel` / `SP1Native` / `SP1Proofs` (selected by submodule globs, e.g. `"SP1Clean.Math.+"`; `SP1Proofs` groups `Proofs` + `Faithful` + `Soundness`). `lake build SP1Extracted` -builds just that layer. Isolation is **by convention** — Lake does not forbid cross-layer -imports within one package; the auto-gen guard is the `Extracted/` "do not hand-edit" headers + the sole -writer `update_extracted.py`. +builds just that layer. Separately, the top-level **test** library `SP1CleanTest` (glob `"SP1CleanTest.+"`, +the `testDriver` → `lake test`) holds the witness/trace conformance anchors; it imports `SP1Clean` but is +**not** part of the umbrella, so `lake build SP1Clean` never compiles it (keeping the main build +`native_decide`-free). Isolation is **by convention** — Lake does not forbid cross-layer +imports within one package; the auto-gen guard is the `Extracted/` + `SP1CleanTest/**/Vectors`/`*TraceVectors` +"do not hand-edit" headers + the sole writer `update_extracted.py`. **Restructure status (updated 2026-06-23).** Landed & green: the `Math`/`Model` split, `Extracted/` auto-gen consolidation (`Circuit/` + `WitnessVectors/`), the `FormalModel/Contracts/` audit surface (all `Spec`s + @@ -190,19 +201,27 @@ These are the keepers from sp1-lean's "faithful sub-circuit composition" discipl "Step 0" reason the package `[leanOptions]` carries no Mathlib linter flags, see `lakefile.toml`). - **Linters — two kinds.** *Syntactic* linters run during `lake build` (option-gated); *environment* linters run as a separate `lake lint` pass over the built environment. - - **Syntactic.** The `SP1Proofs` lake library enables `-D linter.style.lambdaSyntax`/`dollarSyntax` (via its - `moreLeanArgs`); these apply during the normal `lake build SP1Clean` (224 hand-written `Proofs/`/`Faithful/`/ - `Soundness/` modules) with **zero** violations. Package-level Mathlib linter flags do **not** work (the - Clean-only `Math/`/`Model/` files lack the registration) — scope any new linter to a lake lib, not the - package. The auto-gen `Extracted/`+`*Vectors` files carry per-file `set_option linter.all false`. + - **Syntactic.** Every hand-written **core** pillar lake library enables the same eight `-D linter.*` flags + (via its `moreLeanArgs`): `SP1Math`, `SP1Model`, `SP1FormalModel`, `SP1Native`, and `SP1Proofs` + (`Proofs/`+`Faithful/`+`Soundness/`). The flags: `style.lambdaSyntax`/`style.dollarSyntax`, the four + deprecated-tactic guards `style.refine`/`style.cases`/`style.induction`/`style.admit`, and + `oldObtain`/`style.cdot` — all at **zero** violations. They apply during the normal `lake build SP1Clean`. + `linter.style.longLine` is the remaining candidate (real fallout, concentrated in Native/FormalModel; see + `docs/agents/cleanup-backlog.md`). The flags are scoped **per-lib** rather than at package level so the + auto-gen `SP1Extracted` library stays out of the set (it carries per-file `set_option linter.all false`); + keep the five identical `moreLeanArgs` copies in `lakefile.toml` in sync. (Every hand-written pillar + transitively imports Mathlib — Native via Clean — so the `-D linter.*` options register fine; the older + "Clean-only Math/Model lack the registration" note was stale. Still true: a file's imports must precede + its module doc-comment, or the `-D` flag can't be validated — see the Step 0 bullet above.) - **Environment (`lake lint`).** Run `lake lint` (after a build — it imports the oleans) for the Batteries `#lint` checks. The driver is `scripts/sp1Lint.lean` (package `lintDriver = "sp1Lint"`), a thin wrapper over `getChecks`/`lintCore`. We use a **custom driver, not the stock `runLinter` exe**, because `runLinter` scopes by namespace *root* (`getDeclsInPackage module.getRoot`) — it would lint all `SP1Clean.*` incl. `SP1Clean.Extracted.*`, and the per-file `set_option linter.all false` headers do **nothing** against environment linters (those run post-import; only `nolints.json`/`@[nolint]` suppress them). `sp1Lint` - instead filters decls by full module path (drops `Extracted/`+`*Vectors`) and runs a **curated** set of 11 - low-noise linters. Residue lives in `scripts/nolints.json` (21 stable entries — 3 `defLemma` + instead filters decls by full module path (drops `Extracted/`+`*Vectors`) and runs a **curated** set of 13 + low-noise linters (incl. the Mathlib `structureInType`/`deprecatedNoSince` hygiene checks, both at zero + violations). Residue lives in `scripts/nolints.json` (21 stable entries — 3 `defLemma` obligation-bundle defs + 18 `simpComm`/`simpNF` Math/Model/Sail simp lemmas); `lake exe sp1Lint --update` regenerates it; CI runs `lake lint` in the build job. Deliberately **dropped**: `docBlame`/`tacticDocs` (doc-coverage noise) and `unusedArguments` (flags only the uniform field-generic / `ProverData` signature @@ -218,6 +237,12 @@ These are the keepers from sp1-lean's "faithful sub-circuit composition" discipl helper** proved once over variables (the `srl_toNat`/`sra_toNat` pattern), then apply it symbolically — never silence the kernel. See `docs/agents/proof-patterns.md` §"Bit-shift chip soundness" (the `2^64` bullet) for the worked fix. +- **Never `native_decide` in the main `SP1Clean/` library.** It discharges goals by running compiled code, + trusting the **whole compiler** (adds `Lean.ofReduceBool`/`Lean.trustCompiler`) — so headline soundness + theorems would no longer be `[propext, Classical.choice, Quot.sound]`-clean. It is **CI-gated** + (`scripts/check_no_native_decide.sh`, run by the audit + the `guards` job; any hit in `SP1Clean/**/*.lean` + fails the build). Conformance checks that genuinely need it live in the separate top-level `SP1CleanTest` + library (`lake test`); to disclose a new one, put the anchor there, not in `SP1Clean/`. - `mul_eq_zero` won't fire on `ZMod p` (a `Nat.rec` Mul-instance quirk) — derive booleanness via `inv_mul_cancel₀` / a `bool_of_mul_pred`-style lemma instead. - `Word` is an `abbrev` for `Vector` — `w.toBitVec64` dot-notation fails; write `Word.toBitVec64 w`. diff --git a/README.md b/README.md index 79e9ff9d..59cb98a8 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,14 @@ Trace-level consistency properties and the whole-machine capstone. - `GatedVm/` + `SP1GatedVm.lean` — the execution capstone (`sp1_machine_soundness`), the final Clean `FormalEnsemble`. -### `Proofs/WitnessTests/` (+ `Extracted/WitnessVectors/`) -Tests the witness generators for most operations against explicit vectors generated by SP1. The hand-written -`native_decide` anchors (`Witness.lean`) live in `Proofs/WitnessTests/`; the auto-generated vectors live in -`Extracted/WitnessVectors/`. Shows conformance on test cases; doesn't directly prove witness generation faithful. +### `SP1CleanTest/` — the test library (`lake test`) +A separate top-level library (it imports `SP1Clean`, but is **not** part of the main `lake build`) holding +the conformance tests — the project's only `native_decide` (which trusts the whole compiler, so it is kept +out of the main library by `scripts/check_no_native_decide.sh`). Two layers: `WitnessTests/` checks each +operation's witness generator against explicit vectors dumped from SP1 (`Witness.lean` anchors + +auto-generated `WitnessTests/Vectors/`), and `TraceGenTests/` checks whole chip traces re-derived from each +chip's own circuit against SP1's real `generate_trace`. Shows conformance on test cases; doesn't directly +prove witness generation faithful. Run with `lake test`. ## How a proof connects to SP1 diff --git a/SP1Clean.lean b/SP1Clean.lean index 35ae46ca..ef9ceb7b 100644 --- a/SP1Clean.lean +++ b/SP1Clean.lean @@ -336,53 +336,3 @@ import SP1Clean.FormalModel.Trace.Witness import SP1Clean.FormalModel.Contracts.Operations import SP1Clean.FormalModel.Contracts.Readers import SP1Clean.Soundness.RowView -import SP1Clean.Proofs.WitnessTests.AddOperationWitness -import SP1Clean.Extracted.WitnessVectors.AddOperation -import SP1Clean.Proofs.WitnessTests.AddrAddOperationWitness -import SP1Clean.Extracted.WitnessVectors.AddrAddOperation -import SP1Clean.Proofs.WitnessTests.AddwOperationWitness -import SP1Clean.Extracted.WitnessVectors.AddwOperation -import SP1Clean.Proofs.WitnessTests.BitwiseOperationWitness -import SP1Clean.Extracted.WitnessVectors.BitwiseOperation -import SP1Clean.Proofs.WitnessTests.IsEqualWordOperationWitness -import SP1Clean.Extracted.WitnessVectors.IsEqualWordOperation -import SP1Clean.Proofs.WitnessTests.IsZeroOperationWitness -import SP1Clean.Extracted.WitnessVectors.IsZeroOperation -import SP1Clean.Proofs.WitnessTests.IsZeroWordOperationWitness -import SP1Clean.Extracted.WitnessVectors.IsZeroWordOperation -import SP1Clean.Proofs.WitnessTests.LtOperationUnsignedWitness -import SP1Clean.Extracted.WitnessVectors.LtOperationUnsigned -import SP1Clean.Proofs.WitnessTests.MulOperationWitness -import SP1Clean.Extracted.WitnessVectors.MulOperation -import SP1Clean.Proofs.WitnessTests.SubOperationWitness -import SP1Clean.Extracted.WitnessVectors.SubOperation -import SP1Clean.Proofs.WitnessTests.SubwOperationWitness -import SP1Clean.Extracted.WitnessVectors.SubwOperation -import SP1Clean.Proofs.WitnessTests.U16CompareOperationWitness -import SP1Clean.Extracted.WitnessVectors.U16CompareOperation -import SP1Clean.Proofs.WitnessTests.U16MSBOperationWitness -import SP1Clean.Extracted.WitnessVectors.U16MSBOperation -import SP1Clean.Proofs.WitnessTests.WitnessConformance -import SP1Clean.Proofs.TraceGenTests.Conformance -import SP1Clean.Proofs.TraceGenTests.EventPopulate -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.AddChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.AddChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.AddwChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.AddwChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.BitwiseChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.BitwiseChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.DivRemChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.DivRemChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.LtChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.LtChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.MulChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.MulChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.ShiftLeftChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.ShiftLeftChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.ShiftRightChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.ShiftRightChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.SubChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.SubChipTraceWitness -import SP1Clean.Proofs.TraceGenTests.SubwChipTraceVectors -import SP1Clean.Proofs.TraceGenTests.SubwChipTraceWitness diff --git a/SP1Clean/Proofs/Chips/ShiftLeftChip/Populate.lean b/SP1Clean/Proofs/Chips/ShiftLeftChip/Populate.lean index d86a97cd..c79ea4a7 100644 --- a/SP1Clean/Proofs/Chips/ShiftLeftChip/Populate.lean +++ b/SP1Clean/Proofs/Chips/ShiftLeftChip/Populate.lean @@ -13,8 +13,8 @@ variant flags are **not** computable from the row inputs — the prover supplies `"shift_left_flags"` `ProverHint` key (one-hot on real rows, absent/all-zero on padding); the committed `is_sllw_imm` is derived as `is_sllw · imm_c`. -Everything is computable (ℕ arithmetic on `.val`, cast back): `TraceGenTests` derives whole trace -rows from these closures and `native_decide`-checks them against SP1's real `generate_trace`. On +Everything is computable (ℕ arithmetic on `.val`, cast back): the `SP1CleanTest` trace-gen anchors +derive whole trace rows from these closures and check them against SP1's real `generate_trace`. On all-zero inputs + empty hint they reproduce SP1's `padded_row_template` (`v_01 = v_012 = v_0123 = 1`, everything else zero) — the `v_*` encodings are computed ungated because their constraints are ungated. -/ diff --git a/SP1Clean/Proofs/TraceGenTests/AddChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/AddChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/AddChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/AddChipTraceVectors.lean index 709ff786..30cdcd8c 100644 --- a/SP1Clean/Proofs/TraceGenTests/AddChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/AddChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/AddChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/AddChipTraceWitness.lean similarity index 93% rename from SP1Clean/Proofs/TraceGenTests/AddChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/AddChipTraceWitness.lean index 8eaa955d..635afe9c 100644 --- a/SP1Clean/Proofs/TraceGenTests/AddChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/AddChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.AddChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.AddChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.AddChipTraceVectors /-! # Whole-trace conformance anchor for `AddChip` — the circuit IS the trace generator. diff --git a/SP1Clean/Proofs/TraceGenTests/AddwChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/AddwChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/AddwChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/AddwChipTraceVectors.lean index 5e1ab794..b8940bf3 100644 --- a/SP1Clean/Proofs/TraceGenTests/AddwChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/AddwChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/AddwChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/AddwChipTraceWitness.lean similarity index 92% rename from SP1Clean/Proofs/TraceGenTests/AddwChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/AddwChipTraceWitness.lean index 285e32ee..40403737 100644 --- a/SP1Clean/Proofs/TraceGenTests/AddwChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/AddwChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.AddwChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.AddwChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.AddwChipTraceVectors /-! # Whole-trace conformance anchor for `AddwChip` — the first `ALUTypeReader` trace, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/BitwiseChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/BitwiseChipTraceVectors.lean index bfd28f3d..22c999ff 100644 --- a/SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/BitwiseChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/BitwiseChipTraceWitness.lean similarity index 93% rename from SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/BitwiseChipTraceWitness.lean index af31bda8..b5915a32 100644 --- a/SP1Clean/Proofs/TraceGenTests/BitwiseChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/BitwiseChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.BitwiseChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.BitwiseChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.BitwiseChipTraceVectors /-! # Whole-trace conformance anchor for `BitwiseChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/Conformance.lean b/SP1CleanTest/TraceGenTests/Conformance.lean similarity index 100% rename from SP1Clean/Proofs/TraceGenTests/Conformance.lean rename to SP1CleanTest/TraceGenTests/Conformance.lean diff --git a/SP1Clean/Proofs/TraceGenTests/DivRemChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/DivRemChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/DivRemChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/DivRemChipTraceVectors.lean index 9aa99581..c1f9dbb6 100644 --- a/SP1Clean/Proofs/TraceGenTests/DivRemChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/DivRemChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/DivRemChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/DivRemChipTraceWitness.lean similarity index 96% rename from SP1Clean/Proofs/TraceGenTests/DivRemChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/DivRemChipTraceWitness.lean index d02470e7..a7363784 100644 --- a/SP1Clean/Proofs/TraceGenTests/DivRemChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/DivRemChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Proofs.Chips.DivRemChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.DivRemChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.DivRemChipTraceVectors /-! # Whole-trace conformance anchor for `DivRemChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/EventPopulate.lean b/SP1CleanTest/TraceGenTests/EventPopulate.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/EventPopulate.lean rename to SP1CleanTest/TraceGenTests/EventPopulate.lean index 456d035d..3e17c7d6 100644 --- a/SP1Clean/Proofs/TraceGenTests/EventPopulate.lean +++ b/SP1CleanTest/TraceGenTests/EventPopulate.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.Conformance +import SP1CleanTest.TraceGenTests.Conformance /-! # Event → input-column mirrors for the trace-generation conformance layer. diff --git a/SP1Clean/Proofs/TraceGenTests/LtChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/LtChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/LtChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/LtChipTraceVectors.lean index 42039f76..3d728720 100644 --- a/SP1Clean/Proofs/TraceGenTests/LtChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/LtChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/LtChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/LtChipTraceWitness.lean similarity index 93% rename from SP1Clean/Proofs/TraceGenTests/LtChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/LtChipTraceWitness.lean index af57420f..d98c9cc8 100644 --- a/SP1Clean/Proofs/TraceGenTests/LtChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/LtChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.LtChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.LtChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.LtChipTraceVectors /-! # Whole-trace conformance anchor for `LtChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/MulChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/MulChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/MulChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/MulChipTraceVectors.lean index 219ef3c3..b4b63c04 100644 --- a/SP1Clean/Proofs/TraceGenTests/MulChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/MulChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/MulChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/MulChipTraceWitness.lean similarity index 94% rename from SP1Clean/Proofs/TraceGenTests/MulChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/MulChipTraceWitness.lean index c667bc5c..1b1d9450 100644 --- a/SP1Clean/Proofs/TraceGenTests/MulChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/MulChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.MulChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.MulChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.MulChipTraceVectors /-! # Whole-trace conformance anchor for `MulChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/ShiftLeftChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/ShiftLeftChipTraceVectors.lean index 50c6cefa..3d4c0db5 100644 --- a/SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/ShiftLeftChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/ShiftLeftChipTraceWitness.lean similarity index 95% rename from SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/ShiftLeftChipTraceWitness.lean index f6b99bbd..3e4a9b1c 100644 --- a/SP1Clean/Proofs/TraceGenTests/ShiftLeftChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/ShiftLeftChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Proofs.Chips.ShiftLeftChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.ShiftLeftChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.ShiftLeftChipTraceVectors /-! # Whole-trace conformance anchor for `ShiftLeftChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/ShiftRightChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/ShiftRightChipTraceVectors.lean index 45060fec..d9031777 100644 --- a/SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/ShiftRightChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/ShiftRightChipTraceWitness.lean similarity index 95% rename from SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/ShiftRightChipTraceWitness.lean index ebafcca4..6cc23dab 100644 --- a/SP1Clean/Proofs/TraceGenTests/ShiftRightChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/ShiftRightChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Proofs.Chips.ShiftRightChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.ShiftRightChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.ShiftRightChipTraceVectors /-! # Whole-trace conformance anchor for `ShiftRightChip` — hint-driven flags, unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/SubChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/SubChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/SubChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/SubChipTraceVectors.lean index 5d401c03..566b8498 100644 --- a/SP1Clean/Proofs/TraceGenTests/SubChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/SubChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/SubChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/SubChipTraceWitness.lean similarity index 91% rename from SP1Clean/Proofs/TraceGenTests/SubChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/SubChipTraceWitness.lean index 4cf4ece6..a209c4cd 100644 --- a/SP1Clean/Proofs/TraceGenTests/SubChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/SubChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.SubChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.SubChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.SubChipTraceVectors /-! # Whole-trace conformance anchor for `SubChip` — the circuit IS the trace generator. diff --git a/SP1Clean/Proofs/TraceGenTests/SubwChipTraceVectors.lean b/SP1CleanTest/TraceGenTests/SubwChipTraceVectors.lean similarity index 99% rename from SP1Clean/Proofs/TraceGenTests/SubwChipTraceVectors.lean rename to SP1CleanTest/TraceGenTests/SubwChipTraceVectors.lean index cf524512..a64804aa 100644 --- a/SP1Clean/Proofs/TraceGenTests/SubwChipTraceVectors.lean +++ b/SP1CleanTest/TraceGenTests/SubwChipTraceVectors.lean @@ -1,4 +1,4 @@ -import SP1Clean.Proofs.TraceGenTests.EventPopulate +import SP1CleanTest.TraceGenTests.EventPopulate /-! # AUTO-GENERATED — do not edit by hand. diff --git a/SP1Clean/Proofs/TraceGenTests/SubwChipTraceWitness.lean b/SP1CleanTest/TraceGenTests/SubwChipTraceWitness.lean similarity index 92% rename from SP1Clean/Proofs/TraceGenTests/SubwChipTraceWitness.lean rename to SP1CleanTest/TraceGenTests/SubwChipTraceWitness.lean index e8ca4437..e8b0318d 100644 --- a/SP1Clean/Proofs/TraceGenTests/SubwChipTraceWitness.lean +++ b/SP1CleanTest/TraceGenTests/SubwChipTraceWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Chips.SubwChip.Defs -import SP1Clean.Proofs.TraceGenTests.TraceGenerator -import SP1Clean.Proofs.TraceGenTests.SubwChipTraceVectors +import SP1CleanTest.TraceGenTests.TraceGenerator +import SP1CleanTest.TraceGenTests.SubwChipTraceVectors /-! # Whole-trace conformance anchor for `SubwChip` — a W-instruction trace, fully unmasked. diff --git a/SP1Clean/Proofs/TraceGenTests/TraceGenerator.lean b/SP1CleanTest/TraceGenTests/TraceGenerator.lean similarity index 100% rename from SP1Clean/Proofs/TraceGenTests/TraceGenerator.lean rename to SP1CleanTest/TraceGenTests/TraceGenerator.lean diff --git a/SP1Clean/Proofs/WitnessTests/AddOperationWitness.lean b/SP1CleanTest/WitnessTests/AddOperationWitness.lean similarity index 91% rename from SP1Clean/Proofs/WitnessTests/AddOperationWitness.lean rename to SP1CleanTest/WitnessTests/AddOperationWitness.lean index 31880f0d..f89646e2 100644 --- a/SP1Clean/Proofs/WitnessTests/AddOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/AddOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.AddOperation +import SP1CleanTest.WitnessTests.Vectors.AddOperation import SP1Clean.Native.Operations.AddOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `AddOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/AddrAddOperationWitness.lean b/SP1CleanTest/WitnessTests/AddrAddOperationWitness.lean similarity index 92% rename from SP1Clean/Proofs/WitnessTests/AddrAddOperationWitness.lean rename to SP1CleanTest/WitnessTests/AddrAddOperationWitness.lean index e9ebc82f..d8f87910 100644 --- a/SP1Clean/Proofs/WitnessTests/AddrAddOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/AddrAddOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.AddrAddOperation +import SP1CleanTest.WitnessTests.Vectors.AddrAddOperation import SP1Clean.Native.Operations.AddrAddOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `AddrAddOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/AddwOperationWitness.lean b/SP1CleanTest/WitnessTests/AddwOperationWitness.lean similarity index 92% rename from SP1Clean/Proofs/WitnessTests/AddwOperationWitness.lean rename to SP1CleanTest/WitnessTests/AddwOperationWitness.lean index ca18243b..592401b2 100644 --- a/SP1Clean/Proofs/WitnessTests/AddwOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/AddwOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.AddwOperation +import SP1CleanTest.WitnessTests.Vectors.AddwOperation import SP1Clean.Native.Operations.AddwOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `AddwOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/BitwiseOperationWitness.lean b/SP1CleanTest/WitnessTests/BitwiseOperationWitness.lean similarity index 93% rename from SP1Clean/Proofs/WitnessTests/BitwiseOperationWitness.lean rename to SP1CleanTest/WitnessTests/BitwiseOperationWitness.lean index 9d8963a8..70d2d028 100644 --- a/SP1Clean/Proofs/WitnessTests/BitwiseOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/BitwiseOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.BitwiseOperation +import SP1CleanTest.WitnessTests.Vectors.BitwiseOperation import SP1Clean.Native.Operations.BitwiseOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `BitwiseOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/IsEqualWordOperationWitness.lean b/SP1CleanTest/WitnessTests/IsEqualWordOperationWitness.lean similarity index 95% rename from SP1Clean/Proofs/WitnessTests/IsEqualWordOperationWitness.lean rename to SP1CleanTest/WitnessTests/IsEqualWordOperationWitness.lean index 278253e2..dc7fd535 100644 --- a/SP1Clean/Proofs/WitnessTests/IsEqualWordOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/IsEqualWordOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.IsEqualWordOperation +import SP1CleanTest.WitnessTests.Vectors.IsEqualWordOperation import SP1Clean.Native.Operations.IsEqualWordOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `IsEqualWordOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/IsZeroOperationWitness.lean b/SP1CleanTest/WitnessTests/IsZeroOperationWitness.lean similarity index 93% rename from SP1Clean/Proofs/WitnessTests/IsZeroOperationWitness.lean rename to SP1CleanTest/WitnessTests/IsZeroOperationWitness.lean index d7061470..b42ae61c 100644 --- a/SP1Clean/Proofs/WitnessTests/IsZeroOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/IsZeroOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.IsZeroOperation +import SP1CleanTest.WitnessTests.Vectors.IsZeroOperation import SP1Clean.Native.Operations.IsZeroOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `IsZeroOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/IsZeroWordOperationWitness.lean b/SP1CleanTest/WitnessTests/IsZeroWordOperationWitness.lean similarity index 94% rename from SP1Clean/Proofs/WitnessTests/IsZeroWordOperationWitness.lean rename to SP1CleanTest/WitnessTests/IsZeroWordOperationWitness.lean index 37946c1b..7825c1a8 100644 --- a/SP1Clean/Proofs/WitnessTests/IsZeroWordOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/IsZeroWordOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.IsZeroWordOperation +import SP1CleanTest.WitnessTests.Vectors.IsZeroWordOperation import SP1Clean.Native.Operations.IsZeroWordOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `IsZeroWordOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/LtOperationUnsignedWitness.lean b/SP1CleanTest/WitnessTests/LtOperationUnsignedWitness.lean similarity index 94% rename from SP1Clean/Proofs/WitnessTests/LtOperationUnsignedWitness.lean rename to SP1CleanTest/WitnessTests/LtOperationUnsignedWitness.lean index c70bd84d..51158d0c 100644 --- a/SP1Clean/Proofs/WitnessTests/LtOperationUnsignedWitness.lean +++ b/SP1CleanTest/WitnessTests/LtOperationUnsignedWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.LtOperationUnsigned +import SP1CleanTest.WitnessTests.Vectors.LtOperationUnsigned import SP1Clean.Native.Operations.LtOperationUnsigned.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `LtOperationUnsigned`. diff --git a/SP1Clean/Proofs/WitnessTests/MulOperationWitness.lean b/SP1CleanTest/WitnessTests/MulOperationWitness.lean similarity index 95% rename from SP1Clean/Proofs/WitnessTests/MulOperationWitness.lean rename to SP1CleanTest/WitnessTests/MulOperationWitness.lean index 6ed44575..7cda84dc 100644 --- a/SP1Clean/Proofs/WitnessTests/MulOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/MulOperationWitness.lean @@ -1,6 +1,6 @@ import SP1Clean.Native.Operations.MulOperation -import SP1Clean.Extracted.WitnessVectors.MulOperation -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.Vectors.MulOperation +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `MulOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/SubOperationWitness.lean b/SP1CleanTest/WitnessTests/SubOperationWitness.lean similarity index 91% rename from SP1Clean/Proofs/WitnessTests/SubOperationWitness.lean rename to SP1CleanTest/WitnessTests/SubOperationWitness.lean index 80b922e5..c26b2748 100644 --- a/SP1Clean/Proofs/WitnessTests/SubOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/SubOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.SubOperation +import SP1CleanTest.WitnessTests.Vectors.SubOperation import SP1Clean.Native.Operations.SubOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `SubOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/SubwOperationWitness.lean b/SP1CleanTest/WitnessTests/SubwOperationWitness.lean similarity index 92% rename from SP1Clean/Proofs/WitnessTests/SubwOperationWitness.lean rename to SP1CleanTest/WitnessTests/SubwOperationWitness.lean index 09bd35e7..4f902414 100644 --- a/SP1Clean/Proofs/WitnessTests/SubwOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/SubwOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.SubwOperation +import SP1CleanTest.WitnessTests.Vectors.SubwOperation import SP1Clean.Native.Operations.SubwOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `SubwOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/U16CompareOperationWitness.lean b/SP1CleanTest/WitnessTests/U16CompareOperationWitness.lean similarity index 92% rename from SP1Clean/Proofs/WitnessTests/U16CompareOperationWitness.lean rename to SP1CleanTest/WitnessTests/U16CompareOperationWitness.lean index 83321b07..a6fb9c3a 100644 --- a/SP1Clean/Proofs/WitnessTests/U16CompareOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/U16CompareOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.U16CompareOperation +import SP1CleanTest.WitnessTests.Vectors.U16CompareOperation import SP1Clean.Native.Operations.U16CompareOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `U16CompareOperation`. diff --git a/SP1Clean/Proofs/WitnessTests/U16MSBOperationWitness.lean b/SP1CleanTest/WitnessTests/U16MSBOperationWitness.lean similarity index 90% rename from SP1Clean/Proofs/WitnessTests/U16MSBOperationWitness.lean rename to SP1CleanTest/WitnessTests/U16MSBOperationWitness.lean index 9f1392a6..36fd363e 100644 --- a/SP1Clean/Proofs/WitnessTests/U16MSBOperationWitness.lean +++ b/SP1CleanTest/WitnessTests/U16MSBOperationWitness.lean @@ -1,6 +1,6 @@ -import SP1Clean.Extracted.WitnessVectors.U16MSBOperation +import SP1CleanTest.WitnessTests.Vectors.U16MSBOperation import SP1Clean.Native.Operations.U16MSBOperation.Populate -import SP1Clean.Proofs.WitnessTests.WitnessConformance +import SP1CleanTest.WitnessTests.WitnessConformance /-! # Witness-generation conformance anchor for `U16MSBOperation`. diff --git a/SP1Clean/Extracted/WitnessVectors/AddOperation.lean b/SP1CleanTest/WitnessTests/Vectors/AddOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/AddOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/AddOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/AddrAddOperation.lean b/SP1CleanTest/WitnessTests/Vectors/AddrAddOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/AddrAddOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/AddrAddOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/AddwOperation.lean b/SP1CleanTest/WitnessTests/Vectors/AddwOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/AddwOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/AddwOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/BitwiseOperation.lean b/SP1CleanTest/WitnessTests/Vectors/BitwiseOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/BitwiseOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/BitwiseOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/IsEqualWordOperation.lean b/SP1CleanTest/WitnessTests/Vectors/IsEqualWordOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/IsEqualWordOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/IsEqualWordOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/IsZeroOperation.lean b/SP1CleanTest/WitnessTests/Vectors/IsZeroOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/IsZeroOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/IsZeroOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/IsZeroWordOperation.lean b/SP1CleanTest/WitnessTests/Vectors/IsZeroWordOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/IsZeroWordOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/IsZeroWordOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/LtOperationUnsigned.lean b/SP1CleanTest/WitnessTests/Vectors/LtOperationUnsigned.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/LtOperationUnsigned.lean rename to SP1CleanTest/WitnessTests/Vectors/LtOperationUnsigned.lean diff --git a/SP1Clean/Extracted/WitnessVectors/MulOperation.lean b/SP1CleanTest/WitnessTests/Vectors/MulOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/MulOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/MulOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/SubOperation.lean b/SP1CleanTest/WitnessTests/Vectors/SubOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/SubOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/SubOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/SubwOperation.lean b/SP1CleanTest/WitnessTests/Vectors/SubwOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/SubwOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/SubwOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/U16CompareOperation.lean b/SP1CleanTest/WitnessTests/Vectors/U16CompareOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/U16CompareOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/U16CompareOperation.lean diff --git a/SP1Clean/Extracted/WitnessVectors/U16MSBOperation.lean b/SP1CleanTest/WitnessTests/Vectors/U16MSBOperation.lean similarity index 100% rename from SP1Clean/Extracted/WitnessVectors/U16MSBOperation.lean rename to SP1CleanTest/WitnessTests/Vectors/U16MSBOperation.lean diff --git a/SP1Clean/Proofs/WitnessTests/WitnessConformance.lean b/SP1CleanTest/WitnessTests/WitnessConformance.lean similarity index 100% rename from SP1Clean/Proofs/WitnessTests/WitnessConformance.lean rename to SP1CleanTest/WitnessTests/WitnessConformance.lean diff --git a/docs/agents/cleanup-backlog.md b/docs/agents/cleanup-backlog.md index 82030829..ee0c0eb6 100644 --- a/docs/agents/cleanup-backlog.md +++ b/docs/agents/cleanup-backlog.md @@ -91,25 +91,31 @@ actionable**: ### Syntactic (build-time, lib `moreLeanArgs`) -After `style.lambdaSyntax` / `style.dollarSyntax` (zero-fallout, enabled): `linter.oldObtain`, -`linter.style.longLine` (Model/Sail* already suppress it), `linter.style.cdot`. Each has real hits to triage -(fix or per-file suppress) across the heavy cores — enable one at a time, return to 0-warning, then the next. -Do **not** enable `linter.unusedSectionVars` / `linter.unusedSimpArgs` (structurally necessary in circuit -proofs — ~275 deliberate suppressions). Leave the auto-gen `linter.all false` (`Extracted/`, `TraceGenTests/`). -The scoping unit is the `SP1Proofs` lake library's `moreLeanArgs` (see `lakefile.toml` + the AGENTS.md "Linters" -note for the registration caveat). +**Enabled, all at zero violations** (across every hand-written core pillar — `SP1Math`/`SP1Model`/ +`SP1FormalModel`/`SP1Native`/`SP1Proofs`): `style.lambdaSyntax`, `style.dollarSyntax`, the four +deprecated-tactic guards `style.refine` / `style.cases` / `style.induction` / `style.admit`, and `oldObtain` / +`style.cdot`. + +**Remaining candidate:** `linter.style.longLine` (Model/Sail* already suppress it). It has real hits to triage +(reflow or per-file suppress) across the heavy cores — fallout concentrates in `Native/` (~817 lines >100) and +`FormalModel/` (~263); enable it alone, then return to 0-warning. Do **not** enable +`linter.unusedSectionVars` / `linter.unusedSimpArgs` (structurally necessary in circuit proofs — ~275 +deliberate suppressions). Leave the auto-gen `linter.all false` (`Extracted/`, `TraceGenTests/`). The scoping +unit is each core pillar's lake-library `moreLeanArgs` (five identical copies — keep in sync; the auto-gen +`SP1Extracted` library is deliberately excluded). See `lakefile.toml` + the AGENTS.md "Linters" note. ### Environment (`lake lint` / `sp1Lint`) -The curated set runs 11 of the Batteries `#lint` linters. Deferred / dropped, with rationale: +The curated set runs 13 of the Batteries/Mathlib `#lint` linters (the original 11 plus the Mathlib +`structureInType` / `deprecatedNoSince` hygiene checks, both at zero violations). Deferred / dropped, +with rationale: -- **`docBlame` / `docBlameThm` / `tacticDocs` / `deprecatedNoSince`** — doc-coverage linters; thousands of hits +- **`docBlame` / `docBlameThm` / `tacticDocs`** — doc-coverage linters; thousands of hits on undocumented private proof lemmas. Enable only after a deliberate documentation pass (not planned). - **`unusedArguments`** — *dropped, not deferred.* It flags **only** the project's uniform signature args (`{p} [Fact p.Prime] [Fact (2^17` (e.g. `Add`, `BitwiseU16`): Each new module's import goes into the root `SP1Clean.lean`. -## Trace Generation (`Proofs/TraceGenTests/`) +## Trace Generation (`SP1CleanTest/TraceGenTests/`) Partially deriving SP1's `generate_trace` from the circuit definition. -`Proofs/TraceGenTests/TraceGenerator.lean` (axiom-clean, +`SP1CleanTest/TraceGenTests/TraceGenerator.lean` (axiom-clean, generic): `circuitTraceRow` seeds Clean's `FlatOperation.dynamicWitnesses` env-threading fold with one row's input column values, runs `main`'s own witness closures in emission order, then evaluates `main`'s **output struct** under the final environment — the output layout *is* the row layout, so @@ -113,12 +113,16 @@ matrix equals, cell-for-cell, whole traces dumped from SP1's **real** `MachineAi column layout, and padding at once; the per-`populate` vector anchors check only the witness formulas pointwise. -`Proofs/TraceGenTests/` is kept **whole** in the `Proofs/` pillar: its auto-gen -`ChipTraceVectors.lean` batteries import the hand-written scaffold types (`EventPopulate` / -`Conformance`), so — unlike `Extracted/WitnessVectors/` — they are *not* split into the `Extracted/` -auto-gen pillar (that would invert the pillar layering). Its `Conformance.lean` carries the -concrete-prime scaffold; the layer is independent of `Proofs/WitnessTests/` — the older -per-operation `populate` conformance layer, kept as a temporary operation-level bridge. +This trace-gen layer and the older per-operation `populate`-conformance layer (`WitnessTests/`) both +live in the **top-level `SP1CleanTest` test library** (the `lake test` target), *not* the main +`SP1Clean/` tree. The reason is `native_decide`: the `TraceWitness.lean` / `Witness.lean` +anchors are the project's only uses of it (it trusts the whole compiler, adding +`Lean.ofReduceBool`/`Lean.trustCompiler`), so the entire layer — anchors, the `Conformance.lean` / +`EventPopulate.lean` concrete-prime scaffold, and the auto-gen `ChipTraceVectors.lean` / +`WitnessTests/Vectors/.lean` batteries — is quarantined in a library that imports `SP1Clean` but +is never imported by it. `scripts/check_no_native_decide.sh` keeps the main build clean. (Because the +vectors travel with the anchors, the old "can't split the vectors into `Extracted/` without inverting +the pillar layering" tension is moot — the whole test layer is one self-contained library.) Status (10 chips, 44 events + 20 padding rows each, **all unmasked** — every column of every covered chip is compared cell-for-cell): diff --git a/docs/snapshots/axiom-census.txt b/docs/snapshots/axiom-census.txt index 014f5f82..18614798 100644 --- a/docs/snapshots/axiom-census.txt +++ b/docs/snapshots/axiom-census.txt @@ -1,5 +1,5 @@ # Raw #print axioms census — generated by scripts/run_audit.sh -# sp1-lean 650100452a8f9b6fa644d62317078965b8469f73 · 2026-06-23 +# sp1-lean 7b6476af3985b131ca51b9d427d839182adf1caf · 2026-06-25 'SP1Clean.AddChip.soundness' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddChip.completeness' depends on axioms: [propext, Classical.choice, Quot.sound] 'SP1Clean.AddiChip.soundness' depends on axioms: [propext, Classical.choice, Quot.sound] diff --git a/lakefile.toml b/lakefile.toml index ca4adc12..9ce7ae90 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -10,9 +10,17 @@ moreLeanArgs = ["--tstack=400000"] # while keeping `SP1Clean.Proofs`. Needs a built project first (env linters read oleans); run after `lake build`. lintDriver = "sp1Lint" -# Built-in options only (no Mathlib linter options here — those require a Mathlib -# linter module to be imported before the package -D flags are validated, which a -# Clean-only file does not pull in; see the sp1-lean Step 0 finding). +# `lake test` driver — builds/elaborates the top-level `SP1CleanTest` library (the witness- and +# whole-trace conformance anchors). Those anchors are the project's ONLY `native_decide` (which trusts +# the whole Lean compiler via `Lean.ofReduceBool`/`Lean.trustCompiler`), so they are quarantined in +# this test library, OUT of the main `SP1Clean` library — kept `native_decide`-free by the +# `scripts/check_no_native_decide.sh` source gate (the CI `guards` job + the audit). Run with `lake test`. +testDriver = "SP1CleanTest" + +# Built-in options only — the Mathlib style linters are applied per-lib via `moreLeanArgs` +# (see the pillar libraries below), NOT here at package scope: that keeps the auto-gen +# `SP1Extracted` library cleanly out of the linter set. (Every hand-written pillar transitively +# imports Mathlib, so the `-D linter.*` options register fine when applied per-lib.) [leanOptions] pp.unicode.fun = true synthInstance.maxHeartbeats = 1000000 @@ -57,15 +65,37 @@ name = "SP1Clean" # Layer build-targets (select modules by submodule glob; module names stay `SP1Clean.*`, so these add # no import churn). They make each pillar independently buildable — `lake build SP1Extracted`, etc. # NOTE: isolation is by convention, not enforced — Lake does not forbid cross-layer imports within one -# package. The `Native`/`Proofs` pillar libraries are added once those directories land. +# package. +# +# Lib-scoped Mathlib style linters. Every hand-written core pillar (Math/Model/FormalModel/Native + +# Proofs/Faithful/Soundness) plus the `SP1CleanTest` test lib carries the SAME `moreLeanArgs` linter set +# below; they all transitively import Mathlib (Native via Clean), so the `-D linter.*` options are +# registered and these flags apply during `lake build SP1Clean` / `lake test`. All eight are at zero +# violations. `linter.style.longLine` is the remaining candidate (real fallout, concentrated in +# Native/FormalModel) — see `docs/agents/cleanup-backlog.md`. The auto-gen `SP1Extracted` library is +# deliberately left OUT (it carries per-file `set_option linter.all false` instead), as are the +# auto-gen `*Vectors` modules inside `SP1CleanTest` (same per-file suppression). KEEP THE SIX COPIES IN SYNC. [[lean_lib]] name = "SP1Math" globs = ["SP1Clean.Math.+"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] [[lean_lib]] name = "SP1Model" globs = ["SP1Clean.Model.+"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] +# Auto-gen pillar — NO linter args (the generated modules carry `set_option linter.all false`). [[lean_lib]] name = "SP1Extracted" globs = ["SP1Clean.Extracted.+"] @@ -73,18 +103,49 @@ globs = ["SP1Clean.Extracted.+"] [[lean_lib]] name = "SP1FormalModel" globs = ["SP1Clean.FormalModel.+"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] [[lean_lib]] name = "SP1Native" globs = ["SP1Clean.Native.+"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] # The proof pillar: the sound/complete proofs (Proofs/), the faithfulness anchors (Faithful/), -# and the whole-machine/trace layer (Soundness/). -# Lib-scoped Mathlib style linters: these hand-written modules all transitively import the linter -# registration (unlike the Clean-only Math/Model files — see the package `[leanOptions]` note above), -# so the `-D linter.style.*` flags are safe here. Start minimal (lambda/dollar are at zero violations); -# see `docs/agents/cleanup-backlog.md` for the next candidates (oldObtain / longLine / cdot). +# and the whole-machine/trace layer (Soundness/). Same linter set as the other core pillars above. [[lean_lib]] name = "SP1Proofs" globs = ["SP1Clean.Proofs.+", "SP1Clean.Faithful.+", "SP1Clean.Soundness.+"] -moreLeanArgs = ["-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] + +# The test pillar (the `lake test` driver, see `testDriver` above) — the witness-generation and +# whole-trace conformance anchors (`SP1CleanTest.{WitnessTests,TraceGenTests}.*`), which import the +# main `SP1Clean` library and check it against batteries dumped from SP1's real Rust prover. This is +# the ONLY library that may use `native_decide`; it is NOT imported by the umbrella `SP1Clean`, so the +# default `lake build` stays `native_decide`-free. Same syntactic linter set as the core pillars; the +# auto-gen `SP1CleanTest.WitnessTests.Vectors.*` / `*TraceVectors` modules self-suppress with their own +# `set_option linter.all false`. (The `lake lint` env-linter pass skips this lib too — `sp1Lint` keeps +# only `SP1Clean`-prefixed declarations, and `SP1CleanTest` is not such a prefix.) +[[lean_lib]] +name = "SP1CleanTest" +globs = ["SP1CleanTest.+"] +moreLeanArgs = [ + "-Dlinter.style.lambdaSyntax=true", "-Dlinter.style.dollarSyntax=true", + "-Dlinter.style.refine=true", "-Dlinter.style.cases=true", + "-Dlinter.style.induction=true", "-Dlinter.style.admit=true", + "-Dlinter.oldObtain=true", "-Dlinter.style.cdot=true", +] diff --git a/scripts/axiom_probe.lean b/scripts/axiom_probe.lean index b9a2729c..df8a7d81 100644 --- a/scripts/axiom_probe.lean +++ b/scripts/axiom_probe.lean @@ -1,4 +1,27 @@ import SP1Clean +import SP1CleanTest.WitnessTests.AddOperationWitness +import SP1CleanTest.WitnessTests.AddrAddOperationWitness +import SP1CleanTest.WitnessTests.AddwOperationWitness +import SP1CleanTest.WitnessTests.BitwiseOperationWitness +import SP1CleanTest.WitnessTests.IsEqualWordOperationWitness +import SP1CleanTest.WitnessTests.IsZeroOperationWitness +import SP1CleanTest.WitnessTests.IsZeroWordOperationWitness +import SP1CleanTest.WitnessTests.LtOperationUnsignedWitness +import SP1CleanTest.WitnessTests.MulOperationWitness +import SP1CleanTest.WitnessTests.SubOperationWitness +import SP1CleanTest.WitnessTests.SubwOperationWitness +import SP1CleanTest.WitnessTests.U16CompareOperationWitness +import SP1CleanTest.WitnessTests.U16MSBOperationWitness +import SP1CleanTest.TraceGenTests.AddChipTraceWitness +import SP1CleanTest.TraceGenTests.AddwChipTraceWitness +import SP1CleanTest.TraceGenTests.BitwiseChipTraceWitness +import SP1CleanTest.TraceGenTests.DivRemChipTraceWitness +import SP1CleanTest.TraceGenTests.LtChipTraceWitness +import SP1CleanTest.TraceGenTests.MulChipTraceWitness +import SP1CleanTest.TraceGenTests.ShiftLeftChipTraceWitness +import SP1CleanTest.TraceGenTests.ShiftRightChipTraceWitness +import SP1CleanTest.TraceGenTests.SubChipTraceWitness +import SP1CleanTest.TraceGenTests.SubwChipTraceWitness /-! Auto-generated by `scripts/gen_axiom_probe.py` — do not edit by hand. Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). -/ diff --git a/scripts/check_no_native_decide.sh b/scripts/check_no_native_decide.sh new file mode 100755 index 00000000..d48ee800 --- /dev/null +++ b/scripts/check_no_native_decide.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Gate: no `native_decide` anywhere in the main `SP1Clean/` library. +# +# `native_decide` discharges a goal by running compiled code, so it trusts the **entire Lean +# compiler** (it adds the `Lean.ofReduceBool` / `Lean.trustCompiler` axioms) rather than just the +# kernel — the same reason mathlib bans it from its main library. Every headline soundness theorem +# here must stay `[propext, Classical.choice, Quot.sound]`-clean, so `native_decide` is confined to +# the separate top-level `SP1CleanTest` test library (the witness/trace conformance anchors, run by +# `lake test`), which imports `SP1Clean` but is never imported by it. +# +# Scope is `SP1Clean/**/*.lean` only — the `SP1CleanTest/` test library is the sanctioned home, and +# `docs/`/`scripts/` legitimately *mention* the token, so scanning just the main source tree avoids +# those false positives. There are zero legitimate uses in `SP1Clean/`, so any case-insensitive hit +# fails the gate. +# +# Used by `scripts/run_audit.sh` (A2 gate) and `.github/workflows/lean_action_ci.yml` +# (the lightweight `guards` job). Exit 0 = clean, 1 = reintroduced. +# +# Usage: scripts/check_no_native_decide.sh (from the repo root) + +set -uo pipefail +cd "$(dirname "$0")/.." + +if grep -rniE 'native_decide' SP1Clean --include='*.lean'; then + echo "FAIL: native_decide found in SP1Clean/ (see lines above)." >&2 + echo " native_decide trusts the whole compiler (adds Lean.ofReduceBool) — it must not appear" >&2 + echo " in the main library. Move the check into the SP1CleanTest test library (run by" >&2 + echo " 'lake test'), or prove the goal in the kernel (decide / a real proof) instead." >&2 + exit 1 +fi +exit 0 diff --git a/scripts/gen_axiom_probe.py b/scripts/gen_axiom_probe.py index 00e75d47..0a62ee1b 100644 --- a/scripts/gen_axiom_probe.py +++ b/scripts/gen_axiom_probe.py @@ -23,8 +23,10 @@ ("SP1Clean/Proofs/Chips/*/Bridge.lean", r"theorem\s+(correct_\w+|\w*reaches_sail\w*)\b"), ("SP1Clean/Proofs/Chips/*/Bridge.lean", r"def\s+(kind)\b"), ("SP1Clean/Faithful/*.lean", r"theorem\s+(\w*faithful\w*)\b"), - ("SP1Clean/Proofs/WitnessTests/*.lean", r"theorem\s+(\w*conforms\w*)\b"), - ("SP1Clean/Proofs/TraceGenTests/*.lean", r"theorem\s+(\w*conforms\w*)\b"), + # The witness/trace conformance anchors now live in the separate `SP1CleanTest` test library (the + # native_decide quarantine); the census still probes them to disclose their ofReduceBool axioms. + ("SP1CleanTest/WitnessTests/*.lean", r"theorem\s+(\w*conforms\w*)\b"), + ("SP1CleanTest/TraceGenTests/*.lean", r"theorem\s+(\w*conforms\w*)\b"), ("SP1Clean/Soundness/GatedVm/*.lean", r"theorem\s+(exists_trail|chipRows_step_sound|state_trail_of_balance|" r"gatedExecution_of_specs_and_balance|gatedExecution_allChips|" @@ -88,21 +90,37 @@ def fqns_in(path: Path, decl_re: re.Pattern) -> list[str]: def main() -> None: fqns: list[str] = [] + extra_imports: list[str] = [] # modules outside the `SP1Clean` umbrella (e.g. `SP1CleanTest.*`) + seen_imports: set[str] = set() for glob, pattern in TARGETS: decl_re = re.compile(pattern) for path in sorted(ROOT.glob(glob)): - fqns.extend(fqns_in(path, decl_re)) + found = fqns_in(path, decl_re) + if not found: + continue + fqns.extend(found) + # `import SP1Clean` (the umbrella) covers every main-library declaration, but NOT the + # `SP1CleanTest` conformance anchors (that test library is not imported by the umbrella — + # it is the native_decide quarantine). Import each such module explicitly so its FQNs + # resolve in the probe. + rel = path.relative_to(ROOT) + if rel.parts[0] != "SP1Clean": + mod = ".".join(rel.with_suffix("").parts) + if mod not in seen_imports: + seen_imports.add(mod) + extra_imports.append(mod) # dedupe, keep order seen, ordered = set(), [] for f in fqns: if f not in seen: seen.add(f) ordered.append(f) - lines = ["import SP1Clean", - "", - "/-! Auto-generated by `scripts/gen_axiom_probe.py` — do not edit by hand.", - "Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). -/", - ""] + lines = ["import SP1Clean"] + lines += [f"import {m}" for m in extra_imports] + lines += ["", + "/-! Auto-generated by `scripts/gen_axiom_probe.py` — do not edit by hand.", + "Run via `lake env lean scripts/axiom_probe.lean` (see `scripts/run_audit.sh`). -/", + ""] lines += [f"#print axioms {f}" for f in ordered] OUT.write_text("\n".join(lines) + "\n") print(f"wrote {OUT.relative_to(ROOT)} with {len(ordered)} probes") diff --git a/scripts/run_audit.sh b/scripts/run_audit.sh index e14699dc..5488ab2e 100755 --- a/scripts/run_audit.sh +++ b/scripts/run_audit.sh @@ -49,8 +49,17 @@ if scripts/check_no_skipkerneltc.sh; then else echo "FAIL: skipKernelTC reintroduced (see above)"; fail=1 fi -echo "native_decide occurrences (disclosed, witness battery + Sail memory bridges):" -grep -rn 'native_decide' SP1Clean --include='*.lean' | wc -l + +echo +echo "== A2 native_decide guard (gate: none in SP1Clean/) ==" +if scripts/check_no_native_decide.sh; then + echo "PASS: no native_decide in the main library" +else + echo "FAIL: native_decide in SP1Clean/ (see above)"; fail=1 +fi +echo "native_decide occurrences in SP1CleanTest/ (disclosed — the witness/trace conformance battery," +echo "the sole sanctioned native_decide; adds Lean.ofReduceBool/trustCompiler, confined off the main library):" +grep -rn 'native_decide' SP1CleanTest --include='*.lean' | wc -l echo echo "== A3 axiom census (the authoritative oracle) ==" diff --git a/scripts/sp1Lint.lean b/scripts/sp1Lint.lean index af07dce7..5d424936 100644 --- a/scripts/sp1Lint.lean +++ b/scripts/sp1Lint.lean @@ -14,14 +14,16 @@ project in the two ways the stock `runLinter` exe cannot do: elaboration; they do nothing against these *environment* linters, which run post-import over the built environment. `nolints.json` / `@[nolint]` are the only env-linter suppressions. - INVARIANT: keep `getHandwrittenDecls` in sync with `update_extracted.py`'s three output dirs — - `Extracted/`, `Extracted/WitnessVectors/` (both under the `SP1Clean.Extracted` prefix), and - `Proofs/TraceGenTests/*Vectors.lean` (caught by the `endsWith "Vectors"` clause; no hand-written - module ends in "Vectors"). + INVARIANT: `getHandwrittenDecls` keeps only `SP1Clean.*` declarations, minus the auto-gen ones — + the `SP1Clean.Extracted` prefix (the constraint structs + the main-lib `Circuit/` forms) and any + module ending in "Vectors". The whole top-level **test** library `SP1CleanTest.*` (the witness/trace + conformance anchors + their `update_extracted.py`-written `…Vectors`/`Vectors.*` modules) is excluded + automatically, since `SP1Clean` is not a prefix of `SP1CleanTest` — so `lake lint` covers the main + library only (matching mathlib/batteries, which don't env-lint their test libs). * **Curated linter set.** We run a low-noise subset via `runOnly`, omitting the doc-coverage linters - (`docBlame`/`docBlameThm`/`tacticDocs`/`deprecatedNoSince`) that would swamp a proofs project. See - `curatedLinters` below; grow it deliberately (and see AGENTS.md § Linters). + (`docBlame`/`docBlameThm`/`tacticDocs`) that would swamp a proofs project. See `curatedLinters` + below; grow it deliberately (and see AGENTS.md § Linters). Wired as the package `lintDriver`, so `lake lint` runs it (after `lake build`, which the env linters need for oleans). Residue is recorded in `scripts/nolints.json`; `lake exe sp1Lint --update` snapshots the @@ -32,8 +34,10 @@ open Lean Core Elab Batteries.Tactic.Lint open System (FilePath) /-- The curated environment-linter set (short names, as registered by `@[env_linter]`). The -doc-coverage linters (`docBlame`, `docBlameThm`, `tacticDocs`, `deprecatedNoSince`) and `structureInType` -are intentionally omitted — see AGENTS.md § Linters. +doc-coverage linters (`docBlame`, `docBlameThm`, `tacticDocs`) are intentionally omitted — they would +swamp a proofs project — see AGENTS.md § Linters. `structureInType` and `deprecatedNoSince` (both +Mathlib `@[env_linter]`s, reached via the transitive Mathlib import) ARE included: low-noise hygiene +checks for structures that should be `Prop` and `@[deprecated]` attributes missing a `since` date. `unusedArguments` is also intentionally omitted: it flags only the project's *uniform* signature args (`{p} [Fact p.Prime] [Fact (2^17 < p)]` / `NeZero p` instance binders, and the `ProverData`/`ProverHint` @@ -44,7 +48,8 @@ genuinely-dead args. -/ def curatedLinters : List Name := [`dupNamespace, `defLemma, `unusedHavesSuffices, `checkUnivs, `checkType, `synTaut, `impossibleInstance, `nonClassInstance, - `simpNF, `simpVarHead, `simpComm] + `simpNF, `simpVarHead, `simpComm, + `structureInType, `deprecatedNoSince] /-- The list of `nolints` pulled from the `nolints.json` file (linter short-name × declaration). -/ abbrev NoLints := Array (Name × Name) diff --git a/update_extracted.py b/update_extracted.py index b506e3a2..f616f3a6 100644 --- a/update_extracted.py +++ b/update_extracted.py @@ -103,8 +103,8 @@ } # Chips with a **whole-trace** dumper in the `witness_vectors` binary (`--chip `) → an extra -# `Proofs/TraceGenTests/ChipTraceVectors.lean` (the event battery + the full padded matrix from -# SP1's real `generate_trace`) plus a hand-written `Proofs/TraceGenTests/ChipTraceWitness.lean` +# `SP1CleanTest/TraceGenTests/ChipTraceVectors.lean` (the event battery + the full padded matrix from +# SP1's real `generate_trace`) plus a hand-written `SP1CleanTest/TraceGenTests/ChipTraceWitness.lean` # anchor that re-derives every row from the chip's own circuit (`TraceGenerator.lean` + # `EventPopulate.lean`) and checks the matrices match. Value = (expected row width — a layout-drift # tripwire; the dumper also asserts it, reader kind — picks the dumped record type and the Lean @@ -126,7 +126,7 @@ # Ordered JSON keys of one dumped event, per reader kind = the field order of Lean's # `AluEventRec` / `AluTypeEventRec` / `AluEventOpRec` / `AluTypeOpEventRec` -# (`Proofs/TraceGenTests/EventPopulate.lean`). +# (`SP1CleanTest/TraceGenTests/EventPopulate.lean`). TRACE_EVENT_KEYS: Dict[str, List[str]] = { "RType": [ "clk", "pc", "a", "b", "c", "opA", "opB", "opC", @@ -225,8 +225,11 @@ # regenerated files so the extraction provenance is always recorded in-repo. SP1_PINNED_COMMIT = "9d249b8d4fb7d00156bf77f5d295d1dbcaaf4136" EXTRACTED_DIR = os.path.join("SP1Clean", "Extracted") -WITNESS_DIR = os.path.join("SP1Clean", "Extracted", "WitnessVectors") -TRACEGEN_DIR = os.path.join("SP1Clean", "Proofs", "TraceGenTests") +# The witness/trace conformance vectors are test-only data: they live in the top-level +# `SP1CleanTest` test library (built by `lake test`), not the main `SP1Clean` library — which is +# gated `native_decide`-free (the anchors that consume these vectors are the only `native_decide`). +WITNESS_DIR = os.path.join("SP1CleanTest", "WitnessTests", "Vectors") +TRACEGEN_DIR = os.path.join("SP1CleanTest", "TraceGenTests") COMMON_IMPORTS = """import SP1Clean.Math.Word import SP1Clean.Extracted.ExtractionDSL @@ -657,7 +660,7 @@ def render_witness_vectors(operation: str, data: dict) -> str: f"imperative code and cannot be symbolically extracted; these vectors instead tie the Lean\n" f"witness function to the Rust source by **conformance** (agreement on the sampled inputs —\n" f"edge cases + a seeded LCG — not an all-inputs proof). Each entry is `{tuple_desc}`. The\n" - f"check lives in `SP1Clean/WitnessTests/{operation}Witness.lean`. Regenerate with\n" + f"check lives in `SP1CleanTest/WitnessTests/{operation}Witness.lean`. Regenerate with\n" f"`SP1_DIR=… python3 update_extracted.py`. -/" ) return ( @@ -708,12 +711,12 @@ def render_trace_vectors(chip: str, width: int, kind: str, data: dict) -> str: f"`MachineAir::generate_trace` by `update_extracted.py` (the `witness_vectors` binary,\n" f"`--chip {chip}`): the deterministic event battery and the full padded {width}-column\n" f"row-major matrix (including SP1's zero padding rows). The anchor\n" - f"`SP1Clean/Proofs/TraceGenTests/{chip}ChipTraceWitness.lean` re-derives every row from the\n" + f"`SP1CleanTest/TraceGenTests/{chip}ChipTraceWitness.lean` re-derives every row from the\n" f"chip's own circuit (`TraceGenerator.lean`) and checks the matrices match. Regenerate\n" f"with `SP1_DIR=… python3 update_extracted.py`. -/" ) return ( - "import SP1Clean.Proofs.TraceGenTests.EventPopulate\n\n" + "import SP1CleanTest.TraceGenTests.EventPopulate\n\n" + doc + "\n\n" + "namespace SP1Clean.TraceGenTests\nopen SP1Clean\n\n" + LINTERS_OFF + "\n\n" From 85f343db7babc1ddd97cc3ce3f665ca62c70d73a Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Thu, 25 Jun 2026 17:03:16 -0500 Subject: [PATCH 6/8] Extract an additional helper lemma and clean up docs --- AGENTS.md | 9 +- SP1Clean.lean | 1 + SP1Clean/Proofs/Chips/AddChip/Formal.lean | 2 +- SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean | 11 +- .../Chips/DivRemChip/Completeness/Driver.lean | 11 +- SP1Clean/Proofs/Chips/LtChip/Formal.lean | 10 +- SP1Clean/Proofs/Chips/MulChip/Formal.lean | 11 +- .../Proofs/Chips/ShiftLeftChip/Formal.lean | 11 +- .../Proofs/Chips/ShiftRightChip/Formal.lean | 10 +- docs/agents/README.md | 1 - docs/agents/cleanup-backlog.md | 142 ------------------ docs/agents/proof-patterns.md | 34 ++++- docs/roadmap.md | 31 ++++ lakefile.toml | 2 +- 14 files changed, 72 insertions(+), 214 deletions(-) delete mode 100644 docs/agents/cleanup-backlog.md diff --git a/AGENTS.md b/AGENTS.md index db1ab6f2..07805dca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -207,7 +207,7 @@ These are the keepers from sp1-lean's "faithful sub-circuit composition" discipl deprecated-tactic guards `style.refine`/`style.cases`/`style.induction`/`style.admit`, and `oldObtain`/`style.cdot` — all at **zero** violations. They apply during the normal `lake build SP1Clean`. `linter.style.longLine` is the remaining candidate (real fallout, concentrated in Native/FormalModel; see - `docs/agents/cleanup-backlog.md`). The flags are scoped **per-lib** rather than at package level so the + `docs/roadmap.md` § "Cleanup / polish backlog"). The flags are scoped **per-lib** rather than at package level so the auto-gen `SP1Extracted` library stays out of the set (it carries per-file `set_option linter.all false`); keep the five identical `moreLeanArgs` copies in `lakefile.toml` in sync. (Every hand-written pillar transitively imports Mathlib — Native via Clean — so the `-D linter.*` options register fine; the older @@ -226,8 +226,9 @@ These are the keepers from sp1-lean's "faithful sub-circuit composition" discipl regenerates it; CI runs `lake lint` in the build job. Deliberately **dropped**: `docBlame`/`tacticDocs` (doc-coverage noise) and `unusedArguments` (flags only the uniform field-generic / `ProverData` signature args — all structural, and a fresh false-positive per new chip). - - Next candidates + the non-negotiable suppressions (`unusedSectionVars`/`unusedSimpArgs`, the auto-gen - `linter.all false`) are catalogued in `docs/agents/cleanup-backlog.md`. + - The next-candidate linter (`longLine`) is tracked in `docs/roadmap.md` § "Cleanup / polish backlog"; the + non-negotiable suppressions — `unusedSectionVars`/`unusedSimpArgs` (structurally necessary in circuit + proofs) and the auto-gen `linter.all false` — must stay. - Heavy `toBitVec64` rw chains are whnf-expensive — `set_option maxHeartbeats 2000000 in` (carry lemmas need up to `16000000`). - **Never `set_option (debug.)skipKernelTC`.** It bypasses the kernel's type-check re-run — the trust anchor @@ -287,8 +288,6 @@ after installing or toggling. - `docs/agents/porting-recipe.md` — step-by-step checklist to port a new chip from the Add/Bitwise template. - `docs/agents/extraction.md` — the constraint-extraction pipeline (compiler → Python → Lean DSL). - `docs/agents/mul-operation-learnings.md` — Mul-specific soundness/completeness pitfalls. -- `docs/agents/cleanup-backlog.md` — the prioritized follow-up cleanup/refactor backlog + the golfing/cleanup - skills catalog. - `docs/snapshots/compile-profile.md` — per-module wall-clock compile profile + worst offenders + common threads (point-in-time snapshot); re-run with `scripts/profile_compile.sh`. - `docs/snapshots/axiom-ledger.md` — machine-checked `#print axioms` inventory per theorem (point-in-time diff --git a/SP1Clean.lean b/SP1Clean.lean index ef9ceb7b..a77d5487 100644 --- a/SP1Clean.lean +++ b/SP1Clean.lean @@ -218,6 +218,7 @@ import SP1Clean.Faithful.U16toU8OperationSafe import SP1Clean.Faithful.U16toU8OperationUnsafe import SP1Clean.Faithful.UTypeChip import SP1Clean.Math.Bitwise +import SP1Clean.Math.EvalVec import SP1Clean.Math.Gate import SP1Clean.Model.ByteTable import SP1Clean.Model.Channels diff --git a/SP1Clean/Proofs/Chips/AddChip/Formal.lean b/SP1Clean/Proofs/Chips/AddChip/Formal.lean index 93057f96..aa191991 100644 --- a/SP1Clean/Proofs/Chips/AddChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/AddChip/Formal.lean @@ -5,7 +5,7 @@ import SP1Clean.FormalModel.Contracts.ChipAssumptions This is the canonical "template" chip for the porting recipe (`docs/agents/porting-recipe.md`). When golfing / cleaning proofs here or in any sibling chip, follow `docs/agents/proof-patterns.md` § -"Golf & cleanup discipline" (and the backlog in `docs/agents/cleanup-backlog.md`). -/ +"Golf & cleanup discipline". -/ namespace SP1Clean.AddChip diff --git a/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean b/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean index e4f22787..0d583d55 100644 --- a/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/BitwiseChip/Formal.lean @@ -1,4 +1,5 @@ import SP1Clean.Native.Chips.BitwiseChip.Defs +import SP1Clean.Math.EvalVec /-! # `SP1Clean.BitwiseChip` — contract: `Assumptions` / soundness / completeness / `circuit` @@ -131,16 +132,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe exact (BitwiseU16Operation.result_semantic _ hr (h_bw ⟨ha, hb, by rw [hopc]; exact val_lt_three (Or.inr (Or.inr rfl)), h_bin⟩)).2.2 hopc -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator — lets the witness -hint's `populate` operands (written `#v[env op_b_val[k]]` by the generator) be rewritten to the -`Vector.map eval …` form that `h_input` provides. -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 8000000 in theorem completeness : GeneralFormalCircuit.Completeness (ZMod p) main ProverAssumptions (fun _ _ _ => True) := by diff --git a/SP1Clean/Proofs/Chips/DivRemChip/Completeness/Driver.lean b/SP1Clean/Proofs/Chips/DivRemChip/Completeness/Driver.lean index 9ca4adad..e2ff9ace 100644 --- a/SP1Clean/Proofs/Chips/DivRemChip/Completeness/Driver.lean +++ b/SP1Clean/Proofs/Chips/DivRemChip/Completeness/Driver.lean @@ -6,6 +6,7 @@ import SP1Clean.Proofs.Chips.DivRemChip.Populate.Euclid import SP1Clean.Proofs.Chips.DivRemChip.Soundness import SP1Clean.Proofs.Chips.DivRemChip.Assembly import SP1Clean.Proofs.Chips.DivRemChip.Completeness.OwnComplete +import SP1Clean.Math.EvalVec import SP1Clean.Proofs.Chips.DivRemChip.Completeness.BytePulls import SP1Clean.Proofs.Chips.DivRemChip.Completeness.SubSpecs @@ -49,16 +50,6 @@ local instance : Fact (2 ^ 17 < p) := ⟨by have := Fact.out (p := 2 ^ 24 < p); attribute [local circuit_norm ↓ 100000] ProvableType.eval_fromElements -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator (folds the witness -closures' operands back to `Vector.map`; mirrors `ShiftRightChip.vec4_eval`). -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - - set_option maxHeartbeats 64000000 in /-- Completeness: `main`'s honest `Populate` witness closures (flags from the `"div_rem_flags"` hint) satisfy every constraint under `ProverAssumptions`. -/ diff --git a/SP1Clean/Proofs/Chips/LtChip/Formal.lean b/SP1Clean/Proofs/Chips/LtChip/Formal.lean index 427a9661..c5a441af 100644 --- a/SP1Clean/Proofs/Chips/LtChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/LtChip/Formal.lean @@ -1,4 +1,5 @@ import SP1Clean.Native.Chips.LtChip.Defs +import SP1Clean.Math.EvalVec /-! # `SP1Clean.LtChip` — contract: `Assumptions` / soundness / completeness / `circuit` -/ @@ -121,15 +122,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe simp only [resultWord, rv64_sltu_eq, Word.toBitVec64_toNat ha, Word.toBitVec64_toNat hb, toBitVec64_bitWord _ _ h_lt_spec] -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator (lets the witness -hint's `populate` operands, written `#v[env op_*_val[k]]` by the generator, be folded to `Vector.map`). -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 4000000 in theorem completeness : GeneralFormalCircuit.Completeness (ZMod p) main ProverAssumptions (fun _ _ _ => True) := by diff --git a/SP1Clean/Proofs/Chips/MulChip/Formal.lean b/SP1Clean/Proofs/Chips/MulChip/Formal.lean index 0b6cf65b..08e25d44 100644 --- a/SP1Clean/Proofs/Chips/MulChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/MulChip/Formal.lean @@ -1,4 +1,5 @@ import SP1Clean.Native.Chips.MulChip.Defs +import SP1Clean.Math.EvalVec /-! # `SP1Clean.MulChip` — `Assumptions` / soundness / completeness / `circuit` -/ @@ -117,16 +118,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe List.getElem_cons_succ, Nat.reduceLT, dif_pos] <;> first | exact ha0 | exact ha1 | exact ha2 | exact ha3 -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator (folds the witness -hint's `populate` operands, written `#v[env op_*_val[k]]` by the generator, back to `Vector.map`; -mirrors `LtChip.vec4_eval`). -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 40000000 in theorem completeness : GeneralFormalCircuit.Completeness (ZMod p) main ProverAssumptions (fun _ _ _ => True) := by diff --git a/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean b/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean index 37c019fd..f28d9080 100644 --- a/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/ShiftLeftChip/Formal.lean @@ -1,6 +1,7 @@ import SP1Clean.Proofs.Chips.ShiftLeftChip.Defs import SP1Clean.Proofs.Chips.ShiftLeftChip.Soundness.Sll import SP1Clean.Proofs.Chips.ShiftLeftChip.Soundness.Sllw +import SP1Clean.Math.EvalVec /-! # `SP1Clean.ShiftLeftChip` — contract: soundness / completeness / `circuit` @@ -81,16 +82,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe · exact (SoundSllw.soundness i₀ env input_var input h_input h_assumptions h_holds).1 hr · exact (SoundSll.soundness i₀ env input_var input h_input h_assumptions h_holds).2 -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator (folds the witness -closures' operands, written `#v[env op_b[k]]` in `main`, back to `Vector.map`; mirrors -`MulChip.vec4_eval`). -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 16000000 in /-- Completeness: `main`'s honest `Populate` witness closures (flags from the `"shift_left_flags"` hint) satisfy every constraint under `ProverAssumptions`. -/ diff --git a/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean b/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean index 80e48884..e6780a9f 100644 --- a/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean +++ b/SP1Clean/Proofs/Chips/ShiftRightChip/Formal.lean @@ -3,6 +3,7 @@ import SP1Clean.Proofs.Chips.ShiftRightChip.Soundness.Srl import SP1Clean.Proofs.Chips.ShiftRightChip.Soundness.Sra import SP1Clean.Proofs.Chips.ShiftRightChip.Soundness.Srlw import SP1Clean.Proofs.Chips.ShiftRightChip.Soundness.Sraw +import SP1Clean.Math.EvalVec /-! # `SP1Clean.ShiftRightChip` — contract: soundness / completeness / `circuit` @@ -85,15 +86,6 @@ theorem soundness : GeneralFormalCircuit.Soundness (ZMod p) main Assumptions Spe · exact (SoundSraw.soundness i₀ env input_var input h_input h_assumptions h_holds).1 hr · exact (SoundSrl.soundness i₀ env input_var input h_input h_assumptions h_holds).2 -set_option linter.unusedSectionVars false in -/-- A length-4 `#v` of pointwise evaluations is the `Vector.map` of the evaluator (folds the witness -closures' operands back to `Vector.map`; mirrors `ShiftLeftChip.vec4_eval`). -/ -private lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : - (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], - Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by - ext k hk - interval_cases k <;> simp [Vector.getElem_map] - set_option maxHeartbeats 16000000 in /-- Completeness: `main`'s honest `Populate` witness closures (flags from the `"shift_right_flags"` hint) satisfy every constraint under `ProverAssumptions`. -/ diff --git a/docs/agents/README.md b/docs/agents/README.md index 7143b2f5..bc04985a 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -9,7 +9,6 @@ reader-facing docs one level up (start with [`../architecture.md`](../architectu - [porting-recipe.md](porting-recipe.md) — step-by-step checklist to port a new chip from the Add/Bitwise template: create the four artifacts, wire the root import, verify build + axioms. - [proof-patterns.md](proof-patterns.md) — the witnessed-`FormalCircuit` soundness/completeness skeleton; `ZMod p` / `Word` / `circuit_proof_start` landmines; `maxHeartbeats` floors; the `ElaboratedCircuit` field-obligation recipe; the **Golf & cleanup discipline** section (how to golf/clean proofs safely). -- [cleanup-backlog.md](cleanup-backlog.md) — the prioritized follow-up cleanup/refactor backlog (deferred cross-file refactors, `/decompose-proof` + `/split-file` candidates, deferred heavy cores, next-up linters) + the golfing/cleanup skills catalog. - [lean-sail-notes.md](lean-sail-notes.md) — the Lean 4.28 environment: toolchain pins, why public Clean `main`, the GitHub-fetched Sail deps, the `lake update` toolchain-bump trap, and the Clean-main ↔ Batteries `Fin.foldl` collision and its fix. - [extraction.md](extraction.md) — the constraint-extraction pipeline (`sp1-constraint-compiler` → `update_extracted.py` → Lean) and the DSL contract. Point-in-time snapshots (regenerate before release) live under [`../snapshots/`](../snapshots/): diff --git a/docs/agents/cleanup-backlog.md b/docs/agents/cleanup-backlog.md deleted file mode 100644 index ee0c0eb6..00000000 --- a/docs/agents/cleanup-backlog.md +++ /dev/null @@ -1,142 +0,0 @@ -# Cleanup / refactor backlog - -Prioritized follow-up cleanup and refactor work, plus the golfing/cleanup skills available. Distilled from the -2026-06-22/23 proof-cleanup campaign (109 hand-written files golfed, −591 lines, axiom-cleanliness preserved). -For *how* to golf safely, see `proof-patterns.md` § "Golf & cleanup discipline". - -## Already landed (for reference) - -- The per-file golf sweep (109 files) + substrate hoists: `Word.isU64_four` (6 inline sites), Faithful - `val_16`/`bool_iff` dedup → `ChipTactics` (24 files), the `ShiftLeftChip/Core` loose-variable helper dedup. -- The Soundness Memory-layer helper consolidation (`two_lt_p_aux` / `valCast_pos_aux` / - `mem_memEventsFiltered_of_mem_eventsAt`). -- Linters: `style.lambdaSyntax` / `style.dollarSyntax` enabled on the `SP1Proofs` lake library (zero fallout). -- `lake lint` (environment linters) stood up — the `sp1Lint` lintDriver (`scripts/sp1Lint.lean`) runs 11 curated - Batteries `#lint` linters over the hand-written `SP1Clean.*` decls (auto-gen excluded by module-path filter), - with a 21-entry `scripts/nolints.json` baseline; CI runs it in the build job. -- The `val_N` private-lemma sweep — the scattered `(N : ZMod p).val = N` copies (`val_13`×3, `val_14`×3, - `val_16'`/`val_16''`×4, Shift-core `val_4/8/16/32/64`, Native `c65535_ne_zero`×2) consolidated onto the - `Math/Word.lean` family (now `val_2/4/8/13/14/16/32/64/256/32768/65535/65536`); plus a new `Math/Gate.lean` - binary-field/BitVec-compare API (`bool_val_le`, `val_ne`, `eq_one_iff_of_ite` + siblings, - `slt/ult_{true,false}_iff` over `BitVec w`) absorbing the Branch/Lt decision-proof private helpers. Also - fixed the 2 `ShiftRightMath.val_4/64` `simpNF` nolints. - -## Cross-file refactors — deferred with caveats - -### R1 — NeZero / Fact boilerplate (BLOCKED by design) - -`haveI : NeZero p := …` appears ~185× (Fact-derived) + ~14× (Prime-derived). A **global** `NeZero p` instance -derived from `Fact (2^17 < p)` is *deliberately avoided* — it would make the pervasive `omit [Fact (2^17 < p)] -in` clauses illegal (`Model/ByteTable.lean:84`). A `Fact p.Prime`-derived global instance *might* survive the -`omit` pattern (it depends on primality, not the magnitude), eliminating most of the boilerplate — but it goes -against the documented local-instance discipline and risks instance-resolution surprises. **Needs an owner -decision**, not a drive-by change. - -### R2 — eval-map global lemma (MARGINAL) - -A global `eval (map) v[i] = w[i]` lemma could replace the per-file quantified `ea`/`epc` helpers across ~36 -`Formal.lean` files. But the per-file helpers already capture the dedup *within* each file; a global lemma saves -only ~1 line/helper while re-churning 36 already-clean files + a foundational rebuild + form-variation risk. -Low ROI — skip unless a foundational pass is happening anyway. - -## `/decompose-proof` candidates (long proof bodies, >50 lines) - -- `ShiftLeftChip/Formal.lean` + `ShiftRightChip/Formal.lean` — `completeness` (~130–180 lines). -- `LoadHalfChip/Formal.lean` — the 4-way `h_sel_lt` offset-selection case-bash (near-verbatim across soundness - + completeness). -- `BranchChip/Formal.lean` — `soundness` / `completeness` (per-column `env.get` plumbing). - -Caveat: several are perf-tuned heavy files — decompose with care and watch elaboration time. - -## `/split-file` candidates - -None currently exceed 1500 lines after cleanup. `Proofs/Chips/ShiftRightChip/Core.lean` (2177) is the largest -single file but is kept whole on purpose (the dispatch proof is one entangled unit). Note only. - -## Heavy cores — deferred (poor risk/reward) - -Left untouched in the campaign; revisit only with a specific goal: -- `ShiftRight/Core` (2177), `ShiftRight/Dispatch` (1591), the rest of `ShiftLeft/Core` — bv_decide/kernel-fragile. -- `Model/SailMemory` (1270) — foundational, full-rebuild cost. -- **All DivRem files** — fresh perf work landed via the `div-rem-perf` merge; leave alone. - -## Cross-file dedup candidates (re-examine before acting) - -- **SailState-staging bridge preamble** (`hpc_get` / `key` / `hsp_config`) recurs across ~10 store/jal/load - `Bridge.lean` files → a shared lemma. **Re-examine first** — upstream #101/#102 rewrote several bridges in the - 2026-06-23 merge, so the pre-merge shape may have changed. -- Mul `soundness` 5-branch tail — byte-identical block ×5, but a clean factor needs an intractable ~40-line - `resultWord` struct type; **skip** (the duplication is cheaper than the abstraction). -- **`vec4_eval`** — the `#v[eval e v[0],…,v[3]] = Vector.map (eval e) v` lemma recurs ×6 across chip - `Formal.lean` (Mul/Lt/ShiftRight/ShiftLeft/Bitwise + DivRem `Completeness/Driver`). Clean Math candidate - (Clean-DSL-generic), but **2 of the 6 are perf-sensitive** (DivRem Driver, Shift Formals) — fold into a - Shift/DivRem pass rather than a standalone touch. Not done in the val_N sweep. - -## Performance — what's actually actionable (investigated 2026-06-24) - -The "safe mechanical" perf levers were swept over the touched + fair-game heavy files and found **nothing -actionable**: -- **`maxHeartbeats` tightening is the wrong lever.** The heavy Shift/DivRem proofs are **kernel / - type-checking-bound, not heartbeat-bound** — measured `ShiftLeftChip/Soundness/Sll.soundness` at **72** - elaboration heartbeats against its 4M ceiling. The ceilings are non-binding safety margins; lowering them has - no wall-clock effect and only risks a future spike. Leave them. -- No `simp_all` / heavy `simp [circuit_norm]` in the touched files (already targeted), and none carry - `unusedSimpArgs` suppressions, so the simp-tightening / redundant-arg levers are empty there too. -- The **only** real prize is the **Shift tail-dedup** (extract the byte-identical `cpuA/msb*/aluA` tail shared - across the 6 Shift conjuncts, mirroring `DivRemChip/Soundness/Tail.lean`) — a structural change, deferred: - it overlaps the recently-golfed Shift soundness files and is a multi-hour effort. That, plus the term-intrinsic - DivRem cost (off-limits), is where the build time genuinely lives. - -## Linters — next candidates (deferred; real fallout) - -### Syntactic (build-time, lib `moreLeanArgs`) - -**Enabled, all at zero violations** (across every hand-written core pillar — `SP1Math`/`SP1Model`/ -`SP1FormalModel`/`SP1Native`/`SP1Proofs`): `style.lambdaSyntax`, `style.dollarSyntax`, the four -deprecated-tactic guards `style.refine` / `style.cases` / `style.induction` / `style.admit`, and `oldObtain` / -`style.cdot`. - -**Remaining candidate:** `linter.style.longLine` (Model/Sail* already suppress it). It has real hits to triage -(reflow or per-file suppress) across the heavy cores — fallout concentrates in `Native/` (~817 lines >100) and -`FormalModel/` (~263); enable it alone, then return to 0-warning. Do **not** enable -`linter.unusedSectionVars` / `linter.unusedSimpArgs` (structurally necessary in circuit proofs — ~275 -deliberate suppressions). Leave the auto-gen `linter.all false` (`Extracted/`, `TraceGenTests/`). The scoping -unit is each core pillar's lake-library `moreLeanArgs` (five identical copies — keep in sync; the auto-gen -`SP1Extracted` library is deliberately excluded). See `lakefile.toml` + the AGENTS.md "Linters" note. - -### Environment (`lake lint` / `sp1Lint`) - -The curated set runs 13 of the Batteries/Mathlib `#lint` linters (the original 11 plus the Mathlib -`structureInType` / `deprecatedNoSince` hygiene checks, both at zero violations). Deferred / dropped, -with rationale: - -- **`docBlame` / `docBlameThm` / `tacticDocs`** — doc-coverage linters; thousands of hits - on undocumented private proof lemmas. Enable only after a deliberate documentation pass (not planned). -- **`unusedArguments`** — *dropped, not deferred.* It flags **only** the project's uniform signature args - (`{p} [Fact p.Prime] [Fact (2^17` ha run the full `lake build` after a merge even when `git status` shows no conflicts (`Proofs/Chips/BitwiseChip/ Bridge.lean` hit this when upstream #101's immediate-type bridges met a golfed copy). +**Available cleanup skills:** +- **`/cleanup`** — the per-file 7-phase workflow (style audit → per-decl golf → simplify → verify). Best for a + handful of named files. +- **`/cleanup-all`** — the orchestrator marathon (dispatches per-batch workers across the whole tree). Best for a + project-wide sweep; honor the repo guardrails (auto-gen exclusion, axiom-clean, heavy-core caution). +- **`/decompose-proof`** — break one long proof into named sub-lemmas. +- **`/split-file`** — split an over-long file along namespace/section seams. +- **`Skill(simplify)`** — a holistic reuse/altitude review pass on a file (invoked inside `/cleanup` Phase 6.5). + ## "emitted = projection": `Lookups_eq_emitted` (proving a trace projection IS the emission) Goal: prove a hand-written trace-level projection (`Soundness/*Consistency.lean`'s `stateLookups`, diff --git a/docs/roadmap.md b/docs/roadmap.md index 9d014fd4..2f7474e8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -318,3 +318,34 @@ and retires the K6 sampled-conformance reliance for those chips. - The witness-vector battery and `Extracted/` currency are re-checked by `scripts/run_audit.sh` §A4 + CI `lake build`; the `SP1_PINNED_COMMIT` assertion in `update_extracted.py` keeps extraction provenance explicit. Remaining: a CI job that re-extracts and diffs per-PR. + +--- + +## Cleanup / polish backlog (non-blocking) + +Deferred quality/perf TODOs — none gate the VM theorem; pick up opportunistically. The *how-to-golf-safely* +lessons (heavy-core caution, kernel-safe dedup, the `maxHeartbeats`-is-the-wrong-lever finding, the available +`/cleanup` skills) live in `docs/agents/proof-patterns.md` § "Compile-time / performance landmines" + "Golf & +cleanup discipline". + +- **`linter.style.longLine`** — the one remaining syntactic linter not yet enabled (it's the last candidate + noted in AGENTS.md § Linters). ~1080 lines exceed 100 chars (`Native/` ~817, `FormalModel/` ~263). Enable it + alone on the core pillar lake libraries, then reflow or per-file-suppress back to 0 warnings. Heavy, mechanical. +- **Shift soundness tail-dedup** — the real build-time prize. Extract the byte-identical `cpuA/msb*/aluA` + requirements tail shared across the 6 Shift soundness conjuncts into a `requirements_holds`/`SpecObligation` + helper, mirroring `Proofs/Chips/DivRemChip/Soundness/Tail.lean` (recipe: proof-patterns § "Shared-tail + dedup"). Structural, multi-hour, overlaps the recently-golfed Shift soundness files — do it as a dedicated + pass, not a drive-by. +- **`/decompose-proof` candidates** — long proof bodies worth splitting into named sub-lemmas: + `ShiftLeftChip`/`ShiftRightChip` `Formal.lean` `completeness` (~123/~180 lines), `LoadHalfChip`'s 4-way + `h_sel_lt` offset-selection case-bash (near-verbatim across soundness + completeness), `BranchChip` + `soundness`/`completeness` (~156/~290 lines of per-column `env.get` plumbing). Several are perf-tuned — + decompose with care and watch elaboration time. +- **SailState-staging bridge preamble** — the `hpc_get`/`key`/`hsp_config` preamble recurs across ~10 + store/jal/load `Bridge.lean` files → a shared lemma. **Re-examine the shape first** — upstream #101/#102 + rewrote several bridges in the 2026-06-23 merge, so the pre-merge duplication may have shifted. +- **Namespace-isolate the auto-gen (linter hardening, Option B)** — the `sp1Lint` exclusion is a *soft* + module-path filter. A *hard* boundary would relocate all auto-gen to a separate root namespace + `SP1Extracted.*` so the stock `runLinter` excludes it by construction (no custom filter). Cost: ~87 module + renames + import-line edits + `update_extracted.py` writer paths + lakefile globs. Not worth it for linting + alone; reconsider only if a hard auto-gen/hand-written namespace split is wanted for other reasons. diff --git a/lakefile.toml b/lakefile.toml index 9ce7ae90..05a34424 100644 --- a/lakefile.toml +++ b/lakefile.toml @@ -72,7 +72,7 @@ name = "SP1Clean" # below; they all transitively import Mathlib (Native via Clean), so the `-D linter.*` options are # registered and these flags apply during `lake build SP1Clean` / `lake test`. All eight are at zero # violations. `linter.style.longLine` is the remaining candidate (real fallout, concentrated in -# Native/FormalModel) — see `docs/agents/cleanup-backlog.md`. The auto-gen `SP1Extracted` library is +# Native/FormalModel) — see `docs/roadmap.md` § "Cleanup / polish backlog". The auto-gen `SP1Extracted` library is # deliberately left OUT (it carries per-file `set_option linter.all false` instead), as are the # auto-gen `*Vectors` modules inside `SP1CleanTest` (same per-file suppression). KEEP THE SIX COPIES IN SYNC. [[lean_lib]] From 212858bd4c1111378ad1398bfb6a3ccbf8b34a11 Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Thu, 25 Jun 2026 17:05:09 -0500 Subject: [PATCH 7/8] stray file --- SP1Clean/Math/EvalVec.lean | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 SP1Clean/Math/EvalVec.lean diff --git a/SP1Clean/Math/EvalVec.lean b/SP1Clean/Math/EvalVec.lean new file mode 100644 index 00000000..308711b8 --- /dev/null +++ b/SP1Clean/Math/EvalVec.lean @@ -0,0 +1,24 @@ +import Clean.Circuit.Basic +import Clean.Utils.Field +import Mathlib.Tactic.IntervalCases + +/-! # `vec4_eval` — folding a length-4 `#v` of pointwise evaluations back to `Vector.map` + +The constraint extractor writes a witness hint's `populate` operands as an explicit length-4 vector +literal `#v[eval e v[0], eval e v[1], eval e v[2], eval e v[3]]`, whereas `h_input` provides them in +`Vector.map (eval e) v` form. This one lemma bridges the two; it recurs across the Mul / Lt / Bitwise +/ ShiftLeft / ShiftRight chip `Formal` proofs and the DivRem completeness `Driver`, so it lives here +in `Math` rather than being re-declared per file. Generic over a prime field `ZMod p` +(`Expression.eval` needs the `Field` instance, which the primality `Fact` supplies). -/ + +namespace SP1Clean + +variable {p : ℕ} [Fact p.Prime] + +lemma vec4_eval (e : Environment (ZMod p)) (v : Vector (Expression (ZMod p)) 4) : + (#v[Expression.eval e v[0], Expression.eval e v[1], Expression.eval e v[2], + Expression.eval e v[3]] : Vector (ZMod p) 4) = Vector.map (Expression.eval e) v := by + ext k hk + interval_cases k <;> simp [Vector.getElem_map] + +end SP1Clean From 6213f06334a9bbbba51b25ed23a13e68ec8348ff Mon Sep 17 00:00:00 2001 From: Devon Tuma Date: Thu, 25 Jun 2026 19:49:53 -0500 Subject: [PATCH 8/8] remove redundant duplicate files to save cache space --- .github/workflows/lean_action_ci.yml | 37 ++++++++++------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/.github/workflows/lean_action_ci.yml b/.github/workflows/lean_action_ci.yml index ffd6c88e..0c08132b 100644 --- a/.github/workflows/lean_action_ci.yml +++ b/.github/workflows/lean_action_ci.yml @@ -33,28 +33,26 @@ jobs: - uses: actions/checkout@v4 # Test that the project builds without failure # Note: defaults to ignore warnings and deprecations, maybe change eventually + # lean-action's `use-github-cache` caches the WHOLE `.lake` (project build + deps) under a + # commit-keyed key (`lake-----`) with a manifest-prefix + # `restore-keys` fallback. So this build is incremental from the most recent prior commit, and the + # exact-`` cache it saves is what the `lint`/`test` jobs restore (an exact hit → `lake build` + # there just replays, no recompilation). No separate per-commit project cache is needed. - uses: leanprover/lean-action@v1 with: build: "true" test: "false" lint: "false" use-github-cache: "true" - # Hand the freshly-built project oleans to the `lint` job via a commit-keyed cache. - # lean-action's own `use-github-cache` covers dependencies only (keyed on the manifest), not the - # per-commit project build — so without this the lint job would rebuild the whole project. - - name: Cache build outputs for the lint job - uses: actions/cache/save@v4 - with: - path: .lake/build - key: lake-build-${{ github.sha }} lint: # Environment linters (`lake lint` → the `sp1Lint` lintDriver; a curated subset of the Batteries # `#lint` linters over the hand-written `SP1Clean.*` declarations; see AGENTS.md § Linters). Split # into its own job so it is clearly independent in the checks list. `needs: build` so it runs after - # the build has published the commit-keyed olean cache; restoring it lets `lake lint` read the - # already-built project (the lean-action step then just replays). The restore is best-effort — if it - # misses, lean-action rebuilds the project, so the job stays correct either way. + # the build job has saved its exact-`` `.lake` cache; lean-action below restores that on an exact + # hit, so `lake build` just replays the already-built project (no recompilation) before `lake lint`. + # If that cache were ever absent, lean-action's manifest-prefix `restore-keys` fallback does a correct + # incremental build instead, so the job stays correct either way. needs: build runs-on: [ @@ -67,11 +65,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Restore build outputs from the build job - uses: actions/cache/restore@v4 - with: - path: .lake/build - key: lake-build-${{ github.sha }} - uses: leanprover/lean-action@v1 with: build: "true" @@ -87,9 +80,10 @@ jobs: # These live in their own library — the ONLY place `native_decide` is allowed — so they are gated # here, separately from the `native_decide`-free main build. Split into its own job (in parallel with # `lint`) so the trusted-compiler checks are clearly independent in the checks list. `needs: build` - # so it runs after the build has published the commit-keyed olean cache; restoring it lets `lake test` - # reuse the already-built main library and only elaborate the test modules. The restore is - # best-effort — if it misses, lean-action rebuilds the main library, so the job stays correct either way. + # so it runs after the build job has saved its exact-`` `.lake` cache; lean-action below restores + # that on an exact hit, so `lake build` replays the already-built main library (no recompilation) and + # `lake test` only elaborates the test modules. If that cache were ever absent, lean-action's + # manifest-prefix `restore-keys` fallback does a correct incremental build instead. needs: build runs-on: [ @@ -102,11 +96,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Restore build outputs from the build job - uses: actions/cache/restore@v4 - with: - path: .lake/build - key: lake-build-${{ github.sha }} - uses: leanprover/lean-action@v1 with: build: "true"