<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>pereq - Blog</title>
    <subtitle>At pereq, we craft tools that help developers build more robust, secure, and trustworthy software using formal methods and AI.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://pereq.eu/blog/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://pereq.eu/blog/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-10-12T00:00:00+00:00</updated>
    <id>https://pereq.eu/blog/atom.xml</id>
    <entry xml:lang="en">
        <title>Developer-driven Rust verification using hax, Lean, and AI</title>
        <published>2026-10-12T00:00:00+00:00</published>
        <updated>2026-10-12T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/sha3-hax-0-4/"/>
        <id>https://pereq.eu/blog/sha3-hax-0-4/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/sha3-hax-0-4/">&lt;p&gt;As announced in &lt;a href=&quot;https://pereq.eu/blog/hax_0_4_release/&quot;&gt;our recent blog post&lt;/a&gt;, hax 0.4 has a new Lean backend that relies
on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/AeneasVerif/aeneas&quot;&gt;Aeneas&lt;/a&gt;. In this post,
we describe how we used this new backend to verify the SHA-3 implementation
in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux-iot&quot;&gt;libcrux-iot&lt;/a&gt;,
an IoT-friendly cryptographic library developed by &lt;a rel=&quot;external&quot; href=&quot;https://celabs.eu/&quot;&gt;CE Labs&lt;/a&gt;. Our goal was to find out how close we are to a workflow in which a Rust developer without any knowledge of Lean can verify their own code using hax and AI.&lt;/p&gt;
&lt;p&gt;The results of the project are publicly available &lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/provencrypt/tree/main/sha3/libcrux-iot-5df472a&quot;&gt;in this GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, we are not the first to prove the correctness of a SHA-3 implementation. We previously verified &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux/tree/proofs/crates/algorithms/sha3&quot;&gt;libcrux’s SHA-3&lt;/a&gt; using hax and F*. Just this year, Apple &lt;a rel=&quot;external&quot; href=&quot;https://github.com/apple/corecrypto/blob/main/corecrypto_verify/technical_overview/formal-verification-for-apple-corecrypto.md&quot;&gt;verified their assembly and C implementations&lt;/a&gt; of SHA-3 using a combination of
SAW and Isabelle. Microsoft &lt;a rel=&quot;external&quot; href=&quot;https://www.microsoft.com/en-us/research/blog/verifying-rust-cryptography-in-symcrypt-from-standards-to-code/&quot;&gt;verified a Rust implementation&lt;/a&gt; of SHA-3 also using Aeneas and Lean.&lt;/p&gt;
&lt;p&gt;What distinguishes the hax approach is that we try to model as much as possible in Rust: the specification is in Rust, and even our models of the Rust core
libraries are written in Rust. This allows a Rust developer with no knowledge of Lean to test and review the specifications, assumptions, and target theorems. They can then use hax to generate the Lean, invoke their favorite AI agent to fill in the proofs, and run Lean to verify that the proofs are complete.&lt;/p&gt;
&lt;p&gt;The workflow that we followed for SHA-3 is depicted below. The developer works on the red (Rust) parts; the blue (Lean) parts are generated by hax and AI, and checked by Lean.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pereq.eu/blog/sha3-hax-0-4/libcrux-iot-sha3.svg&quot; alt=&quot;Our workflow for verifying libcrux-iot SHA-3&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;an-iot-friendly-implementation-of-sha-3&quot;&gt;An IoT-friendly implementation of SHA-3&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux-iot&quot;&gt;libcrux-iot&lt;/a&gt; library is a collection of cryptographic algorithms implemented in Rust and targeted at IoT devices and other embedded systems that have low memory and operate on simple (32-bit or smaller) processors. Specialized implementations help to cope with these constraints.&lt;/p&gt;
&lt;p&gt;In SHA-3, for example, a central data structure, the &lt;em&gt;lane&lt;/em&gt;, is not implemented as a 64-bit integer, as is usually the case.
Instead, libcrux-iot uses two 32-bit integers, with all of the even-numbered bits going into one 32-bit integer and the odd-numbered bits into the other, which allows the procedure to be executed more efficiently on 32-bit processors.&lt;/p&gt;
&lt;p&gt;The main permutation of SHA-3 consists of 24 rounds of transformations on an internal state, and each round requires an additional working copy of the internal state. The libcrux-iot implementation instead operates fully in-place to save memory, by reorganizing accesses to the internal state such that its layout conforms to the specification only every 4 rounds.&lt;/p&gt;
&lt;p&gt;These optimizations make the implementation look very different from the specification.&lt;/p&gt;
&lt;h2 id=&quot;specification-in-rust&quot;&gt;Specification in Rust&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf&quot;&gt;NIST FIPS 202&lt;/a&gt; standard
specifies the SHA-3 algorithm in pseudocode.
To be able to verify our implementation against it, we used AI to transcribe SHA-3 into Rust, staying
as close as possible to what’s described in the FIPS 202 standard.
We call this Rust specification &lt;code&gt;pedantic_sha3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, here is how the standard describes ρ, one of the internal permutation functions
used by SHA-3. It permutes the &lt;em&gt;state array&lt;/em&gt;,
a 5×5×w array of bits, for some constant w that determines its size.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pereq.eu/blog/sha3-hax-0-4/rho.svg&quot; alt=&quot;The description of ρ in the official FIPS specification&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is the corresponding part of our Rust specification:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;/// Algorithm 2: `ρ(A)`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; rho&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; W&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;: &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;StateArray&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; StateArray&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; W&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; i64&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    let mut&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; out&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; StateArray&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;zero&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;    // 1. For all z, A′[0, 0, z] = A[0, 0, z].&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;    for&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; z&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        out&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;] =&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;    // 2. Let (x, y) = (1, 0).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;mut&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;) = (&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;    // 3. For t from 0 to 23:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;    for&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;        // a. A′[x, y, z] = A[x, y, (z - (t+1)(t+2)/2) mod w].&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; offset&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; = (&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;        for&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; z&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;            out&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;] =&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;imod&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; i64&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; offset&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;        // b. (x, y) = (y, (2x + 3y) mod 5).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;nx&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; ny&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;) = (&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;, (&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; x&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; %&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; nx&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        y&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; ny&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;    out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By comparing them side by side, the developer can check that the specification reflects the standard. Moreover, the Rust specification is executable, so we can increase our confidence by extensive testing. We test the properties stated in
the FIPS 202 standard itself, run NIST’s CAVP vectors, and compare with other implementations on random inputs, including a C reference implementation.&lt;/p&gt;
&lt;p&gt;An issue we encountered while writing the Rust specification is that Rust arrays are limited to
a theoretical maximum of &lt;code&gt;usize::MAX&lt;/code&gt; elements (only actually reachable using zero-sized types), which is reflected in hax’s Lean model of Rust arrays.
The limit applies to both the implementation and the Rust specification, but there are several
reasons why the Rust specification should allow arbitrary lengths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;FIPS 202 does not impose any limits on the length of the processed messages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Rust specification appends, following FIPS 202, a suffix and padding to the input message,
whereas the implementation packs these things more efficiently. So the implementation can handle
slightly longer inputs than the specification.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We would like to use the same Rust specification to also verify the incremental API of SHA-3,
which can in theory accept inputs of arbitrary length.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To resolve this, we implemented a special Rust type &lt;code&gt;BitStr&lt;/code&gt; for arbitrary-length bitstrings and
a special Rust type &lt;code&gt;Nat&lt;/code&gt; to hold the length of such a bitstring.
In Rust, these are simply
wrappers for &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; and &lt;code&gt;u128&lt;/code&gt;, and when executed they have the same limitations as before.
But for verification, we exclude their definitions from extraction to Lean and instead define their
Lean models to be Lean’s &lt;code&gt;List Bool&lt;/code&gt; and Lean’s &lt;code&gt;Nat&lt;/code&gt;. So the resulting Lean semantics are
exactly what FIPS 202 describes: arbitrary-length bit strings.&lt;/p&gt;
&lt;h2 id=&quot;linking-the-implementation-to-the-specification&quot;&gt;Linking the implementation to the specification&lt;/h2&gt;
&lt;p&gt;To state the correspondence between our implementation and our specification,
we use the Rust annotations &lt;code&gt;hax_lib::requires&lt;/code&gt; and &lt;code&gt;hax_lib::ensures&lt;/code&gt;.
For example, for &lt;code&gt;sha256&lt;/code&gt;, one of the top-level API functions of the SHA-3 crate,
this annotation looks as follows:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;#[&lt;/span&gt;&lt;span&gt;hax_lib&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span&gt;ensures&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;declassify&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    ==&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; pedantic_sha3&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;sha3_256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;declassify_ref&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;])]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; sha256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;: &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;U8&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;U8&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-20&quot;&gt; SHA3_256_DIGEST_SIZE&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function computes a 256-bit hash of a given &lt;code&gt;payload&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hax_lib::ensures&lt;/code&gt;-annotation states that the annotated function &lt;code&gt;sha256&lt;/code&gt;
does not panic and computes the same value as our specification &lt;code&gt;pedantic_sha3::bytes::sha3_256&lt;/code&gt;. The signatures of these two functions differ slightly:
The implementation uses a custom integer type &lt;code&gt;U8&lt;/code&gt; that ensures
that we use only constant-time integer operations (on the Rust side; not part of the Lean verification). That is why we need to convert &lt;code&gt;U8&lt;/code&gt; into &lt;code&gt;u8&lt;/code&gt; using
&lt;code&gt;.declassify()&lt;/code&gt; and &lt;code&gt;.declassify_ref()&lt;/code&gt;.
We must use the “&lt;code&gt;[..]&lt;/code&gt;” to work around the fact that hax’s model of Rust core is
currently missing the &lt;code&gt;PartialEq&lt;/code&gt; implementation for comparing a &lt;code&gt;Vec&lt;/code&gt; with an array.&lt;/p&gt;
&lt;h4 id=&quot;sha3-256-with-an-externally-allocated-buffer&quot;&gt;SHA3-256 with an externally allocated buffer&lt;/h4&gt;
&lt;p&gt;The top-level API functions all come in an external-memory-allocation
variant such as &lt;code&gt;sha256_ema&lt;/code&gt;. They require the user
to provide a &lt;code&gt;digest&lt;/code&gt; slice, into which the function writes
its output.
We annotate it as follows:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;#[&lt;/span&gt;&lt;span&gt;hax_lib&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span&gt;requires&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; SHA3_256_DIGEST_SIZE&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;#[&lt;/span&gt;&lt;span&gt;hax_lib&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span&gt;ensures&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span&gt; future&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;declassify_ref&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        == &amp;amp;&lt;/span&gt;&lt;span&gt;pedantic_sha3&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span&gt;sha3_256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span&gt;declassify_ref&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;())[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;])]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; sha256_ema&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;: &amp;amp;mut&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;U8&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; payload&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;: &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;U8&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;hax_lib::requires&lt;/code&gt;-annotation specifies that the &lt;code&gt;digest&lt;/code&gt; slice must have the expected length.
Otherwise, our verification makes no claims about what the function might do.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;hax_lib::ensures&lt;/code&gt;-annotation states the same property as above, but
for &lt;code&gt;future(digest)&lt;/code&gt;, which represents &lt;code&gt;digest&lt;/code&gt;’s value after the function has run.&lt;/p&gt;
&lt;p&gt;These annotations are the theorems we prove. They are written in Rust, refer only to Rust code, and are what a developer needs to review to know what has been verified.&lt;/p&gt;
&lt;h2 id=&quot;extraction-to-lean&quot;&gt;Extraction to Lean&lt;/h2&gt;
&lt;p&gt;Implementation, specification, and annotations all get translated via hax into Lean,
where we can finally prove that the equivalences hold.
For this translation, hax’s new Lean backend uses &lt;a rel=&quot;external&quot; href=&quot;https://github.com/AeneasVerif/aeneas&quot;&gt;Aeneas&lt;/a&gt; under the hood.
For example, the specification’s &lt;code&gt;sha3_256&lt;/code&gt; is defined in Rust as follows:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; sha3_256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;: &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;BitStr&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; BitStr&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;    keccak_c&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        512&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;BitStr&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;from_bits&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-20&quot;&gt;HASH_SUFFIX&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;        Nat&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hax (via Aeneas) translates this into the following Lean function:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;lean&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; sha3.sha3_256&lt;/span&gt;&lt;span&gt; (m : bits.BitStr) : RustM bits.BitStr := &lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  let&lt;/span&gt;&lt;span&gt; s ← lift (Array.to_slice sha3.HASH_SUFFIX)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  let&lt;/span&gt;&lt;span&gt; bs ← bits.BitStr.from_bits s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  let&lt;/span&gt;&lt;span&gt; bs1 ← bits.BitStr.concat m bs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  let&lt;/span&gt;&lt;span&gt; n ← nat.Nat.new &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;256&lt;/span&gt;&lt;span&gt;#u128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sponge.keccak_c &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;512&lt;/span&gt;&lt;span&gt;#usize bs1 n&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The developer does not need to read this code; we show it only to illustrate what hax produces.&lt;/p&gt;
&lt;h4 id=&quot;changing-the-code-to-aid-verification&quot;&gt;Changing the code to aid verification&lt;/h4&gt;
&lt;p&gt;In a few cases, we had to change the implementation itself to make the Lean extraction work. Most of these changes (e.g., issues with mutability, name clashes) are no longer needed because we fixed lots of bugs both in Aeneas and in hax before releasing hax 0.4.
We still have issues with code containing string formatting (e.g., &lt;code&gt;derive(Debug)&lt;/code&gt;)
and with some unverified parts of the crate (&lt;code&gt;Digest&lt;/code&gt; and &lt;code&gt;Hasher&lt;/code&gt; impls); these
are excluded from extraction.&lt;/p&gt;
&lt;p&gt;Another issue we encountered was that the original code contained a couple of functions with 400+ lines of code. While extraction works and proofs are feasible in principle, iterating on these proofs takes too long. Our solution is to use ordinary Rust refactoring to split these functions into smaller ones:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pereq.eu/blog/sha3-hax-0-4/huge_function.png&quot; alt=&quot;Chopping up a huge function&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;using-hax-s-new-cli-features&quot;&gt;Using hax’s new CLI features&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;
hax 0.4 comes with a lot of handy CLI features, and we have started to use them
in this project
&lt;/summary&gt;
&lt;p&gt;Previously, we used Bash or Python scripts to pin tool versions and to
keep track of the exact command-line arguments they needed to be invoked with.
This has become much simpler now.&lt;/p&gt;
&lt;p&gt;The specification and the implementation are two crates in a shared Cargo workspace.
To pin hax to version 0.4.2, we added the following lines to the workspace’s &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;hax-lib&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; = {&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; version&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;=0.4.2&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;metadata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;cargo-hax&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; = {&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; version&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;0.4.2&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; bins&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; = [&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;cargo-hax&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; locked&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; true&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Along with &lt;code&gt;hax&lt;/code&gt;, this also pins the versions of all the tools and libraries that hax
uses under the hood. All that remains to be installed manually is Cargo, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/dustinblackman/cargo-run-bin&quot;&gt;cargo-run-bin&lt;/a&gt;, and Lean (more
specifically Lean’s version manager elan).&lt;/p&gt;
&lt;p&gt;To be able to call hax via &lt;code&gt;cargo hax&lt;/code&gt; instead of &lt;code&gt;cargo bin cargo-hax&lt;/code&gt;,
we added the following to &lt;code&gt;.cargo/config.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;hax&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; = [&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;cargo-hax&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To configure hax extractions, hax 0.4 now supports a &lt;code&gt;hax.toml&lt;/code&gt; file.
This project’s &lt;code&gt;hax.toml&lt;/code&gt; currently contains
only two short entries, one for the SHA-3 specification and one for the implementation:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;pedantic-sha3&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;pedantic-sha3&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;lean&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;scenario&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;libcrux-iot-sha3&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;libcrux-iot-sha3&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;lean&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;output-dir&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;../proofs&lt;/span&gt;&lt;span class=&quot;z-19&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If special options were needed to make the extraction work, we could specify them in this file.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id=&quot;verification-with-ai-assistance&quot;&gt;Verification with AI assistance&lt;/h2&gt;
&lt;p&gt;In Lean, we then proved that the extracted implementation is equivalent to
the extracted specification, as stated by the extracted annotations.&lt;/p&gt;
&lt;p&gt;The proof uses an intermediate specification of SHA-3 in pure Lean.
It proves both the extracted specification and the extracted implementation equivalent to this Lean specification. Finally, it
composes these two equivalences to prove the overall correctness theorem.&lt;/p&gt;
&lt;p&gt;The proofs were developed almost entirely by AI, with limited human guidance. The proofs are around 15,700 lines long, excluding comments and blank lines. None of this Lean code needs to be reviewed by the developer. The intermediate specification and the proofs are not trusted: Lean checks them, and what they establish is stated by the Rust annotations.&lt;/p&gt;
&lt;p&gt;The initial proof development took around a week. We reworked the proof
many times: to account for small changes in the implementation,
new hax developments, new Lean versions, and changes to the Rust specification.
Using AI, keeping up with these changes was surprisingly simple.
Even adapting the proofs to a completely different Rust specification took only a couple of hours.
Lean played a crucial role here: the fast turnaround and detailed feedback provided by the proof assistant allow the AI agent to easily fix proofs.
From our work on verifying libcrux with F*, we have noticed that
F* proofs are much harder to maintain, even with AI.&lt;/p&gt;
&lt;p&gt;Sometimes, however, the AI got stuck at seemingly simple obstacles.&lt;/p&gt;
&lt;h4 id=&quot;when-claude-opus-4-7-got-stuck&quot;&gt;When Claude Opus 4.7 got stuck&lt;/h4&gt;
&lt;p&gt;In one situation,
Claude Opus 4.7 got stuck because
a simp lemma failed to trigger. The underlying reason was a type mismatch in an expression of the
form&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;lean&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lift_lane_bv a0 a1 ^^^ lift_lane_bv b0 b1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where both sides were of type &lt;code&gt;BitVec 64&lt;/code&gt; in the simp lemma
and of type &lt;code&gt;BitVec U64.numBits&lt;/code&gt; in the goal.
While &lt;code&gt;U64.numBits&lt;/code&gt; is defined as &lt;code&gt;64&lt;/code&gt;, this definition is irreducible,
which is why the simp lemma does not trigger.
The &lt;code&gt;^^^&lt;/code&gt;-notation hides the mismatch a little, but for a human Lean expert, it’s
trivial to hover over the notation and see the underlying issue. In principle,
the AI should have the means to find the issue via the Lean MCP server as well.&lt;/p&gt;
&lt;h4 id=&quot;when-claude-fable-5-got-stuck&quot;&gt;When Claude Fable 5 got stuck&lt;/h4&gt;
&lt;p&gt;Even more recent models can go wrong, as shown by the following example,
which we encountered using Claude Fable 5 during our work on the verification
of ML-KEM, which builds on top of SHA-3.
Currently, hax’s &lt;code&gt;hax_lib::implies&lt;/code&gt; predicate does not
support lazy evaluation. That is, an expression &lt;code&gt;hax_lib::implies(A,B)&lt;/code&gt; can
panic, even when &lt;code&gt;A&lt;/code&gt; evaluates to &lt;code&gt;false&lt;/code&gt;.
This is a problem because we cannot write expressions like&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[requires(forall(|k| implies(k &amp;lt; a.len(), a[k] &amp;lt;= bound)))]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the array access &lt;code&gt;a[k]&lt;/code&gt; can panic when &lt;code&gt;k&lt;/code&gt; is too large, and it is modeled
to panic even when the condition &lt;code&gt;k &amp;lt; a.len()&lt;/code&gt; evaluates to false.
A simple workaround is to write&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[requires(forall(|k| if k &amp;lt; a.len() { a[k] &amp;lt;= bound } else { true }))]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For some reason, Fable 5 failed to come up with that workaround
and proposed using the following instead:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[requires(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    let mut ok = true;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    for k in 0..a.len() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ok &amp;amp;= a[k] &amp;lt;= bound;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This formulation would have been much harder to prove properties about.&lt;/p&gt;
&lt;p&gt;Note that the fix here is a change to a Rust annotation, which a Rust developer can spot and make without any Lean knowledge. The Opus 4.7 case, by contrast, needed genuine Lean expertise.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;This verification of SHA-3 still has many limitations
and room for improvement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Not all top-level API functions have been verified.
In particular,
we did not verify the incremental API (either buffered or unbuffered),
and we did not verify the &lt;code&gt;Digest&lt;/code&gt;/&lt;code&gt;Hasher&lt;/code&gt; implementations.
Also, secret independence is only ensured through
Rust type mechanisms, not through Lean.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our proofs do not rely on any non-standard Lean axioms, but we’d like to also run &lt;a rel=&quot;external&quot; href=&quot;https://github.com/leanprover/comparator&quot;&gt;comparator&lt;/a&gt;
to rule out that the AI proved something other than what we stated.
This is currently blocked on a Lean upgrade.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hax does not currently support multi-crate extractions, so the plumbing between implementation and specification crates in Lean is done by hand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The hax model of Rust core is still missing a few items that we need in our SHA-3 specification. We currently model them ourselves.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also see room to improve the proof approach taken by the AI agents. The Lean code generated by hax is designed to be processed by Lean’s verification condition generator &lt;code&gt;mvcgen&lt;/code&gt;, but the AI mostly bypassed
it: it opened up the monad that hax uses to model Rust computations and worked directly with tactics like &lt;code&gt;rw&lt;/code&gt;, &lt;code&gt;simp&lt;/code&gt;, &lt;code&gt;omega&lt;/code&gt;, and &lt;code&gt;decide&lt;/code&gt;. Using &lt;code&gt;mvcgen&lt;/code&gt; throughout would separate proofs of
panic freedom from proofs of functional correctness, and might make the intermediate Lean specification unnecessary. We expect that more detailed, hax-specific proof skills for the AI will lead to cleaner and more maintainable proofs in the future.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So can a Rust developer without any Lean knowledge verify their own code using hax and AI? For SHA-3, almost.
The libcrux-iot code itself was written by Rust developers with no Lean background, and verifying it required only modest Rust-level changes.
The verification, admittedly, was carried out by a Lean expert, but for the most part, the steps that need human judgment happen in
Rust: specification, contract annotations, modifications of the source code.
Lean expertise is still needed in a few places: to fill in missing models, connect multiple crates, and help the AI with the few proofs where it got stuck.
We are now working on improving hax to close these gaps. Stay tuned for hax 0.5!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hax 0.4!</title>
        <published>2026-10-01T00:00:00+00:00</published>
        <updated>2026-10-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/hax_0_4_release/"/>
        <id>https://pereq.eu/blog/hax_0_4_release/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/hax_0_4_release/">&lt;p&gt;The world of mathematics has been shaken by an earthquake: LLMs can now generate
large Lean developments autonomously: &lt;a rel=&quot;external&quot; href=&quot;https://openai.com/index/navier-stokes-solution/&quot;&gt;Navier-Stokes
solution&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://www.anthropic.com/research/formalizing-fermats-last-theorem&quot;&gt;FLT
proof&lt;/a&gt;, and
many more. The narrow stream of proof-work has the potential to open into a
free-flowing river.&lt;/p&gt;
&lt;p&gt;This offers a promise for software verification: what used to require
&lt;a rel=&quot;external&quot; href=&quot;https://project-everest.github.io/&quot;&gt;multi-year effort by skilled teams&lt;/a&gt; might
become more broadly available, provided that the flow can be tamed into useful
ways, that is, that formal verification toolchains adapt and improve.&lt;/p&gt;
&lt;p&gt;We’ve been updating and improving hax, our Rust verification framework, to
leverage this newfound potential. With Rust being a popular choice for
AI-written code, and with Lean seeing rapid adoption and tooling investment, we
believe our Rust-to-Lean pipeline could play a key role in improving confidence
in many critical codebases.&lt;/p&gt;
&lt;p&gt;We’re proud to announce the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/hax/releases&quot;&gt;0.4 release of
hax&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: We improved &lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/hax&quot;&gt;hax&lt;/a&gt; to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have a &lt;strong&gt;greater reach&lt;/strong&gt; by expanding its support for the Rust language&lt;/li&gt;
&lt;li&gt;be more broadly &lt;strong&gt;accessible&lt;/strong&gt; to users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This new release achieves that through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the collaboration with &lt;a rel=&quot;external&quot; href=&quot;https://github.com/AeneasVerif/aeneas&quot;&gt;Aeneas&lt;/a&gt; as our
&lt;strong&gt;new engine for the Rust→Lean&lt;/strong&gt; pipeline&lt;/li&gt;
&lt;li&gt;a new &lt;strong&gt;streamlined&lt;/strong&gt; installation and &lt;strong&gt;setup&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more technical details on the release, see the &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/blog/2026/09/29/hax-04-easier-to-install-and-configure-and-a-new-lean-backend/&quot;&gt;companion
post&lt;/a&gt;
on the hax blog. You can also &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/play/&quot;&gt;try hax
online&lt;/a&gt;!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;adopting-aeneas-for-the-lean-backend&quot;&gt;Adopting Aeneas for the Lean backend&lt;/h2&gt;
&lt;p&gt;Hax was from the beginning thought of as a multi-backend tool, i.e. targeting
several proof assistants, even though historically,
&lt;a rel=&quot;external&quot; href=&quot;https://fstar-lang.org/&quot;&gt;F*&lt;/a&gt; was our preferred option. We added a new Lean
backend over the course of 2025, which gave us interesting insights and
promising results.&lt;/p&gt;
&lt;p&gt;While the collaboration with the Aeneas team, which started at Inria, was still
ongoing on the frontend side of hax and
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/AeneasVerif/charon&quot;&gt;Charon&lt;/a&gt;, we decided in early 2026 to
pivot and directly use the Charon+Aeneas pipeline as the translation engine for
our Lean backend. Since then, we’ve been increasing our collaboration,
contributing bug fixes and improvements.&lt;/p&gt;
&lt;p&gt;While the Charon+Aeneas pipeline was made available in 0.3.7, the 0.4
release makes it officially our main Lean backend. This increases support for
Rust, notably &lt;em&gt;functions returning mutable borrows&lt;/em&gt;. Here is an example from the
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/openmls/openmls/blob/ff94cdc2b036fa91e676a5a9252b5150534d66df/openmls/src/treesync/treesync_node.rs#L133&quot;&gt;OpenMLS
codebase&lt;/a&gt;,
one of our ongoing proof efforts, that was previously rejected by our engine:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; node_mut&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;&amp;amp;mut&lt;/span&gt;&lt;span class=&quot;z-20&quot;&gt; self&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&amp;gt; &amp;amp;mut&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; Option&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;ParentNode&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt; {&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; ...&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Functionalizing calls to such a function, say &lt;code&gt;let x = n.node_mut()&lt;/code&gt;, is
non-trivial: it requires inserting “&lt;em&gt;backward functions&lt;/em&gt;” at the end of the
lifetime of &lt;code&gt;x&lt;/code&gt; to “update” the value of &lt;code&gt;n&lt;/code&gt;. Those backward functions must be
computed from the code and inserted at the right locations. This is the role of
the &lt;a rel=&quot;external&quot; href=&quot;https://dl.acm.org/doi/10.1145/3547647&quot;&gt;Aeneas translation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-hax-workflow&quot;&gt;The hax workflow&lt;/h2&gt;
&lt;p&gt;Proving properties of Rust code is not only about translation. This release also
packages various extensions to Aeneas, to support the key design choices and
workflows of the &lt;em&gt;hax way&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a strong library of &lt;strong&gt;Rust-written models for core/std/alloc&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;a set of macros for &lt;strong&gt;writing specs directly in the code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;and use of Lean’s built-in &lt;code&gt;mvcgen&lt;/code&gt; tactic as our main &lt;strong&gt;proof framework&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All tools verifying Rust code have to handle its dependencies: compiler
intrinsics, core/std/alloc libraries. Often, it is done by providing a
hand-written model directly in the prover, say Lean for instance. This is
usually error-prone, hard to audit, hard to extend and backend-specific. To
solve those issues, we’ve been developing a &lt;strong&gt;Rust-written library&lt;/strong&gt; of &lt;strong&gt;models
for core&lt;/strong&gt;. They are easily &lt;strong&gt;extensible, backend-agnostic&lt;/strong&gt;, and heavily
&lt;strong&gt;tested&lt;/strong&gt; against their real core counterparts. Those models rely on a
small, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/hax/tree/main/hax-lib/core-models/rust_primitives&quot;&gt;well-defined set of
primitives&lt;/a&gt;
that have to be modeled in the library of each backend. We’re very excited about
the progress there; we’ll cover it in more detail in an upcoming post.&lt;/p&gt;
&lt;p&gt;A key part of the hax methodology is the collocation of code and specs: we
encourage writing the &lt;strong&gt;specs alongside the code&lt;/strong&gt;, directly in Rust (when
applicable). We extended Aeneas with preliminary support for those (restricted
to pre/post on standalone items for now). This eases review and sync of code and
proofs.&lt;/p&gt;
&lt;p&gt;Finally, we decided to adopt the &lt;a rel=&quot;external&quot; href=&quot;https://lean-lang.org/doc/reference/latest/The--mvcgen--tactic/&quot;&gt;&lt;code&gt;mvcgen&lt;/code&gt;
tactic&lt;/a&gt; as our
entry point for actual proofs. We believe in using standard, community-backed
tools as much as possible. The development speed of &lt;code&gt;mvcgen&lt;/code&gt; is astonishing, and
we’ve already used it in large-scale proofs with great success.&lt;/p&gt;
&lt;h2 id=&quot;a-streamlined-installation-and-setup&quot;&gt;A streamlined installation and setup&lt;/h2&gt;
&lt;p&gt;A key friction point for formal verification tools is usually installation,
setup, and maintenance. With this release, hax 0.4 is available directly with
&lt;code&gt;cargo install cargo-hax&lt;/code&gt; (or via a binary installation). Once installed, hax
handles the other dependencies (Charon+Aeneas) seamlessly for the user, via the
versatile command &lt;code&gt;cargo hax tools&lt;/code&gt; (fetching the binaries, managing versions,
etc.). See the &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/manual/tools/&quot;&gt;manual&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;All the relevant settings for verification can now be stored in a single,
version-controlled, crate-level file &lt;code&gt;hax.toml&lt;/code&gt;. In that file, the user can
define &lt;em&gt;proof scenarios&lt;/em&gt;, with a specific backend, that target a subset of the
crate. This is key to enabling &lt;strong&gt;extensible verification&lt;/strong&gt;, or to target different
properties with different backends.&lt;/p&gt;
&lt;h2 id=&quot;trust-assumptions&quot;&gt;Trust &amp;amp; assumptions&lt;/h2&gt;
&lt;p&gt;Hax makes assumptions, and there are certainly bugs in the hax codebase or in
dependencies. We aim to document our trusted computing base, publish in
open-source and test our toolchain comprehensively. Notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hax depends on open-source tools (Cargo, rustc, Lean, F*, Aeneas) that are
backed and scrutinized by a community. Hax 0.4 targets Lean v4.31.0, which has
some &lt;a rel=&quot;external&quot; href=&quot;https://leodemoura.github.io/blog/2026-8-1-postmortem-for-kernel-soundness-bug-14576/&quot;&gt;known soundness
bugs&lt;/a&gt;.
Upgrading the Lean version is planned for the next release.&lt;/li&gt;
&lt;li&gt;We rely on our modeling of Rust primitives and our core models being faithful
to the rustc implementation. We mitigate the risk of mismatch by heavily
testing them.&lt;/li&gt;
&lt;li&gt;Issues can hide in the overall workflow: flawed specs, excluded code, ignored
theorems, etc. We are building tools to surface the TCB for every hax project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We welcome feedback and findings to continuously improve the trustworthiness of
the toolchain.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hax 0.4 is a pivotal moment for pereq&lt;a class=&quot;fn-ref&quot; href=&quot;#renamed&quot; id=&quot;renamed-ref&quot;&gt;*&lt;/a&gt;, leveling up to the challenges
ahead. The next release will contain a new
&lt;a rel=&quot;external&quot; href=&quot;https://bblanche.gitlabpages.inria.fr/proverif/&quot;&gt;ProVerif&lt;/a&gt; backend, increase
overall robustness and improve proof engineering.&lt;/p&gt;
&lt;p&gt;For more details on hax 0.4, look at the &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/blog/2026/09/29/hax-04-easier-to-install-and-configure-and-a-new-lean-backend/&quot;&gt;companion blog
post&lt;/a&gt;
on the hax blog, the docs, or the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/hax&quot;&gt;hax repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Stay tuned for hax 0.5!&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>EIC Transition: Spectrum</title>
        <published>2026-06-25T00:00:00+00:00</published>
        <updated>2026-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/eic-transition-spectrum/"/>
        <id>https://pereq.eu/blog/eic-transition-spectrum/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/eic-transition-spectrum/">&lt;p&gt;We are excited to have been awarded a Transition Grant from the European Innovation Council to build a specification and verification toolkit that developers can use to build and deploy provably-secure software. The project is called &lt;a href=&quot;https://pereq.eu/spectrum/&quot;&gt;Spectrum&lt;/a&gt; and it builds on our &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/&quot;&gt;hax&lt;/a&gt; open-source framework.&lt;/p&gt;
&lt;p&gt;In the coming months, we will describe how we and our customers use the Spectrum tools on a daily basis to design and implement security-critical systems. If you would like to know how Spectrum can help you, get in touch.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;btn&quot; href=&quot;mailto:access-perspec@pereq.eu&quot;&gt;Get in touch&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Software Verification in Lean</title>
        <published>2026-04-22T00:00:00+00:00</published>
        <updated>2026-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/software-verification-in-lean-2026/"/>
        <id>https://pereq.eu/blog/software-verification-in-lean-2026/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/software-verification-in-lean-2026/">&lt;p&gt;This week in Paris, pereq&lt;a class=&quot;fn-ref&quot; href=&quot;#renamed&quot; id=&quot;renamed-ref&quot;&gt;*&lt;/a&gt; is delighted to be co-hosting
&lt;a rel=&quot;external&quot; href=&quot;https://beneficial-ai-foundation.github.io/SVIL2026/&quot;&gt;Software Verification in Lean 2026&lt;/a&gt;, a
workshop and associated hackathon organized by the
&lt;a rel=&quot;external&quot; href=&quot;https://www.beneficialaifoundation.org/&quot;&gt;Beneficial AI Foundation&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://lean-lang.org/fro/&quot;&gt;Lean FRO&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SVIL 2026 brings together leading researchers and practitioners
working on using the &lt;a rel=&quot;external&quot; href=&quot;https://lean-lang.org/&quot;&gt;Lean proof assistant&lt;/a&gt; to
formalize and verify real-world software. On Monday, I gave a talk on
the challenges of verifying cryptographic applications, and the
opportunities that are being opened up by the rapid innovations
in Lean and by the emergence of AI-assisted formal proofs.&lt;/p&gt;
&lt;p&gt;SVIL 2026 is initiating several exciting new collaborations. On
Monday, BAIF and Lean FRO announced the &lt;a rel=&quot;external&quot; href=&quot;https://www.beneficialaifoundation.org/signal-shot&quot;&gt;Signal
Shot&lt;/a&gt;, a challenge
to the community to formally verify the Signal protocol and Signal
app.  Pereq intends to contribute our tools and our expertise to this
effort, in collaboration with our colleagues in the
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/AeneasVerif/aeneas&quot;&gt;Aeneas&lt;/a&gt; team.&lt;/p&gt;
&lt;p&gt;If you want to learn more about how pereq can help you build ambitious software with
strong security and correctness guarantees, &lt;a href=&quot;mailto:info@pereq.eu&quot;&gt;reach out to us&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Formally Specifying and Testing the Rust Standard Library</title>
        <published>2025-10-28T00:00:00+00:00</published>
        <updated>2025-10-28T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/specify-rust-simd/"/>
        <id>https://pereq.eu/blog/specify-rust-simd/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/specify-rust-simd/">&lt;p&gt;Modern programming languages typically come with a large standard
library that implements essential language features like machine
arithmetic and I/O, offers efficient data structures, provides
interfaces to system libraries, etc. Although it is often overlooked,
this standard library should be considered as much part of the trusted
computing base (TCB) as the language compiler. Indeed, any bug in the standard
library is likely to break applications and could result in security vulnerabilities.&lt;/p&gt;
&lt;p&gt;Recognizing this concern, the Rust community has &lt;a rel=&quot;external&quot; href=&quot;https://model-checking.github.io/verify-rust-std/&quot;&gt;initiated an
effort&lt;/a&gt; to formally
verify the Rust standard library. Pereq&lt;a class=&quot;fn-ref&quot; href=&quot;#renamed&quot; id=&quot;renamed-ref&quot;&gt;*&lt;/a&gt; is participating in this
project, and as our first contribution, we proposed and implemented a
new methodology to formally specify and test the platform-specific
SIMD functions in the Rust core library. These functions are used in
a variety of performance-sensitive Rust applications, including cryptographic
libraries like &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux&quot;&gt;libcrux&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;modeling-rust-s-core-arch-library&quot;&gt;Modeling Rust’s &lt;code&gt;core::arch&lt;/code&gt; library&lt;/h2&gt;
&lt;p&gt;The
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/tree/master/crates/core_arch&quot;&gt;&lt;code&gt;core::arch&lt;/code&gt;&lt;/a&gt;
library in Rust provides a number of platform-specific
functionalities, including wrappers for specific machine instructions.
In particular, it provides functions corresponding to the
single-instruction-multiple-data (SIMD) instructions provided by
different vectorized architectures such as Intel’s AVX, AVX2 and
AVX-512, and Arm’s Aarch64/Neon.&lt;/p&gt;
&lt;p&gt;To model the behaviour of each SIMD instruction in a testable manner,
we essentially write a reference implementation of the
function in Rust based on the instruction’s documentation.&lt;/p&gt;
&lt;p&gt;There are three kinds of SIMD intrinsics in &lt;code&gt;core::arch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first kind are &lt;code&gt;builtin&lt;/code&gt; Rust compiler intrinsics, some of which are
in the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/model-checking/verify-rust-std/blob/b5775bd53495289b797a6a9671965597be4b74d0/library/core/src/intrinsics/simd.rs&quot;&gt;&lt;code&gt;intrinsics/simd.rs&lt;/code&gt; file&lt;/a&gt;
in the &lt;code&gt;core&lt;/code&gt; crate, and others are in the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/model-checking/verify-rust-std/blob/main/library/stdarch/crates/core_arch/src/simd.rs&quot;&gt;&lt;code&gt;simd.rs&lt;/code&gt; file of &lt;code&gt;core_arch&lt;/code&gt;&lt;/a&gt;.
These builtin intrinsics define generic SIMD operations that the Rust compiler knows how to implement on each platform.&lt;/p&gt;
&lt;p&gt;The second kind are &lt;code&gt;extern&lt;/code&gt; intrinsics that are links to definitions in LLVM.
See, for example, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/blob/master/crates/core_arch/src/x86/avx2.rs#L3596C8-L3596C14&quot;&gt;this list&lt;/a&gt;
of &lt;code&gt;extern&lt;/code&gt; intrinsics used in the Intel x86 AVX2 library.
These extern intrinsics are typically platform-specific functions that map to low-level instructions.&lt;/p&gt;
&lt;p&gt;The third kind are &lt;code&gt;defined&lt;/code&gt; intrinsics that are given proper definitions in Rust, and their code may
depend on the builtin intrinsics or the extern intrinsics. These defined intrinsics represent higher-level
operations that are wrappers around one or more assembly instructions.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;builtin&lt;/code&gt; and &lt;code&gt;extern&lt;/code&gt; intrinsics, we need to define reference
implementations ourselves, but for &lt;code&gt;defined&lt;/code&gt; intrinsics, we have a
choice: we can either define a completely independent model of the
intrinsic based on its documentation, or we can copy and adapt the
implementation provided in the Rust core library and treat it as a
reference implementation. If our goal is to check the Rust core
library, it is always better to write a fresh model that can serve as
the official specification. Conversely, deriving models from the Rust
library implementations allows us to quickly obtain models for a large
class of functions, and these models accurately reflect what Rust does
in practice, which is useful when analyzing Rust programs that use
these intrinsics. We need to balance this tradeoff based on our goals,
and here, we only derive models for intrinsics whose bodies are
shallow wrappers around builtin or extern intrinsics.&lt;/p&gt;
&lt;h2 id=&quot;an-example-mm256-bsrli-epi128&quot;&gt;An Example: &lt;code&gt;_mm256_bsrli_epi128&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;For example, let us say the intrinsic we are modeling is
&lt;code&gt;_mm256_bsrli_epi128&lt;/code&gt; from the Intel AVX2 feature set.&lt;/p&gt;
&lt;p&gt;The official &lt;a rel=&quot;external&quot; href=&quot;https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_bsrli_epi128&amp;amp;ig_expand=628&quot;&gt;Intel documentation of this function&lt;/a&gt; provides a textual description:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Shift 128-bit lanes in a right by imm8 bytes while shifting in zeros, and store the results in dst.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also provides pseudocode for the instruction:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tmp := imm8[7:0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;IF tmp &amp;gt; 15&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tmp := 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;FI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dst[127:0] := a[127:0] &amp;gt;&amp;gt; (tmp*8)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dst[255:128] := a[255:128] &amp;gt;&amp;gt; (tmp*8)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dst[MAX:256] := 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on this pseudocode, we can write a &lt;code&gt;model&lt;/code&gt; for the function,
interpreting the 256-bit vector type &lt;code&gt;__m256i&lt;/code&gt; as an array of two
128-bit integers (&lt;code&gt;i128x2&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;pub fn&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; _mm256_bsrli_epi128&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-20&quot;&gt; IMM8&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; i32&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; i128x2&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; i128x2&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-11&quot;&gt;	i128x2&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt;from_fn&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;	    let mut&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-20&quot;&gt; IMM8&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;	    if&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 15&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; {&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;	    ((&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; u128&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; )&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; i128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-7&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, in the Rust core library, this is a &lt;code&gt;defined&lt;/code&gt; intrinsic, and
when we began our project, this intrinsic was implemented &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/blob/35595690d7881b20bd419875c2b64f284534d237/crates/core_arch/src/x86/avx2.rs#L2778&quot;&gt;as shown
here&lt;/a&gt;. Notably,
the implementation aims to be efficient by branching over the shift
value (&lt;code&gt;tmp&lt;/code&gt;) and implementing the shift using byte-level shuffling
operations, presumably because this is faster than using a shift instruction.&lt;/p&gt;
&lt;p&gt;We now have two different interpretations of the same
instruction, a &lt;code&gt;model&lt;/code&gt; that follows the Intel documentation and an
&lt;code&gt;implementation&lt;/code&gt; that aims to be efficient. Since both these functions
are executable, we can test them against each other.&lt;/p&gt;
&lt;h2 id=&quot;testing-the-model-agains-the-rust-core-library&quot;&gt;Testing the Model agains the Rust Core library&lt;/h2&gt;
&lt;p&gt;We use the standard Rust testing framework to run a large number of tests for each intrinsic we model against the same intrinsic in the Rust core library. For convenience, we implemented a set of macros that allows us to generate random tests for large inputs and comprehensive tests for small inputs.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;_mm256_bsrli_epi128&lt;/code&gt; function above, we test it for all
values of &lt;code&gt;IMM8&lt;/code&gt; from 0 to 256 (since the Intel pseudocode only looks
at the bottom 8 bits), and for a 100 random bitvectors representing
the &lt;code&gt;a&lt;/code&gt; vector (the number of tests is configurable). For each input,
we test whether our model returns the same output as the Rust
core. Often, this process reveals bugs in our hand-written models,
where we may have misinterpreted the Intel or Arm documentation.&lt;/p&gt;
&lt;p&gt;In the case of &lt;code&gt;_mm256_bsrli_epi128&lt;/code&gt; our tests revealed a bug in the
Rust core library. When the value of &lt;code&gt;tmp&lt;/code&gt; in the pseudocode above is
greater than 15, instead of setting the value to 16 as specified by
Intel, the Rust core implementation set it to &lt;code&gt;tmp % 16&lt;/code&gt;. This bug can
be seen on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/blob/35595690d7881b20bd419875c2b64f284534d237/crates/core_arch/src/x86/avx2.rs#L2782&quot;&gt;this
line&lt;/a&gt;. As
a result, the tests fail for values of &lt;code&gt;IMM8&lt;/code&gt; above 15.&lt;/p&gt;
&lt;p&gt;A similar bug affects &lt;code&gt;_mm512_bsrli_epi128&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We opened an issue in the Rust &lt;code&gt;stdarch&lt;/code&gt; repository, reporting this bug,
along with the precise tests that fail. This helped the Rust developers
quickly agree on the bug and accept our fix, which has since been upstreamed.&lt;/p&gt;
&lt;h2 id=&quot;testable-models-for-the-rust-simd-intrinsics&quot;&gt;Testable Models for the Rust SIMD Intrinsics&lt;/h2&gt;
&lt;p&gt;In total, we formally specified 384 intrinsics for x86 platforms, 181
intrinsics for aarch64, and tested these specifications against the
Rust core library. In the course of this testing, we found and fixed
bugs in two intrinsics in the Rust library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our challenge to Verify Rust Std: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/model-checking/verify-rust-std/issues/173&quot;&gt;Challenge 15&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Our solution to the challenge: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/model-checking/verify-rust-std/pull/423&quot;&gt;Testable Models for SIMD Intrinsics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The bug we found in the Rust SIMD libraries: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/issues/1822&quot;&gt;Bug&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rust-lang/stdarch/pull/1823&quot;&gt;Fix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This work was done as part of Aniket Mishra’s Internship at pereq, and is part
of a larger project to formally specify the Rust &lt;code&gt;core&lt;/code&gt; and &lt;code&gt;std&lt;/code&gt; libraries in a way that can be used in formal proofs of Rust programs in &lt;a rel=&quot;external&quot; href=&quot;https://pereq.dev/hax/&quot;&gt;hax&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If you want to learn more about how we test and verify Rust libraries, &lt;a href=&quot;mailto:info@pereq.eu&quot;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Helping Secure Signal&#39;s Post-Quantum Transition</title>
        <published>2025-10-02T00:00:00+00:00</published>
        <updated>2025-10-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://pereq.eu/blog/signal-spqr-verification/"/>
        <id>https://pereq.eu/blog/signal-spqr-verification/</id>
        
        <content type="html" xml:base="https://pereq.eu/blog/signal-spqr-verification/">&lt;p&gt;Signal just &lt;a rel=&quot;external&quot; href=&quot;https://signal.org/blog/spqr/&quot;&gt;announced&lt;/a&gt; the deployment
of their new post-quantum ratcheting protocol, called the Sparse
Post-Quantum Ratchet (SPQR), and pereq&lt;a class=&quot;fn-ref&quot; href=&quot;#renamed&quot; id=&quot;renamed-ref&quot;&gt;*&lt;/a&gt; is proud to have contributed
to the formal analysis of the design and implementation of this new
Signal feature.&lt;/p&gt;
&lt;p&gt;The seeds of our collaboration with Signal reach back to 2023, when
Signal first published PQXDH, the post-quantum key establishment
protocol that marked the beginning of Signal’s post-quantum
transition.  We helped Signal to formally analyze and improve
PQXDH, resulting in a &lt;a rel=&quot;external&quot; href=&quot;https://www.usenix.org/conference/usenixsecurity24/presentation/bhargavan&quot;&gt;paper at
Usenix Security&lt;/a&gt;.
This time, pereq and Signal agreed to collaborate from the very
beginning of the design process for SPQR all the way through to its
implementation.&lt;/p&gt;
&lt;p&gt;The Signal &lt;a rel=&quot;external&quot; href=&quot;https://signal.org/blog/spqr/&quot;&gt;blog post&lt;/a&gt; wonderfully describes SPQR and the role
formal verification played in its design and implementation.
Here, we will briefly note three contributions pereq had towards
this effort, leaving deeper technical details to a future publication.&lt;/p&gt;
&lt;h2 id=&quot;analyzing-the-spqr-protocol-design&quot;&gt;Analyzing the SPQR Protocol Design&lt;/h2&gt;
&lt;p&gt;In our previous analysis of PQXDH, pereq collaborated with Charlie
Jacomme at Inria and Rolfe Schmidt at Signal to build formal models of
the protocol that could be analyzed symbolically by
&lt;a rel=&quot;external&quot; href=&quot;https://proverif.inria.fr&quot;&gt;ProVerif&lt;/a&gt; and verified computationally by
&lt;a rel=&quot;external&quot; href=&quot;https://cryptoverif.inria.fr&quot;&gt;CryptoVerif&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For SPQR, we decided to build models in ProVerif, because the
protocol engineers found it easy to read and write ProVerif,
and the fully automatic tool provided quick and reliable
feedback on each design. We helped Signal engineers write the initial
models, but they were able to modify and maintain these models as the
protocol underwent a series of revisions until it reached its final
form. Using formal verification in this organic way allows protocol
designers to quickly try out new ideas, checking if a design change
breaks a desired security property. When all the protocol details
are settled, the final model can be comprehensively verified for all
desired security goals.&lt;/p&gt;
&lt;p&gt;The ProVerif models for SPQR are maintained in the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/signalapp/SparsePostQuantumRatchet/tree/main/proofs/proverif&quot;&gt;SPQR
repository&lt;/a&gt; and are verified as part of CI.&lt;/p&gt;
&lt;h2 id=&quot;providing-post-quantum-cryptography&quot;&gt;Providing Post-Quantum Cryptography&lt;/h2&gt;
&lt;p&gt;The SPQR implementation uses our &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux&quot;&gt;libcrux&lt;/a&gt;
implementation of the &lt;a rel=&quot;external&quot; href=&quot;https://csrc.nist.gov/pubs/fips/203/final&quot;&gt;ML-KEM&lt;/a&gt; post-quantum key encapsulation mechanism. Our Rust implementation is formally verified
for panic-freedom, functional correctness, and secret independence, and hence provides a high degree of assurance.&lt;/p&gt;
&lt;p&gt;Interestingly, Signal’s SPQR design required a novel &lt;em&gt;incremental&lt;/em&gt; API for ML-KEM, different
from the ones usually provided by crypto libraries. At the request of the Signal
developers, we implemented this new API in libcrux and helped them
integrate it into the SPQR implementation.
See our &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux/pull/757&quot;&gt;PR&lt;/a&gt; for details on the incremental API, and see &lt;a rel=&quot;external&quot; href=&quot;https://github.com/celabshq/libcrux/blob/825f4a63aad0a1e69e1942d643750bc1f4be63c8/libcrux-ml-kem/tests/self.rs#L135&quot;&gt;this test&lt;/a&gt;
to understand how to use the API.&lt;/p&gt;
&lt;h2 id=&quot;verifying-the-spqr-implementation&quot;&gt;Verifying the SPQR Implementation&lt;/h2&gt;
&lt;p&gt;As the design of SPQR matured, the Signal team started implementing it
in Rust.  At this stage, we collaborated with them to build a
formal verification pipeline for their implementation using
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/cryspen/hax&quot;&gt;hax&lt;/a&gt; and its
&lt;a rel=&quot;external&quot; href=&quot;https://fstar-lang.org/&quot;&gt;F*&lt;/a&gt; proof backend. We helped verify the new code
for panic-freedom and some correctness goals.&lt;/p&gt;
&lt;p&gt;The hax verification scripts are hosted on the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/signalapp/SparsePostQuantumRatchet/tree/main/proofs&quot;&gt;SPQR repository&lt;/a&gt;.
They run in CI and flag errors whenever a code change
breaks some expected invariant and might (say) cause a panic at runtime. These
proofs only cover the core of the new SPQR code, and formally verifying
the full Signal codebase is still some distance away, but it is already
exciting to see how formal proofs like these can be maintained and
extended by Signal engineers in the normal course of their work.&lt;/p&gt;
&lt;p&gt;This experience taught us a lot about real-world code verification
and resulted in several improvements to hax and to our models of
the Rust libraries. We are excited to continue collaborating with
the Signal team to cover more of the Signal codebase and verify
it for more ambitious correctness and security properties in the future.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;To learn more about how pereq can help you with your cryptographic
protocol design or software verification, &lt;a href=&quot;mailto:info@pereq.eu&quot;&gt;email us&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
</feed>
