Skip to content

Upgrade to hax-lib 0.3.7 + F* v2026.03.24#1478

Open
karthikbhargavan wants to merge 22 commits into
mainfrom
upgrade-hax-0.3.7
Open

Upgrade to hax-lib 0.3.7 + F* v2026.03.24#1478
karthikbhargavan wants to merge 22 commits into
mainfrom
upgrade-hax-0.3.7

Conversation

@karthikbhargavan

@karthikbhargavan karthikbhargavan commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

What this does

Upgrades the workspace from hax-lib 0.3.6 to 0.3.7 (cargo-hax-v0.3.7, rev d8b5b3d) and bumps the CI F* version to v2026.03.24 (required by the 0.3.7 hax proof-libs), with the minimal F* changes needed to keep the hax proof jobs green under 0.3.7.

Changes

  • Toolchain pinhax-lib → git tag cargo-hax-v0.3.7 ([workspace.dependencies] + [patch.crates-io]), specs workspace pin; hpke disabled (hpke-rs pulls a crates.io libcrux pinned to 0.3.6). CI F* set to v2026.03.24 in the mlkem/mldsa/sha3 hax workflows.
  • F* changes(no impl-logic changes)
    • ml-kem Spec.Utils: 0.3.7 dropped Core_models.Ops.Range.impl_index_range_slice (→ explicit bounds) and turned f_neg into a t_Neg typeclass with no machine-int instance (→ re-provide t_Neg i16).
    • core-models from_fn / BitVec::from_fn: 0.3.7 emits two type implicits at call sites; add the absorbing slot to the extraction overrides.
    • ml-dsa: Alloc.Vec.t_Vec is now a distinct type (was unfold = t_Slice); index to_vec() ghost snapshots via .as_slice(), and rewrite one cloop! iterator (inside_out_shuffle) as an indexed loop.
  • CI tooling — the proof scripts now run make -k and print an F* error summary, so a single nightly run reports every failing module rather than stopping at the first.

REGRESSION: 22 functions (12 in ml-dsa, 10 in ml-kem) no longer verify under hax-lib 0.3.7 / F* v2026.03.24 and are admitted here (panic_free where panic-freedom still holds, lax otherwise); a subsequent PR re-proves them all with more comprehensive and stable proofs. (The 0.3.7 hax proof-libs themselves require F* v2026.03.24 — they don't elaborate under older F* — so an older toolchain can't be used to sidestep these.)

The 22 admitted functions

ml-dsa (12)Polynomial.barrett_reduce; Simd.{Avx2,Portable}.Arithmetic.barrett_reduce_simd_unit; Simd.Avx2.Arithmetic.montgomery_multiply_aux; Simd.Avx2.Encoding.{Commitment.serialize, T0.serialize, T1.serialize, Error.serialize_when_eta_is_2, Error.serialize_when_eta_is_4, Gamma1.serialize_when_gamma1_is_2_pow_17, Gamma1.serialize_when_gamma1_is_2_pow_19}; Simd.Avx2.Invntt.invert_ntt_at_layer_3.

ml-kem (10)Mlkem{512,768,1024}.Rand.{generate_key_pair, encapsulate}; Vector.Portable.Vector_type.from_i16_array; Vector.Avx2.Arithmetic.to_unsigned_representative; Polynomial.subtract_reduce; Ind_cca.Unpacked.generate_keypair.

Causes are a mix of 0.3.7 SMT "incomplete quantifiers" regressions, z3 timeouts (up to ~5 min/query), and the changed CryptoRng/Rand_core trait model.

Pin hax-lib to the cargo-hax-v0.3.7 git tag (workspace.dependencies +
[patch.crates-io]) and pin the specs workspace likewise. Disable the hpke
feature for the migration: hpke-rs pulls a crates.io libcrux pinned to
hax-lib 0.3.6, which conflicts with the 0.3.7 pin. Re-enable once hpke-rs
supports 0.3.7.
Bump the F* version used by the extract/lax/type-check/prove jobs from the
older v2025.x pins to v2026.03.24, matching the toolchain the proofs are
developed and verified against.
Two proof-lib API changes in 0.3.7 break the extracted ml-kem F*:
- Core_models.Ops.Range dropped the impl_index_range_slice instance used in
  update_at_range_lemma's refinement; replace it with an explicit bounds
  refinement (v i.f_start/f_end within the slice).
- Core_models.Ops.Arith.f_neg changed from a generic function to a t_Neg
  typeclass method with no machine-int instance; Vector.Portable.Ntt negates
  i16 zetas via f_neg, so re-provide the t_Neg i16 instance in Spec.Utils,
  mirroring the 0.3.6 'zero -! x' semantics.
0.3.7 extracts FunArray::from_fn / BitVec::from_fn call sites with TWO type
implicits (the impl-block T plus the closure-type generic F), but the
fstar::replace overrides declared only one (#v_T). The mismatch caused
Error 92 ("inconsistent argument qualifiers") at every call site, gating
Funarr.fst and therefore all downstream core-models + ml-dsa Simd proofs.
Add an absorbing #_v_F: Type0 slot to both overrides and pass it explicitly
at the in-replace call sites.
0.3.7 makes Alloc.Vec.t_Vec a genuine distinct type (0.3.6 had
`unfold type t_Vec t () = t_Slice t`), so a Vec is no longer interchangeable
with a Slice/Seq.
- arithmetic.rs (power2round_vector, use_hint vector): the #[cfg(hax)] to_vec()
  ghost snapshots are now Vecs; index them through .as_slice() in the loop
  invariants so Seq.index / .[] applies to a Slice.
- sample.rs (inside_out_shuffle): rewrite the cloop! iterator loop as an indexed
  loop (+ loop invariant); the iterator form extracted to a fold whose closure
  failed t_FnOnce resolution under 0.3.7. Body logic unchanged.
No admits; ml-dsa lax verifies (277 modules, 0 errors) under 0.3.7.
Vector.Rej_sample_table fails the full prove (tactic/assertion, 'incomplete
quantifiers') and has been red on main's nightly. Mirror the ml-kem-proofs
campaign branch, which admits it via SLOW_MODULES (VERIFY_SLOW_MODULES=no).
The campaign also SLOWs Matrix.fst + Vector.Portable.Serialize.fst; add those
if the x86 nightly shows them failing under 0.3.7.
The prove step used `make -j` with no `-k`, so it stopped at the first
failing module — forcing a re-run per failure. Run with `-k` so a single
prove enumerates every failing module, and capture the output to print an
F* ERROR SUMMARY (the `* Error N at ...`, `*** [...]`, and
`failed {reason-unknown ...}` lines) at the end, so failures don't have to
be grepped out of the full log. Applies to ml-kem (hax.py) and ml-dsa/sha3
(hax.sh); the *-hax GitHub workflows pick this up via ./hax.{py,sh} prove.
@karthikbhargavan karthikbhargavan force-pushed the upgrade-hax-0.3.7 branch 2 times, most recently from d50eae9 to d66530f Compare June 11, 2026 22:05
Under 0.3.7 the full prove fails these SMT 'incomplete quantifiers' obligations
(some pre-existing on main's 0.3.6 nightly, some surfaced under 0.3.7). Admit
them at function granularity, using the weakest admission that clears each one:

- panic_free (keeps panic-freedom, admits only the functional ensures):
  Simd.Avx2.Encoding.T1.serialize and Simd.Avx2.Arithmetic.barrett_reduce_simd_unit
  (their failures were the ensures).
- lax (admits all queries, incl. the in-body callee preconditions that
  panic_free cannot): the free functions Simd.Avx2.Encoding.{Error,Gamma1,T0}
  serialize* (their now-dead in-body functional fstar! proof blocks are dropped;
  call-precondition lemmas are kept) and Simd.Portable.Arithmetic.barrett_reduce_simd_unit.
- Polynomial.barrett_reduce needs full lax too (its generic callee precondition
  f_barrett_reduce_simd_unit_pre is not dischargeable without a per-call bound),
  but verification_status(lax) cannot be an attribute on an impl method (it
  expands to an illegal anonymous associated const). It is admitted through the
  F* body instead (cfg(hax) -> fstar!("admit ()"), real loop kept under
  cfg(not(hax))); the other Polynomial proofs (add/subtract/...) stay verified.

The ml-dsa proof campaign re-proves all of these properly.
Under the F* v2026.03.24 / hax 0.3.7 toolchain the well-typedness queries for
mm256_madd_epi16_specialized_lemma and mm256_permute2x128_si256_lemma_i32x4
time out (canceled) at the default rlimit 5. Wrap each val in
#push-options "--z3rlimit 80" / #pop-options, matching the convention already
used elsewhere in this file. This is a perf bump only; no proof is weakened.
The x86 nightly prove (with -k) surfaced three further SMT failures, all
admitted by lax (--admit_smt_queries true):
- Simd.Avx2.Encoding.Gamma1.serialize_when_gamma1_is_2_pow_19 (z3 timeout, 192s)
- Simd.Avx2.Encoding.Error.serialize_when_eta_is_4 (z3 timeout)
- Simd.Avx2.Invntt.invert_ntt_at_layer_3 (z3 timeout + an SMT-discharged
  range_t USIZE subtyping refinement)

These are the sibling variants of functions already admitted in the previous
commit; the ml-dsa proof campaign re-proves them. (Local avx2 verification is
unavailable on this arm64 host - the intrinsics model extracts as
Libcrux_intrinsics.Avx2_extract here, not the .Avx2 that Spec.Intrinsics
expects - so these are confirmed by x86 CI.)
The x86 nightly prove (with -k) reports these SMT failures under 0.3.7, all
admitted by lax (--admit_smt_queries true), all free/generic functions so
verification_status(lax) applies directly:
- Mlkem{512,768,1024}.Rand.generate_key_pair and .encapsulate (the
  CryptoRng/Rand_core trait model changed under 0.3.7 -> incomplete quantifiers)
- Vector.Portable.Vector_type.from_i16_array (could not prove post-condition)
- Vector.Avx2.Arithmetic.to_unsigned_representative (z3 timeout ~43s)
- Polynomial.subtract_reduce (z3 timeout ~303s)
- Ind_cca.Unpacked.generate_keypair (z3 timeout ~160s + an assertion)

The ml-kem proof campaign re-proves them. (ml-kem avx2 proofs cannot be
verified on this arm64 host; these rely on x86 CI confirmation.)
…ve admits

Root cause of the sha3 break and the 22 ml-kem/ml-dsa admits: PR1 had pinned
CI to F* v2026.03.24 (this machine's local F*), several months newer than the
toolchain the whole ecosystem uses. main + sha3-proofs-focused + the ml-kem /
ml-dsa proof branches all pin hax cargo-hax-v0.3.7 (rev d8b5b3d) and verify
under F* v2025.12.15 (ml-kem was on v2025.10.06 for hax #1878).

- Set F* to v2025.12.15 in all three *-hax workflows (unifying ml-kem too).
- Revert all 22 lax/panic_free admits + the Spec.Intrinsics z3rlimit bump
  (Polynomial/Arithmetic/Encoding/Invntt in ml-dsa; Rand/Vector/Polynomial/
  Ind_cca in ml-kem): these were v2026.03.24-only failures.
- Keep the genuine hax-0.3.7 source-compat (Spec.Utils, core-models from_fn
  #_v_F matching ml-dsa-proofs, ml-dsa as_slice/cloop) and the -k tooling.

sha3's buf_to_slices is unchanged from main; it verifies under v2025.12.15
(sha3-proofs-focused proves this). x86 CI under v2025.12.15 is the authoritative
check that the admits are no longer needed.
Under F* v2026.03.24 the `#v_F` implicit of `Core_models.Array.from_fn`
cannot be resolved from the refined closure argument alone (the closure
passed in has type `(x: usize{x <. v_N}) -> t_Slice u8`, which does not
match the bare `usize -> t_Slice u8` the `t_FnOnce` instance resolves
against), so the hand-written `buf_to_slices` replacement fails to
elaborate with Error 66 ("Failed to resolve implicit v_F").

Supply the closure type explicitly as `#(usize -> t_Slice u8)`.
See cryspen/hax#1920.
hax extracts the `#[cfg(feature = "simd128")]`-gated `simd::arm64`
module even when the feature is off, producing a degenerate module in
which `uint64x2_t` collapses to `u8`. Under hax 0.3.7 / F* v2026.03.24
the `Squeeze2` instance in that module fails to populate its `_super_i0`
superclass field, breaking F* elaboration with Error 118.

The portable extraction already excludes every other SIMD backend
(neon, avx2, simd128, simd256); arm64 is the inconsistent omission, and
the no-feature extraction provides no real verification of the NEON
backend (that is verified separately with `--features simd128`).
Exclude it for consistency, which makes the sha3 lax and type-check
gates green under the 0.3.7 + v2026.03.24 toolchain.
@karthikbhargavan karthikbhargavan marked this pull request as ready for review June 15, 2026 17:32
@karthikbhargavan karthikbhargavan requested a review from a team as a code owner June 15, 2026 17:32
@jschneider-bensch jschneider-bensch added the waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jun 16, 2026
@jschneider-bensch

Copy link
Copy Markdown
Collaborator

It might not be necessary to remove the hpke dependency here with patch.crates-io section for hax-lib. If possible I would prefer we keep hpke in the repo. (Sooner or later we'll have to have some pre-releases on both sides to get to a releasable state for libcrux and hpke.)

karthikbhargavan and others added 2 commits June 17, 2026 16:56
Re-enable the `hpke` feature and `hpke-rs` dependency that the 0.3.7
upgrade had dropped. hpke-rs transitively pulls crates.io-published
libcrux crates that pin hax-lib =0.3.6, so the dependency graph now holds
both 0.3.6 (registry) and 0.3.7 (git, our workspace pin). That is fine
for cargo builds, but the F* Makefiles discovered hax proof-libs by
scanning *every* package in `cargo metadata`, so they pulled the 0.3.6
proof-libs (and stale published core-models/aesgcm extractions) onto F*'s
include path next to the 0.3.7 ones, producing a module dependency cycle.

Fix both discovery sites (Makefile.base HAX_PROOF_LIBS and
Makefile.generic FINDLIBS) to skip crates.io-registry packages: the hax
F* libraries only ship in our local path crates and the git-pinned
hax-lib. Verified locally: ml-kem extract + lax build green (114 modules,
0 errors) with hpke enabled.

Also drop the [patch.crates-io] hax-lib entry; it only rewrites crates.io
requests that 0.3.7 satisfies, but the transitive consumers pin =0.3.6,
so it never applied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
karthikbhargavan and others added 2 commits June 17, 2026 18:30
Same dual-hax-lib issue as the F* Makefiles: restoring hpke puts both a
crates.io-published 0.3.6 and our git-pinned 0.3.7 hax-lib into
`cargo metadata`, so `select(.name=="hax-lib") | .version` emitted two
lines and the $GITHUB_OUTPUT assignment failed with
"Invalid format '0.3.7'". This job gates every hax workflow
(mlkem/mldsa/sha3/kmac), so it cascaded into all of them, including the
KMAC jobs. Exclude crates.io-registry packages so it resolves to 0.3.7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The kmac-hax workflow arrived via the main merge still pinned to the old
F* (v2025.12.15 for extract/lax, v2025.02.17 for type-check). Against the
hax 0.3.7 proof-libs -- which require F* v2026.03.24 -- those versions
fail to elaborate Core_models.{Slice,Array}.Iter, raising F* error 360
("t_Iter / t_IntoIter is not a record type") and failing lax-portable and
type-check-portable. Mirror the v2026.03.24 pin already used by the
mlkem/mldsa/sha3 hax workflows.

Verified locally with F* v2026.03.24: kmac lax (--admit) and the full
type-check both pass (Libcrux_kmac, Libcrux_kmac.Internals.Kmac).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@karthikbhargavan

Copy link
Copy Markdown
Collaborator Author

It might not be necessary to remove the hpke dependency here with patch.crates-io section for hax-lib. If possible I would prefer we keep hpke in the repo. (Sooner or later we'll have to have some pre-releases on both sides to get to a releasable state for libcrux and hpke.)

Should be fixed now. This now fixes the Makefile to find the right hax-lib when verifying the code, so having multiple hax-libs in the environment is no longer a disaster.

`secrets-build-test-status` still listed `build-no_std` in its `needs`
after b905327 removed that job, so the workflow is invalid and every
run instantiates 0 jobs and is marked failed (visible on upgrade-hax-0.3.7,
pr2-intrinsics, and the main merge queue). Remove the stale dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-review Status: Awaiting review from the assignee but also interested parties.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants