hacl-pulse: Re-verifying HACL* in Pulse, with Agents Gabriel Ebner's hacl-pulse project reimplemented the HACL* verified cryptographic library from scratch in F* and Pulse, extracting to C via F*'s new Custard backend, with nearly all of the roughly six-week effort written by AI agents running Claude Opus 5 and Opus 5.5. The library covers every algorithm HACL* offers and adds ML-KEM, ML-DSA, and HPKE in all four modes, comprising about 164,000 lines of F* and Pulse (57K specification, 101K implementation and proof) extracted to a single Hacl.c and Hacl.h, with no assembly. Every primitive runs within 1.32x of HACL*'s best configuration and 16 run faster, with 44 C test harnesses checking against published vectors. hacl-pulse: Re-verifying HACL in Pulse, with Agents - Author: Gabriel Ebner TL;DR: hacl-pulse https://github.com/FStarLang/hacl-pulse is a from-scratch reimplementation of the HACL https://github.com/hacl-star/hacl-star verified cryptographic library in F https://fstar-lang.org and Pulse https://fstar-lang.org/tutorial/book/pulse/pulse.html , extracted to C by F ’s new Custard backend. It covers every algorithm HACL offers though not yet its platforms, bindings, or agile EverCrypt layer , and adds ML-KEM, ML-DSA, and HPKE in all four modes. It proves correctness theorems about the specs themselves, including the elliptic-curve group laws that HACL admits. Every primitive runs within 1.32x of HACL ’s best configuration, and 16 run faster. It took about six weeks, and nearly all of it was written by AI agents running Claude Opus 5 and Opus 5.5. Why rebuild HACL ? Section titled “Why rebuild HACL ?” why-rebuild-hacl HACL is one of the success stories of formal verification. Its verified C code ships in Firefox, Linux, the Tezos and Python ecosystems, and elsewhere. It was written over many years in Low , a C-like subset of F , and carries workarounds for features F lacked at the time: one integer type indexed by width and secrecy, a custom tactic to specialize width-generic code, and 32-bit lengths everywhere. Its fastest paths use Vale https://github.com/project-everest/vale , a separate framework for writing and verifying x86-64 assembly. F has changed a lot since then. Pulse https://fstar-lang.org/tutorial/book/pulse/pulse.html is a mature separation-logic language for imperative code, type classes are first-class, and Custard https://github.com/FStarLang/FStar/blob/master/doc/ref/custard.md extracts Pulse straight to C, monomorphizing type-class code as it goes. And, as we’ve written in earlier https://risemsr.github.io/blog/2026-02-04-nik-agentic-pop posts https://risemsr.github.io/blog/2026-03-06-autoclrs , agents can now carry most of the proof engineering. So we asked: what would HACL look like if we wrote it today? The answer is not a port. Wherever HACL worked around a missing language feature, hacl-pulse uses the feature. What’s in the box Section titled “What’s in the box” whats-in-the-box All of the following are specified, implemented, verified, extracted to C, and tested against published vectors 44 C test harnesses : - Hashes: SHA-1, SHA-2, SHA-3/SHAKE, BLAKE2s/b, and MD5, one-shot and incremental. - MACs and KDFs: HMAC one-shot and incremental , HKDF, and Poly1305. - Ciphers and AEADs: ChaCha20, Salsa20/XSalsa20, AES-GCM, ChaCha20-Poly1305, and NaCl secretbox / box . - Curves: X25519, Ed25519, P-256, and secp256k1 with low-S for Bitcoin . - Bignum: Montgomery arithmetic, constant-time modexp, FFDHE all five RFC 7919 groups , and RSA-PSS. - Randomness: HMAC-DRBG with an OS entropy shim. - Post-quantum: FrodoKEM all four parameter sets , and ML-KEM and ML-DSA all three parameter sets each . - HPKE RFC 9180 in all four modes with all three AEADs. That’s about 164,000 lines of F and Pulse 57K of specification, 101K of implementation and proof , extracted to a single Hacl.c and Hacl.h . There is no assembly. Hardware fast paths AES-NI, PCLMULQDQ, the SHA extensions, AVX2, and AVX-512 are C intrinsics behind narrow, axiomatized interfaces, selected at run time. On other architectures the same Hacl.c compiles to the portable code alone, and CI tests it on aarch64. Modernizing the design Section titled “Modernizing the design” modernizing-the-design hacl-pulse departs from HACL ’s design in five places details in the design notes https://github.com/FStarLang/hacl-pulse/blob/master/design/notes.md the-five-design-commitments : 1. Secret integers are types, not an index. The idea comes from HACL ’s secret integers; hacl-pulse makes each secret width its own abstract type, whose interface exposes only constant-time operations: no comparison, no division, no branching. Declassification goes through a single module, so auditing where secrets become public is a search for one name. In C these are plain uint32 t and friends. 2. Width-generic code is a type class, not a tactic. SHA-2’s core is written once against a word type class, and Custard monomorphizes it into uint32 t and uint64 t copies with no indirection. Algorithms are parameter records, so SHA-224/256/384/512 become four specialized C functions with their constants as literals. 3. size t , not 32-bit lengths. Buffers larger than 4 GiB work without chunking. 4. No assembly. The trusted C is small and explicit: the entropy shim, the vector intrinsics, a 64-bit add-with-carry, and memory wiping. 5. Scratch memory is wiped, and the type system enforces it. Every stack array carries a ghost must wipe resource that only wipe consumes. Pulse rejects a function that ends holding a resource, so a forgotten wipe is a type error , not a code-review comment. Lints and a -fstack-usage check also ensure no stack array is sized by caller input. Proving the specs mean what they should Section titled “Proving the specs mean what they should” proving-the-specs-mean-what-they-should A refinement proof shows that the C computes what the spec says, not that the spec is right. Test vectors are part of the answer. The rest is a set of theorems about the specifications , the statements a cryptographer would write down first: - AEADs: decryption inverts encryption for ChaCha20-Poly1305, AES-GCM, and NaCl secretbox and box. - Key exchange: both parties agree for X25519, P-256 and secp256k1 ECDH, and all five FFDHE groups. - Signatures: honest signatures verify for Ed25519, ECDSA on both curves, and RSA-PSS. - KEMs and HPKE: encapsulation and seal/open round-trip for ML-KEM, FrodoKEM, and every HPKE mode. The lattice KEM theorems assume an explicit noise bound. Along the way, the library proves facts that are usually assumed: that the P-256 and secp256k1 group orders are prime, that the ML-KEM NTT is a ring isomorphism, that the X25519 ladder computes a scalar multiple on the Edwards group, and the group laws for Ed25519, P-256, and secp256k1 . The last of these is the most interesting, so let’s zoom in. Zooming in: the elliptic-curve group laws Section titled “Zooming in: the elliptic-curve group laws” zooming-in-the-elliptic-curve-group-laws Why we needed them. Fast implementations compute a fixed-base scalar multiple k G from a precomputed table of multiples of G . For example, a 4-bit window splits k into digits and adds up d