ワンクリックで
lean-uint-bitvec
Use when Lean 4 proofs involve UInt8/UInt16/UInt32/BitVec conversions, bv_decide, or bridging between numeric types and Nat.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when Lean 4 proofs involve UInt8/UInt16/UInt32/BitVec conversions, bv_decide, or bridging between numeric types and Nat.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Standard claim/branch/verify/publish workflow for pod agent sessions. Read this skill at the start of any feature, review, summarize, or meditate session.
Produce before/after native speed-vs-ratio comparison graphs (against the other-language curves), post them to the PR, and show them to Kim BEFORE merging. PROACTIVELY REQUIRED for any lean-zip performance PR (perf:/runtime/throughput change to compress or decode): the moment such a PR goes green, invoke this YOURSELF without being asked — generating and posting the graphs is part of finishing the PR, never a step that waits for Kim to request it. Do not report the PR as done, and do not merely offer to "produce them if she wants", until the graphs are generated and posted; only the merge itself waits for her go-ahead. Most interesting for compression changes.
Use when fixing merge conflicts on agent PRs, rebasing stale branches, or deciding whether to salvage vs. redo a PR. Also use when a rebase/fix-PR plan issue is claimed.
Use when adding a new concrete-shape closed-form rung to a checksum ladder — Adler-32, CRC32, or any future checksum with a Spec/Native split (e.g. XxHash). Covers the three-part Spec identity → Native bridge → public wrapper template, the hypothesis-bearing invariant pattern, `@[simp]` and visibility discipline, and the boundary where the template stops applying (non-Nat algebra).
Use when writing a test that must match an error message thrown by lean-zip — bomb-limit tests, malformed-archive assertThrows, CD/LH consistency assertions, or any `.toBaseIO` + `msg.contains` block. Tabulates the error-substring families so you pick the right match string the first time.
Use when landing a PR that closes a numbered item in `SECURITY_INVENTORY.md` *Recommended policy* or *Missing work*, or when threading a new parameter through public APIs with a deferred default flip. Covers the *Executed past-tense one-liner* phrasing and when to use the half-closed two-step.
| name | lean-uint-bitvec |
| description | Use when Lean 4 proofs involve UInt8/UInt16/UInt32/BitVec conversions, bv_decide, or bridging between numeric types and Nat. |
| allowed-tools | Read, Bash, Grep |
bv_decide for UInt32/BitVecEffective for bitvector reasoning. Proved CRC linearity (crcBit_xor_high) and the
8-fold split (crcBits8_split) each in one line.
Caveat: fails when expressions contain UInt32.ofNat x.toNat (abstracted as opaque).
Use generalize to unify shared subexpressions first (see below).
bv_decideUse bv_decide as the final step when the goal is purely bit-level on
UInt8/16/32/64/BitVec:
readUInt32LE (writeUInt32LE val) 0 = val —
simp only to normalize getElem!/set! first, then bv_decide.((v &&& 0xFF).toUInt8).toUInt16 ||| (((v >>> 8) &&& 0xFF).toUInt8).toUInt16 <<< 8 = vgeneralize: for ByteArray indexing, generalize data[pos].toUInt32 = x
to abstract array access into a BitVec variable, then bv_decide.bv_decide vs bv_omega vs decide_cbvbv_decide handles BitVec/UInt goals via SAT solving — fast for symbolic reasoning,
handles bitwise AND/OR/XOR/shiftbv_omega extends omega with some BitVec support but cannot reason about
bitwise AND/OR/XOR — it only handles linear arithmetic. Use bv_decide instead
when the goal involves &&&, |||, ^^^, or similar bitwise operations.decide_cbv uses kernel evaluation — works for concrete decidable propositions but
fails on large arrays (e.g., 288-element Huffman tables)decide with set_option maxHeartbeats 1600000
instead of decide_cbvCommon pattern — UInt32 match catch-all elimination:
When split on a UInt32 match creates a catch-all case with hypotheses like
¬(expr &&& 3 = 0), ¬(expr &&& 3 = 1), ¬(expr &&& 3 = 2), ¬(expr &&& 3 = 3),
use exfalso; bv_decide to close the goal. The SAT solver recognizes that
x &&& 3 can only produce values 0-3.
Common pattern — if x == 0 then a else b = c on UInt32:
prefer bv_decide. It handles the case-split inline without manual
by_cases. Precedent: the raw_eq helper inside crc32_append at
Zip/Native/Crc32.lean folds
the post-init zero-check into a single XOR via one bv_decide.
bv_decideWhen bv_decide fails on UInt32.ofNat byte.toNat, rewrite to ⟨byte.toBitVec.setWidth 32⟩
using BitVec.ofNat_toNat. Then use show + congr 1 to expose the inner BitVec:
rw [UInt32_ofNat_UInt8_toNat] -- rewrites via BitVec.ofNat_toNat
show UInt32.ofBitVec (... bitvec expr ...) = UInt32.ofBitVec (...)
congr 1; bv_decide
testBit goals over a variable indexFor a goal like (reverse16 x).toNat.testBit j = x.toNat.testBit (15 - j) with
j : Nat, hj : j < 16 (bit permutations, swap networks, masks):
Nat.testBit → BitVec.getLsbD so bv_decide can see it.
For any UInt{8,16,32,64} value, x.toNat = x.toBitVec.toNat holds by rfl,
and BitVec.testBit_toNat : v.toNat.testBit i = v.getLsbD i is also rfl:
rw [show (reverse16 x).toNat = (reverse16 x).toBitVec.toNat from rfl,
show x.toNat = x.toBitVec.toNat from rfl,
BitVec.testBit_toNat, BitVec.testBit_toNat]
simp only [reverse16] -- unfold the def so bv_decide sees the bit ops
interval_cases/omega-split are
Mathlib and NOT available here. Use an explicit exhaustive match on the
value + its bound, with an arithmetic-contradiction catch-all:
match j, hj with
| 0, _ | 1, _ | 2, _ | 3, _ | 4, _ | 5, _ | 6, _ | 7, _
| 8, _ | 9, _ | 10, _ | 11, _ | 12, _ | 13, _ | 14, _ | 15, _ => bv_decide
| _ + 16, h => omega
Each concrete j makes getLsbD j / 15 - j literal, which bv_decide
closes; omega discharges the impossible _ + 16 < 16 arm.Likewise norm_num is unavailable: prove 2^a < 2^b with
Nat.pow_lt_pow_right (by omega) (by omega) and n < 2^64-style bounds with
Nat.lt_of_le_of_lt h (by decide) (the kernel evaluates 2^64 fine).
generalize Before bv_decide for Shared SubexpressionsWhen bv_decide fails with "spurious counterexample" because it abstracts the same
expression (e.g., data[pos]) as multiple opaque variables, use
generalize data[pos].toUInt32 = x first to unify them into a single variable.
A stronger variant of the same pitfall: when the same GetElem expression arr[i]
appears twice with different proof-of-bounds terms — one side has the
theorem-signature's named bounds proof, the other gets a term of shape hidx ▸ hlt
produced by getElem_congr_idx — bv_decide's abstraction pass treats them as two
opaque variables and returns a spurious counterexample with conflicting assignments.
Proof irrelevance is not applied during abstraction; the two proof terms have to be
collapsed manually. First align the indices with getElem_congr_idx, then
generalize the unified expression to a fresh variable, then bv_decide:
rw [getElem_congr_idx (c := arr) hidx]
generalize arr[i]'(hidx ▸ hlt) = t
bv_decide
Precedents in Zip/Spec/Crc32.lean: the closed-form
proof Crc32.Spec.checksum_singleton uses the full three-step dance to collapse
mkTable[0xFF ^^^ b.toNat]'(hidx ▸ hlt) for the single-byte CRC; checksum_pair
reuses the index-alignment half (rw [getElem_congr_idx ...]) for the inner byte of
the two-byte CRC.
When a Nat (or UInt*) is built by x ||| (y <<< n) with
x < 2 ^ n (and the high half stays in range), its toNat (or
value) equals x + y * 2 ^ n. The library lemma is
Nat.two_pow_add_eq_or_of_lt; for BitVec / UInt* use the
bitwidth-aware equivalent.
A reusable Nat-level helper for two-nibble packs (use whenever a checksum,
packed header, or bitfield adds lower bits into a higher-bit container):
lemma pack_toNat_of_bounds {x y n : Nat} (hx : x < 2 ^ n) :
(y <<< n ||| x) = y * 2 ^ n + x := by
rw [Nat.two_pow_add_eq_or_of_lt hx]; grind -- `ring` is unavailable (no Mathlib); `grind` subsumes it
The UInt32-specific instance lives as a private helper in
Zip/Spec/Adler32.lean:
pack_toNat_of_bounds (ha : a < 65536) (hb : b < 65536) : (pack (a, b)).toNat = a + b * 65536.
Promote to a public Nat-level lemma when a third caller needs it.
Nat.testBitTo prove (byte.toUInt32 >>> off.toUInt32) &&& 1 = if byte.toNat.testBit off then 1 else 0:
UInt32.toNat_inj.mp to reduce to NatUInt32.toNat_and/UInt32.toNat_shiftRight/UInt8.toNat_toUInt32Nat.testBit unfolds to 1 &&& m >>> n != 0 — use Nat.and_comm + Nat.one_and_eq_mod_two + split <;> omegaNat.and_one_is_mod and Nat.one_and_eq_mod_twoFor bridging Nat.testBit (which uses 1 &&& (m >>> n)) to % 2:
Nat.one_and_eq_mod_two : 1 &&& n = n % 2 (matches testBit order)Nat.and_one_is_mod : x &&& 1 = x % 2 (matches code order)UInt32.shiftLeft reduces the shift amount mod 32 — for bit <<< shift.toUInt32
with shift ≥ 32, the bit is placed at position shift % 32, not shift.
Any theorem about readBits (which accumulates via bit <<< shift) needs n ≤ 32.
▸ with UInt32/BitVec GoalsThe ▸ (subst rewrite) tactic triggers full whnf reduction, which can
deterministic-timeout on goals involving UInt32 or BitVec operations. Use
obtain ⟨rfl, _⟩ := h + rw [...] + exact ... instead.
In v4.29.0-rc1+, UInt16 is BitVec-based:
sym < 256 (UInt16 lt) directly proves sym.toNat < 256 via exact hsym¬(sym < 256) gives sym.toNat ≥ 256 via Nat.le_of_not_lt hgesym.toNat = 256 proves sym = 256 via UInt16.toNat_inj.mp (by simp; exact heq)sym.toUInt8 equals sym.toNat.toUInt8 by rflomega CANNOT directly bridge UInt16 comparisons to Nat — extract hypotheses firstWhen hsym_ne : ¬(sym == N) = true (Nat beq) but you have h : sym.toUInt16 = N
(UInt16 equality from rw [beq_iff_eq] at h), bridge via:
have := congrArg UInt16.toNat h -- sym.toUInt16.toNat = N.toNat
rw [hsym_toNat] at this -- sym = N.toNat (= N by simp)
exact absurd (beq_iff_eq.mpr (by simpa using this)) hsym_ne
Don't try exact absurd h hsym_ne — types differ (UInt16 vs Nat beq).
When native code uses UInt8 comparisons (e.g. bw.bitCount + 1 >= 8) but proofs
work in Nat (e.g. bw.bitCount.toNat + 1 >= 8), bridge with
UInt8.le_iff_toNat_le, UInt8.toNat_add, UInt8.toNat_ofNat + omega.
Prefer plain induction + by_cases on the Nat condition, then convert to UInt8
for the goal's if using an iff bridging lemma.
split/if Need Nat Annotation for omegaWhen split or if h : cond then ... introduces a UInt comparison hypothesis
(e.g., hlen_pos : lengths[start] > (0 : UInt8)), omega CANNOT use it directly
because UInt comparison is opaque to omega. Add an explicit Nat annotation:
have hlen_pos_nat : 0 < lengths[start].toNat := hlen_pos
This forces Lean to elaborate the UInt comparison into a Nat constraint that omega
can see. The have looks like a redundant alias but is NOT — removing it breaks
downstream omega calls. This applies to UInt8, UInt16, UInt32, and UInt64.
When you have hne0 : (lengths.toList.map UInt8.toNat)[s] ≠ 0 and need
lengths[s] > 0 (UInt8 comparison):
have hs_i : (...)[s] = lengths[s].toNat := by simp only [...]; rflhave hne0_nat : lengths[s].toNat ≠ 0 := hs_i ▸ hne0simp only [GT.gt, UInt8.lt_iff_toNat_lt, UInt8.toNat_ofNat]; omegaPlain omega can't bridge UInt8 > to Nat directly.
toUInt32.toNat for Small Natrep.toUInt32.toNat = rep when rep < 2^n for small n (e.g., from
readBitsLSB_bound). Use Nat.mod_eq_of_lt (by omega) directly. Don't use
show rep % UInt32.size = rep; omega — omega can't reason about %.
beq FalseTo prove (n == m) = false for Nat with n ≠ m:
cases heq : n == m <;> simp_all [beq_iff_eq]
Direct omega and rw [beq_iff_eq] don't work — omega doesn't understand
BEq and beq_iff_eq is about = true, not = false.
Bool.false_eq_true for Stuck if false = trueAfter substituting (x == y) = false via simp, ↓reduceIte can't reduce
if false = true then ... else ... because false = true is a Prop. Add
Bool.false_eq_true to rewrite it to False, then ↓reduceIte can reduce the if.
When proving ∀ d : UInt8, P d and automated tactics fail (decide_cbv, bv_decide,
grind all struggle with mixed Nat/UInt64 arithmetic from .toNat conversions):
Use decide on Fin 256 instead:
set_option maxRecDepth 1024 in
theorem foo (d : UInt8) : P d := by
have h : ∀ i : Fin 256, P ⟨⟨i⟩⟩ := by decide
exact h d.toBitVec.toFin
Why this works:
UInt8 = BitVec 8 = { toFin : Fin 256 } (two nested structures)⟨⟨i⟩⟩ constructs UInt8 from Fin 256 (outer UInt8.mk, inner BitVec.ofFin)Fin n has Decidable (∀ i : Fin n, P i) — so decide enumerates all 256 values⟨⟨d.toBitVec.toFin⟩⟩ = d definitionallyset_option maxRecDepth 1024 is needed for 256-case recursionWhen to use: Properties of functions that take UInt8 and produce UInt16/UInt32/UInt64,
especially when the function mixes .toNat conversions between different UInt widths.
bv_decide abstracts .toNat as opaque; decide_cbv can't reduce the mixed arithmetic;
decide +revert fails because BitVec lacks a Decidable (∀ x : BitVec n, ...) instance.
Tactics that DON'T work for this pattern:
decide_cbv — gets stuck on UInt64 operationsbv_decide — spurious counterexample from opaque .toNat abstractionsdecide +revert — no Decidable (∀ d : UInt8, ...) instancedecide alone — "free variables" errorgrind — can't handle UInt64 modular arithmetic